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

View File

@ -109,6 +109,11 @@ writer.options = Object.assign({},
exports._builtinLibs = internalModule.builtinLibs; 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, function REPLServer(prompt,
stream, stream,
eval_, eval_,
@ -149,6 +154,7 @@ function REPLServer(prompt,
} }
var self = this; var self = this;
replMap.set(self, self);
self._domain = dom || domain.create(); self._domain = dom || domain.create();
self.useGlobal = !!useGlobal; self.useGlobal = !!useGlobal;
@ -165,41 +171,6 @@ function REPLServer(prompt,
self.rli = this; self.rli = this;
const savedRegExMatches = ['', '', '', '', '', '', '', '', '', '']; 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) { function defaultEval(code, context, file, cb) {
var err, result, script, wrappedErr; var err, result, script, wrappedErr;
var wrappedCmd = false; var wrappedCmd = false;
@ -331,7 +302,6 @@ function REPLServer(prompt,
if (awaitPromise && !err) { if (awaitPromise && !err) {
let sigintListener; let sigintListener;
pause();
let promise = result; let promise = result;
if (self.breakEvalOnSigint) { if (self.breakEvalOnSigint) {
const interrupt = new Promise((resolve, reject) => { const interrupt = new Promise((resolve, reject) => {
@ -350,7 +320,6 @@ function REPLServer(prompt,
prioritizedSigintQueue.delete(sigintListener); prioritizedSigintQueue.delete(sigintListener);
finishExecution(undefined, result); finishExecution(undefined, result);
unpause();
}, (err) => { }, (err) => {
// Remove prioritized SIGINT listener if it was not called. // Remove prioritized SIGINT listener if it was not called.
prioritizedSigintQueue.delete(sigintListener); prioritizedSigintQueue.delete(sigintListener);
@ -360,7 +329,6 @@ function REPLServer(prompt,
Object.defineProperty(err, 'stack', { value: '' }); Object.defineProperty(err, 'stack', { value: '' });
} }
unpause();
if (err && process.domain) { if (err && process.domain) {
debug('not recoverable, send to domain'); debug('not recoverable, send to domain');
process.domain.emit('error', err); 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.eval = self._domain.bind(eval_);
self._domain.on('error', function debugDomainError(e) { self._domain.on('error', function debugDomainError(e) {
@ -405,6 +403,7 @@ function REPLServer(prompt,
top.clearBufferedCommand(); top.clearBufferedCommand();
top.lines.level = []; top.lines.level = [];
top.displayPrompt(); top.displayPrompt();
unpause();
}); });
if (!input && !output) { if (!input && !output) {
@ -593,6 +592,7 @@ function REPLServer(prompt,
const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n'; const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n';
debug('eval %j', evalCmd); debug('eval %j', evalCmd);
pause();
self.eval(evalCmd, self.context, 'repl', finish); self.eval(evalCmd, self.context, 'repl', finish);
function finish(e, ret) { function finish(e, ret) {
@ -605,6 +605,7 @@ function REPLServer(prompt,
'(Press Control-D to exit.)\n'); '(Press Control-D to exit.)\n');
self.clearBufferedCommand(); self.clearBufferedCommand();
self.displayPrompt(); self.displayPrompt();
unpause();
return; return;
} }
@ -642,6 +643,7 @@ function REPLServer(prompt,
// Display prompt again // Display prompt again
self.displayPrompt(); self.displayPrompt();
unpause();
} }
}); });
@ -724,7 +726,6 @@ exports.start = function(prompt,
ignoreUndefined, ignoreUndefined,
replMode); replMode);
if (!exports.repl) exports.repl = repl; if (!exports.repl) exports.repl = repl;
replMap.set(repl, repl);
return repl; return repl;
}; };

View File

@ -29,7 +29,22 @@ const repl = require('repl');
common.globalCheck = false; common.globalCheck = false;
const putIn = new common.ArrayStream(); 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(); test1();
@ -48,6 +63,11 @@ function test1() {
} }
}; };
assert(!gotWrite); assert(!gotWrite);
callbacks.push(common.mustCall((cb, err, result) => {
assert.ifError(err);
assert.strictEqual(result, require('fs'));
cb(err, result);
}));
putIn.run(['fs']); putIn.run(['fs']);
assert(gotWrite); assert(gotWrite);
} }
@ -66,6 +86,11 @@ function test2() {
const val = {}; const val = {};
global.url = val; global.url = val;
assert(!gotWrite); assert(!gotWrite);
callbacks.push(common.mustCall((cb, err, result) => {
assert.ifError(err);
assert.strictEqual(result, val);
cb(err, result);
}));
putIn.run(['url']); putIn.run(['url']);
assert(gotWrite); assert(gotWrite);
} }

View File

@ -27,40 +27,57 @@ function testContext(repl) {
repl.close(); 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) { function testContextSideEffects(server) {
assert.ok(!server.underscoreAssigned); assert.ok(!server.underscoreAssigned);
assert.strictEqual(server.lines.length, 0); assert.strictEqual(server.lines.length, 0);
// an assignment to '_' in the repl server // 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'); 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'; 'use strict';
const common = require('../common'); const common = require('../common');
const assert = require('assert');
const repl = require('repl'); const repl = require('repl');
let terminalExit = 0;
let regularExit = 0;
// Create a dummy stream that does nothing // Create a dummy stream that does nothing
const stream = new common.ArrayStream(); const stream = new common.ArrayStream();
@ -41,11 +38,10 @@ function testTerminalMode() {
stream.emit('data', '\u0004'); stream.emit('data', '\u0004');
}); });
r1.on('exit', function() { r1.on('exit', common.mustCall(function() {
// should be fired from the simulated ^D keypress // should be fired from the simulated ^D keypress
terminalExit++;
testRegularMode(); testRegularMode();
}); }));
} }
function testRegularMode() { function testRegularMode() {
@ -59,17 +55,11 @@ function testRegularMode() {
stream.emit('end'); stream.emit('end');
}); });
r2.on('exit', function() { r2.on('exit', common.mustCall(function() {
// should be fired from the simulated 'end' event // should be fired from the simulated 'end' event
regularExit++; }));
});
} }
process.on('exit', function() {
assert.strictEqual(terminalExit, 1);
assert.strictEqual(regularExit, 1);
});
// start // start
testTerminalMode(); testTerminalMode();

View File

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

View File

@ -3,31 +3,31 @@ const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const repl = require('repl'); const repl = require('repl');
{ const exitTests = [];
let evalCalledWithExpectedArgs = false; 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 = { const r = repl.start(options);
eval: common.mustCall((cmd, context) => { r.context = { foo: 'bar' };
// 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); try {
r.context = { foo: 'bar' }; // Default preprocessor transforms
// function f() {} to
try { // var f = function f() {}
// Default preprocessor transforms // Test to ensure that original input is preserved.
// function f() {} to // Reference: https://github.com/nodejs/node/issues/9743
// var f = function f() {} r.write('function f() {}\n');
// Test to ensure that original input is preserved. } finally {
// Reference: https://github.com/nodejs/node/issues/9743 r.write('.exit\n');
r.write('function f() {}\n');
} finally {
r.write('.exit\n');
}
assert(evalCalledWithExpectedArgs);
} }

View File

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

View File

@ -19,35 +19,49 @@ tests.forEach(function(test) {
function testSloppyMode() { function testSloppyMode() {
const cli = initRepl(repl.REPL_MODE_SLOPPY); 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'); 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'); cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
} }
function testStrictMode() { function testStrictMode() {
const cli = initRepl(repl.REPL_MODE_STRICT); 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'); 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'); cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
} }
function testAutoMode() { function testAutoMode() {
const cli = initRepl(repl.REPL_MODE_MAGIC); 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'); 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'); cli.input.emit('data', 'let y = 3\n');
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
} }
function initRepl(mode) { function initRepl(mode) {
@ -62,11 +76,28 @@ function initRepl(mode) {
output.accumulator = []; output.accumulator = [];
output.writable = true; output.writable = true;
return repl.start({ const replserver = repl.start({
input: input, input: input,
output: output, output: output,
useColors: false, useColors: false,
terminal: false, terminal: false,
replMode: mode 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'; 'use strict';
require('../common'); const common = require('../common');
const repl = require('repl'); const repl = require('repl');
const assert = require('assert'); const assert = require('assert');
const Stream = require('stream'); const Stream = require('stream');
@ -18,7 +18,6 @@ const replserver = repl.start({
replserver.emit('line', 'process.nextTick(() => { throw null; })'); replserver.emit('line', 'process.nextTick(() => { throw null; })');
replserver.emit('line', '.exit'); replserver.emit('line', '.exit');
setTimeout(() => { replserver.on('exit', common.mustCall(() => {
console.log(text);
assert(text.includes('Thrown: null')); assert(text.includes('Thrown: null'));
}, 0); }));

View File

@ -1,9 +1,28 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const repl = require('repl'); const repl = require('repl');
const assert = require('assert'); const assert = require('assert');
const callbacks = [
common.mustCall((err, value) => {
assert.ifError(err);
assert.strictEqual(value, undefined);
})
];
const replserver = new repl.REPLServer(); 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; replserver._inTemplateLiteral = true;

View File

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

View File

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

View File

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

View File

@ -1,18 +1,20 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
// This test ensures that the repl does not // This test ensures that the repl does not
// crash or emit error when throwing `null|undefined` // crash or emit error when throwing `null|undefined`
// ie `throw null` or `throw undefined` // ie `throw null` or `throw undefined`
const assert = require('assert');
const repl = require('repl'); 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(() => { replserver.write('.exit\n');
r.write('throw null\n');
r.write('throw undefined\n');
}, TypeError, 'repl crashes/throw error on `throw null|undefined`');
r.write('.exit\n');

View File

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

View File

@ -7,13 +7,6 @@ const stream = require('stream');
const repl = require('internal/repl'); const repl = require('internal/repl');
const assert = require('assert'); const assert = require('assert');
// Array of [useGlobal, expectedResult] pairs
const globalTestCases = [
[false, 'undefined'],
[true, '\'tacos\''],
[undefined, 'undefined']
];
const globalTest = (useGlobal, cb, output) => (err, repl) => { const globalTest = (useGlobal, cb, output) => (err, repl) => {
if (err) if (err)
return cb(err); return cb(err);
@ -26,26 +19,12 @@ const globalTest = (useGlobal, cb, output) => (err, repl) => {
global.lunch = 'tacos'; global.lunch = 'tacos';
repl.write('global.lunch;\n'); repl.write('global.lunch;\n');
repl.close(); repl.close();
delete global.lunch; repl.on('exit', common.mustCall(() => {
cb(null, str.trim()); 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) => { const processTest = (useGlobal, cb, output) => (err, repl) => {
if (err) if (err)
return cb(err); return cb(err);
@ -57,15 +36,37 @@ const processTest = (useGlobal, cb, output) => (err, repl) => {
repl.write('let process;\n'); repl.write('let process;\n');
repl.write('21 * 2;\n'); repl.write('21 * 2;\n');
repl.close(); repl.close();
cb(null, str.trim()); repl.on('exit', common.mustCall((err) => {
assert.ifError(err);
cb(null, str.trim());
}));
}; };
for (const option of processTestCases) { // Array of [useGlobal, expectedResult, fn] pairs
runRepl(option, processTest, common.mustCall((err, output) => { const testCases = [
assert.ifError(err); // Test how the global object behaves in each state for useGlobal
assert.strictEqual(output, 'undefined\n42'); [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) { function runRepl(useGlobal, testFunc, cb) {
const inputStream = new stream.PassThrough(); const inputStream = new stream.PassThrough();