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 <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
1 parent 17df800 commit 704b1fa

4 files changed

Lines changed: 222 additions & 34 deletions

File tree

‎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
constpreview=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+
constcommon=require('../common');
4+
constArrayStream=require('../common/arraystream');
5+
constassert=require('assert');
6+
const{ describe, it }=require('node:test');
7+
8+
common.skipIfInspectorDisabled();
9+
10+
constrepl=require('repl');
11+
12+
consttestingReplPrompt='_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+
functiongetReplRunOutput(input,replOptions){
17+
returnnewPromise((resolve)=>{
18+
constinputStream=newArrayStream();
19+
constoutputStream=newArrayStream();
20+
21+
constreplServer=repl.start({
22+
input: inputStream,
23+
output: outputStream,
24+
prompt: testingReplPrompt,
25+
...replOptions,
26+
});
27+
28+
letoutput='';
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+
constinput="'Hello custom' + ' eval World!'";
48+
constoutput=awaitgetReplRunOutput(
49+
input,
50+
{
51+
terminal: true,
52+
eval: (code,_ctx,_replRes,cb)=>cb(null,eval(code)),
53+
},
54+
);
55+
constlines=getSingleCommandLines(output);
56+
assert.match(lines.command,/^'Hellocustom'\+'evalWorld!'/);
57+
assert.match(lines.prompt,newRegExp(`${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+
constinput="'Hello custom' + ' eval World!'";
64+
constoutput=awaitgetReplRunOutput(
65+
input,
66+
{
67+
terminal: true,
68+
eval: (code,_ctx,_replRes,cb)=>cb(null,eval(code)),
69+
preview: true,
70+
},
71+
);
72+
constlines=getSingleCommandLines(output);
73+
assert.match(lines.command,/^'Hellocustom'\+'evalWorld!'/);
74+
assert.match(lines.prompt,newRegExp(`${testingReplPrompt}$`));
75+
assert.strictEqual(lines.result,"'Hello custom eval World!'");
76+
assert.match(lines.preview,/'HellocustomevalWorld!'/);
77+
});
78+
});
79+
80+
functiongetSingleCommandLines(output){
81+
constoutputLines=output.split('\n');
82+
83+
// The first line contains the command being run
84+
constcommand=outputLines.shift();
85+
86+
// The last line contains the prompt (asking for some new input)
87+
constprompt=outputLines.pop();
88+
89+
// The line before the last one contains the result of the command
90+
constresult=outputLines.pop();
91+
92+
// The line before that contains the preview of the command
93+
constpreview=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+
constArrayStream=require('../common/arraystream');
5+
constassert=require('assert');
6+
const{ describe, it }=require('node:test');
7+
8+
constrepl=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+
functiongetReplRunOutput(input,replOptions){
13+
returnnewPromise((resolve)=>{
14+
constinputStream=newArrayStream();
15+
constoutputStream=newArrayStream();
16+
17+
consttestingReplPrompt='_REPL_TESTING_PROMPT_>';
18+
19+
constreplServer=repl.start({
20+
input: inputStream,
21+
output: outputStream,
22+
prompt: testingReplPrompt,
23+
...replOptions,
24+
});
25+
26+
letoutput='';
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+
constoutput=awaitgetReplRunOutput('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+
/Convertthistouppercase\r\n'CONVERTTHISTOUPPERCASE\\n'/
52+
);
53+
});
54+
55+
it('surfaces errors as expected',async()=>{
56+
constoutput=awaitgetReplRunOutput('Convert this to upper case',{
57+
terminal: true,
58+
eval: (_code,_ctx,_replRes,cb)=>cb(newError('Testing Error')),
59+
});
60+
assert.match(output,/UncaughtError:TestingError\n/);
61+
});
62+
63+
it('provides a repl context to the eval callback',async()=>{
64+
constcontext=awaitnewPromise((resolve)=>{
65+
constr=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+
constcontext=awaitnewPromise((resolve)=>{
76+
constr=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+
deleteglobal.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+
constcontext=awaitnewPromise((resolve)=>{
91+
constr=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+
deleteglobal.bar;
102+
deleteglobal.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+
constcmd=awaitnewPromise((resolve)=>{
114+
constr=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)