repl: refactor tests to not rely on timing

Tests relying on synchronous timing have been migrated to use events.

PR-URL: https://github.com/nodejs/node/pull/17828
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
This commit is contained in:
Bradley Farias 2017-12-22 11:19:50 -06:00 committed by Ruben Bridgewater
parent 6007a9cc0e
commit de848ac1e0
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
18 changed files with 392 additions and 274 deletions

View File

@ -8,7 +8,7 @@ const os = require('os');
const util = require('util');
const debug = util.debuglog('repl');
module.exports = Object.create(REPL);
module.exports.createInternalRepl = createRepl;
module.exports.createInternalRepl = createInternalRepl;
// XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary.
// The debounce is to guard against code pasted into the REPL.
@ -19,7 +19,7 @@ function _writeToOutput(repl, message) {
repl._refreshLine();
}
function createRepl(env, opts, cb) {
function createInternalRepl(env, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = null;

View File

@ -109,6 +109,11 @@ writer.options = Object.assign({},
exports._builtinLibs = internalModule.builtinLibs;
const sep = '\u0000\u0000\u0000';
const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
`${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
`${sep}(.*)$`);
function REPLServer(prompt,
stream,
eval_,
@ -149,6 +154,7 @@ function REPLServer(prompt,
}
var self = this;
replMap.set(self, self);
self._domain = dom || domain.create();
self.useGlobal = !!useGlobal;
@ -165,41 +171,6 @@ function REPLServer(prompt,
self.rli = this;
const savedRegExMatches = ['', '', '', '', '', '', '', '', '', ''];
const sep = '\u0000\u0000\u0000';
const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
`${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
`${sep}(.*)$`);
eval_ = eval_ || defaultEval;
// Pause taking in new input, and store the keys in a buffer.
const pausedBuffer = [];
let paused = false;
function pause() {
paused = true;
}
function unpause() {
if (!paused) return;
paused = false;
let entry;
while (entry = pausedBuffer.shift()) {
const [type, payload] = entry;
switch (type) {
case 'key': {
const [d, key] = payload;
self._ttyWrite(d, key);
break;
}
case 'close':
self.emit('exit');
break;
}
if (paused) {
break;
}
}
}
function defaultEval(code, context, file, cb) {
var err, result, script, wrappedErr;
var wrappedCmd = false;
@ -331,7 +302,6 @@ function REPLServer(prompt,
if (awaitPromise && !err) {
let sigintListener;
pause();
let promise = result;
if (self.breakEvalOnSigint) {
const interrupt = new Promise((resolve, reject) => {
@ -350,7 +320,6 @@ function REPLServer(prompt,
prioritizedSigintQueue.delete(sigintListener);
finishExecution(undefined, result);
unpause();
}, (err) => {
// Remove prioritized SIGINT listener if it was not called.
prioritizedSigintQueue.delete(sigintListener);
@ -360,7 +329,6 @@ function REPLServer(prompt,
Object.defineProperty(err, 'stack', { value: '' });
}
unpause();
if (err && process.domain) {
debug('not recoverable, send to domain');
process.domain.emit('error', err);
@ -377,6 +345,36 @@ function REPLServer(prompt,
}
}
eval_ = eval_ || defaultEval;
// Pause taking in new input, and store the keys in a buffer.
const pausedBuffer = [];
let paused = false;
function pause() {
paused = true;
}
function unpause() {
if (!paused) return;
paused = false;
let entry;
while (entry = pausedBuffer.shift()) {
const [type, payload] = entry;
switch (type) {
case 'key': {
const [d, key] = payload;
self._ttyWrite(d, key);
break;
}
case 'close':
self.emit('exit');
break;
}
if (paused) {
break;
}
}
}
self.eval = self._domain.bind(eval_);
self._domain.on('error', function debugDomainError(e) {
@ -405,6 +403,7 @@ function REPLServer(prompt,
top.clearBufferedCommand();
top.lines.level = [];
top.displayPrompt();
unpause();
});
if (!input && !output) {
@ -593,6 +592,7 @@ function REPLServer(prompt,
const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n';
debug('eval %j', evalCmd);
pause();
self.eval(evalCmd, self.context, 'repl', finish);
function finish(e, ret) {
@ -605,6 +605,7 @@ function REPLServer(prompt,
'(Press Control-D to exit.)\n');
self.clearBufferedCommand();
self.displayPrompt();
unpause();
return;
}
@ -642,6 +643,7 @@ function REPLServer(prompt,
// Display prompt again
self.displayPrompt();
unpause();
}
});
@ -724,7 +726,6 @@ exports.start = function(prompt,
ignoreUndefined,
replMode);
if (!exports.repl) exports.repl = repl;
replMap.set(repl, repl);
return repl;
};

View File

@ -29,7 +29,22 @@ const repl = require('repl');
common.globalCheck = false;
const putIn = new common.ArrayStream();
repl.start('', putIn, null, true);
const replserver = repl.start('', putIn, null, true);
const callbacks = [];
const $eval = replserver.eval;
replserver.eval = function(code, context, file, cb) {
const expected = callbacks.shift();
return $eval.call(this, code, context, file, (...args) => {
try {
expected(cb, ...args);
} catch (e) {
console.error(e);
process.exit(1);
}
});
};
test1();
@ -48,6 +63,11 @@ function test1() {
}
};
assert(!gotWrite);
callbacks.push(common.mustCall((cb, err, result) => {
assert.ifError(err);
assert.strictEqual(result, require('fs'));
cb(err, result);
}));
putIn.run(['fs']);
assert(gotWrite);
}
@ -66,6 +86,11 @@ function test2() {
const val = {};
global.url = val;
assert(!gotWrite);
callbacks.push(common.mustCall((cb, err, result) => {
assert.ifError(err);
assert.strictEqual(result, val);
cb(err, result);
}));
putIn.run(['url']);
assert(gotWrite);
}

View File

@ -27,40 +27,57 @@ function testContext(repl) {
repl.close();
}
testContextSideEffects(repl.start({ input: stream, output: stream }));
const replserver = repl.start({ input: stream, output: stream });
const callbacks = [];
const $eval = replserver.eval;
replserver.eval = function(code, context, file, cb) {
const expected = callbacks.shift();
return $eval.call(this, code, context, file, (...args) => {
try {
expected(cb, ...args);
} catch (e) {
console.error(e);
process.exit(1);
}
});
};
testContextSideEffects(replserver);
function testContextSideEffects(server) {
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
// an assignment to '_' in the repl server
callbacks.push(common.mustCall((cb, ...args) => {
assert.ok(server.underscoreAssigned);
assert.strictEqual(server.last, 500);
cb(...args);
assert.strictEqual(server.lines.length, 1);
assert.strictEqual(server.lines[0], '_ = 500;');
// use the server to create a new context
const context = server.createContext();
// ensure that creating a new context does not
// have side effects on the server
assert.ok(server.underscoreAssigned);
assert.strictEqual(server.lines.length, 1);
assert.strictEqual(server.lines[0], '_ = 500;');
assert.strictEqual(server.last, 500);
// reset the server context
server.resetContext();
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
// ensure that assigning to '_' in the new context
// does not change the value in our server.
assert.ok(!server.underscoreAssigned);
vm.runInContext('_ = 1000;\n', context);
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
server.close();
}));
server.write('_ = 500;\n');
assert.ok(server.underscoreAssigned);
assert.strictEqual(server.lines.length, 1);
assert.strictEqual(server.lines[0], '_ = 500;');
assert.strictEqual(server.last, 500);
// use the server to create a new context
const context = server.createContext();
// ensure that creating a new context does not
// have side effects on the server
assert.ok(server.underscoreAssigned);
assert.strictEqual(server.lines.length, 1);
assert.strictEqual(server.lines[0], '_ = 500;');
assert.strictEqual(server.last, 500);
// reset the server context
server.resetContext();
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
// ensure that assigning to '_' in the new context
// does not change the value in our server.
assert.ok(!server.underscoreAssigned);
vm.runInContext('_ = 1000;\n', context);
assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0);
server.close();
}

View File

@ -21,10 +21,7 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const repl = require('repl');
let terminalExit = 0;
let regularExit = 0;
// Create a dummy stream that does nothing
const stream = new common.ArrayStream();
@ -41,11 +38,10 @@ function testTerminalMode() {
stream.emit('data', '\u0004');
});
r1.on('exit', function() {
r1.on('exit', common.mustCall(function() {
// should be fired from the simulated ^D keypress
terminalExit++;
testRegularMode();
});
}));
}
function testRegularMode() {
@ -59,17 +55,11 @@ function testRegularMode() {
stream.emit('end');
});
r2.on('exit', function() {
r2.on('exit', common.mustCall(function() {
// should be fired from the simulated 'end' event
regularExit++;
});
}));
}
process.on('exit', function() {
assert.strictEqual(terminalExit, 1);
assert.strictEqual(regularExit, 1);
});
// start
testTerminalMode();

View File

@ -3,21 +3,27 @@ const common = require('../common');
const assert = require('assert');
const repl = require('repl');
{
const stream = new common.ArrayStream();
const options = {
eval: common.mustCall((cmd, context) => {
assert.strictEqual(cmd, '.scope\n');
assert.deepStrictEqual(context, { animal: 'Sterrance' });
}),
input: stream,
output: stream,
terminal: true
};
const exitTests = [];
process.on('exit', () => {
for (const test of exitTests) test();
});
const CONTEXT = { animal: 'Sterrance' };
const stream = new common.ArrayStream();
const options = {
eval: common.mustCall((cmd, context) => {
// need to escape the domain
exitTests.push(common.mustCall(() => {
assert.strictEqual(cmd, '.scope');
assert.ok(context === CONTEXT);
}));
}),
input: stream,
output: stream,
terminal: true
};
const r = repl.start(options);
r.context = { animal: 'Sterrance' };
const r = repl.start(options);
r.context = CONTEXT;
stream.emit('data', '\t');
stream.emit('.exit\n');
}
stream.emit('data', '\t');
stream.emit('.exit\n');

View File

@ -3,31 +3,31 @@ const common = require('../common');
const assert = require('assert');
const repl = require('repl');
{
let evalCalledWithExpectedArgs = false;
const exitTests = [];
process.on('exit', () => {
for (const test of exitTests) test();
});
const options = {
eval: common.mustCall((cmd, context) => {
// Assertions here will not cause the test to exit with an error code
// so set a boolean that is checked later instead.
exitTests.push(common.mustCall(() => {
assert.strictEqual(cmd, 'function f() {}\n');
assert.strictEqual(context.foo, 'bar');
}));
})
};
const options = {
eval: common.mustCall((cmd, context) => {
// Assertions here will not cause the test to exit with an error code
// so set a boolean that is checked later instead.
evalCalledWithExpectedArgs = (cmd === 'function f() {}\n' &&
context.foo === 'bar');
})
};
const r = repl.start(options);
r.context = { foo: 'bar' };
const r = repl.start(options);
r.context = { foo: 'bar' };
try {
// Default preprocessor transforms
// function f() {} to
// var f = function f() {}
// Test to ensure that original input is preserved.
// Reference: https://github.com/nodejs/node/issues/9743
r.write('function f() {}\n');
} finally {
r.write('.exit\n');
}
assert(evalCalledWithExpectedArgs);
try {
// Default preprocessor transforms
// function f() {} to
// var f = function f() {}
// Test to ensure that original input is preserved.
// Reference: https://github.com/nodejs/node/issues/9743
r.write('function f() {}\n');
} finally {
r.write('.exit\n');
}

View File

@ -7,32 +7,47 @@ const stream = require('stream');
common.globalCheck = false;
const r = initRepl();
const input = new stream();
input.write = input.pause = input.resume = () => {};
input.readable = true;
r.input.emit('data', 'function a() { return 42; } (1)\n');
r.input.emit('data', 'a\n');
r.input.emit('data', '.exit');
const output = new stream();
output.writable = true;
output.accumulator = [];
const expected = '1\n[Function: a]\n';
const got = r.output.accumulator.join('');
assert.strictEqual(got, expected);
output.write = (data) => output.accumulator.push(data);
function initRepl() {
const input = new stream();
input.write = input.pause = input.resume = () => {};
input.readable = true;
const output = new stream();
output.writable = true;
output.accumulator = [];
output.write = (data) => output.accumulator.push(data);
return repl.start({
input,
output,
useColors: false,
terminal: false,
prompt: ''
const replserver = repl.start({
input,
output,
useColors: false,
terminal: false,
prompt: ''
});
const callbacks = [];
const $eval = replserver.eval;
replserver.eval = function(code, context, file, cb) {
const expected = callbacks.shift();
return $eval.call(this, code, context, file, (...args) => {
try {
expected(...args);
} catch (e) {
console.error(e);
process.exit(1);
}
cb(...args);
});
}
};
callbacks.push(common.mustCall((err, result) => {
assert.ifError(err);
assert.strictEqual(result, 1);
}));
replserver.input.emit('data', 'function a() { return 42; } (1)\n');
callbacks.push(common.mustCall((err, result) => {
assert.ifError(err);
assert.strictEqual(typeof result, 'function');
assert.strictEqual(result.toString(), 'function a() { return 42; }');
}));
replserver.input.emit('data', 'a\n');
replserver.input.emit('data', '.exit');

View File

@ -36,5 +36,7 @@ const r = repl.start({
});
r.write(`${command}\n`);
assert.strictEqual(accum.replace(terminalCodeRegex, ''), expected);
r.on('exit', common.mustCall(() => {
assert.strictEqual(accum.replace(terminalCodeRegex, ''), expected);
}));
r.close();

View File

@ -19,35 +19,49 @@ tests.forEach(function(test) {
function testSloppyMode() {
const cli = initRepl(repl.REPL_MODE_SLOPPY);
cli.callbacks.push(common.mustCall((err, result) => {
assert.ifError(err);
assert.strictEqual(result, 3);
}));
cli.input.emit('data', 'x = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), '> 3\n> ');
cli.output.accumulator.length = 0;
cli.callbacks.push(common.mustCall((err, result) => {
assert.ifError(err);
assert.strictEqual(result, undefined);
}));
cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
}
function testStrictMode() {
const cli = initRepl(repl.REPL_MODE_STRICT);
cli._domain.once('error', common.mustCall((err) => {
assert.ok(err);
assert.ok(/ReferenceError: x is not defined/.test(err.message));
}));
cli.input.emit('data', 'x = 3\n');
assert.ok(/ReferenceError: x is not defined/.test(
cli.output.accumulator.join('')));
cli.output.accumulator.length = 0;
cli.callbacks.push(common.mustCall((err, result) => {
assert.ifError(err);
assert.strictEqual(result, undefined);
}));
cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
}
function testAutoMode() {
const cli = initRepl(repl.REPL_MODE_MAGIC);
cli.callbacks.push(common.mustCall((err, result) => {
assert.ifError(err);
assert.strictEqual(result, 3);
}));
cli.input.emit('data', 'x = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), '> 3\n> ');
cli.output.accumulator.length = 0;
cli.callbacks.push(common.mustCall((err, result) => {
assert.ifError(err);
assert.strictEqual(result, undefined);
}));
cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
}
function initRepl(mode) {
@ -62,11 +76,28 @@ function initRepl(mode) {
output.accumulator = [];
output.writable = true;
return repl.start({
const replserver = repl.start({
input: input,
output: output,
useColors: false,
terminal: false,
replMode: mode
});
const callbacks = [];
const $eval = replserver.eval;
replserver.eval = function(code, context, file, cb) {
const expected = callbacks.shift();
return $eval.call(this, code, context, file, (...args) => {
console.log('EVAL RET', args);
try {
expected(...args);
} catch (e) {
console.error(e);
process.exit(1);
}
cb(...args);
});
};
replserver.callbacks = callbacks;
return replserver;
}

View File

@ -1,5 +1,5 @@
'use strict';
require('../common');
const common = require('../common');
const repl = require('repl');
const assert = require('assert');
const Stream = require('stream');
@ -18,7 +18,6 @@ const replserver = repl.start({
replserver.emit('line', 'process.nextTick(() => { throw null; })');
replserver.emit('line', '.exit');
setTimeout(() => {
console.log(text);
replserver.on('exit', common.mustCall(() => {
assert(text.includes('Thrown: null'));
}, 0);
}));

View File

@ -1,9 +1,28 @@
'use strict';
require('../common');
const common = require('../common');
const repl = require('repl');
const assert = require('assert');
const callbacks = [
common.mustCall((err, value) => {
assert.ifError(err);
assert.strictEqual(value, undefined);
})
];
const replserver = new repl.REPLServer();
const $eval = replserver.eval;
replserver.eval = function(code, context, file, cb) {
const expected = callbacks.shift();
return $eval.call(this, code, context, file, (...args) => {
try {
expected(...args);
} catch (e) {
console.error(e);
process.exit(1);
}
cb(...args);
});
};
replserver._inTemplateLiteral = true;

View File

@ -22,7 +22,9 @@ function run({ command, expected }) {
});
r.write(`${command}\n`);
assert.strictEqual(accum, expected);
r.on('exit', common.mustCall(() => {
assert.strictEqual(accum, expected);
}));
r.close();
}

View File

@ -22,7 +22,9 @@ function run({ command, expected }) {
});
r.write(`${command}\n`);
assert.strictEqual(accum, expected);
r.on('exit', common.mustCall(() => {
assert.strictEqual(accum, expected);
}));
r.close();
}

View File

@ -4,14 +4,11 @@ const common = require('../common');
const assert = require('assert');
const repl = require('repl');
let evalCount = 0;
let recovered = false;
let rendered = false;
function customEval(code, context, file, cb) {
evalCount++;
return cb(evalCount === 1 ? new repl.Recoverable() : null, true);
return cb(!recovered ? new repl.Recoverable() : null, true);
}
const putIn = new common.ArrayStream();
@ -26,7 +23,7 @@ putIn.write = function(msg) {
}
};
repl.start('', putIn, customEval);
repl.start('', putIn, common.mustCall(customEval, 2));
// https://github.com/nodejs/node/issues/2939
// Expose recoverable errors to the consumer.
@ -36,5 +33,4 @@ putIn.emit('data', '2\n');
process.on('exit', function() {
assert(recovered, 'REPL never recovered');
assert(rendered, 'REPL never rendered the result');
assert.strictEqual(evalCount, 2);
});

View File

@ -1,18 +1,20 @@
'use strict';
require('../common');
const common = require('../common');
// This test ensures that the repl does not
// crash or emit error when throwing `null|undefined`
// ie `throw null` or `throw undefined`
const assert = require('assert');
const repl = require('repl');
const r = repl.start();
const replserver = repl.start();
const $eval = replserver.eval;
replserver.eval = function(code, context, file, cb) {
return $eval.call(this, code, context, file,
common.mustNotCall(
'repl crashes/throw error on `throw null|undefined`'));
};
replserver.write('throw null\n');
replserver.write('throw undefined\n');
assert.doesNotThrow(() => {
r.write('throw null\n');
r.write('throw undefined\n');
}, TypeError, 'repl crashes/throw error on `throw null|undefined`');
r.write('.exit\n');
replserver.write('.exit\n');

View File

@ -28,20 +28,22 @@ function testSloppyMode() {
_; // remains 30 from user input
`);
assertOutput(r.output, [
'undefined',
'undefined',
'undefined',
'10',
'10',
'Expression assignment to _ now disabled.',
'20',
'20',
'30',
'30',
'40',
'30'
]);
r.on('exit', () => {
assertOutput(r.output, [
'undefined',
'undefined',
'undefined',
'10',
'10',
'Expression assignment to _ now disabled.',
'20',
'20',
'30',
'30',
'40',
'30'
]);
});
}
function testStrictMode() {
@ -61,20 +63,22 @@ function testStrictMode() {
_; // remains 30 from user input
`);
assertOutput(r.output, [
'undefined',
'undefined',
'undefined',
'undefined',
'20',
'30',
'30',
'undefined',
'30',
'undefined',
'undefined',
'30'
]);
r.on('exit', () => {
assertOutput(r.output, [
'undefined',
'undefined',
'undefined',
'undefined',
'20',
'30',
'30',
'undefined',
'30',
'undefined',
'undefined',
'30'
]);
});
}
function testMagicMode() {
@ -94,20 +98,22 @@ function testMagicMode() {
_; // remains 30 from user input
`);
assertOutput(r.output, [
'undefined',
'10',
'10',
'undefined',
'20',
'30',
'30',
'undefined',
'30',
'undefined',
'50',
'30'
]);
r.on('exit', () => {
assertOutput(r.output, [
'undefined',
'10',
'10',
'undefined',
'20',
'30',
'30',
'undefined',
'30',
'undefined',
'50',
'30'
]);
});
}
function testResetContext() {
@ -121,15 +127,17 @@ function testResetContext() {
_; // expect 20
`);
assertOutput(r.output, [
'Expression assignment to _ now disabled.',
'10',
'10',
'Clearing context...',
'10',
'20',
'20'
]);
r.on('exit', () => {
assertOutput(r.output, [
'Expression assignment to _ now disabled.',
'10',
'10',
'Clearing context...',
'10',
'20',
'20'
]);
});
}
function testResetContextGlobal() {
@ -141,12 +149,14 @@ function testResetContextGlobal() {
_; // remains 10
`);
assertOutput(r.output, [
'Expression assignment to _ now disabled.',
'10',
'10',
'10',
]);
r.on('exit', () => {
assertOutput(r.output, [
'Expression assignment to _ now disabled.',
'10',
'10',
'10',
]);
});
// delete globals leaked by REPL when `useGlobal` is `true`
delete global.module;

View File

@ -7,13 +7,6 @@ const stream = require('stream');
const repl = require('internal/repl');
const assert = require('assert');
// Array of [useGlobal, expectedResult] pairs
const globalTestCases = [
[false, 'undefined'],
[true, '\'tacos\''],
[undefined, 'undefined']
];
const globalTest = (useGlobal, cb, output) => (err, repl) => {
if (err)
return cb(err);
@ -26,26 +19,12 @@ const globalTest = (useGlobal, cb, output) => (err, repl) => {
global.lunch = 'tacos';
repl.write('global.lunch;\n');
repl.close();
delete global.lunch;
cb(null, str.trim());
repl.on('exit', common.mustCall(() => {
delete global.lunch;
cb(null, str.trim());
}));
};
// Test how the global object behaves in each state for useGlobal
for (const [option, expected] of globalTestCases) {
runRepl(option, globalTest, common.mustCall((err, output) => {
assert.ifError(err);
assert.strictEqual(output, expected);
}));
}
// Test how shadowing the process object via `let`
// behaves in each useGlobal state. Note: we can't
// actually test the state when useGlobal is true,
// because the exception that's generated is caught
// (see below), but errors are printed, and the test
// suite is aware of it, causing a failure to be flagged.
//
const processTestCases = [false, undefined];
const processTest = (useGlobal, cb, output) => (err, repl) => {
if (err)
return cb(err);
@ -57,15 +36,37 @@ const processTest = (useGlobal, cb, output) => (err, repl) => {
repl.write('let process;\n');
repl.write('21 * 2;\n');
repl.close();
cb(null, str.trim());
repl.on('exit', common.mustCall((err) => {
assert.ifError(err);
cb(null, str.trim());
}));
};
for (const option of processTestCases) {
runRepl(option, processTest, common.mustCall((err, output) => {
assert.ifError(err);
assert.strictEqual(output, 'undefined\n42');
}));
}
// Array of [useGlobal, expectedResult, fn] pairs
const testCases = [
// Test how the global object behaves in each state for useGlobal
[false, 'undefined', globalTest],
[true, '\'tacos\'', globalTest],
[undefined, 'undefined', globalTest],
// Test how shadowing the process object via `let`
// behaves in each useGlobal state. Note: we can't
// actually test the state when useGlobal is true,
// because the exception that's generated is caught
// (see below), but errors are printed, and the test
// suite is aware of it, causing a failure to be flagged.
[false, 'undefined\n42', processTest]
];
const next = common.mustCall(() => {
if (testCases.length) {
const [option, expected, runner] = testCases.shift();
runRepl(option, runner, common.mustCall((err, output) => {
assert.strictEqual(output, expected);
next();
}));
}
}, testCases.length + 1);
next();
function runRepl(useGlobal, testFunc, cb) {
const inputStream = new stream.PassThrough();