Skip to content

Commit 704b1fa

Browse files
dario-piotrowicztargos
authored andcommitted
test: add tests for REPL custom evals
this commit reintroduces the REPL custom eval tests that have been introduced in #57691 but reverted in #57793 the tests turned out problematic before because `getReplOutput`, the function used to return the repl output wasn't taking into account that input processing and output emitting are asynchronous operation can resolve with a small delay the new implementation here replaces `getReplOutput` with `getReplRunOutput` that resolves repl inputs by running them and using the repl prompt as an indicator to when the input processing has completed PR-URL: #57850 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
1 parent 17df800 commit 704b1fa

File tree

4 files changed

+222
-34
lines changed

4 files changed

+222
-34
lines changed

lib/repl.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ function REPLServer(prompt,
310310
options.useColors = shouldColorize(options.output);
311311
}
312312

313-
// TODO(devsnek): Add a test case for custom eval functions.
314313
const preview = options.terminal &&
315314
(options.preview !== undefined ? !!options.preview : !eval_);
316315

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const ArrayStream = require('../common/arraystream');
5+
const assert = require('assert');
6+
const { describe, it } = require('node:test');
7+
8+
common.skipIfInspectorDisabled();
9+
10+
const repl = require('repl');
11+
12+
const testingReplPrompt = '_REPL_TESTING_PROMPT_>';
13+
14+
// Processes some input in a REPL instance and returns a promise that
15+
// resolves to the produced output (as a string).
16+
function getReplRunOutput(input, replOptions) {
17+
return new Promise((resolve) => {
18+
const inputStream = new ArrayStream();
19+
const outputStream = new ArrayStream();
20+
21+
const replServer = repl.start({
22+
input: inputStream,
23+
output: outputStream,
24+
prompt: testingReplPrompt,
25+
...replOptions,
26+
});
27+
28+
let output = '';
29+
30+
outputStream.write = (chunk) => {
31+
output += chunk;
32+
// The prompt appears after the input has been processed
33+
if (output.includes(testingReplPrompt)) {
34+
replServer.close();
35+
resolve(output);
36+
}
37+
};
38+
39+
inputStream.emit('data', input);
40+
41+
inputStream.run(['']);
42+
});
43+
}
44+
45+
describe('with previews', () => {
46+
it("doesn't show previews by default", async () => {
47+
const input = "'Hello custom' + ' eval World!'";
48+
const output = await getReplRunOutput(
49+
input,
50+
{
51+
terminal: true,
52+
eval: (code, _ctx, _replRes, cb) => cb(null, eval(code)),
53+
},
54+
);
55+
const lines = getSingleCommandLines(output);
56+
assert.match(lines.command, /^'Hello custom' \+ ' eval World!'/);
57+
assert.match(lines.prompt, new RegExp(`${testingReplPrompt}$`));
58+
assert.strictEqual(lines.result, "'Hello custom eval World!'");
59+
assert.strictEqual(lines.preview, undefined);
60+
});
61+
62+
it('does show previews if `preview` is set to `true`', async () => {
63+
const input = "'Hello custom' + ' eval World!'";
64+
const output = await getReplRunOutput(
65+
input,
66+
{
67+
terminal: true,
68+
eval: (code, _ctx, _replRes, cb) => cb(null, eval(code)),
69+
preview: true,
70+
},
71+
);
72+
const lines = getSingleCommandLines(output);
73+
assert.match(lines.command, /^'Hello custom' \+ ' eval World!'/);
74+
assert.match(lines.prompt, new RegExp(`${testingReplPrompt}$`));
75+
assert.strictEqual(lines.result, "'Hello custom eval World!'");
76+
assert.match(lines.preview, /'Hello custom eval World!'/);
77+
});
78+
});
79+
80+
function getSingleCommandLines(output) {
81+
const outputLines = output.split('\n');
82+
83+
// The first line contains the command being run
84+
const command = outputLines.shift();
85+
86+
// The last line contains the prompt (asking for some new input)
87+
const prompt = outputLines.pop();
88+
89+
// The line before the last one contains the result of the command
90+
const result = outputLines.pop();
91+
92+
// The line before that contains the preview of the command
93+
const preview = outputLines.shift();
94+
95+
return {
96+
command,
97+
prompt,
98+
result,
99+
preview,
100+
};
101+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
'use strict';
2+
3+
require('../common');
4+
const ArrayStream = require('../common/arraystream');
5+
const assert = require('assert');
6+
const { describe, it } = require('node:test');
7+
8+
const repl = require('repl');
9+
10+
// Processes some input in a REPL instance and returns a promise that
11+
// resolves to the produced output (as a string).
12+
function getReplRunOutput(input, replOptions) {
13+
return new Promise((resolve) => {
14+
const inputStream = new ArrayStream();
15+
const outputStream = new ArrayStream();
16+
17+
const testingReplPrompt = '_REPL_TESTING_PROMPT_>';
18+
19+
const replServer = repl.start({
20+
input: inputStream,
21+
output: outputStream,
22+
prompt: testingReplPrompt,
23+
...replOptions,
24+
});
25+
26+
let output = '';
27+
28+
outputStream.write = (chunk) => {
29+
output += chunk;
30+
// The prompt appears after the input has been processed
31+
if (output.includes(testingReplPrompt)) {
32+
replServer.close();
33+
resolve(output);
34+
}
35+
};
36+
37+
inputStream.emit('data', input);
38+
39+
inputStream.run(['']);
40+
});
41+
}
42+
43+
describe('repl with custom eval', { concurrency: true }, () => {
44+
it('uses the custom eval function as expected', async () => {
45+
const output = await getReplRunOutput('Convert this to upper case', {
46+
terminal: true,
47+
eval: (code, _ctx, _replRes, cb) => cb(null, code.toUpperCase()),
48+
});
49+
assert.match(
50+
output,
51+
/Convert this to upper case\r\n'CONVERT THIS TO UPPER CASE\\n'/
52+
);
53+
});
54+
55+
it('surfaces errors as expected', async () => {
56+
const output = await getReplRunOutput('Convert this to upper case', {
57+
terminal: true,
58+
eval: (_code, _ctx, _replRes, cb) => cb(new Error('Testing Error')),
59+
});
60+
assert.match(output, /Uncaught Error: Testing Error\n/);
61+
});
62+
63+
it('provides a repl context to the eval callback', async () => {
64+
const context = await new Promise((resolve) => {
65+
const r = repl.start({
66+
eval: (_cmd, context) => resolve(context),
67+
});
68+
r.context = { foo: 'bar' };
69+
r.write('\n.exit\n');
70+
});
71+
assert.strictEqual(context.foo, 'bar');
72+
});
73+
74+
it('provides the global context to the eval callback', async () => {
75+
const context = await new Promise((resolve) => {
76+
const r = repl.start({
77+
useGlobal: true,
78+
eval: (_cmd, context) => resolve(context),
79+
});
80+
global.foo = 'global_foo';
81+
r.write('\n.exit\n');
82+
});
83+
84+
assert.strictEqual(context.foo, 'global_foo');
85+
delete global.foo;
86+
});
87+
88+
it('inherits variables from the global context but does not use it afterwords if `useGlobal` is false', async () => {
89+
global.bar = 'global_bar';
90+
const context = await new Promise((resolve) => {
91+
const r = repl.start({
92+
useGlobal: false,
93+
eval: (_cmd, context) => resolve(context),
94+
});
95+
global.baz = 'global_baz';
96+
r.write('\n.exit\n');
97+
});
98+
99+
assert.strictEqual(context.bar, 'global_bar');
100+
assert.notStrictEqual(context.baz, 'global_baz');
101+
delete global.bar;
102+
delete global.baz;
103+
});
104+
105+
/**
106+
* Default preprocessor transforms
107+
* function f() {} to
108+
* var f = function f() {}
109+
* This test ensures that original input is preserved.
110+
* Reference: https://github.com/nodejs/node/issues/9743
111+
*/
112+
it('preserves the original input', async () => {
113+
const cmd = await new Promise((resolve) => {
114+
const r = repl.start({
115+
eval: (cmd) => resolve(cmd),
116+
});
117+
r.write('function f() {}\n.exit\n');
118+
});
119+
assert.strictEqual(cmd, 'function f() {}\n');
120+
});
121+
});

test/parallel/test-repl-eval.js

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)