1'use strict';
2
3// Flags: --expose-internals
4
5const common = require('../common');
6const stream = require('stream');
7const REPL = require('internal/repl');
8const assert = require('assert');
9const fs = require('fs');
10const path = require('path');
11const { inspect } = require('util');
12
13common.skipIfDumbTerminal();
14
15const tmpdir = require('../common/tmpdir');
16tmpdir.refresh();
17
18process.throwDeprecation = true;
19process.on('warning', common.mustNotCall());
20
21const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
22
23// Create an input stream specialized for testing an array of actions
24class ActionStream extends stream.Stream {
25  run(data) {
26    const _iter = data[Symbol.iterator]();
27    const doAction = () => {
28      const next = _iter.next();
29      if (next.done) {
30        // Close the repl. Note that it must have a clean prompt to do so.
31        this.emit('keypress', '', { ctrl: true, name: 'd' });
32        return;
33      }
34      const action = next.value;
35
36      if (typeof action === 'object') {
37        this.emit('keypress', '', action);
38      } else {
39        this.emit('data', `${action}`);
40      }
41      setImmediate(doAction);
42    };
43    doAction();
44  }
45  resume() {}
46  pause() {}
47}
48ActionStream.prototype.readable = true;
49
50// Mock keys
51const ENTER = { name: 'enter' };
52const UP = { name: 'up' };
53const DOWN = { name: 'down' };
54const LEFT = { name: 'left' };
55const RIGHT = { name: 'right' };
56const DELETE = { name: 'delete' };
57const BACKSPACE = { name: 'backspace' };
58const WORD_LEFT = { name: 'left', ctrl: true };
59const WORD_RIGHT = { name: 'right', ctrl: true };
60const GO_TO_END = { name: 'end' };
61const DELETE_WORD_LEFT = { name: 'backspace', ctrl: true };
62const SIGINT = { name: 'c', ctrl: true };
63const ESCAPE = { name: 'escape', meta: true };
64
65const prompt = '> ';
66const WAIT = '€';
67
68const prev = process.features.inspector;
69
70let completions = 0;
71
72const tests = [
73  { // Creates few history to navigate for
74    env: { NODE_REPL_HISTORY: defaultHistoryPath },
75    test: [ 'let ab = 45', ENTER,
76            '555 + 909', ENTER,
77            'let autocompleteMe = 123', ENTER,
78            '{key : {key2 :[] }}', ENTER,
79            'Array(100).fill(1).map((e, i) => i ** i)', LEFT, LEFT, DELETE,
80            '2', ENTER],
81    expected: [],
82    clean: false
83  },
84  {
85    env: { NODE_REPL_HISTORY: defaultHistoryPath },
86    test: [UP, UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN],
87    expected: [prompt,
88               `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
89               prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
90                 '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' +
91                 ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' +
92                 '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' +
93                 ' 2025, 2116, 2209,...',
94               `${prompt}{key : {key2 :[] }}`,
95               prev && '\n// { key: { key2: [] } }',
96               `${prompt}let autocompleteMe = 123`,
97               `${prompt}555 + 909`,
98               prev && '\n// 1464',
99               `${prompt}let ab = 45`,
100               prompt,
101               `${prompt}let ab = 45`,
102               `${prompt}555 + 909`,
103               prev && '\n// 1464',
104               `${prompt}let autocompleteMe = 123`,
105               `${prompt}{key : {key2 :[] }}`,
106               prev && '\n// { key: { key2: [] } }',
107               `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
108               prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
109                 '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' +
110                 ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' +
111                 '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' +
112                 ' 2025, 2116, 2209,...',
113               prompt].filter((e) => typeof e === 'string'),
114    clean: false
115  },
116  { // Creates more history entries to navigate through.
117    env: { NODE_REPL_HISTORY: defaultHistoryPath },
118    test: [
119      '555 + 909', ENTER, // Add a duplicate to the history set.
120      'const foo = true', ENTER,
121      '555n + 111n', ENTER,
122      '5 + 5', ENTER,
123      '55 - 13 === 42', ENTER,
124    ],
125    expected: [],
126    clean: false
127  },
128  {
129    env: { NODE_REPL_HISTORY: defaultHistoryPath },
130    checkTotal: true,
131    preview: false,
132    showEscapeCodes: true,
133    test: [
134      '55', UP, UP, UP, UP, UP, UP, UP, ENTER,
135    ],
136    expected: [
137      '\x1B[1G', '\x1B[0J', prompt, '\x1B[3G',
138      // '55'
139      '5', '5',
140      // UP
141      '\x1B[1G', '\x1B[0J',
142      '> 55 - 13 === 42', '\x1B[17G',
143      // UP - skipping 5 + 5
144      '\x1B[1G', '\x1B[0J',
145      '> 555n + 111n', '\x1B[14G',
146      // UP - skipping const foo = true
147      '\x1B[1G', '\x1B[0J',
148      '> 555 + 909', '\x1B[12G',
149      // UP, UP
150      // UPs at the end of the history reset the line to the original input.
151      '\x1B[1G', '\x1B[0J',
152      '> 55', '\x1B[5G',
153      // ENTER
154      '\r\n', '55\n',
155      '\x1B[1G', '\x1B[0J',
156      '> ', '\x1B[3G',
157      '\r\n',
158    ],
159    clean: true
160  },
161  {
162    env: { NODE_REPL_HISTORY: defaultHistoryPath },
163    skip: !process.features.inspector,
164    test: [
165      // あ is a full width character with a length of one.
166      // � is a full width character with a length of two.
167      // � is a half width character with the length of two.
168      // '\u0301', '0x200D', '\u200E' are zero width characters.
169      `const x1 = '${''.repeat(124)}'`, ENTER, // Fully visible
170      ENTER,
171      `const y1 = '${''.repeat(125)}'`, ENTER, // Cut off
172      ENTER,
173      `const x2 = '${''.repeat(124)}'`, ENTER, // Fully visible
174      ENTER,
175      `const y2 = '${''.repeat(125)}'`, ENTER, // Cut off
176      ENTER,
177      `const x3 = '${''.repeat(248)}'`, ENTER, // Fully visible
178      ENTER,
179      `const y3 = '${''.repeat(249)}'`, ENTER, // Cut off
180      ENTER,
181      `const x4 = 'a${'\u0301'.repeat(1000)}'`, ENTER, // á
182      ENTER,
183      `const ${'veryLongName'.repeat(30)} = 'I should be previewed'`,
184      ENTER,
185      'const e = new RangeError("visible\\ninvisible")',
186      ENTER,
187      'e',
188      ENTER,
189      'veryLongName'.repeat(30),
190      ENTER,
191      `${'\x1B[90m \x1B[39m'.repeat(229)} aut`,
192      ESCAPE,
193      ENTER,
194      `${' '.repeat(230)} aut`,
195      ESCAPE,
196      ENTER,
197    ],
198    expected: [],
199    clean: false
200  },
201  {
202    env: { NODE_REPL_HISTORY: defaultHistoryPath },
203    columns: 250,
204    checkTotal: true,
205    showEscapeCodes: true,
206    skip: !process.features.inspector,
207    test: [
208      UP,
209      UP,
210      UP,
211      WORD_LEFT,
212      UP,
213      BACKSPACE,
214      'x1',
215      BACKSPACE,
216      '2',
217      BACKSPACE,
218      '3',
219      BACKSPACE,
220      '4',
221      DELETE_WORD_LEFT,
222      'y1',
223      BACKSPACE,
224      '2',
225      BACKSPACE,
226      '3',
227      SIGINT,
228    ],
229    // A = Cursor n up
230    // B = Cursor n down
231    // C = Cursor n forward
232    // D = Cursor n back
233    // G = Cursor to column n
234    // J = Erase in screen; 0 = right; 1 = left; 2 = total
235    // K = Erase in line; 0 = right; 1 = left; 2 = total
236    expected: [
237      // 0. Start
238      '\x1B[1G', '\x1B[0J',
239      prompt, '\x1B[3G',
240      // 1. UP
241      // This exceeds the maximum columns (250):
242      // Whitespace + prompt + ' // '.length + 'autocompleteMe'.length
243      // 230 + 2 + 4 + 14
244      '\x1B[1G', '\x1B[0J',
245      `${prompt}${' '.repeat(230)} aut`, '\x1B[237G',
246      ' // ocompleteMe', '\x1B[237G',
247      '\n// 123', '\x1B[237G',
248      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
249      '\x1B[0K',
250      // 2. UP
251      '\x1B[1G', '\x1B[0J',
252      `${prompt}${' '.repeat(229)} aut`, '\x1B[236G',
253      ' // ocompleteMe', '\x1B[236G',
254      '\n// 123', '\x1B[236G',
255      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
256      // Preview cleanup
257      '\x1B[0K',
258      // 3. UP
259      '\x1B[1G', '\x1B[0J',
260      // 'veryLongName'.repeat(30).length === 360
261      // prompt.length === 2
262      // 360 % 250 + 2 === 112 (+1)
263      `${prompt}${'veryLongName'.repeat(30)}`, '\x1B[113G',
264      // "// 'I should be previewed'".length + 86 === 112 (+1)
265      "\n// 'I should be previewed'", '\x1B[113G', '\x1B[1A',
266      // Preview cleanup
267      '\x1B[1B', '\x1B[2K', '\x1B[1A',
268      // 4. WORD LEFT
269      // Almost identical as above. Just one extra line.
270      // Math.floor(360 / 250) === 1
271      '\x1B[1A',
272      '\x1B[1G', '\x1B[0J',
273      `${prompt}${'veryLongName'.repeat(30)}`, '\x1B[3G', '\x1B[1A',
274      '\x1B[1B', "\n// 'I should be previewed'", '\x1B[3G', '\x1B[2A',
275      // Preview cleanup
276      '\x1B[2B', '\x1B[2K', '\x1B[2A',
277      // 5. UP
278      '\x1B[1G', '\x1B[0J',
279      `${prompt}e`, '\x1B[4G',
280      // '// RangeError: visible'.length - 19 === 3 (+1)
281      '\n// RangeError: visible', '\x1B[4G', '\x1B[1A',
282      // Preview cleanup
283      '\x1B[1B', '\x1B[2K', '\x1B[1A',
284      // 6. Backspace
285      '\x1B[1G', '\x1B[0J',
286      '> ', '\x1B[3G', 'x', '1',
287      `\n// '${'あ'.repeat(124)}'`,
288      '\x1B[5G', '\x1B[1A',
289      '\x1B[1B', '\x1B[2K', '\x1B[1A',
290      '\x1B[1G', '\x1B[0J',
291      '> x', '\x1B[4G', '2',
292      `\n// '${'�'.repeat(124)}'`,
293      '\x1B[5G', '\x1B[1A',
294      '\x1B[1B', '\x1B[2K', '\x1B[1A',
295      '\x1B[1G', '\x1B[0J',
296      '> x', '\x1B[4G', '3',
297      `\n// '${'�'.repeat(248)}'`,
298      '\x1B[5G', '\x1B[1A',
299      '\x1B[1B', '\x1B[2K', '\x1B[1A',
300      '\x1B[1G', '\x1B[0J',
301      '> x', '\x1B[4G', '4',
302      `\n// 'a${'\u0301'.repeat(1000)}'`,
303      '\x1B[5G', '\x1B[1A',
304      '\x1B[1B', '\x1B[2K', '\x1B[1A',
305      '\x1B[1G', '\x1B[0J',
306      '> ', '\x1B[3G', 'y', '1',
307      `\n// '${'あ'.repeat(121)}...`,
308      '\x1B[5G', '\x1B[1A',
309      '\x1B[1B', '\x1B[2K', '\x1B[1A',
310      '\x1B[1G', '\x1B[0J',
311      '> y', '\x1B[4G', '2',
312      `\n// '${'�'.repeat(121)}...`,
313      '\x1B[5G', '\x1B[1A',
314      '\x1B[1B', '\x1B[2K', '\x1B[1A',
315      '\x1B[1G', '\x1B[0J',
316      '> y', '\x1B[4G', '3',
317      `\n// '${'�'.repeat(242)}...`,
318      '\x1B[5G', '\x1B[1A',
319      '\x1B[1B', '\x1B[2K', '\x1B[1A',
320      '\r\n',
321      '\x1B[1G', '\x1B[0J',
322      '> ', '\x1B[3G',
323      '\r\n',
324    ],
325    clean: true
326  },
327  {
328    env: { NODE_REPL_HISTORY: defaultHistoryPath },
329    showEscapeCodes: true,
330    skip: !process.features.inspector,
331    checkTotal: true,
332    test: [
333      'au',
334      't',
335      RIGHT,
336      BACKSPACE,
337      LEFT,
338      LEFT,
339      'A',
340      BACKSPACE,
341      GO_TO_END,
342      BACKSPACE,
343      WORD_LEFT,
344      WORD_RIGHT,
345      ESCAPE,
346      ENTER,
347      UP,
348      LEFT,
349      ENTER,
350      UP,
351      ENTER,
352    ],
353    // C = Cursor n forward
354    // D = Cursor n back
355    // G = Cursor to column n
356    // J = Erase in screen; 0 = right; 1 = left; 2 = total
357    // K = Erase in line; 0 = right; 1 = left; 2 = total
358    expected: [
359      // 0.
360      // 'a'
361      '\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 'a',
362      // 'u'
363      'u', ' // tocompleteMe', '\x1B[5G',
364      '\n// 123', '\x1B[5G',
365      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
366      // 't' - Cleanup
367      '\x1B[0K',
368      't', ' // ocompleteMe', '\x1B[6G',
369      '\n// 123', '\x1B[6G',
370      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
371      // 1. Right. Cleanup
372      '\x1B[0K',
373      'ocompleteMe',
374      '\n// 123', '\x1B[17G',
375      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
376      // 2. Backspace. Refresh
377      '\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`, '\x1B[16G',
378      // Autocomplete and refresh?
379      ' // e', '\x1B[16G',
380      '\n// 123', '\x1B[16G',
381      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
382      // 3. Left. Cleanup
383      '\x1B[0K',
384      '\x1B[1D', '\x1B[16G', ' // e', '\x1B[15G',
385      // 4. Left. Cleanup
386      '\x1B[16G', '\x1B[0K', '\x1B[15G',
387      '\x1B[1D', '\x1B[16G', ' // e', '\x1B[14G',
388      // 5. 'A' - Cleanup
389      '\x1B[16G', '\x1B[0K', '\x1B[14G',
390      // Refresh
391      '\x1B[1G', '\x1B[0J', `${prompt}autocompletAeM`, '\x1B[15G',
392      // 6. Backspace. Refresh
393      '\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`,
394      '\x1B[14G', '\x1B[16G', ' // e',
395      '\x1B[14G', '\x1B[16G', ' // e',
396      '\x1B[14G', '\x1B[16G',
397      // 7. Go to end. Cleanup
398      '\x1B[0K', '\x1B[14G', '\x1B[2C',
399      'e',
400      '\n// 123', '\x1B[17G',
401      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
402      // 8. Backspace. Refresh
403      '\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`, '\x1B[16G',
404      // Autocomplete
405      ' // e', '\x1B[16G',
406      '\n// 123', '\x1B[16G',
407      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
408      // 9. Word left. Cleanup
409      '\x1B[0K', '\x1B[13D', '\x1B[16G', ' // e', '\x1B[3G', '\x1B[16G',
410      // 10. Word right. Cleanup
411      '\x1B[0K', '\x1B[3G', '\x1B[13C', ' // e', '\x1B[16G',
412      '\n// 123', '\x1B[16G',
413      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
414      // 11. ESCAPE
415      '\x1B[0K',
416      // 12. ENTER
417      '\r\n',
418      'Uncaught ReferenceError: autocompleteM is not defined\n',
419      '\x1B[1G', '\x1B[0J',
420      // 13. UP
421      prompt, '\x1B[3G', '\x1B[1G', '\x1B[0J',
422      `${prompt}autocompleteM`, '\x1B[16G',
423      ' // e', '\x1B[16G',
424      '\n// 123', '\x1B[16G',
425      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
426      // 14. LEFT
427      '\x1B[0K', '\x1B[1D', '\x1B[16G',
428      ' // e', '\x1B[15G', '\x1B[16G',
429      // 15. ENTER
430      '\x1B[0K', '\x1B[15G', '\x1B[1C',
431      '\r\n',
432      'Uncaught ReferenceError: autocompleteM is not defined\n',
433      '\x1B[1G', '\x1B[0J',
434      prompt, '\x1B[3G',
435      // 16. UP
436      '\x1B[1G', '\x1B[0J',
437      `${prompt}autocompleteM`, '\x1B[16G',
438      ' // e', '\x1B[16G',
439      '\n// 123', '\x1B[16G',
440      '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A',
441      '\x1B[0K',
442      // 17. ENTER
443      'e', '\r\n',
444      '123\n',
445      '\x1B[1G', '\x1B[0J',
446      prompt, '\x1B[3G',
447      '\r\n',
448    ],
449    clean: true
450  },
451  {
452    // Check changed inspection defaults.
453    env: { NODE_REPL_HISTORY: defaultHistoryPath },
454    skip: !process.features.inspector,
455    test: [
456      'util.inspect.replDefaults.showHidden',
457      ENTER,
458    ],
459    expected: [],
460    clean: false
461  },
462  {
463    env: { NODE_REPL_HISTORY: defaultHistoryPath },
464    skip: !process.features.inspector,
465    checkTotal: true,
466    test: [
467      '[ ]',
468      WORD_LEFT,
469      WORD_LEFT,
470      UP,
471      ' = true',
472      ENTER,
473      '[ ]',
474      ENTER,
475    ],
476    expected: [
477      prompt,
478      '[', ' ', ']',
479      '\n// []', '\n// []', '\n// []',
480      '> util.inspect.replDefaults.showHidden',
481      '\n// false',
482      ' ', '=', ' ', 't', 'r', 'u', 'e',
483      'true\n',
484      '> ', '[', ' ', ']',
485      '\n// [ [length]: 0 ]',
486      '[ [length]: 0 ]\n',
487      '> ',
488    ],
489    clean: true
490  },
491  {
492    // Check that the completer ignores completions that are outdated.
493    env: { NODE_REPL_HISTORY: defaultHistoryPath },
494    completer(line, callback) {
495      if (line.endsWith(WAIT)) {
496        if (completions++ === 0) {
497          callback(null, [[`${WAIT}WOW`], line]);
498        } else {
499          setTimeout(callback, 1000, null, [[`${WAIT}WOW`], line]).unref();
500        }
501      } else {
502        callback(null, [[' Always visible'], line]);
503      }
504    },
505    skip: !process.features.inspector,
506    test: [
507      WAIT, // The first call is awaited before new input is triggered!
508      BACKSPACE,
509      's',
510      BACKSPACE,
511      WAIT, // The second call is not awaited. It won't trigger the preview.
512      BACKSPACE,
513      's',
514      BACKSPACE,
515    ],
516    expected: [
517      prompt,
518      WAIT,
519      ' // WOW',
520      prompt,
521      's',
522      ' // Always visible',
523      prompt,
524      WAIT,
525      prompt,
526      's',
527      ' // Always visible',
528      prompt,
529    ],
530    clean: true
531  },
532  {
533    env: { NODE_REPL_HISTORY: defaultHistoryPath },
534    test: (function*() {
535      // Deleting Array iterator should not break history feature.
536      //
537      // Using a generator function instead of an object to allow the test to
538      // keep iterating even when Array.prototype[Symbol.iterator] has been
539      // deleted.
540      yield 'const ArrayIteratorPrototype =';
541      yield '  Object.getPrototypeOf(Array.prototype[Symbol.iterator]());';
542      yield ENTER;
543      yield 'const {next} = ArrayIteratorPrototype;';
544      yield ENTER;
545      yield 'const realArrayIterator = Array.prototype[Symbol.iterator];';
546      yield ENTER;
547      yield 'delete Array.prototype[Symbol.iterator];';
548      yield ENTER;
549      yield 'delete ArrayIteratorPrototype.next;';
550      yield ENTER;
551      yield UP;
552      yield UP;
553      yield DOWN;
554      yield DOWN;
555      yield 'fu';
556      yield 'n';
557      yield RIGHT;
558      yield BACKSPACE;
559      yield LEFT;
560      yield LEFT;
561      yield 'A';
562      yield BACKSPACE;
563      yield GO_TO_END;
564      yield BACKSPACE;
565      yield WORD_LEFT;
566      yield WORD_RIGHT;
567      yield ESCAPE;
568      yield ENTER;
569      yield 'Array.proto';
570      yield RIGHT;
571      yield '.pu';
572      yield ENTER;
573      yield 'ArrayIteratorPrototype.next = next;';
574      yield ENTER;
575      yield 'Array.prototype[Symbol.iterator] = realArrayIterator;';
576      yield ENTER;
577    })(),
578    expected: [],
579    clean: false
580  },
581  {
582    env: { NODE_REPL_HISTORY: defaultHistoryPath },
583    test: ['const util = {}', ENTER,
584           'ut', RIGHT, ENTER],
585    expected: [
586      prompt, ...'const util = {}',
587      'undefined\n',
588      prompt, ...'ut', ...(prev ? [' // il', '\n// {}',
589                                   'il', '\n// {}'] : [' // il', 'il']),
590      '{}\n',
591      prompt,
592    ],
593    clean: false
594  },
595  {
596    env: { NODE_REPL_HISTORY: defaultHistoryPath },
597    test: [
598      'const utilDesc = Reflect.getOwnPropertyDescriptor(globalThis, "util")',
599      ENTER,
600      'globalThis.util = {}', ENTER,
601      'ut', RIGHT, ENTER,
602      'Reflect.defineProperty(globalThis, "util", utilDesc)', ENTER],
603    expected: [
604      prompt, ...'const utilDesc = ' +
605      'Reflect.getOwnPropertyDescriptor(globalThis, "util")',
606      'undefined\n',
607      prompt, ...'globalThis.util = {}',
608      '{}\n',
609      prompt, ...'ut', ' // il', 'il',
610      '{}\n',
611      prompt, ...'Reflect.defineProperty(globalThis, "util", utilDesc)',
612      'true\n',
613      prompt,
614    ],
615    clean: false
616  },
617  {
618    // Test that preview should not be removed when pressing ESCAPE key
619    env: { NODE_REPL_HISTORY: defaultHistoryPath },
620    skip: !process.features.inspector,
621    test: [
622      '1+1',
623      ESCAPE,
624      ENTER,
625    ],
626    expected: [
627      prompt, ...'1+1',
628      '\n// 2',
629      '\n// 2',
630      '2\n',
631      prompt,
632    ],
633    clean: false
634  },
635];
636const numtests = tests.length;
637
638const runTestWrap = common.mustCall(runTest, numtests);
639
640function cleanupTmpFile() {
641  try {
642    // Write over the file, clearing any history
643    fs.writeFileSync(defaultHistoryPath, '');
644  } catch (err) {
645    if (err.code === 'ENOENT') return true;
646    throw err;
647  }
648  return true;
649}
650
651function runTest() {
652  const opts = tests.shift();
653  if (!opts) return; // All done
654
655  const { expected, skip } = opts;
656
657  // Test unsupported on platform.
658  if (skip) {
659    setImmediate(runTestWrap, true);
660    return;
661  }
662  const lastChunks = [];
663  let i = 0;
664
665  REPL.createInternalRepl(opts.env, {
666    input: new ActionStream(),
667    output: new stream.Writable({
668      write(chunk, _, next) {
669        const output = chunk.toString();
670
671        if (!opts.showEscapeCodes &&
672            (output[0] === '\x1B' || /^[\r\n]+$/.test(output))) {
673          return next();
674        }
675
676        lastChunks.push(output);
677
678        if (expected.length && !opts.checkTotal) {
679          try {
680            assert.strictEqual(output, expected[i]);
681          } catch (e) {
682            console.error(`Failed test # ${numtests - tests.length}`);
683            console.error('Last outputs: ' + inspect(lastChunks, {
684              breakLength: 5, colors: true
685            }));
686            throw e;
687          }
688          // TODO(BridgeAR): Auto close on last chunk!
689          i++;
690        }
691
692        next();
693      }
694    }),
695    completer: opts.completer,
696    prompt,
697    useColors: false,
698    preview: opts.preview,
699    terminal: true
700  }, function(err, repl) {
701    if (err) {
702      console.error(`Failed test # ${numtests - tests.length}`);
703      throw err;
704    }
705
706    repl.once('close', () => {
707      if (opts.clean)
708        cleanupTmpFile();
709
710      if (opts.checkTotal) {
711        assert.deepStrictEqual(lastChunks, expected);
712      } else if (expected.length !== i) {
713        console.error(tests[numtests - tests.length - 1]);
714        throw new Error(`Failed test # ${numtests - tests.length}`);
715      }
716
717      setImmediate(runTestWrap, true);
718    });
719
720    if (opts.columns) {
721      Object.defineProperty(repl, 'columns', {
722        value: opts.columns,
723        enumerable: true
724      });
725    }
726    repl.input.run(opts.test);
727  });
728}
729
730// run the tests
731runTest();
732