• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
19
20const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
21
22// Create an input stream specialized for testing an array of actions
23class ActionStream extends stream.Stream {
24  run(data) {
25    const _iter = data[Symbol.iterator]();
26    const doAction = () => {
27      const next = _iter.next();
28      if (next.done) {
29        // Close the repl. Note that it must have a clean prompt to do so.
30        this.emit('keypress', '', { ctrl: true, name: 'd' });
31        return;
32      }
33      const action = next.value;
34
35      if (typeof action === 'object') {
36        this.emit('keypress', '', action);
37      } else {
38        this.emit('data', `${action}`);
39      }
40      setImmediate(doAction);
41    };
42    doAction();
43  }
44  resume() {}
45  pause() {}
46}
47ActionStream.prototype.readable = true;
48
49// Mock keys
50const ENTER = { name: 'enter' };
51const UP = { name: 'up' };
52const DOWN = { name: 'down' };
53const LEFT = { name: 'left' };
54const RIGHT = { name: 'right' };
55const BACKSPACE = { name: 'backspace' };
56const TABULATION = { name: 'tab' };
57const WORD_LEFT = { name: 'left', ctrl: true };
58const WORD_RIGHT = { name: 'right', ctrl: true };
59const GO_TO_END = { name: 'end' };
60const SIGINT = { name: 'c', ctrl: true };
61const ESCAPE = { name: 'escape', meta: true };
62
63const prompt = '> ';
64
65const tests = [
66  {
67    env: { NODE_REPL_HISTORY: defaultHistoryPath },
68    test: (function*() {
69      // Deleting Array iterator should not break history feature.
70      //
71      // Using a generator function instead of an object to allow the test to
72      // keep iterating even when Array.prototype[Symbol.iterator] has been
73      // deleted.
74      yield 'const ArrayIteratorPrototype =';
75      yield '  Object.getPrototypeOf(Array.prototype[Symbol.iterator]());';
76      yield ENTER;
77      yield 'const {next} = ArrayIteratorPrototype;';
78      yield ENTER;
79      yield 'const realArrayIterator = Array.prototype[Symbol.iterator];';
80      yield ENTER;
81      yield 'delete Array.prototype[Symbol.iterator];';
82      yield ENTER;
83      yield 'delete ArrayIteratorPrototype.next;';
84      yield ENTER;
85      yield UP;
86      yield UP;
87      yield DOWN;
88      yield DOWN;
89      yield 'fu';
90      yield 'n';
91      yield RIGHT;
92      yield BACKSPACE;
93      yield LEFT;
94      yield LEFT;
95      yield 'A';
96      yield BACKSPACE;
97      yield GO_TO_END;
98      yield BACKSPACE;
99      yield WORD_LEFT;
100      yield WORD_RIGHT;
101      yield ESCAPE;
102      yield ENTER;
103      yield 'require("./';
104      yield TABULATION;
105      yield SIGINT;
106      yield 'import("./';
107      yield TABULATION;
108      yield SIGINT;
109      yield 'Array.proto';
110      yield RIGHT;
111      yield '.pu';
112      yield ENTER;
113      yield 'ArrayIteratorPrototype.next = next;';
114      yield ENTER;
115      yield 'Array.prototype[Symbol.iterator] = realArrayIterator;';
116      yield ENTER;
117    })(),
118    expected: [],
119    clean: false
120  },
121];
122const numtests = tests.length;
123
124const runTestWrap = common.mustCall(runTest, numtests);
125
126function cleanupTmpFile() {
127  try {
128    // Write over the file, clearing any history
129    fs.writeFileSync(defaultHistoryPath, '');
130  } catch (err) {
131    if (err.code === 'ENOENT') return true;
132    throw err;
133  }
134  return true;
135}
136
137function runTest() {
138  const opts = tests.shift();
139  if (!opts) return; // All done
140
141  const { expected, skip } = opts;
142
143  // Test unsupported on platform.
144  if (skip) {
145    setImmediate(runTestWrap, true);
146    return;
147  }
148  const lastChunks = [];
149  let i = 0;
150
151  REPL.createInternalRepl(opts.env, {
152    input: new ActionStream(),
153    output: new stream.Writable({
154      write(chunk, _, next) {
155        const output = chunk.toString();
156
157        if (!opts.showEscapeCodes &&
158            (output[0] === '\x1B' || /^[\r\n]+$/.test(output))) {
159          return next();
160        }
161
162        lastChunks.push(output);
163
164        if (expected.length && !opts.checkTotal) {
165          try {
166            assert.strictEqual(output, expected[i]);
167          } catch (e) {
168            console.error(`Failed test # ${numtests - tests.length}`);
169            console.error('Last outputs: ' + inspect(lastChunks, {
170              breakLength: 5, colors: true
171            }));
172            throw e;
173          }
174          // TODO(BridgeAR): Auto close on last chunk!
175          i++;
176        }
177
178        next();
179      }
180    }),
181    allowBlockingCompletions: true,
182    completer: opts.completer,
183    prompt,
184    useColors: false,
185    preview: opts.preview,
186    terminal: true
187  }, function(err, repl) {
188    if (err) {
189      console.error(`Failed test # ${numtests - tests.length}`);
190      throw err;
191    }
192
193    repl.once('close', () => {
194      if (opts.clean)
195        cleanupTmpFile();
196
197      if (opts.checkTotal) {
198        assert.deepStrictEqual(lastChunks, expected);
199      } else if (expected.length !== i) {
200        console.error(tests[numtests - tests.length - 1]);
201        throw new Error(`Failed test # ${numtests - tests.length}`);
202      }
203
204      setImmediate(runTestWrap, true);
205    });
206
207    if (opts.columns) {
208      Object.defineProperty(repl, 'columns', {
209        value: opts.columns,
210        enumerable: true
211      });
212    }
213    repl.input.run(opts.test);
214  });
215}
216
217// run the tests
218runTest();
219