From 1942440696b77c2acce750c27c9300945e983885 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 31 Dec 2017 22:04:25 +0100 Subject: [PATCH] test: refactor test-repl - Switch over to async tracking through promises/async fns - Remove an unused temp dir refresh - Inline the multiline/npm text prompts into expectations - Unify handling prompts/stripping prompts out - Make sure no unexpected data is received by requireing all *lines* to be matched, rather than chunks received from the REPL. This made the test too loose in terms of matched lines and too strict in terms of stream chunking requirements. - Some general cleanup PR-URL: https://github.com/nodejs/node/pull/17926 Reviewed-By: Colin Ihrig Reviewed-By: Rich Trott Reviewed-By: Bradley Farias Reviewed-By: Jon Moss Reviewed-By: James M Snell --- test/parallel/test-repl.js | 1175 +++++++++++++++++++++--------------- 1 file changed, 690 insertions(+), 485 deletions(-) diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index 74db2ba0cd6d7f..9d660607991c24 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -24,543 +24,748 @@ const common = require('../common'); const fixtures = require('../common/fixtures'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); +const net = require('net'); +const repl = require('repl'); common.globalCheck = false; tmpdir.refresh(); +common.crashOnUnhandledRejection(); -const net = require('net'); -const repl = require('repl'); const message = 'Read, Eval, Print Loop'; const prompt_unix = 'node via Unix socket> '; const prompt_tcp = 'node via TCP socket> '; -const prompt_multiline = '... '; -const prompt_npm = 'npm should be run outside of the ' + - 'node repl, in your normal shell.\n' + - '(Press Control-D to exit.)\n'; -const expect_npm = prompt_npm + prompt_unix; -let server_tcp, server_unix, client_tcp, client_unix, replServer; - // absolute path to test/fixtures/a.js const moduleFilename = fixtures.path('a'); -console.error('repl test'); - // function for REPL to run global.invoke_me = function(arg) { return `invoked ${arg}`; }; -function send_expect(list) { - if (list.length > 0) { - const cur = list.shift(); - console.error(`sending ${JSON.stringify(cur.send)}`); +// Helpers for describing the expected output: +const kArrow = /^ *\^+ *$/; // Arrow of ^ pointing to syntax error location +const kSource = Symbol('kSource'); // Placeholder standing for input readback - cur.client.expect = cur.expect; - cur.client.list = list; - if (cur.send.length > 0) { - cur.client.write(`${cur.send}\n`); - } - } -} +async function runReplTests(socket, prompt, tests) { + let lineBuffer = ''; -function clean_up() { - client_tcp.end(); - client_unix.end(); -} + for (const { send, expect } of tests) { + // expect can be a single line or multiple lines + const expectedLines = Array.isArray(expect) ? expect : [ expect ]; -function strict_mode_error_test() { - send_expect([ - { client: client_unix, send: 'ref = 1', - expect: /^ReferenceError:\sref\sis\snot\sdefined\nnode via Unix socket> $/ }, - ]); -} + console.error('out:', JSON.stringify(send)); + socket.write(`${send}\n`); -function error_test() { - // The other stuff is done so reuse unix socket - let read_buffer = ''; - let run_strict_test = true; - client_unix.removeAllListeners('data'); - - client_unix.on('data', function(data) { - read_buffer += data.toString('ascii', 0, data.length); - console.error( - `Unix data: ${JSON.stringify(read_buffer)}, expecting ${ - client_unix.expect.exec ? - client_unix.expect : - JSON.stringify(client_unix.expect)}`); - - if (read_buffer.includes(prompt_unix)) { - // if it's an exact match, then don't do the regexp - if (read_buffer !== client_unix.expect) { - let expect = client_unix.expect; - if (expect === prompt_multiline) - expect = /[.]{3} /; - assert.ok(RegExp(expect).test(read_buffer)); - console.error('match'); - } - read_buffer = ''; - if (client_unix.list && client_unix.list.length > 0) { - send_expect(client_unix.list); - } else if (run_strict_test) { - replServer.replMode = repl.REPL_MODE_STRICT; - run_strict_test = false; - strict_mode_error_test(); - } else { - console.error('End of Error test, running TCP test.'); - tcp_test(); + for (let expectedLine of expectedLines) { + // special value: kSource refers to last sent source text + if (expectedLine === kSource) + expectedLine = send; + + while (!lineBuffer.includes('\n')) { + lineBuffer += await event(socket, 'data'); + + // Cut away the initial prompt + while (lineBuffer.startsWith(prompt)) + lineBuffer = lineBuffer.substr(prompt.length); + + // Allow to match partial text if no newline was received, because + // sending newlines from the REPL itself would be redundant + // (e.g. in the `... ` multiline prompt: The user already pressed + // enter for that, so the REPL shouldn't do it again!). + if (lineBuffer === expectedLine && !expectedLine.includes('\n')) + lineBuffer += '\n'; } - } else if (read_buffer.includes(prompt_multiline)) { - // Check that you meant to send a multiline test - assert.strictEqual(prompt_multiline, client_unix.expect); - read_buffer = ''; - if (client_unix.list && client_unix.list.length > 0) { - send_expect(client_unix.list); - } else if (run_strict_test) { - replServer.replMode = repl.REPL_MODE_STRICT; - run_strict_test = false; - strict_mode_error_test(); + // Split off the current line. + const newlineOffset = lineBuffer.indexOf('\n'); + let actualLine = lineBuffer.substr(0, newlineOffset); + lineBuffer = lineBuffer.substr(newlineOffset + 1); + + // This might have been skipped in the loop above because the buffer + // already contained a \n to begin with and the entire loop was skipped. + while (actualLine.startsWith(prompt)) + actualLine = actualLine.substr(prompt.length); + + console.error('in:', JSON.stringify(actualLine)); + + // Match a string directly, or a RegExp through .test(). + if (typeof expectedLine === 'string') { + assert.strictEqual(actualLine, expectedLine); } else { - console.error('End of Error test, running TCP test.\n'); - tcp_test(); + assert(expectedLine.test(actualLine), + `${actualLine} match ${expectedLine}`); } - - } else { - console.error('didn\'t see prompt yet, buffering.'); } - }); + } - send_expect([ - // Uncaught error throws and prints out - { client: client_unix, send: 'throw new Error(\'test error\');', - expect: /^Error: test error/ }, - // Common syntax error is treated as multiline command - { client: client_unix, send: 'function test_func() {', - expect: prompt_multiline }, - // You can recover with the .break command - { client: client_unix, send: '.break', - expect: prompt_unix }, - // But passing the same string to eval() should throw - { client: client_unix, send: 'eval("function test_func() {")', - expect: /^SyntaxError: Unexpected end of input/ }, - // Can handle multiline template literals - { client: client_unix, send: '`io.js', - expect: prompt_multiline }, - // Special REPL commands still available - { client: client_unix, send: '.break', - expect: prompt_unix }, - // Template expressions can cross lines - { client: client_unix, send: '`io.js ${"1.0"', - expect: prompt_multiline }, - { client: client_unix, send: '+ ".2"}`', - expect: `'io.js 1.0.2'\n${prompt_unix}` }, - // Dot prefix in multiline commands aren't treated as commands - { client: client_unix, send: '("a"', - expect: prompt_multiline }, - { client: client_unix, send: '.charAt(0))', - expect: `'a'\n${prompt_unix}` }, - // Floating point numbers are not interpreted as REPL commands. - { client: client_unix, send: '.1234', - expect: '0.1234' }, - // Floating point expressions are not interpreted as REPL commands - { client: client_unix, send: '.1+.1', - expect: '0.2' }, - // Can parse valid JSON - { client: client_unix, send: 'JSON.parse(\'{"valid": "json"}\');', - expect: '{ valid: \'json\' }' }, - // invalid input to JSON.parse error is special case of syntax error, - // should throw - { client: client_unix, send: 'JSON.parse(\'{invalid: \\\'json\\\'}\');', - expect: /^SyntaxError: Unexpected token i/ }, - // end of input to JSON.parse error is special case of syntax error, - // should throw - { client: client_unix, send: 'JSON.parse(\'066\');', - expect: /^SyntaxError: Unexpected number/ }, - // should throw - { client: client_unix, send: 'JSON.parse(\'{\');', - expect: /^SyntaxError: Unexpected end of JSON input/ }, - // invalid RegExps are a special case of syntax error, - // should throw - { client: client_unix, send: '/(/;', - expect: /^SyntaxError: Invalid regular expression:/ }, - // invalid RegExp modifiers are a special case of syntax error, - // should throw (GH-4012) - { client: client_unix, send: 'new RegExp("foo", "wrong modifier");', - expect: /^SyntaxError: Invalid flags supplied to RegExp constructor/ }, - // strict mode syntax errors should be caught (GH-5178) - { client: client_unix, - send: '(function() { "use strict"; return 0755; })()', - expect: /\bSyntaxError: Octal literals are not allowed in strict mode/ }, - { - client: client_unix, - send: '(function(a, a, b) { "use strict"; return a + b + c; })()', - expect: - /\bSyntaxError: Duplicate parameter name not allowed in this context/ - }, - { - client: client_unix, - send: '(function() { "use strict"; with (this) {} })()', - expect: /\bSyntaxError: Strict mode code may not include a with statement/ - }, - { - client: client_unix, - send: '(function() { "use strict"; var x; delete x; })()', - expect: - /\bSyntaxError: Delete of an unqualified identifier in strict mode/ - }, - { client: client_unix, - send: '(function() { "use strict"; eval = 17; })()', - expect: /\bSyntaxError: Unexpected eval or arguments in strict mode/ }, - { - client: client_unix, - send: '(function() { "use strict"; if (true) function f() { } })()', - expect: - /\bSyntaxError: In strict mode code, functions can only be declared at top level or inside a block\./ - }, - // Named functions can be used: - { client: client_unix, send: 'function blah() { return 1; }', - expect: prompt_unix }, - { client: client_unix, send: 'blah()', - expect: `1\n${prompt_unix}` }, - // Functions should not evaluate twice (#2773) - { client: client_unix, send: 'var I = [1,2,3,function() {}]; I.pop()', - expect: '[Function]' }, - // Multiline object - { client: client_unix, send: '{ a: ', - expect: prompt_multiline }, - { client: client_unix, send: '1 }', - expect: '{ a: 1 }' }, - // Multiline anonymous function with comment - { client: client_unix, send: '(function() {', - expect: prompt_multiline }, - { client: client_unix, send: '// blah', - expect: prompt_multiline }, - { client: client_unix, send: 'return 1;', - expect: prompt_multiline }, - { client: client_unix, send: '})()', - expect: '1' }, - // Multiline function call - { client: client_unix, send: 'function f(){}; f(f(1,', - expect: prompt_multiline }, - { client: client_unix, send: '2)', - expect: prompt_multiline }, - { client: client_unix, send: ')', - expect: `undefined\n${prompt_unix}` }, - // npm prompt error message - { client: client_unix, send: 'npm install foobar', - expect: expect_npm }, - { client: client_unix, send: '(function() {\n\nreturn 1;\n})()', - expect: '1' }, - { client: client_unix, send: '{\n\na: 1\n}', - expect: '{ a: 1 }' }, - { client: client_unix, send: 'url.format("http://google.com")', - expect: 'http://google.com/' }, - { client: client_unix, send: 'var path = 42; path', - expect: '42' }, - // this makes sure that we don't print `undefined` when we actually print - // the error message - { client: client_unix, send: '.invalid_repl_command', - expect: `Invalid REPL keyword\n${prompt_unix}` }, - // this makes sure that we don't crash when we use an inherited property as - // a REPL command - { client: client_unix, send: '.toString', - expect: `Invalid REPL keyword\n${prompt_unix}` }, - // fail when we are not inside a String and a line continuation is used - { client: client_unix, send: '[] \\', - expect: /\bSyntaxError: Invalid or unexpected token/ }, - // do not fail when a String is created with line continuation - { client: client_unix, send: '\'the\\\nfourth\\\neye\'', - expect: `${prompt_multiline}${prompt_multiline}'thefourtheye'\n${ - prompt_unix}` }, - // Don't fail when a partial String is created and line continuation is used - // with whitespace characters at the end of the string. We are to ignore it. - // This test is to make sure that we properly remove the whitespace - // characters at the end of line, unlike the buggy `trimWhitespace` function - { client: client_unix, send: ' \t .break \t ', - expect: prompt_unix }, - // multiline strings preserve whitespace characters in them - { client: client_unix, send: '\'the \\\n fourth\t\t\\\n eye \'', - expect: `${prompt_multiline}${ - prompt_multiline}'the fourth\\t\\t eye '\n${prompt_unix}` }, - // more than one multiline strings also should preserve whitespace chars - { client: client_unix, send: '\'the \\\n fourth\' + \'\t\t\\\n eye \'', - expect: `${prompt_multiline}${ - prompt_multiline}'the fourth\\t\\t eye '\n${prompt_unix}` }, - // using REPL commands within a string literal should still work - { client: client_unix, send: '\'\\\n.break', - expect: prompt_unix }, - // using REPL command "help" within a string literal should still work - { client: client_unix, send: '\'thefourth\\\n.help\neye\'', - expect: /'thefourtheye'/ }, - // empty lines in the REPL should be allowed - { client: client_unix, send: '\n\r\n\r\n', - expect: prompt_unix + prompt_unix + prompt_unix }, - // empty lines in the string literals should not affect the string - { client: client_unix, send: '\'the\\\n\\\nfourtheye\'\n', - expect: `${prompt_multiline}${ - prompt_multiline}'thefourtheye'\n${prompt_unix}` }, - // Regression test for https://github.com/nodejs/node/issues/597 - { client: client_unix, - send: '/(.)(.)(.)(.)(.)(.)(.)(.)(.)/.test(\'123456789\')\n', - expect: `true\n${prompt_unix}` }, - // the following test's result depends on the RegExp's match from the above - { client: client_unix, - send: 'RegExp.$1\nRegExp.$2\nRegExp.$3\nRegExp.$4\nRegExp.$5\n' + - 'RegExp.$6\nRegExp.$7\nRegExp.$8\nRegExp.$9\n', - expect: ['\'1\'\n', '\'2\'\n', '\'3\'\n', '\'4\'\n', '\'5\'\n', '\'6\'\n', - '\'7\'\n', '\'8\'\n', '\'9\'\n'].join(`${prompt_unix}`) }, - // regression tests for https://github.com/nodejs/node/issues/2749 - { client: client_unix, send: 'function x() {\nreturn \'\\n\';\n }', - expect: `${prompt_multiline}${prompt_multiline}undefined\n${ - prompt_unix}` }, - { client: client_unix, send: 'function x() {\nreturn \'\\\\\';\n }', - expect: `${prompt_multiline}${prompt_multiline}undefined\n${ - prompt_unix}` }, - // regression tests for https://github.com/nodejs/node/issues/3421 - { client: client_unix, send: 'function x() {\n//\'\n }', - expect: `${prompt_multiline}${prompt_multiline}undefined\n${ - prompt_unix}` }, - { client: client_unix, send: 'function x() {\n//"\n }', - expect: `${prompt_multiline}${prompt_multiline}undefined\n${ - prompt_unix}` }, - { client: client_unix, send: 'function x() {//\'\n }', - expect: `${prompt_multiline}undefined\n${prompt_unix}` }, - { client: client_unix, send: 'function x() {//"\n }', - expect: `${prompt_multiline}undefined\n${prompt_unix}` }, - { client: client_unix, send: 'function x() {\nvar i = "\'";\n }', - expect: `${prompt_multiline}${prompt_multiline}undefined\n${ - prompt_unix}` }, - { client: client_unix, send: 'function x(/*optional*/) {}', - expect: `undefined\n${prompt_unix}` }, - { client: client_unix, send: 'function x(/* // 5 */) {}', - expect: `undefined\n${prompt_unix}` }, - { client: client_unix, send: '// /* 5 */', - expect: `undefined\n${prompt_unix}` }, - { client: client_unix, send: '"//"', - expect: `'//'\n${prompt_unix}` }, - { client: client_unix, send: '"data /*with*/ comment"', - expect: `'data /*with*/ comment'\n${prompt_unix}` }, - { client: client_unix, send: 'function x(/*fn\'s optional params*/) {}', - expect: `undefined\n${prompt_unix}` }, - { client: client_unix, send: '/* \'\n"\n\'"\'\n*/', - expect: `undefined\n${prompt_unix}` }, - // REPL should get a normal require() function, not one that allows - // access to internal modules without the --expose_internals flag. - { client: client_unix, send: 'require("internal/repl")', - expect: /^Error: Cannot find module 'internal\/repl'/ }, - // REPL should handle quotes within regexp literal in multiline mode - { client: client_unix, - send: "function x(s) {\nreturn s.replace(/'/,'');\n}", - expect: `${prompt_multiline}${prompt_multiline}` + - `undefined\n${prompt_unix}` }, - { client: client_unix, - send: "function x(s) {\nreturn s.replace(/'/,'');\n}", - expect: `${prompt_multiline}${prompt_multiline}` + - `undefined\n${prompt_unix}` }, - { client: client_unix, - send: 'function x(s) {\nreturn s.replace(/"/,"");\n}', - expect: `${prompt_multiline}${prompt_multiline}` + - `undefined\n${prompt_unix}` }, - { client: client_unix, - send: 'function x(s) {\nreturn s.replace(/.*/,"");\n}', - expect: `${prompt_multiline}${prompt_multiline}` + - `undefined\n${prompt_unix}` }, - { client: client_unix, send: '{ var x = 4; }', - expect: `undefined\n${prompt_unix}` }, - // Illegal token is not recoverable outside string literal, RegExp literal, - // or block comment. https://github.com/nodejs/node/issues/3611 - { client: client_unix, send: 'a = 3.5e', - expect: /\bSyntaxError: Invalid or unexpected token/ }, - // Mitigate https://github.com/nodejs/node/issues/548 - { client: client_unix, send: 'function name(){ return "node"; };name()', - expect: `'node'\n${prompt_unix}` }, - { client: client_unix, send: 'function name(){ return "nodejs"; };name()', - expect: `'nodejs'\n${prompt_unix}` }, - // Avoid emitting repl:line-number for SyntaxError - { client: client_unix, send: 'a = 3.5e', - expect: /^(?!repl)/ }, - // Avoid emitting stack trace - { client: client_unix, send: 'a = 3.5e', - expect: /^(?!\s+at\s)/m }, - - // https://github.com/nodejs/node/issues/9850 - { client: client_unix, send: 'function* foo() {}; foo().next();', - expect: '{ value: undefined, done: true }' }, - - { client: client_unix, send: 'function *foo() {}; foo().next();', - expect: '{ value: undefined, done: true }' }, - - { client: client_unix, send: 'function*foo() {}; foo().next();', - expect: '{ value: undefined, done: true }' }, - - { client: client_unix, send: 'function * foo() {}; foo().next()', - expect: '{ value: undefined, done: true }' }, - - // https://github.com/nodejs/node/issues/9300 - { - client: client_unix, send: 'function foo() {\nvar bar = 1 / 1; // "/"\n}', - expect: `${prompt_multiline}${prompt_multiline}undefined\n${prompt_unix}` - }, - - { - client: client_unix, send: '(function() {\nreturn /foo/ / /bar/;\n}())', - expect: `${prompt_multiline}${prompt_multiline}NaN\n${prompt_unix}` - }, - - { - client: client_unix, send: '(function() {\nif (false) {} /bar"/;\n}())', - expect: `${prompt_multiline}${prompt_multiline}undefined\n${prompt_unix}` - }, - - // Newline within template string maintains whitespace. - { client: client_unix, send: '`foo \n`', - expect: `${prompt_multiline}'foo \\n'\n${prompt_unix}` }, - // Whitespace is not evaluated. - { client: client_unix, send: ' \t \n', - expect: prompt_unix }, - // Do not parse `...[]` as a REPL keyword - { client: client_unix, send: '...[]\n', - expect: `${prompt_multiline}` }, - // bring back the repl to prompt - { client: client_unix, send: '.break', - expect: `${prompt_unix}` } - ]); + const remainder = socket.read(); + assert(remainder === '' || remainder === null); } -function tcp_test() { - server_tcp = net.createServer(function(socket) { - assert.strictEqual(server_tcp, socket.server); +const unixTests = [ + { + send: '', + expect: '' + }, + { + send: 'message', + expect: `'${message}'` + }, + { + send: 'invoke_me(987)', + expect: '\'invoked 987\'' + }, + { + send: 'a = 12345', + expect: '12345' + }, + { + send: '{a:1}', + expect: '{ a: 1 }' + } +]; + +const strictModeTests = [ + { + send: 'ref = 1', + expect: /^ReferenceError:\s/ + } +]; + +const errorTests = [ + // Uncaught error throws and prints out + { + send: 'throw new Error(\'test error\');', + expect: /^Error: test error/ + }, + // Common syntax error is treated as multiline command + { + send: 'function test_func() {', + expect: '... ' + }, + // You can recover with the .break command + { + send: '.break', + expect: '' + }, + // But passing the same string to eval() should throw + { + send: 'eval("function test_func() {")', + expect: /^SyntaxError: / + }, + // Can handle multiline template literals + { + send: '`io.js', + expect: '... ' + }, + // Special REPL commands still available + { + send: '.break', + expect: '' + }, + // Template expressions can cross lines + { + send: '`io.js ${"1.0"', + expect: '... ' + }, + { + send: '+ ".2"}`', + expect: '\'io.js 1.0.2\'' + }, + // Dot prefix in multiline commands aren't treated as commands + { + send: '("a"', + expect: '... ' + }, + { + send: '.charAt(0))', + expect: '\'a\'' + }, + // Floating point numbers are not interpreted as REPL commands. + { + send: '.1234', + expect: '0.1234' + }, + // Floating point expressions are not interpreted as REPL commands + { + send: '.1+.1', + expect: '0.2' + }, + // Can parse valid JSON + { + send: 'JSON.parse(\'{"valid": "json"}\');', + expect: '{ valid: \'json\' }' + }, + // invalid input to JSON.parse error is special case of syntax error, + // should throw + { + send: 'JSON.parse(\'{invalid: \\\'json\\\'}\');', + expect: [/^SyntaxError: /, ''] + }, + // end of input to JSON.parse error is special case of syntax error, + // should throw + { + send: 'JSON.parse(\'066\');', + expect: [/^SyntaxError: /, ''] + }, + // should throw + { + send: 'JSON.parse(\'{\');', + expect: [/^SyntaxError: /, ''] + }, + // invalid RegExps are a special case of syntax error, + // should throw + { + send: '/(/;', + expect: /^SyntaxError: / + }, + // invalid RegExp modifiers are a special case of syntax error, + // should throw (GH-4012) + { + send: 'new RegExp("foo", "wrong modifier");', + expect: [/^SyntaxError: /, ''] + }, + // strict mode syntax errors should be caught (GH-5178) + { + send: '(function() { "use strict"; return 0755; })()', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + { + send: '(function(a, a, b) { "use strict"; return a + b + c; })()', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + { + send: '(function() { "use strict"; with (this) {} })()', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + { + send: '(function() { "use strict"; var x; delete x; })()', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + { + send: '(function() { "use strict"; eval = 17; })()', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + { + send: '(function() { "use strict"; if (true) function f() { } })()', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + // Named functions can be used: + { + send: 'function blah() { return 1; }', + expect: 'undefined' + }, + { + send: 'blah()', + expect: '1' + }, + // Functions should not evaluate twice (#2773) + { + send: 'var I = [1,2,3,function() {}]; I.pop()', + expect: '[Function]' + }, + // Multiline object + { + send: '{ a: ', + expect: '... ' + }, + { + send: '1 }', + expect: '{ a: 1 }' + }, + // Multiline anonymous function with comment + { + send: '(function() {', + expect: '... ' + }, + { + send: '// blah', + expect: '... ' + }, + { + send: 'return 1;', + expect: '... ' + }, + { + send: '})()', + expect: '1' + }, + // Multiline function call + { + send: 'function f(){}; f(f(1,', + expect: '... ' + }, + { + send: '2)', + expect: '... ' + }, + { + send: ')', + expect: 'undefined' + }, + // npm prompt error message + { + send: 'npm install foobar', + expect: [ + 'npm should be run outside of the node repl, in your normal shell.', + '(Press Control-D to exit.)' + ] + }, + { + send: '(function() {\n\nreturn 1;\n})()', + expect: '... ... ... 1' + }, + { + send: '{\n\na: 1\n}', + expect: '... ... ... { a: 1 }' + }, + { + send: 'url.format("http://google.com")', + expect: '\'http://google.com/\'' + }, + { + send: 'var path = 42; path', + expect: '42' + }, + // this makes sure that we don't print `undefined` when we actually print + // the error message + { + send: '.invalid_repl_command', + expect: 'Invalid REPL keyword' + }, + // this makes sure that we don't crash when we use an inherited property as + // a REPL command + { + send: '.toString', + expect: 'Invalid REPL keyword' + }, + // fail when we are not inside a String and a line continuation is used + { + send: '[] \\', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + // do not fail when a String is created with line continuation + { + send: '\'the\\\nfourth\\\neye\'', + expect: ['... ... \'thefourtheye\''] + }, + // Don't fail when a partial String is created and line continuation is used + // with whitespace characters at the end of the string. We are to ignore it. + // This test is to make sure that we properly remove the whitespace + // characters at the end of line, unlike the buggy `trimWhitespace` function + { + send: ' \t .break \t ', + expect: '' + }, + // multiline strings preserve whitespace characters in them + { + send: '\'the \\\n fourth\t\t\\\n eye \'', + expect: '... ... \'the fourth\\t\\t eye \'' + }, + // more than one multiline strings also should preserve whitespace chars + { + send: '\'the \\\n fourth\' + \'\t\t\\\n eye \'', + expect: '... ... \'the fourth\\t\\t eye \'' + }, + // using REPL commands within a string literal should still work + { + send: '\'\\\n.break', + expect: '... ' + prompt_unix + }, + // using REPL command "help" within a string literal should still work + { + send: '\'thefourth\\\n.help\neye\'', + expect: [ + /\.break/, + /\.clear/, + /\.editor/, + /\.exit/, + /\.help/, + /\.load/, + /\.save/, + /'thefourtheye'/ + ] + }, + // empty lines in the REPL should be allowed + { + send: '\n\r\n\r\n', + expect: '' + }, + // empty lines in the string literals should not affect the string + { + send: '\'the\\\n\\\nfourtheye\'\n', + expect: '... ... \'thefourtheye\'' + }, + // Regression test for https://github.com/nodejs/node/issues/597 + { + send: '/(.)(.)(.)(.)(.)(.)(.)(.)(.)/.test(\'123456789\')\n', + expect: 'true' + }, + // the following test's result depends on the RegEx's match from the above + { + send: 'RegExp.$1\nRegExp.$2\nRegExp.$3\nRegExp.$4\nRegExp.$5\n' + + 'RegExp.$6\nRegExp.$7\nRegExp.$8\nRegExp.$9\n', + expect: ['\'1\'', '\'2\'', '\'3\'', '\'4\'', '\'5\'', '\'6\'', + '\'7\'', '\'8\'', '\'9\''] + }, + // regression tests for https://github.com/nodejs/node/issues/2749 + { + send: 'function x() {\nreturn \'\\n\';\n }', + expect: '... ... undefined' + }, + { + send: 'function x() {\nreturn \'\\\\\';\n }', + expect: '... ... undefined' + }, + // regression tests for https://github.com/nodejs/node/issues/3421 + { + send: 'function x() {\n//\'\n }', + expect: '... ... undefined' + }, + { + send: 'function x() {\n//"\n }', + expect: '... ... undefined' + }, + { + send: 'function x() {//\'\n }', + expect: '... undefined' + }, + { + send: 'function x() {//"\n }', + expect: '... undefined' + }, + { + send: 'function x() {\nvar i = "\'";\n }', + expect: '... ... undefined' + }, + { + send: 'function x(/*optional*/) {}', + expect: 'undefined' + }, + { + send: 'function x(/* // 5 */) {}', + expect: 'undefined' + }, + { + send: '// /* 5 */', + expect: 'undefined' + }, + { + send: '"//"', + expect: '\'//\'' + }, + { + send: '"data /*with*/ comment"', + expect: '\'data /*with*/ comment\'' + }, + { + send: 'function x(/*fn\'s optional params*/) {}', + expect: 'undefined' + }, + { + send: '/* \'\n"\n\'"\'\n*/', + expect: '... ... ... undefined' + }, + // REPL should get a normal require() function, not one that allows + // access to internal modules without the --expose_internals flag. + { + send: 'require("internal/repl")', + expect: [ + /^Error: Cannot find module 'internal\/repl'/, + /^ at .*/, + /^ at .*/, + /^ at .*/, + /^ at .*/ + ] + }, + // REPL should handle quotes within regexp literal in multiline mode + { + send: "function x(s) {\nreturn s.replace(/'/,'');\n}", + expect: '... ... undefined' + }, + { + send: "function x(s) {\nreturn s.replace(/'/,'');\n}", + expect: '... ... undefined' + }, + { + send: 'function x(s) {\nreturn s.replace(/"/,"");\n}', + expect: '... ... undefined' + }, + { + send: 'function x(s) {\nreturn s.replace(/.*/,"");\n}', + expect: '... ... undefined' + }, + { + send: '{ var x = 4; }', + expect: 'undefined' + }, + // Illegal token is not recoverable outside string literal, RegExp literal, + // or block comment. https://github.com/nodejs/node/issues/3611 + { + send: 'a = 3.5e', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + // Mitigate https://github.com/nodejs/node/issues/548 + { + send: 'function name(){ return "node"; };name()', + expect: '\'node\'' + }, + { + send: 'function name(){ return "nodejs"; };name()', + expect: '\'nodejs\'' + }, + // Avoid emitting repl:line-number for SyntaxError + { + send: 'a = 3.5e', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + // Avoid emitting stack trace + { + send: 'a = 3.5e', + expect: [ + kSource, + kArrow, + '', + /^SyntaxError: /, + '' + ] + }, + + // https://github.com/nodejs/node/issues/9850 + { + send: 'function* foo() {}; foo().next();', + expect: '{ value: undefined, done: true }' + }, + + { + send: 'function *foo() {}; foo().next();', + expect: '{ value: undefined, done: true }' + }, + + { + send: 'function*foo() {}; foo().next();', + expect: '{ value: undefined, done: true }' + }, + + { + send: 'function * foo() {}; foo().next()', + expect: '{ value: undefined, done: true }' + }, + + // https://github.com/nodejs/node/issues/9300 + { + send: 'function foo() {\nvar bar = 1 / 1; // "/"\n}', + expect: '... ... undefined' + }, + + { + send: '(function() {\nreturn /foo/ / /bar/;\n}())', + expect: '... ... NaN' + }, + + { + send: '(function() {\nif (false) {} /bar"/;\n}())', + expect: '... ... undefined' + }, + + // Newline within template string maintains whitespace. + { + send: '`foo \n`', + expect: '... \'foo \\n\'' + }, + // Whitespace is not evaluated. + { + send: ' \t \n', + expect: 'undefined' + }, + // Do not parse `...[]` as a REPL keyword + { + send: '...[]\n', + expect: '... ... ' + }, + // bring back the repl to prompt + { + send: '.break', + expect: '' + } +]; + +const tcpTests = [ + { + send: '', + expect: '' + }, + { + send: 'invoke_me(333)', + expect: '\'invoked 333\'' + }, + { + send: 'a += 1', + expect: '12346' + }, + { + send: `require(${JSON.stringify(moduleFilename)}).number`, + expect: '42' + } +]; + +(async function() { + { + const [ socket, replServer ] = await startUnixRepl(); - socket.on('end', function() { + await runReplTests(socket, prompt_unix, unixTests); + await runReplTests(socket, prompt_unix, errorTests); + replServer.replMode = repl.REPL_MODE_STRICT; + await runReplTests(socket, prompt_unix, strictModeTests); + + socket.end(); + } + { + const [ socket ] = await startTCPRepl(); + + await runReplTests(socket, prompt_tcp, tcpTests); + + socket.end(); + } +})(); + +function startTCPRepl() { + let resolveSocket, resolveReplServer; + + const server = net.createServer(common.mustCall((socket) => { + assert.strictEqual(server, socket.server); + + socket.on('end', common.mustCall(() => { socket.end(); - }); + })); - repl.start(prompt_tcp, socket); - }); + resolveReplServer(repl.start(prompt_tcp, socket)); + })); - server_tcp.listen(0, function() { - let read_buffer = ''; - - client_tcp = net.createConnection(this.address().port); - - client_tcp.on('connect', function() { - assert.strictEqual(true, client_tcp.readable); - assert.strictEqual(true, client_tcp.writable); - - send_expect([ - { client: client_tcp, send: '', - expect: prompt_tcp }, - { client: client_tcp, send: 'invoke_me(333)', - expect: (`'invoked 333'\n${prompt_tcp}`) }, - { client: client_tcp, send: 'a += 1', - expect: (`12346\n${prompt_tcp}`) }, - { client: client_tcp, - send: `require(${JSON.stringify(moduleFilename)}).number`, - expect: (`42\n${prompt_tcp}`) } - ]); - }); + server.listen(0, common.mustCall(() => { + const client = net.createConnection(server.address().port); - client_tcp.on('data', function(data) { - read_buffer += data.toString('ascii', 0, data.length); - console.error(`TCP data: ${JSON.stringify(read_buffer)}, expecting ${ - JSON.stringify(client_tcp.expect)}`); - if (read_buffer.includes(prompt_tcp)) { - assert.strictEqual(client_tcp.expect, read_buffer); - console.error('match'); - read_buffer = ''; - if (client_tcp.list && client_tcp.list.length > 0) { - send_expect(client_tcp.list); - } else { - console.error('End of TCP test.\n'); - clean_up(); - } - } else { - console.error('didn\'t see prompt yet, buffering'); - } - }); + client.setEncoding('utf8'); - client_tcp.on('error', function(e) { - throw e; - }); + client.on('connect', common.mustCall(() => { + assert.strictEqual(true, client.readable); + assert.strictEqual(true, client.writable); - client_tcp.on('close', function() { - server_tcp.close(); - }); - }); + resolveSocket(client); + })); + + client.on('close', common.mustCall(() => { + server.close(); + })); + })); + return Promise.all([ + new Promise((resolve) => resolveSocket = resolve), + new Promise((resolve) => resolveReplServer = resolve) + ]); } -function unix_test() { - server_unix = net.createServer(function(socket) { - assert.strictEqual(server_unix, socket.server); +function startUnixRepl() { + let resolveSocket, resolveReplServer; + + const server = net.createServer(common.mustCall((socket) => { + assert.strictEqual(server, socket.server); - socket.on('end', function() { + socket.on('end', common.mustCall(() => { socket.end(); - }); + })); - replServer = repl.start({ + const replServer = repl.start({ prompt: prompt_unix, input: socket, output: socket, useGlobal: true }); replServer.context.message = message; - }); + resolveReplServer(replServer); + })); - server_unix.on('listening', function() { - let read_buffer = ''; - - client_unix = net.createConnection(common.PIPE); - - client_unix.on('connect', function() { - assert.strictEqual(true, client_unix.readable); - assert.strictEqual(true, client_unix.writable); - - send_expect([ - { client: client_unix, send: '', - expect: prompt_unix }, - { client: client_unix, send: 'message', - expect: (`'${message}'\n${prompt_unix}`) }, - { client: client_unix, send: 'invoke_me(987)', - expect: (`'invoked 987'\n${prompt_unix}`) }, - { client: client_unix, send: 'a = 12345', - expect: (`12345\n${prompt_unix}`) }, - { client: client_unix, send: '{a:1}', - expect: (`{ a: 1 }\n${prompt_unix}`) } - ]); - }); + server.listen(common.PIPE, common.mustCall(() => { + const client = net.createConnection(common.PIPE); - client_unix.on('data', function(data) { - read_buffer += data.toString('ascii', 0, data.length); - console.error(`Unix data: ${JSON.stringify(read_buffer)}, expecting ${ - JSON.stringify(client_unix.expect)}`); - if (read_buffer.includes(prompt_unix)) { - assert.strictEqual(client_unix.expect, read_buffer); - console.error('match'); - read_buffer = ''; - if (client_unix.list && client_unix.list.length > 0) { - send_expect(client_unix.list); - } else { - console.error('End of Unix test, running Error test.\n'); - process.nextTick(error_test); - } - } else { - console.error('didn\'t see prompt yet, buffering.'); - } - }); + client.setEncoding('utf8'); - client_unix.on('error', function(e) { - throw e; - }); + client.on('connect', common.mustCall(() => { + assert.strictEqual(true, client.readable); + assert.strictEqual(true, client.writable); - client_unix.on('close', function() { - server_unix.close(); - }); - }); + resolveSocket(client); + })); + + client.on('close', common.mustCall(() => { + server.close(); + })); + })); - server_unix.listen(common.PIPE); + return Promise.all([ + new Promise((resolve) => resolveSocket = resolve), + new Promise((resolve) => resolveReplServer = resolve) + ]); } -unix_test(); +function event(ee, eventName) { + return new Promise((resolve) => { + ee.once(eventName, common.mustCall(resolve)); + }); +}