Files
node/test/parallel/test-repl-reverse-search.js
Adam Majer df8db42082 test: skip some console tests on dumb terminal
Add capabilities to common test module to detect and skip tests
on dumb terminals.

In some of our build environments, like s390x, the terminal
is a dumb terminal meaning it has very rudimentary capabilities.
These in turn prevent some of the tests from completing with errors
as below.

    not ok 1777 parallel/test-readline-tab-complete
      ---
      duration_ms: 0.365
      severity: fail
      exitcode: 1
      stack: |-
        assert.js:103
          throw new AssertionError(obj);
          ^

        AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

        '\t' !== ''

            at /home/abuild/rpmbuild/BUILD/node-git.8698dd98bb/test/parallel/test-readline-tab-complete.js:63:14
            at Array.forEach (<anonymous>)
            at /home/abuild/rpmbuild/BUILD/node-git.8698dd98bb/test/parallel/test-readline-tab-complete.js:18:17
            at Array.forEach (<anonymous>)
            at Object.<anonymous> (/home/abuild/rpmbuild/BUILD/node-git.8698dd98bb/test/parallel/test-readline-tab-complete.js:17:3)
            at Module._compile (internal/modules/cjs/loader.js:1176:30)
            at Object.Module._extensions..js (internal/modules/cjs/loader.js:1196:10)
            at Module.load (internal/modules/cjs/loader.js:1040:32)
            at Function.Module._load (internal/modules/cjs/loader.js:929:14)
            at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12) {
          generatedMessage: true,
          code: 'ERR_ASSERTION',
          actual: '\t',
          expected: '',
          operator: 'strictEqual'
        }
      ...

PR-URL: https://github.com/nodejs/node/pull/33165
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
2020-05-08 01:26:01 +02:00

365 lines
9.7 KiB
JavaScript

'use strict';
// Flags: --expose-internals
const common = require('../common');
const stream = require('stream');
const REPL = require('internal/repl');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { inspect } = require('util');
common.skipIfDumbTerminal();
common.allowGlobals('aaaa');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
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) {
const _iter = data[Symbol.iterator]();
const doAction = () => {
const next = _iter.next();
if (next.done) {
// Close the repl. Note that it must have a clean prompt to do so.
this.emit('keypress', '', { ctrl: true, name: 'd' });
return;
}
const action = next.value;
if (typeof action === 'object') {
this.emit('keypress', '', action);
} else {
this.emit('data', `${action}`);
}
setImmediate(doAction);
};
doAction();
}
resume() {}
pause() {}
}
ActionStream.prototype.readable = true;
// Mock keys
const ENTER = { name: 'enter' };
const UP = { name: 'up' };
const DOWN = { name: 'down' };
const BACKSPACE = { name: 'backspace' };
const SEARCH_BACKWARDS = { name: 'r', ctrl: true };
const SEARCH_FORWARDS = { name: 's', ctrl: true };
const ESCAPE = { name: 'escape' };
const CTRL_C = { name: 'c', ctrl: true };
const DELETE_WORD_LEFT = { name: 'w', ctrl: true };
const prompt = '> ';
// TODO(BridgeAR): Add tests for lines that exceed the maximum columns.
const tests = [
{ // Creates few history to navigate for
env: { NODE_REPL_HISTORY: defaultHistoryPath },
test: [
'console.log("foo")', ENTER,
'ab = "aaaa"', ENTER,
'repl.repl.historyIndex', ENTER,
'console.log("foo")', ENTER,
'let ba = 9', ENTER,
'ab = "aaaa"', ENTER,
'555 - 909', ENTER,
'{key : {key2 :[] }}', ENTER,
'Array(100).fill(1)', ENTER
],
expected: [],
clean: false
},
{
env: { NODE_REPL_HISTORY: defaultHistoryPath },
showEscapeCodes: true,
checkTotal: true,
useColors: true,
test: [
'7', // 1
SEARCH_FORWARDS,
SEARCH_FORWARDS, // 3
'a',
SEARCH_BACKWARDS, // 5
SEARCH_FORWARDS,
SEARCH_BACKWARDS, // 7
'a',
BACKSPACE, // 9
DELETE_WORD_LEFT,
'aa', // 11
SEARCH_BACKWARDS,
SEARCH_BACKWARDS, // 13
SEARCH_BACKWARDS,
SEARCH_BACKWARDS, // 15
SEARCH_FORWARDS,
ESCAPE, // 17
ENTER
],
// A = Cursor n up
// B = Cursor n down
// C = Cursor n forward
// D = Cursor n back
// G = Cursor to column n
// J = Erase in screen; 0 = right; 1 = left; 2 = total
// K = Erase in line; 0 = right; 1 = left; 2 = total
expected: [
// 0. Start
'\x1B[1G', '\x1B[0J',
prompt, '\x1B[3G',
// 1. '7'
'7',
// 2. SEARCH FORWARDS
'\nfwd-i-search: _', '\x1B[1A', '\x1B[4G',
// 3. SEARCH FORWARDS
'\x1B[3G', '\x1B[0J',
'7\nfwd-i-search: _', '\x1B[1A', '\x1B[4G',
// 4. 'a'
'\x1B[3G', '\x1B[0J',
'7\nfailed-fwd-i-search: a_', '\x1B[1A', '\x1B[4G',
// 5. SEARCH BACKWARDS
'\x1B[3G', '\x1B[0J',
'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
'\x1B[1A', '\x1B[6G',
// 6. SEARCH FORWARDS
'\x1B[3G', '\x1B[0J',
'7\nfailed-fwd-i-search: a_', '\x1B[1A', '\x1B[4G',
// 7. SEARCH BACKWARDS
'\x1B[3G', '\x1B[0J',
'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
'\x1B[1A', '\x1B[6G',
// 8. 'a'
'\x1B[3G', '\x1B[0J',
'ab = "aa\x1B[4maa\x1B[24m"\nbck-i-search: aa_',
'\x1B[1A', '\x1B[11G',
// 9. BACKSPACE
'\x1B[3G', '\x1B[0J',
'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
'\x1B[1A', '\x1B[6G',
// 10. DELETE WORD LEFT (works as backspace)
'\x1B[3G', '\x1B[0J',
'7\nbck-i-search: _', '\x1B[1A', '\x1B[4G',
// 11. 'a'
'\x1B[3G', '\x1B[0J',
'Arr\x1B[4ma\x1B[24my(100).fill(1)\nbck-i-search: a_',
'\x1B[1A', '\x1B[6G',
// 11. 'aa' - continued
'\x1B[3G', '\x1B[0J',
'ab = "aa\x1B[4maa\x1B[24m"\nbck-i-search: aa_',
'\x1B[1A', '\x1B[11G',
// 12. SEARCH BACKWARDS
'\x1B[3G', '\x1B[0J',
'ab = "a\x1B[4maa\x1B[24ma"\nbck-i-search: aa_',
'\x1B[1A', '\x1B[10G',
// 13. SEARCH BACKWARDS
'\x1B[3G', '\x1B[0J',
'ab = "\x1B[4maa\x1B[24maa"\nbck-i-search: aa_',
'\x1B[1A', '\x1B[9G',
// 14. SEARCH BACKWARDS
'\x1B[3G', '\x1B[0J',
'7\nfailed-bck-i-search: aa_', '\x1B[1A', '\x1B[4G',
// 15. SEARCH BACKWARDS
'\x1B[3G', '\x1B[0J',
'7\nfailed-bck-i-search: aa_', '\x1B[1A', '\x1B[4G',
// 16. SEARCH FORWARDS
'\x1B[3G', '\x1B[0J',
'ab = "\x1B[4maa\x1B[24maa"\nfwd-i-search: aa_',
'\x1B[1A', '\x1B[9G',
// 17. ESCAPE
'\x1B[3G', '\x1B[0J',
'7',
// 18. ENTER
'\r\n',
'\x1B[33m7\x1B[39m\n',
'\x1B[1G', '\x1B[0J',
prompt,
'\x1B[3G',
'\r\n'
],
clean: false
},
{
env: { NODE_REPL_HISTORY: defaultHistoryPath },
showEscapeCodes: true,
skip: !process.features.inspector,
checkTotal: true,
useColors: false,
test: [
'fu', // 1
SEARCH_BACKWARDS,
'}', // 3
SEARCH_BACKWARDS,
CTRL_C, // 5
CTRL_C,
'1+1', // 7
ENTER,
SEARCH_BACKWARDS, // 9
'+',
'\r', // 11
'2',
SEARCH_BACKWARDS, // 13
're',
UP, // 15
DOWN,
SEARCH_FORWARDS, // 17
'\n'
],
expected: [
'\x1B[1G', '\x1B[0J',
prompt, '\x1B[3G',
'f', 'u', ' // nction',
'\x1B[5G', '\x1B[0K',
'\nbck-i-search: _', '\x1B[1A', '\x1B[5G',
'\x1B[3G', '\x1B[0J',
'{key : {key2 :[] }}\nbck-i-search: }_', '\x1B[1A', '\x1B[21G',
'\x1B[3G', '\x1B[0J',
'{key : {key2 :[] }}\nbck-i-search: }_', '\x1B[1A', '\x1B[20G',
'\x1B[3G', '\x1B[0J',
'fu',
'\r\n',
'\x1B[1G', '\x1B[0J',
prompt, '\x1B[3G',
'1', '+', '1', '\n// 2', '\x1B[6G', '\x1B[1A',
'\x1B[1B', '\x1B[2K', '\x1B[1A',
'\r\n',
'2\n',
'\x1B[1G', '\x1B[0J',
prompt, '\x1B[3G',
'\nbck-i-search: _', '\x1B[1A',
'\x1B[3G', '\x1B[0J',
'1+1\nbck-i-search: +_', '\x1B[1A', '\x1B[4G',
'\x1B[3G', '\x1B[0J',
'1+1', '\x1B[4G',
'\x1B[2C',
'\r\n',
'2\n',
'\x1B[1G', '\x1B[0J',
prompt, '\x1B[3G',
'2', '\n// 2', '\x1B[4G', '\x1B[1A',
'\x1B[1B', '\x1B[2K', '\x1B[1A',
'\nbck-i-search: _', '\x1B[1A', '\x1B[4G',
'\x1B[3G', '\x1B[0J',
'Array(100).fill(1)\nbck-i-search: r_', '\x1B[1A', '\x1B[5G',
'\x1B[3G', '\x1B[0J',
'repl.repl.historyIndex\nbck-i-search: re_', '\x1B[1A', '\x1B[8G',
'\x1B[3G', '\x1B[0J',
'repl.repl.historyIndex', '\x1B[8G',
'\x1B[1G', '\x1B[0J',
`${prompt}ab = "aaaa"`, '\x1B[14G',
'\x1B[1G', '\x1B[0J',
`${prompt}repl.repl.historyIndex`, '\x1B[25G', '\n// 8',
'\x1B[25G', '\x1B[1A',
'\x1B[1B', '\x1B[2K', '\x1B[1A',
'\nfwd-i-search: _', '\x1B[1A', '\x1B[25G',
'\x1B[3G', '\x1B[0J',
'repl.repl.historyIndex',
'\r\n',
'-1\n',
'\x1B[1G', '\x1B[0J',
prompt, '\x1B[3G',
'\r\n'
],
clean: false
}
];
const numtests = tests.length;
const runTestWrap = common.mustCall(runTest, numtests);
function cleanupTmpFile() {
try {
// Write over the file, clearing any history
fs.writeFileSync(defaultHistoryPath, '');
} catch (err) {
if (err.code === 'ENOENT') return true;
throw err;
}
return true;
}
function runTest() {
const opts = tests.shift();
if (!opts) return; // All done
const { expected, skip } = opts;
// Test unsupported on platform.
if (skip) {
setImmediate(runTestWrap, true);
return;
}
const lastChunks = [];
let i = 0;
REPL.createInternalRepl(opts.env, {
input: new ActionStream(),
output: new stream.Writable({
write(chunk, _, next) {
const output = chunk.toString();
if (!opts.showEscapeCodes &&
(output[0] === '\x1B' || /^[\r\n]+$/.test(output))) {
return next();
}
lastChunks.push(output);
if (expected.length && !opts.checkTotal) {
try {
assert.strictEqual(output, expected[i]);
} catch (e) {
console.error(`Failed test # ${numtests - tests.length}`);
console.error('Last outputs: ' + inspect(lastChunks, {
breakLength: 5, colors: true
}));
throw e;
}
i++;
}
next();
}
}),
completer: opts.completer,
prompt,
useColors: opts.useColors || false,
terminal: true
}, function(err, repl) {
if (err) {
console.error(`Failed test # ${numtests - tests.length}`);
throw err;
}
repl.once('close', () => {
if (opts.clean)
cleanupTmpFile();
if (opts.checkTotal) {
assert.deepStrictEqual(lastChunks, expected);
} else if (expected.length !== i) {
console.error(tests[numtests - tests.length - 1]);
throw new Error(`Failed test # ${numtests - tests.length}`);
}
setImmediate(runTestWrap, true);
});
if (opts.columns) {
Object.defineProperty(repl, 'columns', {
value: opts.columns,
enumerable: true
});
}
repl.inputStream.run(opts.test);
});
}
// run the tests
runTest();