From f30b771fd214d6b6f115c07571b8416bc6ab6944 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 13 Dec 2019 02:44:32 +0100 Subject: [PATCH] test: add multiple repl preview tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This improves the coverage for the preview feature signficantly. Quite a few edge cases get testet here to prevent regressions. PR-URL: https://github.com/nodejs/node/pull/30907 Reviewed-By: Michaël Zasso Reviewed-By: Rich Trott --- test/parallel/test-repl-history-navigation.js | 214 +++++++++++++++++- test/parallel/test-repl-preview.js | 4 +- test/root.status | 1 + 3 files changed, 206 insertions(+), 13 deletions(-) diff --git a/test/parallel/test-repl-history-navigation.js b/test/parallel/test-repl-history-navigation.js index 3bd198880fc015..f73fbb9b0fd278 100644 --- a/test/parallel/test-repl-history-navigation.js +++ b/test/parallel/test-repl-history-navigation.js @@ -8,6 +8,7 @@ const REPL = require('internal/repl'); const assert = require('assert'); const fs = require('fs'); const path = require('path'); +const { inspect } = require('util'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); @@ -17,6 +18,7 @@ const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history'); // Create an input stream specialized for testing an array of actions class ActionStream extends stream.Stream { run(data) { + let reallyWait = true; const _iter = data[Symbol.iterator](); const doAction = () => { const next = _iter.next(); @@ -32,24 +34,34 @@ class ActionStream extends stream.Stream { } else { this.emit('data', `${action}`); } - setImmediate(doAction); + if (action === WAIT && reallyWait) { + setTimeout(doAction, common.platformTimeout(50)); + reallyWait = false; + } else { + setImmediate(doAction); + } }; - setImmediate(doAction); + doAction(); } resume() {} pause() {} } ActionStream.prototype.readable = true; - // Mock keys const ENTER = { name: 'enter' }; const UP = { name: 'up' }; const DOWN = { name: 'down' }; const LEFT = { name: 'left' }; +const RIGHT = { name: 'right' }; const DELETE = { name: 'delete' }; +const BACKSPACE = { name: 'backspace' }; +const WORD_LEFT = { name: 'left', ctrl: true }; +const WORD_RIGHT = { name: 'right', ctrl: true }; +const GO_TO_END = { name: 'end' }; const prompt = '> '; +const WAIT = '€'; const prev = process.features.inspector; @@ -91,6 +103,162 @@ const tests = [ ' 2025, 2116, 2209, ...', prompt].filter((e) => typeof e === 'string'), clean: true + }, + { + env: { NODE_REPL_HISTORY: defaultHistoryPath }, + skip: !process.features.inspector, + test: [ + `const ${'veryLongName'.repeat(30)} = 'I should not be previewed'`, + ENTER, + 'const e = new RangeError("visible\\ninvisible")', + ENTER, + 'e', + ENTER, + 'veryLongName'.repeat(30), + ENTER, + `${'\x1B[90m \x1B[39m'.repeat(235)} fun`, + ENTER, + `${' '.repeat(236)} fun`, + ENTER + ], + expected: [], + clean: false + }, + { + env: { NODE_REPL_HISTORY: defaultHistoryPath }, + columns: 250, + skip: !process.features.inspector, + test: [ + UP, + UP, + UP, + UP, + BACKSPACE + ], + expected: [ + prompt, + // This exceeds the maximum columns (250): + // Whitespace + prompt + ' // '.length + 'function'.length + // 236 + 2 + 4 + 8 + `${prompt}${' '.repeat(236)} fun`, + `${prompt}${' '.repeat(235)} fun`, + ' // ction', + ' // ction', + `${prompt}${'veryLongName'.repeat(30)}`, + `${prompt}e`, + '\n// RangeError: visible', + prompt + ], + clean: true + }, + { + env: { NODE_REPL_HISTORY: defaultHistoryPath }, + showEscapeCodes: true, + skip: !process.features.inspector, + test: [ + 'fun', + RIGHT, + BACKSPACE, + LEFT, + LEFT, + 'A', + BACKSPACE, + GO_TO_END, + BACKSPACE, + WORD_LEFT, + WORD_RIGHT, + ENTER + ], + // C = Cursor forward + // D = Cursor back + // G = Cursor to column n + // J = Erase in screen + // K = Erase in line + expected: [ + // 0. + // 'f' + '\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 'f', + // 'u' + 'u', ' // nction', '\x1B[5G', + // 'n' - Cleanup + '\x1B[0K', + 'n', ' // ction', '\x1B[6G', + // 1. Right. Cleanup + '\x1B[0K', + 'ction', + // 2. Backspace. Refresh + '\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G', + // Autocomplete and refresh? + ' // n', '\x1B[10G', ' // n', '\x1B[10G', + // 3. Left. Cleanup + '\x1B[0K', + '\x1B[1D', '\x1B[10G', ' // n', '\x1B[9G', + // 4. Left. Cleanup + '\x1B[10G', '\x1B[0K', '\x1B[9G', + '\x1B[1D', '\x1B[10G', ' // n', '\x1B[8G', + // 5. 'A' - Cleanup + '\x1B[10G', '\x1B[0K', '\x1B[8G', + // Refresh + '\x1B[1G', '\x1B[0J', `${prompt}functAio`, '\x1B[9G', + // 6. Backspace. Refresh + '\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[8G', '\x1B[10G', ' // n', + '\x1B[8G', '\x1B[10G', ' // n', + '\x1B[8G', '\x1B[10G', + // 7. Go to end. Cleanup + '\x1B[0K', '\x1B[8G', '\x1B[2C', + 'n', + // 8. Backspace. Refresh + '\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G', + // Autocomplete + ' // n', '\x1B[10G', ' // n', '\x1B[10G', + // 9. Word left. Cleanup + '\x1B[0K', '\x1B[7D', '\x1B[10G', ' // n', '\x1B[3G', '\x1B[10G', + // 10. Word right. Cleanup + '\x1B[0K', '\x1B[3G', '\x1B[7C', ' // n', '\x1B[10G', + '\x1B[0K', + ], + clean: true + }, + { + // Check that the completer ignores completions that are outdated. + env: { NODE_REPL_HISTORY: defaultHistoryPath }, + completer(line, callback) { + if (line.endsWith(WAIT)) { + setTimeout( + callback, + common.platformTimeout(40), + null, + [[`${WAIT}WOW`], line] + ); + } else { + callback(null, [[' Always visible'], line]); + } + }, + skip: !process.features.inspector, + test: [ + WAIT, // The first call is awaited before new input is triggered! + BACKSPACE, + 's', + BACKSPACE, + WAIT, // The second call is not awaited. It won't trigger the preview. + BACKSPACE, + 's', + BACKSPACE + ], + expected: [ + prompt, + WAIT, + ' // WOW', + prompt, + 's', + ' // Always visible', + prompt, + WAIT, + prompt, + 's', + ' // Always visible', + ], + clean: true } ]; const numtests = tests.length; @@ -112,29 +280,47 @@ function runTest() { const opts = tests.shift(); if (!opts) return; // All done - const env = opts.env; - const test = opts.test; - const expected = opts.expected; + const { expected, skip } = opts; + + // Test unsupported on platform. + if (skip) { + setImmediate(runTestWrap, true); + return; + } + + const lastChunks = []; - REPL.createInternalRepl(env, { + REPL.createInternalRepl(opts.env, { input: new ActionStream(), output: new stream.Writable({ write(chunk, _, next) { const output = chunk.toString(); - if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output)) { + if (!opts.showEscapeCodes && + output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output)) { return next(); } + lastChunks.push(inspect(output)); + if (expected.length) { - assert.strictEqual(output, expected[0]); + try { + assert.strictEqual(output, expected[0]); + } catch (e) { + console.error(`Failed test # ${numtests - tests.length}`); + console.error('Last outputs: ' + inspect(lastChunks, { + breakLength: 5, colors: true + })); + throw e; + } expected.shift(); } next(); } }), - prompt: prompt, + completer: opts.completer, + prompt, useColors: false, terminal: true }, function(err, repl) { @@ -153,7 +339,13 @@ function runTest() { setImmediate(runTestWrap, true); }); - repl.inputStream.run(test); + if (opts.columns) { + Object.defineProperty(repl, 'columns', { + value: opts.columns, + enumerable: true + }); + } + repl.inputStream.run(opts.test); }); } diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index 65b56904cb03b9..cd34c461d80671 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -3,7 +3,7 @@ const common = require('../common'); const ArrayStream = require('../common/arraystream'); const assert = require('assert'); -const Repl = require('repl'); +const { REPLServer } = require('repl'); common.skipIfInspectorDisabled(); @@ -52,7 +52,7 @@ function runAndWait(cmds, repl) { } async function tests(options) { - const repl = Repl.start({ + const repl = REPLServer({ prompt: PROMPT, stream: new REPLStream(), ignoreUndefined: true, diff --git a/test/root.status b/test/root.status index 91aad08caa3527..6edb9ddec34ca2 100644 --- a/test/root.status +++ b/test/root.status @@ -66,6 +66,7 @@ parallel/test-next-tick-fixed-queue-regression: SLOW parallel/test-npm-install: SLOW parallel/test-preload: SLOW parallel/test-repl: SLOW +parallel/test-repl-history-navigation.js: SLOW parallel/test-repl-tab-complete: SLOW parallel/test-repl-top-level-await: SLOW parallel/test-stdio-pipe-access: SLOW