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/improve persistent repl history #2356

Merged
merged 2 commits into from
Oct 19, 2015
Merged
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
4 changes: 2 additions & 2 deletions lib/internal/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function setupHistory(repl, historyPath, oldHistoryPath, ready) {
}

if (data) {
repl.history = data.split(/[\n\r]+/).slice(-repl.historySize);
repl.history = data.split(/[\n\r]+/, repl.historySize);
} else if (oldHistoryPath) {
// Grab data from the older pre-v3.0 JSON NODE_REPL_HISTORY_FILE format.
repl._writeToOutput(
Expand All @@ -123,7 +123,7 @@ function setupHistory(repl, historyPath, oldHistoryPath, ready) {
if (!Array.isArray(repl.history)) {
throw new Error('Expected array, got ' + typeof repl.history);
}
repl.history = repl.history.slice(-repl.historySize);
repl.history = repl.history.slice(0, repl.historySize);
} catch (err) {
if (err.code !== 'ENOENT') {
return ready(
Expand Down
95 changes: 80 additions & 15 deletions test/sequential/test-repl-persistent-history.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const historyFixturePath = path.join(fixtures, '.node_repl_history');
const historyPath = path.join(common.tmpDir, '.fixture_copy_repl_history');
const oldHistoryPath = path.join(fixtures, 'old-repl-history-file.json');
const enoentHistoryPath = path.join(fixtures, 'enoent-repl-history-file.json');
const defaultHistoryPath = path.join(common.tmpDir, '.node_repl_history');


const tests = [{
Expand Down Expand Up @@ -113,11 +114,7 @@ const tests = [{
},
{
env: { NODE_REPL_HISTORY_FILE: oldHistoryPath },
test: [UP, CLEAR, '\'42\'', ENTER/*, function(cb) {
// XXX(Fishrock123) Allow the REPL to save to disk.
// There isn't a way to do this programmatically right now.
setTimeout(cb, 50);
}*/],
test: [UP, CLEAR, '\'42\'', ENTER],
expected: [prompt, convertMsg, prompt, prompt + '\'=^.^=\'', prompt, '\'',
'4', '2', '\'', '\'42\'\n', prompt, prompt],
after: function ensureHistoryFixture() {
Expand All @@ -132,12 +129,24 @@ const tests = [{
'\'Stay Fresh~\'' + os.EOL);
}
},
{
{ // Requires the above testcase
env: {},
test: [UP, UP, ENTER],
expected: [prompt, prompt + '\'42\'', prompt + '\'=^.^=\'', '\'=^.^=\'\n',
prompt]
},
{
env: { NODE_REPL_HISTORY: historyPath,
NODE_REPL_HISTORY_SIZE: 1 },
test: [UP, UP, CLEAR],
expected: [prompt, prompt + '\'you look fabulous today\'', prompt]
},
{
env: { NODE_REPL_HISTORY_FILE: oldHistoryPath,
NODE_REPL_HISTORY_SIZE: 1 },
test: [UP, UP, UP, CLEAR],
expected: [prompt, convertMsg, prompt, prompt + '\'=^.^=\'', prompt]
},
{ // Make sure this is always the last test, since we change os.homedir()
before: function mockHomedirFailure() {
// Mock os.homedir() failure
Expand All @@ -149,16 +158,45 @@ const tests = [{
test: [UP],
expected: [prompt, homedirErr, prompt, replDisabled, prompt]
}];
const numtests = tests.length;


var testsNotRan = tests.length;

process.on('beforeExit', () =>
assert.strictEqual(testsNotRan, 0)
);

function cleanupTmpFile() {
try {
// Write over the file, clearing any history
fs.writeFileSync(defaultHistoryPath, '');
} catch (err) {
if (err.code === 'ENOENT') return true;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm, this needs throw err; I think.

throw err;
}
return true;
}

// Copy our fixture to the tmp directory
fs.createReadStream(historyFixturePath)
.pipe(fs.createWriteStream(historyPath)).on('unpipe', runTest);
.pipe(fs.createWriteStream(historyPath)).on('unpipe', () => runTest());

function runTest() {
function runTest(assertCleaned) {
const opts = tests.shift();
if (!opts) return; // All done

if (assertCleaned) {
try {
assert.strictEqual(fs.readFileSync(defaultHistoryPath, 'utf8'), '');
} catch (e) {
if (e.code !== 'ENOENT') {
console.error(`Failed test # ${numtests - tests.length}`);
throw e;
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@targos That other bit allows this to run properly.

I forgot to originally comment, but I'm not sure if I should keep this check. it's not strictly necessary and sort-of ends up testing fs.{write|read}FileSync() more than anything.


const env = opts.env;
const test = opts.test;
const expected = opts.expected;
Expand All @@ -177,24 +215,51 @@ function runTest() {
if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
return next();

assert.strictEqual(output, expected.shift());
try {
assert.strictEqual(output, expected.shift());
} catch (err) {
console.error(`Failed test # ${numtests - tests.length}`);
throw err;
}
next();
}
}),
prompt: prompt,
useColors: false,
terminal: true
}, function(err, repl) {
if (err) throw err;
if (err) {
console.error(`Failed test # ${numtests - tests.length}`);
throw err;
}

if (after) repl.on('close', after);
repl.once('close', () => {
if (repl._flushing) {
repl.once('flushHistory', onClose);
return;
}

repl.on('close', function() {
// Ensure everything that we expected was output
assert.strictEqual(expected.length, 0);
setImmediate(runTest);
onClose();
});
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@silverwind @evanlucas can I get a last LGTM on this? this works without any even minor breaking changes now.


function onClose() {
if (after) {
var cleaned = after();
} else {
var cleaned = cleanupTmpFile();
}

try {
// Ensure everything that we expected was output
assert.strictEqual(expected.length, 0);
testsNotRan--;
setImmediate(runTest, cleaned);
} catch (err) {
console.error(`Failed test # ${numtests - tests.length}`);
throw err;
}
}

repl.inputStream.run(test);
});
}