node/test/parallel/test-repl-programmatic-history.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

272 lines
7.1 KiB
JavaScript

'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const stream = require('stream');
const REPL = require('repl');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const os = require('os');
const util = require('util');
common.skipIfDumbTerminal();
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Mock os.homedir()
os.homedir = function() {
return tmpdir.path;
};
// 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.
setImmediate(() => {
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}\n`);
}
setImmediate(doAction);
};
setImmediate(doAction);
}
resume() {}
pause() {}
}
ActionStream.prototype.readable = true;
// Mock keys
const UP = { name: 'up' };
const DOWN = { name: 'down' };
const ENTER = { name: 'enter' };
const CLEAR = { ctrl: true, name: 'u' };
// File paths
const historyFixturePath = fixtures.path('.node_repl_history');
const historyPath = path.join(tmpdir.path, '.fixture_copy_repl_history');
const historyPathFail = fixtures.path('nonexistent_folder', 'filename');
const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
const emptyHiddenHistoryPath = fixtures.path('.empty-hidden-repl-history-file');
const devNullHistoryPath = path.join(tmpdir.path,
'.dev-null-repl-history-file');
// Common message bits
const prompt = '> ';
const replDisabled = '\nPersistent history support disabled. Set the ' +
'NODE_REPL_HISTORY environment\nvariable to a valid, ' +
'user-writable path to enable.\n';
const homedirErr = '\nError: Could not get the home directory.\n' +
'REPL session history will not be persisted.\n';
const replFailedRead = '\nError: Could not open history file.\n' +
'REPL session history will not be persisted.\n';
const tests = [
{
env: { NODE_REPL_HISTORY: '' },
test: [UP],
expected: [prompt, replDisabled, prompt]
},
{
env: { NODE_REPL_HISTORY: ' ' },
test: [UP],
expected: [prompt, replDisabled, prompt]
},
{
env: { NODE_REPL_HISTORY: historyPath },
test: [UP, CLEAR],
expected: [prompt, `${prompt}'you look fabulous today'`, prompt]
},
{
env: {},
test: [UP, '21', ENTER, "'42'", ENTER],
expected: [
prompt,
// TODO(BridgeAR): The line is refreshed too many times. The double prompt
// is redundant and can be optimized away.
'2', '1', '21\n', prompt, prompt,
"'", '4', '2', "'", "'42'\n", prompt, prompt
],
clean: false
},
{ // Requires the above test case
env: {},
test: [UP, UP, UP, DOWN, ENTER],
expected: [
prompt,
`${prompt}'42'`,
`${prompt}21`,
prompt,
`${prompt}21`,
'21\n',
prompt
]
},
{
env: { NODE_REPL_HISTORY: historyPath,
NODE_REPL_HISTORY_SIZE: 1 },
test: [UP, UP, DOWN, CLEAR],
expected: [
prompt,
`${prompt}'you look fabulous today'`,
prompt,
`${prompt}'you look fabulous today'`,
prompt
]
},
{
env: { NODE_REPL_HISTORY: historyPathFail,
NODE_REPL_HISTORY_SIZE: 1 },
test: [UP],
expected: [prompt, replFailedRead, prompt, replDisabled, prompt]
},
{
before: function before() {
if (common.isWindows) {
const execSync = require('child_process').execSync;
execSync(`ATTRIB +H "${emptyHiddenHistoryPath}"`, (err) => {
assert.ifError(err);
});
}
},
env: { NODE_REPL_HISTORY: emptyHiddenHistoryPath },
test: [UP],
expected: [prompt]
},
{
before: function before() {
if (!common.isWindows)
fs.symlinkSync('/dev/null', devNullHistoryPath);
},
env: { NODE_REPL_HISTORY: devNullHistoryPath },
test: [UP],
expected: [prompt]
},
{ // Make sure this is always the last test, since we change os.homedir()
before: function before() {
// Mock os.homedir() failure
os.homedir = function() {
throw new Error('os.homedir() failure');
};
},
env: {},
test: [UP],
expected: [prompt, homedirErr, prompt, replDisabled, prompt]
}
];
const numtests = tests.length;
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;
}
// Copy our fixture to the tmp directory
fs.createReadStream(historyFixturePath)
.pipe(fs.createWriteStream(historyPath)).on('unpipe', () => runTest());
const runTestWrap = common.mustCall(runTest, numtests);
function runTest(assertCleaned) {
const opts = tests.shift();
if (!opts) return; // All done
console.log('NEW');
if (assertCleaned) {
try {
assert.strictEqual(fs.readFileSync(defaultHistoryPath, 'utf8'), '');
} catch (e) {
if (e.code !== 'ENOENT') {
console.error(`Failed test # ${numtests - tests.length}`);
throw e;
}
}
}
const test = opts.test;
const expected = opts.expected;
const clean = opts.clean;
const before = opts.before;
const historySize = opts.env.NODE_REPL_HISTORY_SIZE;
const historyFile = opts.env.NODE_REPL_HISTORY;
if (before) before();
const repl = REPL.start({
input: new ActionStream(),
output: new stream.Writable({
write(chunk, _, next) {
const output = chunk.toString();
console.log('INPUT', util.inspect(output));
// Ignore escapes and blank lines
if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
return next();
try {
assert.strictEqual(output, expected.shift());
} catch (err) {
console.error(`Failed test # ${numtests - tests.length}`);
throw err;
}
next();
}
}),
prompt: prompt,
useColors: false,
terminal: true,
historySize: historySize
});
repl.setupHistory(historyFile, function(err, repl) {
if (err) {
console.error(`Failed test # ${numtests - tests.length}`);
throw err;
}
repl.once('close', () => {
if (repl._flushing) {
repl.once('flushHistory', onClose);
return;
}
onClose();
});
function onClose() {
const cleaned = clean === false ? false : cleanupTmpFile();
try {
// Ensure everything that we expected was output
assert.strictEqual(expected.length, 0);
setImmediate(runTestWrap, cleaned);
} catch (err) {
console.error(`Failed test # ${numtests - tests.length}`);
throw err;
}
}
repl.inputStream.run(test);
});
}