test: add tests for persistent repl history
authorJeremiah Senkpiel <fishrock123@rocketmail.com>
Sun, 2 Aug 2015 05:38:28 +0000 (22:38 -0700)
committerRod Vagg <rod@vagg.org>
Tue, 4 Aug 2015 18:56:18 +0000 (11:56 -0700)
PR-URL: https://github.com/nodejs/io.js/pull/2224
Reviewed-By: Michaƫl Zasso <mic.besace@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
.eslintrc
lib/internal/repl.js
test/fixtures/.node_repl_history [new file with mode: 0644]
test/fixtures/old-repl-history-file.json [new file with mode: 0644]
test/sequential/test-repl-persistent-history.js [new file with mode: 0644]

index c90fa5e..532ac8b 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
@@ -10,6 +10,8 @@ ecmaFeatures:
   generators: true
   forOf: true
   objectLiteralShorthandProperties: true
+  objectLiteralShorthandMethods: true
+  classes: true
 
 rules:
   # Possible Errors
index 902e81f..278035f 100644 (file)
@@ -17,8 +17,12 @@ function replStart() {
   return REPL.start.apply(REPL, arguments);
 }
 
-function createRepl(env, cb) {
-  const opts = {
+function createRepl(env, opts, cb) {
+  if (typeof opts === 'function') {
+    cb = opts;
+    opts = null;
+  }
+  opts = opts || {
     ignoreUndefined: false,
     terminal: process.stdout.isTTY,
     useGlobal: true
diff --git a/test/fixtures/.node_repl_history b/test/fixtures/.node_repl_history
new file mode 100644 (file)
index 0000000..31ad6d3
--- /dev/null
@@ -0,0 +1,2 @@
+'you look fabulous today'
+'Stay Fresh~'
diff --git a/test/fixtures/old-repl-history-file.json b/test/fixtures/old-repl-history-file.json
new file mode 100644 (file)
index 0000000..963d93d
--- /dev/null
@@ -0,0 +1,4 @@
+[
+  "'=^.^='",
+  "'hello world'"
+]
diff --git a/test/sequential/test-repl-persistent-history.js b/test/sequential/test-repl-persistent-history.js
new file mode 100644 (file)
index 0000000..8d550f6
--- /dev/null
@@ -0,0 +1,193 @@
+'use strict';
+
+// Flags: --expose-internals
+
+const common = require('../common');
+const stream = require('stream');
+const REPL = require('internal/repl');
+const assert = require('assert');
+const fs = require('fs');
+const util = require('util');
+const path = require('path');
+const os = require('os');
+
+common.refreshTmpDir();
+
+// Mock os.homedir()
+os.homedir = function() {
+  return common.tmpDir;
+};
+
+// Create an input stream specialized for testing an array of actions
+class ActionStream extends stream.Stream {
+  run(data) {
+    const _iter = data[Symbol.iterator]();
+    const self = this;
+
+    function doAction() {
+      const next = _iter.next();
+      if (next.done) {
+        // Close the repl. Note that it must have a clean prompt to do so.
+        setImmediate(function() {
+          self.emit('keypress', '', { ctrl: true, name: 'd' });
+        });
+        return;
+      }
+      const action = next.value;
+
+      if (typeof action === 'object') {
+        self.emit('keypress', '', action);
+      } else {
+        self.emit('data', action + '\n');
+      }
+      setImmediate(doAction);
+    }
+    setImmediate(doAction);
+  }
+  resume() {}
+  pause() {}
+}
+ActionStream.prototype.readable = true;
+
+
+// Mock keys
+const UP = { name: 'up' };
+const ENTER = { name: 'enter' };
+const CLEAR = { ctrl: true, name: 'u' };
+// Common message bits
+const prompt = '> ';
+const replDisabled = '\nPersistent history support disabled. Set the ' +
+                     'NODE_REPL_HISTORY environment\nvariable to a valid, ' +
+                     'user-writable path to enable.\n';
+const convertMsg = '\nConverting old JSON repl history to line-separated ' +
+                   'history.\nThe new repl history file can be found at ' +
+                   path.join(common.tmpDir, '.node_repl_history') + '.\n';
+const homedirErr = '\nError: Could not get the home directory.\n' +
+                   'REPL session history will not be persisted.\n';
+// File paths
+const fixtures = path.join(common.testDir, 'fixtures');
+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 tests = [{
+  env: { NODE_REPL_HISTORY: '' },
+  test: [UP],
+  expected: [prompt, replDisabled, prompt]
+},
+{
+  env: { NODE_REPL_HISTORY: '',
+         NODE_REPL_HISTORY_FILE: oldHistoryPath },
+  test: [UP],
+  expected: [prompt, replDisabled, prompt]
+},
+{
+  env: { NODE_REPL_HISTORY: historyPath },
+  test: [UP, CLEAR],
+  expected: [prompt, prompt + '\'you look fabulous today\'', prompt]
+},
+{
+  env: { NODE_REPL_HISTORY: historyPath,
+         NODE_REPL_HISTORY_FILE: oldHistoryPath },
+  test: [UP, CLEAR],
+  expected: [prompt, prompt + '\'you look fabulous today\'', prompt]
+},
+{
+  env: { NODE_REPL_HISTORY: historyPath,
+         NODE_REPL_HISTORY_FILE: '' },
+  test: [UP, CLEAR],
+  expected: [prompt, prompt + '\'you look fabulous today\'', prompt]
+},
+{
+  env: {},
+  test: [UP],
+  expected: [prompt]
+},
+{
+  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);
+  }*/],
+  expected: [prompt, convertMsg, prompt, prompt + '\'=^.^=\'', prompt, '\'',
+             '4', '2', '\'', '\'42\'\n', prompt, prompt],
+  after: function ensureHistoryFixture() {
+    // XXX(Fishrock123) Make sure nothing weird happened to our fixture
+    //  or it's temporary copy.
+    // Sometimes this test used to erase the fixture and I'm not sure why.
+    const history = fs.readFileSync(historyFixturePath, 'utf8');
+    assert.strictEqual(history,
+                       '\'you look fabulous today\'\n\'Stay Fresh~\'\n');
+    const historyCopy = fs.readFileSync(historyPath, 'utf8');
+    assert.strictEqual(historyCopy, '\'you look fabulous today\'' + os.EOL +
+                                    '\'Stay Fresh~\'' + os.EOL);
+  }
+},
+{
+  env: {},
+  test: [UP, UP, ENTER],
+  expected: [prompt, prompt + '\'42\'', prompt + '\'=^.^=\'', '\'=^.^=\'\n',
+             prompt]
+},
+{ // Make sure this is always the last test, since we change os.homedir()
+  before: function mockHomedirFailure() {
+    // Mock os.homedir() failure
+    os.homedir = function() {
+      throw new Error('os.homedir() failure');
+    };
+  },
+  env: {},
+  test: [UP],
+  expected: [prompt, homedirErr, prompt, replDisabled, prompt]
+}];
+
+
+// Copy our fixture to the tmp directory
+fs.createReadStream(historyFixturePath)
+  .pipe(fs.createWriteStream(historyPath)).on('unpipe', runTest);
+
+function runTest() {
+  const opts = tests.shift();
+  if (!opts) return; // All done
+
+  const env = opts.env;
+  const test = opts.test;
+  const expected = opts.expected;
+  const after = opts.after;
+  const before = opts.before;
+
+  if (before) before();
+
+  REPL.createInternalRepl(env, {
+    input: new ActionStream(),
+    output: new stream.Writable({
+      write(chunk, _, next) {
+        const output = chunk.toString();
+
+        // Ignore escapes and blank lines
+        if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
+          return next();
+
+        assert.strictEqual(output, expected.shift());
+        next();
+      }
+    }),
+    prompt: prompt,
+    useColors: false,
+    terminal: true
+  }, function(err, repl) {
+    if (err) throw err;
+
+    if (after) repl.on('close', after);
+
+    repl.on('close', function() {
+      // Ensure everything that we expected was output
+      assert.strictEqual(expected.length, 0);
+      setImmediate(runTest);
+    });
+
+    repl.inputStream.run(test);
+  });
+}