Skip to content

Commit

Permalink
test: add multiple repl preview tests
Browse files Browse the repository at this point in the history
This improves the coverage for the preview feature signficantly.
Quite a few edge cases get testet here to prevent regressions.

PR-URL: nodejs#30907
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
BridgeAR authored and targos committed Apr 25, 2020
1 parent e800888 commit aaf2cf2
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 13 deletions.
214 changes: 203 additions & 11 deletions test/parallel/test-repl-history-navigation.js
Expand Up @@ -8,6 +8,7 @@ const REPL = require('internal/repl');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { inspect } = require('util');

const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
Expand All @@ -17,6 +18,7 @@ const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
// Create an input stream specialized for testing an array of actions
class ActionStream extends stream.Stream {
run(data) {
let reallyWait = true;
const _iter = data[Symbol.iterator]();
const doAction = () => {
const next = _iter.next();
Expand All @@ -32,24 +34,34 @@ class ActionStream extends stream.Stream {
} else {
this.emit('data', `${action}`);
}
setImmediate(doAction);
if (action === WAIT && reallyWait) {
setTimeout(doAction, common.platformTimeout(50));
reallyWait = false;
} else {
setImmediate(doAction);
}
};
setImmediate(doAction);
doAction();
}
resume() {}
pause() {}
}
ActionStream.prototype.readable = true;


// Mock keys
const ENTER = { name: 'enter' };
const UP = { name: 'up' };
const DOWN = { name: 'down' };
const LEFT = { name: 'left' };
const RIGHT = { name: 'right' };
const DELETE = { name: 'delete' };
const BACKSPACE = { name: 'backspace' };
const WORD_LEFT = { name: 'left', ctrl: true };
const WORD_RIGHT = { name: 'right', ctrl: true };
const GO_TO_END = { name: 'end' };

const prompt = '> ';
const WAIT = '€';

const prev = process.features.inspector;

Expand Down Expand Up @@ -91,6 +103,162 @@ const tests = [
' 2025, 2116, 2209, ...',
prompt].filter((e) => typeof e === 'string'),
clean: true
},
{
env: { NODE_REPL_HISTORY: defaultHistoryPath },
skip: !process.features.inspector,
test: [
`const ${'veryLongName'.repeat(30)} = 'I should not be previewed'`,
ENTER,
'const e = new RangeError("visible\\ninvisible")',
ENTER,
'e',
ENTER,
'veryLongName'.repeat(30),
ENTER,
`${'\x1B[90m \x1B[39m'.repeat(235)} fun`,
ENTER,
`${' '.repeat(236)} fun`,
ENTER
],
expected: [],
clean: false
},
{
env: { NODE_REPL_HISTORY: defaultHistoryPath },
columns: 250,
skip: !process.features.inspector,
test: [
UP,
UP,
UP,
UP,
BACKSPACE
],
expected: [
prompt,
// This exceeds the maximum columns (250):
// Whitespace + prompt + ' // '.length + 'function'.length
// 236 + 2 + 4 + 8
`${prompt}${' '.repeat(236)} fun`,
`${prompt}${' '.repeat(235)} fun`,
' // ction',
' // ction',
`${prompt}${'veryLongName'.repeat(30)}`,
`${prompt}e`,
'\n// RangeError: visible',
prompt
],
clean: true
},
{
env: { NODE_REPL_HISTORY: defaultHistoryPath },
showEscapeCodes: true,
skip: !process.features.inspector,
test: [
'fun',
RIGHT,
BACKSPACE,
LEFT,
LEFT,
'A',
BACKSPACE,
GO_TO_END,
BACKSPACE,
WORD_LEFT,
WORD_RIGHT,
ENTER
],
// C = Cursor forward
// D = Cursor back
// G = Cursor to column n
// J = Erase in screen
// K = Erase in line
expected: [
// 0.
// 'f'
'\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 'f',
// 'u'
'u', ' // nction', '\x1B[5G',
// 'n' - Cleanup
'\x1B[0K',
'n', ' // ction', '\x1B[6G',
// 1. Right. Cleanup
'\x1B[0K',
'ction',
// 2. Backspace. Refresh
'\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G',
// Autocomplete and refresh?
' // n', '\x1B[10G', ' // n', '\x1B[10G',
// 3. Left. Cleanup
'\x1B[0K',
'\x1B[1D', '\x1B[10G', ' // n', '\x1B[9G',
// 4. Left. Cleanup
'\x1B[10G', '\x1B[0K', '\x1B[9G',
'\x1B[1D', '\x1B[10G', ' // n', '\x1B[8G',
// 5. 'A' - Cleanup
'\x1B[10G', '\x1B[0K', '\x1B[8G',
// Refresh
'\x1B[1G', '\x1B[0J', `${prompt}functAio`, '\x1B[9G',
// 6. Backspace. Refresh
'\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[8G', '\x1B[10G', ' // n',
'\x1B[8G', '\x1B[10G', ' // n',
'\x1B[8G', '\x1B[10G',
// 7. Go to end. Cleanup
'\x1B[0K', '\x1B[8G', '\x1B[2C',
'n',
// 8. Backspace. Refresh
'\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G',
// Autocomplete
' // n', '\x1B[10G', ' // n', '\x1B[10G',
// 9. Word left. Cleanup
'\x1B[0K', '\x1B[7D', '\x1B[10G', ' // n', '\x1B[3G', '\x1B[10G',
// 10. Word right. Cleanup
'\x1B[0K', '\x1B[3G', '\x1B[7C', ' // n', '\x1B[10G',
'\x1B[0K',
],
clean: true
},
{
// Check that the completer ignores completions that are outdated.
env: { NODE_REPL_HISTORY: defaultHistoryPath },
completer(line, callback) {
if (line.endsWith(WAIT)) {
setTimeout(
callback,
common.platformTimeout(40),
null,
[[`${WAIT}WOW`], line]
);
} else {
callback(null, [[' Always visible'], line]);
}
},
skip: !process.features.inspector,
test: [
WAIT, // The first call is awaited before new input is triggered!
BACKSPACE,
's',
BACKSPACE,
WAIT, // The second call is not awaited. It won't trigger the preview.
BACKSPACE,
's',
BACKSPACE
],
expected: [
prompt,
WAIT,
' // WOW',
prompt,
's',
' // Always visible',
prompt,
WAIT,
prompt,
's',
' // Always visible',
],
clean: true
}
];
const numtests = tests.length;
Expand All @@ -112,29 +280,47 @@ function runTest() {
const opts = tests.shift();
if (!opts) return; // All done

const env = opts.env;
const test = opts.test;
const expected = opts.expected;
const { expected, skip } = opts;

// Test unsupported on platform.
if (skip) {
setImmediate(runTestWrap, true);
return;
}

const lastChunks = [];

REPL.createInternalRepl(env, {
REPL.createInternalRepl(opts.env, {
input: new ActionStream(),
output: new stream.Writable({
write(chunk, _, next) {
const output = chunk.toString();

if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output)) {
if (!opts.showEscapeCodes &&
output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output)) {
return next();
}

lastChunks.push(inspect(output));

if (expected.length) {
assert.strictEqual(output, expected[0]);
try {
assert.strictEqual(output, expected[0]);
} catch (e) {
console.error(`Failed test # ${numtests - tests.length}`);
console.error('Last outputs: ' + inspect(lastChunks, {
breakLength: 5, colors: true
}));
throw e;
}
expected.shift();
}

next();
}
}),
prompt: prompt,
completer: opts.completer,
prompt,
useColors: false,
terminal: true
}, function(err, repl) {
Expand All @@ -153,7 +339,13 @@ function runTest() {
setImmediate(runTestWrap, true);
});

repl.inputStream.run(test);
if (opts.columns) {
Object.defineProperty(repl, 'columns', {
value: opts.columns,
enumerable: true
});
}
repl.inputStream.run(opts.test);
});
}

Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-repl-preview.js
Expand Up @@ -3,7 +3,7 @@
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const Repl = require('repl');
const { REPLServer } = require('repl');

common.skipIfInspectorDisabled();

Expand Down Expand Up @@ -52,7 +52,7 @@ function runAndWait(cmds, repl) {
}

async function tests(options) {
const repl = Repl.start({
const repl = REPLServer({
prompt: PROMPT,
stream: new REPLStream(),
ignoreUndefined: true,
Expand Down
1 change: 1 addition & 0 deletions test/root.status
Expand Up @@ -64,6 +64,7 @@ parallel/test-next-tick-fixed-queue-regression: SLOW
parallel/test-npm-install: SLOW
parallel/test-preload: SLOW
parallel/test-repl: SLOW
parallel/test-repl-history-navigation.js: SLOW
parallel/test-repl-tab-complete: SLOW
parallel/test-repl-top-level-await: SLOW
parallel/test-stdio-pipe-access: SLOW
Expand Down

0 comments on commit aaf2cf2

Please sign in to comment.