Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repl: fix repl .load #28608

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1535,10 +1535,10 @@ function defineDefaultCommands(repl) {
help: 'Save all evaluated commands in this REPL session to a file',
action: function(file) {
try {
fs.writeFileSync(file, this.lines.join('\n') + '\n');
this.outputStream.write('Session saved to: ' + file + '\n');
fs.writeFileSync(file, this.lines.join('\n'));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The originally added line break caused the complete function not to work as expected. Saving should just plainly save exactly the users data and not add the extra line break, since that's not what the user actually did.

this.outputStream.write(`Session saved to: ${file}\n`);
} catch {
this.outputStream.write('Failed to save: ' + file + '\n');
this.outputStream.write(`Failed to save: ${file}\n`);
}
this.displayPrompt();
}
Expand All @@ -1548,23 +1548,20 @@ function defineDefaultCommands(repl) {
help: 'Load JS from a file into the REPL session',
action: function(file) {
try {
var stats = fs.statSync(file);
const stats = fs.statSync(file);
if (stats && stats.isFile()) {
_turnOnEditorMode(this);
var data = fs.readFileSync(file, 'utf8');
var lines = data.split('\n');
for (var n = 0; n < lines.length; n++) {
if (lines[n])
this.write(`${lines[n]}\n`);
Copy link
Member Author

@BridgeAR BridgeAR Jul 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changed behavior here is also a bugfix: in case the saved load would be a template string with multiple line breaks, it would have changed the users data. That is not good and we should always load all data 1-to-1.

}
const data = fs.readFileSync(file, 'utf8');
this.write(data);
_turnOffEditorMode(this);
this.write('\n');
} else {
this.outputStream.write('Failed to load: ' + file +
' is not a valid file\n');
this.outputStream.write(
`Failed to load: ${file} is not a valid file\n`
);
}
} catch {
this.outputStream.write('Failed to load: ' + file + '\n');
this.outputStream.write(`Failed to load: ${file}\n`);
}
this.displayPrompt();
}
Expand Down
1 change: 1 addition & 0 deletions test/parallel/test-repl-load-multiline.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const expected = `${command}
const getLunch = () =>
placeOrder('tacos')
.then(eat);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's the bug fix mentioned here: #28608 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we're in a string template here, which wasn't obvious from the GitHub interface. I see now.

const placeOrder = (order) => Promise.resolve(order);
const eat = (food) => '<nom nom nom>';

Expand Down
12 changes: 8 additions & 4 deletions test/parallel/test-repl-pretty-custom-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const fixtures = require('../common/fixtures');
const assert = require('assert');
const repl = require('repl');

const stackRegExp = /repl:[0-9]+:[0-9]+/g;

function run({ command, expected }) {
let accum = '';
Expand All @@ -23,7 +24,10 @@ function run({ command, expected }) {
});

r.write(`${command}\n`);
assert.strictEqual(accum, expected);
assert.strictEqual(
accum.replace(stackRegExp, 'repl:*:*'),
expected.replace(stackRegExp, 'repl:*:*')
);
r.close();
}

Expand All @@ -44,8 +48,8 @@ const tests = [
{
// test .load for a file that throws
command: `.load ${fixtures.path('repl-pretty-stack.js')}`,
expected: 'Thrown:\nError: Whoops!--->\nrepl:9:24--->\nd (repl:12:3)' +
'--->\nc (repl:9:3)--->\nb (repl:6:3)--->\na (repl:3:3)\n'
expected: 'Thrown:\nError: Whoops!--->\nrepl:*:*--->\nd (repl:*:*)' +
'--->\nc (repl:*:*)--->\nb (repl:*:*)--->\na (repl:*:*)\n'
},
{
command: 'let x y;',
Expand All @@ -63,7 +67,7 @@ const tests = [
// test anonymous IIFE
{
command: '(function() { throw new Error(\'Whoops!\'); })()',
expected: 'Thrown:\nError: Whoops!--->\nrepl:1:21\n'
expected: 'Thrown:\nError: Whoops!--->\nrepl:*:*\n'
}
];

Expand Down
18 changes: 11 additions & 7 deletions test/parallel/test-repl-pretty-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const fixtures = require('../common/fixtures');
const assert = require('assert');
const repl = require('repl');

const stackRegExp = /(at .*repl:)[0-9]+:[0-9]+/g;

function run({ command, expected, ...extraREPLOptions }) {
let accum = '';
Expand All @@ -24,17 +25,20 @@ function run({ command, expected, ...extraREPLOptions }) {
});

r.write(`${command}\n`);
assert.strictEqual(accum, expected);
assert.strictEqual(
accum.replace(stackRegExp, '$1*:*'),
expected.replace(stackRegExp, '$1*:*')
);
r.close();
}

const tests = [
{
// Test .load for a file that throws.
command: `.load ${fixtures.path('repl-pretty-stack.js')}`,
expected: 'Thrown:\nError: Whoops!\n at repl:9:24\n' +
' at d (repl:12:3)\n at c (repl:9:3)\n' +
' at b (repl:6:3)\n at a (repl:3:3)\n'
expected: 'Thrown:\nError: Whoops!\n at repl:*:*\n' +
' at d (repl:*:*)\n at c (repl:*:*)\n' +
' at b (repl:*:*)\n at a (repl:*:*)\n'
},
{
command: 'let x y;',
Expand All @@ -48,12 +52,12 @@ const tests = [
{
command: '(() => { const err = Error(\'Whoops!\'); ' +
'err.foo = \'bar\'; throw err; })()',
expected: "Thrown:\nError: Whoops!\n at repl:1:22 {\n foo: 'bar'\n}\n",
expected: "Thrown:\nError: Whoops!\n at repl:*:* {\n foo: 'bar'\n}\n",
},
{
command: '(() => { const err = Error(\'Whoops!\'); ' +
'err.foo = \'bar\'; throw err; })()',
expected: 'Thrown:\nError: Whoops!\n at repl:1:22 {\n foo: ' +
expected: 'Thrown:\nError: Whoops!\n at repl:*:* {\n foo: ' +
"\u001b[32m'bar'\u001b[39m\n}\n",
useColors: true
},
Expand All @@ -64,7 +68,7 @@ const tests = [
// Test anonymous IIFE.
{
command: '(function() { throw new Error(\'Whoops!\'); })()',
expected: 'Thrown:\nError: Whoops!\n at repl:1:21\n'
expected: 'Thrown:\nError: Whoops!\n at repl:*:*\n'
}
];

Expand Down
115 changes: 61 additions & 54 deletions test/parallel/test-repl-save-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';
require('../common');
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const join = require('path').join;
Expand All @@ -36,96 +36,103 @@ const works = [['inner.one'], 'inner.o'];
const putIn = new ArrayStream();
const testMe = repl.start('', putIn);

// Some errors might be passed to the domain.
testMe._domain.on('error', function(reason) {
const err = new Error('Test failed');
err.reason = reason;
throw err;
});

const testFile = [
'var top = function() {',
'var inner = {one:1};'
];
const saveFileName = join(tmpdir.path, 'test.save.js');

// input some data
// Add some data.
putIn.run(testFile);

// save it to a file
// Save it to a file.
putIn.run([`.save ${saveFileName}`]);

// The file should have what I wrote
// The file should have what I wrote.
assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'),
`${testFile.join('\n')}\n`);
testFile.join('\n'));

{
// save .editor mode code
const cmds = [
'function testSave() {',
'return "saved";',
'}'
];
const putIn = new ArrayStream();
const replServer = repl.start({ terminal: true, stream: putIn });

putIn.run(['.editor']);
putIn.run(cmds);
replServer.write('', { ctrl: true, name: 'd' });

putIn.run([`.save ${saveFileName}`]);
replServer.close();
assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'),
`${cmds.join('\n')}\n\n`);
}

// Make sure that the REPL data is "correct"
// so when I load it back I know I'm good
testMe.complete('inner.o', function(error, data) {
// Make sure that the REPL data is "correct".
testMe.complete('inner.o', common.mustCall(function(error, data) {
assert.ifError(error);
assert.deepStrictEqual(data, works);
});
}));

// clear the REPL
// Clear the REPL.
putIn.run(['.clear']);

// Load the file back in
// Load the file back in.
putIn.run([`.load ${saveFileName}`]);

// Make sure that the REPL data is "correct"
testMe.complete('inner.o', function(error, data) {
// Make sure that the REPL data is "correct".
testMe.complete('inner.o', common.mustCall(function(error, data) {
assert.ifError(error);
assert.deepStrictEqual(data, works);
});
}));

// clear the REPL
// Clear the REPL.
putIn.run(['.clear']);

let loadFile = join(tmpdir.path, 'file.does.not.exist');

// should not break
putIn.write = function(data) {
// Make sure I get a failed to load message and not some crazy error
assert.strictEqual(data, `Failed to load:${loadFile}\n`);
// Eat me to avoid work
// Should not break.
putIn.write = common.mustCall(function(data) {
// Make sure I get a failed to load message and not some crazy error.
assert.strictEqual(data, `Failed to load: ${loadFile}\n`);
// Eat me to avoid work.
putIn.write = () => {};
};
});
putIn.run([`.load ${loadFile}`]);

// Throw error on loading directory
// Throw error on loading directory.
loadFile = tmpdir.path;
putIn.write = function(data) {
assert.strictEqual(data, `Failed to load:${loadFile} is not a valid file\n`);
putIn.write = common.mustCall(function(data) {
assert.strictEqual(data, `Failed to load: ${loadFile} is not a valid file\n`);
putIn.write = () => {};
};
});
putIn.run([`.load ${loadFile}`]);

// clear the REPL
// Clear the REPL.
putIn.run(['.clear']);

// NUL (\0) is disallowed in filenames in UNIX-like operating systems and
// Windows so we can use that to test failed saves
// Windows so we can use that to test failed saves.
const invalidFileName = join(tmpdir.path, '\0\0\0\0\0');

// should not break
putIn.write = function(data) {
// Make sure I get a failed to save message and not some other error
assert.strictEqual(data, `Failed to save:${invalidFileName}\n`);
// reset to no-op
// Should not break.
putIn.write = common.mustCall(function(data) {
// Make sure I get a failed to save message and not some other error.
assert.strictEqual(data, `Failed to save: ${invalidFileName}\n`);
// Reset to no-op.
putIn.write = () => {};
};
});

// save it to a file
// Save it to a file.
putIn.run([`.save ${invalidFileName}`]);

{
// Save .editor mode code.
const cmds = [
'function testSave() {',
'return "saved";',
'}'
];
const putIn = new ArrayStream();
const replServer = repl.start({ terminal: true, stream: putIn });

putIn.run(['.editor']);
putIn.run(cmds);
replServer.write('', { ctrl: true, name: 'd' });

putIn.run([`.save ${saveFileName}`]);
replServer.close();
assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'),
`${cmds.join('\n')}\n`);
}