blob: 73023df038b2c9b6e9724961a49d7a0bfaf3a02b [file] [log] [blame]
'use strict';
require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const { stripVTControlCharacters } = require('internal/readline');
const repl = require('repl');
// Flags: --expose-internals --experimental-repl-await
const PROMPT = 'await repl > ';
class REPLStream extends ArrayStream {
constructor() {
super();
this.waitingForResponse = false;
this.lines = [''];
}
write(chunk, encoding, callback) {
if (Buffer.isBuffer(chunk)) {
chunk = chunk.toString(encoding);
}
const chunkLines = stripVTControlCharacters(chunk).split('\n');
this.lines[this.lines.length - 1] += chunkLines[0];
if (chunkLines.length > 1) {
this.lines.push(...chunkLines.slice(1));
}
this.emit('line');
if (callback) callback();
return true;
}
wait(lookFor = PROMPT) {
if (this.waitingForResponse) {
throw new Error('Currently waiting for response to another command');
}
this.lines = [''];
return new Promise((resolve, reject) => {
const onError = (err) => {
this.removeListener('line', onLine);
reject(err);
};
const onLine = () => {
if (this.lines[this.lines.length - 1].includes(lookFor)) {
this.removeListener('error', onError);
this.removeListener('line', onLine);
resolve(this.lines);
}
};
this.once('error', onError);
this.on('line', onLine);
});
}
}
const putIn = new REPLStream();
const testMe = repl.start({
prompt: PROMPT,
stream: putIn,
terminal: true,
useColors: false,
breakEvalOnSigint: true
});
function runAndWait(cmds, lookFor) {
const promise = putIn.wait(lookFor);
for (const cmd of cmds) {
if (typeof cmd === 'string') {
putIn.run([cmd]);
} else {
testMe.write('', cmd);
}
}
return promise;
}
async function ordinaryTests() {
// These tests were created based on
// https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/http/tests/devtools/console/console-top-level-await.js?rcl=5d0ea979f0ba87655b7ef0e03b58fa3c04986ba6
putIn.run([
'function foo(x) { return x; }',
'function koo() { return Promise.resolve(4); }'
]);
const testCases = [
[ 'await Promise.resolve(0)', '0' ],
[ '{ a: await Promise.resolve(1) }', '{ a: 1 }' ],
[ '_', '{ a: 1 }' ],
[ 'let { a, b } = await Promise.resolve({ a: 1, b: 2 }), f = 5;',
'undefined' ],
[ 'a', '1' ],
[ 'b', '2' ],
[ 'f', '5' ],
[ 'let c = await Promise.resolve(2)', 'undefined' ],
[ 'c', '2' ],
[ 'let d;', 'undefined' ],
[ 'd', 'undefined' ],
[ 'let [i, { abc: { k } }] = [0, { abc: { k: 1 } }];', 'undefined' ],
[ 'i', '0' ],
[ 'k', '1' ],
[ 'var l = await Promise.resolve(2);', 'undefined' ],
[ 'l', '2' ],
[ 'foo(await koo())', '4' ],
[ '_', '4' ],
[ 'const m = foo(await koo());', 'undefined' ],
[ 'm', '4' ],
[ 'const n = foo(await\nkoo());', 'undefined' ],
[ 'n', '4' ],
// eslint-disable-next-line no-template-curly-in-string
[ '`status: ${(await Promise.resolve({ status: 200 })).status}`',
"'status: 200'"],
[ 'for (let i = 0; i < 2; ++i) await i', 'undefined' ],
[ 'for (let i = 0; i < 2; ++i) { await i }', 'undefined' ],
[ 'await 0', '0' ],
[ 'await 0; function foo() {}', 'undefined' ],
[ 'foo', '[Function: foo]' ],
[ 'class Foo {}; await 1;', '1' ],
[ 'Foo', '[Function: Foo]' ],
[ 'if (await true) { function bar() {}; }', 'undefined' ],
[ 'bar', '[Function: bar]' ],
[ 'if (await true) { class Bar {}; }', 'undefined' ],
[ 'Bar', 'ReferenceError: Bar is not defined', { line: 1 } ],
[ 'await 0; function* gen(){}', 'undefined' ],
[ 'for (var i = 0; i < 10; ++i) { await i; }', 'undefined' ],
[ 'i', '10' ],
[ 'for (let j = 0; j < 5; ++j) { await j; }', 'undefined' ],
[ 'j', 'ReferenceError: j is not defined', { line: 1 } ],
[ 'gen', '[GeneratorFunction: gen]' ],
[ 'return 42; await 5;', 'SyntaxError: Illegal return statement',
{ line: 4 } ],
[ 'let o = await 1, p', 'undefined' ],
[ 'p', 'undefined' ],
[ 'let q = 1, s = await 2', 'undefined' ],
[ 's', '2' ],
[ 'for await (let i of [1,2,3]) console.log(i)', 'undefined', { line: 3 } ]
];
for (const [input, expected, options = {}] of testCases) {
console.log(`Testing ${input}`);
const toBeRun = input.split('\n');
const lines = await runAndWait(toBeRun);
if ('line' in options) {
assert.strictEqual(lines[toBeRun.length + options.line], expected);
} else {
const echoed = toBeRun.map((a, i) => `${i > 0 ? '... ' : ''}${a}\r`);
assert.deepStrictEqual(lines, [...echoed, expected, PROMPT]);
}
}
}
async function ctrlCTest() {
putIn.run([
`const timeout = (msecs) => new Promise((resolve) => {
setTimeout(resolve, msecs).unref();
});`
]);
console.log('Testing Ctrl+C');
assert.deepStrictEqual(await runAndWait([
'await timeout(100000)',
{ ctrl: true, name: 'c' }
]), [
'await timeout(100000)\r',
'Thrown:',
'Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' +
'Script execution was interrupted by `SIGINT`',
PROMPT
]);
}
async function main() {
await ordinaryTests();
await ctrlCTest();
}
main();