11cb0ef41Sopenharmony_ci'use strict';
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ci// Flags: --expose-internals
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ciconst common = require('../common');
61cb0ef41Sopenharmony_ciconst fixtures = require('../common/fixtures');
71cb0ef41Sopenharmony_ciconst stream = require('stream');
81cb0ef41Sopenharmony_ciconst REPL = require('internal/repl');
91cb0ef41Sopenharmony_ciconst assert = require('assert');
101cb0ef41Sopenharmony_ciconst fs = require('fs');
111cb0ef41Sopenharmony_ciconst path = require('path');
121cb0ef41Sopenharmony_ciconst os = require('os');
131cb0ef41Sopenharmony_ciconst util = require('util');
141cb0ef41Sopenharmony_ci
151cb0ef41Sopenharmony_cicommon.skipIfDumbTerminal();
161cb0ef41Sopenharmony_ci
171cb0ef41Sopenharmony_ciconst tmpdir = require('../common/tmpdir');
181cb0ef41Sopenharmony_citmpdir.refresh();
191cb0ef41Sopenharmony_ci
201cb0ef41Sopenharmony_ci// Mock os.homedir()
211cb0ef41Sopenharmony_cios.homedir = function() {
221cb0ef41Sopenharmony_ci  return tmpdir.path;
231cb0ef41Sopenharmony_ci};
241cb0ef41Sopenharmony_ci
251cb0ef41Sopenharmony_ci// Create an input stream specialized for testing an array of actions
261cb0ef41Sopenharmony_ciclass ActionStream extends stream.Stream {
271cb0ef41Sopenharmony_ci  run(data) {
281cb0ef41Sopenharmony_ci    const _iter = data[Symbol.iterator]();
291cb0ef41Sopenharmony_ci    const doAction = () => {
301cb0ef41Sopenharmony_ci      const next = _iter.next();
311cb0ef41Sopenharmony_ci      if (next.done) {
321cb0ef41Sopenharmony_ci        // Close the repl. Note that it must have a clean prompt to do so.
331cb0ef41Sopenharmony_ci        setImmediate(() => {
341cb0ef41Sopenharmony_ci          this.emit('keypress', '', { ctrl: true, name: 'd' });
351cb0ef41Sopenharmony_ci        });
361cb0ef41Sopenharmony_ci        return;
371cb0ef41Sopenharmony_ci      }
381cb0ef41Sopenharmony_ci      const action = next.value;
391cb0ef41Sopenharmony_ci
401cb0ef41Sopenharmony_ci      if (typeof action === 'object') {
411cb0ef41Sopenharmony_ci        this.emit('keypress', '', action);
421cb0ef41Sopenharmony_ci      } else {
431cb0ef41Sopenharmony_ci        this.emit('data', `${action}\n`);
441cb0ef41Sopenharmony_ci      }
451cb0ef41Sopenharmony_ci      setImmediate(doAction);
461cb0ef41Sopenharmony_ci    };
471cb0ef41Sopenharmony_ci    setImmediate(doAction);
481cb0ef41Sopenharmony_ci  }
491cb0ef41Sopenharmony_ci  resume() {}
501cb0ef41Sopenharmony_ci  pause() {}
511cb0ef41Sopenharmony_ci}
521cb0ef41Sopenharmony_ciActionStream.prototype.readable = true;
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci
551cb0ef41Sopenharmony_ci// Mock keys
561cb0ef41Sopenharmony_ciconst UP = { name: 'up' };
571cb0ef41Sopenharmony_ciconst DOWN = { name: 'down' };
581cb0ef41Sopenharmony_ciconst ENTER = { name: 'enter' };
591cb0ef41Sopenharmony_ciconst CLEAR = { ctrl: true, name: 'u' };
601cb0ef41Sopenharmony_ci
611cb0ef41Sopenharmony_ci// File paths
621cb0ef41Sopenharmony_ciconst historyFixturePath = fixtures.path('.node_repl_history');
631cb0ef41Sopenharmony_ciconst historyPath = path.join(tmpdir.path, '.fixture_copy_repl_history');
641cb0ef41Sopenharmony_ciconst historyPathFail = fixtures.path('nonexistent_folder', 'filename');
651cb0ef41Sopenharmony_ciconst defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
661cb0ef41Sopenharmony_ciconst emptyHiddenHistoryPath = fixtures.path('.empty-hidden-repl-history-file');
671cb0ef41Sopenharmony_ciconst devNullHistoryPath = path.join(tmpdir.path,
681cb0ef41Sopenharmony_ci                                     '.dev-null-repl-history-file');
691cb0ef41Sopenharmony_ci// Common message bits
701cb0ef41Sopenharmony_ciconst prompt = '> ';
711cb0ef41Sopenharmony_ciconst replDisabled = '\nPersistent history support disabled. Set the ' +
721cb0ef41Sopenharmony_ci                     'NODE_REPL_HISTORY environment\nvariable to a valid, ' +
731cb0ef41Sopenharmony_ci                     'user-writable path to enable.\n';
741cb0ef41Sopenharmony_ciconst homedirErr = '\nError: Could not get the home directory.\n' +
751cb0ef41Sopenharmony_ci                   'REPL session history will not be persisted.\n';
761cb0ef41Sopenharmony_ciconst replFailedRead = '\nError: Could not open history file.\n' +
771cb0ef41Sopenharmony_ci                       'REPL session history will not be persisted.\n';
781cb0ef41Sopenharmony_ci
791cb0ef41Sopenharmony_ciconst tests = [
801cb0ef41Sopenharmony_ci  {
811cb0ef41Sopenharmony_ci    env: { NODE_REPL_HISTORY: '' },
821cb0ef41Sopenharmony_ci    test: [UP],
831cb0ef41Sopenharmony_ci    expected: [prompt, replDisabled, prompt]
841cb0ef41Sopenharmony_ci  },
851cb0ef41Sopenharmony_ci  {
861cb0ef41Sopenharmony_ci    env: { NODE_REPL_HISTORY: ' ' },
871cb0ef41Sopenharmony_ci    test: [UP],
881cb0ef41Sopenharmony_ci    expected: [prompt, replDisabled, prompt]
891cb0ef41Sopenharmony_ci  },
901cb0ef41Sopenharmony_ci  {
911cb0ef41Sopenharmony_ci    env: { NODE_REPL_HISTORY: historyPath },
921cb0ef41Sopenharmony_ci    test: [UP, CLEAR],
931cb0ef41Sopenharmony_ci    expected: [prompt, `${prompt}'you look fabulous today'`, prompt]
941cb0ef41Sopenharmony_ci  },
951cb0ef41Sopenharmony_ci  {
961cb0ef41Sopenharmony_ci    env: {},
971cb0ef41Sopenharmony_ci    test: [UP, '21', ENTER, "'42'", ENTER],
981cb0ef41Sopenharmony_ci    expected: [
991cb0ef41Sopenharmony_ci      prompt,
1001cb0ef41Sopenharmony_ci      '2', '1', '21\n', prompt, prompt,
1011cb0ef41Sopenharmony_ci      "'", '4', '2', "'", "'42'\n", prompt, prompt,
1021cb0ef41Sopenharmony_ci    ],
1031cb0ef41Sopenharmony_ci    clean: false
1041cb0ef41Sopenharmony_ci  },
1051cb0ef41Sopenharmony_ci  { // Requires the above test case
1061cb0ef41Sopenharmony_ci    env: {},
1071cb0ef41Sopenharmony_ci    test: [UP, UP, CLEAR, ENTER, DOWN, CLEAR, ENTER, UP, ENTER],
1081cb0ef41Sopenharmony_ci    expected: [
1091cb0ef41Sopenharmony_ci      prompt,
1101cb0ef41Sopenharmony_ci      `${prompt}'42'`,
1111cb0ef41Sopenharmony_ci      `${prompt}21`,
1121cb0ef41Sopenharmony_ci      prompt,
1131cb0ef41Sopenharmony_ci      prompt,
1141cb0ef41Sopenharmony_ci      `${prompt}'42'`,
1151cb0ef41Sopenharmony_ci      prompt,
1161cb0ef41Sopenharmony_ci      prompt,
1171cb0ef41Sopenharmony_ci      `${prompt}21`,
1181cb0ef41Sopenharmony_ci      '21\n',
1191cb0ef41Sopenharmony_ci      prompt,
1201cb0ef41Sopenharmony_ci    ]
1211cb0ef41Sopenharmony_ci  },
1221cb0ef41Sopenharmony_ci  {
1231cb0ef41Sopenharmony_ci    env: { NODE_REPL_HISTORY: historyPath,
1241cb0ef41Sopenharmony_ci           NODE_REPL_HISTORY_SIZE: 1 },
1251cb0ef41Sopenharmony_ci    test: [UP, UP, DOWN, CLEAR],
1261cb0ef41Sopenharmony_ci    expected: [
1271cb0ef41Sopenharmony_ci      prompt,
1281cb0ef41Sopenharmony_ci      `${prompt}'you look fabulous today'`,
1291cb0ef41Sopenharmony_ci      prompt,
1301cb0ef41Sopenharmony_ci      `${prompt}'you look fabulous today'`,
1311cb0ef41Sopenharmony_ci      prompt,
1321cb0ef41Sopenharmony_ci    ]
1331cb0ef41Sopenharmony_ci  },
1341cb0ef41Sopenharmony_ci  {
1351cb0ef41Sopenharmony_ci    env: { NODE_REPL_HISTORY: historyPathFail,
1361cb0ef41Sopenharmony_ci           NODE_REPL_HISTORY_SIZE: 1 },
1371cb0ef41Sopenharmony_ci    test: [UP],
1381cb0ef41Sopenharmony_ci    expected: [prompt, replFailedRead, prompt, replDisabled, prompt]
1391cb0ef41Sopenharmony_ci  },
1401cb0ef41Sopenharmony_ci  {
1411cb0ef41Sopenharmony_ci    before: function before() {
1421cb0ef41Sopenharmony_ci      if (common.isWindows) {
1431cb0ef41Sopenharmony_ci        const execSync = require('child_process').execSync;
1441cb0ef41Sopenharmony_ci        execSync(`ATTRIB +H "${emptyHiddenHistoryPath}"`, (err) => {
1451cb0ef41Sopenharmony_ci          assert.ifError(err);
1461cb0ef41Sopenharmony_ci        });
1471cb0ef41Sopenharmony_ci      }
1481cb0ef41Sopenharmony_ci    },
1491cb0ef41Sopenharmony_ci    env: { NODE_REPL_HISTORY: emptyHiddenHistoryPath },
1501cb0ef41Sopenharmony_ci    test: [UP],
1511cb0ef41Sopenharmony_ci    expected: [prompt]
1521cb0ef41Sopenharmony_ci  },
1531cb0ef41Sopenharmony_ci  {
1541cb0ef41Sopenharmony_ci    before: function before() {
1551cb0ef41Sopenharmony_ci      if (!common.isWindows)
1561cb0ef41Sopenharmony_ci        fs.symlinkSync('/dev/null', devNullHistoryPath);
1571cb0ef41Sopenharmony_ci    },
1581cb0ef41Sopenharmony_ci    env: { NODE_REPL_HISTORY: devNullHistoryPath },
1591cb0ef41Sopenharmony_ci    test: [UP],
1601cb0ef41Sopenharmony_ci    expected: [prompt]
1611cb0ef41Sopenharmony_ci  },
1621cb0ef41Sopenharmony_ci  { // Make sure this is always the last test, since we change os.homedir()
1631cb0ef41Sopenharmony_ci    before: function before() {
1641cb0ef41Sopenharmony_ci      // Mock os.homedir() failure
1651cb0ef41Sopenharmony_ci      os.homedir = function() {
1661cb0ef41Sopenharmony_ci        throw new Error('os.homedir() failure');
1671cb0ef41Sopenharmony_ci      };
1681cb0ef41Sopenharmony_ci    },
1691cb0ef41Sopenharmony_ci    env: {},
1701cb0ef41Sopenharmony_ci    test: [UP],
1711cb0ef41Sopenharmony_ci    expected: [prompt, homedirErr, prompt, replDisabled, prompt]
1721cb0ef41Sopenharmony_ci  },
1731cb0ef41Sopenharmony_ci];
1741cb0ef41Sopenharmony_ciconst numtests = tests.length;
1751cb0ef41Sopenharmony_ci
1761cb0ef41Sopenharmony_ci
1771cb0ef41Sopenharmony_cifunction cleanupTmpFile() {
1781cb0ef41Sopenharmony_ci  try {
1791cb0ef41Sopenharmony_ci    // Write over the file, clearing any history
1801cb0ef41Sopenharmony_ci    fs.writeFileSync(defaultHistoryPath, '');
1811cb0ef41Sopenharmony_ci  } catch (err) {
1821cb0ef41Sopenharmony_ci    if (err.code === 'ENOENT') return true;
1831cb0ef41Sopenharmony_ci    throw err;
1841cb0ef41Sopenharmony_ci  }
1851cb0ef41Sopenharmony_ci  return true;
1861cb0ef41Sopenharmony_ci}
1871cb0ef41Sopenharmony_ci
1881cb0ef41Sopenharmony_ci// Copy our fixture to the tmp directory
1891cb0ef41Sopenharmony_cifs.createReadStream(historyFixturePath)
1901cb0ef41Sopenharmony_ci  .pipe(fs.createWriteStream(historyPath)).on('unpipe', () => runTest());
1911cb0ef41Sopenharmony_ci
1921cb0ef41Sopenharmony_ciconst runTestWrap = common.mustCall(runTest, numtests);
1931cb0ef41Sopenharmony_ci
1941cb0ef41Sopenharmony_cifunction runTest(assertCleaned) {
1951cb0ef41Sopenharmony_ci  const opts = tests.shift();
1961cb0ef41Sopenharmony_ci  if (!opts) return; // All done
1971cb0ef41Sopenharmony_ci
1981cb0ef41Sopenharmony_ci  console.log('NEW');
1991cb0ef41Sopenharmony_ci
2001cb0ef41Sopenharmony_ci  if (assertCleaned) {
2011cb0ef41Sopenharmony_ci    try {
2021cb0ef41Sopenharmony_ci      assert.strictEqual(fs.readFileSync(defaultHistoryPath, 'utf8'), '');
2031cb0ef41Sopenharmony_ci    } catch (e) {
2041cb0ef41Sopenharmony_ci      if (e.code !== 'ENOENT') {
2051cb0ef41Sopenharmony_ci        console.error(`Failed test # ${numtests - tests.length}`);
2061cb0ef41Sopenharmony_ci        throw e;
2071cb0ef41Sopenharmony_ci      }
2081cb0ef41Sopenharmony_ci    }
2091cb0ef41Sopenharmony_ci  }
2101cb0ef41Sopenharmony_ci
2111cb0ef41Sopenharmony_ci  const env = opts.env;
2121cb0ef41Sopenharmony_ci  const test = opts.test;
2131cb0ef41Sopenharmony_ci  const expected = opts.expected;
2141cb0ef41Sopenharmony_ci  const clean = opts.clean;
2151cb0ef41Sopenharmony_ci  const before = opts.before;
2161cb0ef41Sopenharmony_ci
2171cb0ef41Sopenharmony_ci  if (before) before();
2181cb0ef41Sopenharmony_ci
2191cb0ef41Sopenharmony_ci  REPL.createInternalRepl(env, {
2201cb0ef41Sopenharmony_ci    input: new ActionStream(),
2211cb0ef41Sopenharmony_ci    output: new stream.Writable({
2221cb0ef41Sopenharmony_ci      write(chunk, _, next) {
2231cb0ef41Sopenharmony_ci        const output = chunk.toString();
2241cb0ef41Sopenharmony_ci        console.log('INPUT', util.inspect(output));
2251cb0ef41Sopenharmony_ci
2261cb0ef41Sopenharmony_ci        // Ignore escapes and blank lines
2271cb0ef41Sopenharmony_ci        if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
2281cb0ef41Sopenharmony_ci          return next();
2291cb0ef41Sopenharmony_ci
2301cb0ef41Sopenharmony_ci        try {
2311cb0ef41Sopenharmony_ci          assert.strictEqual(output, expected.shift());
2321cb0ef41Sopenharmony_ci        } catch (err) {
2331cb0ef41Sopenharmony_ci          console.error(`Failed test # ${numtests - tests.length}`);
2341cb0ef41Sopenharmony_ci          throw err;
2351cb0ef41Sopenharmony_ci        }
2361cb0ef41Sopenharmony_ci        next();
2371cb0ef41Sopenharmony_ci      }
2381cb0ef41Sopenharmony_ci    }),
2391cb0ef41Sopenharmony_ci    prompt,
2401cb0ef41Sopenharmony_ci    useColors: false,
2411cb0ef41Sopenharmony_ci    terminal: true
2421cb0ef41Sopenharmony_ci  }, function(err, repl) {
2431cb0ef41Sopenharmony_ci    if (err) {
2441cb0ef41Sopenharmony_ci      console.error(`Failed test # ${numtests - tests.length}`);
2451cb0ef41Sopenharmony_ci      throw err;
2461cb0ef41Sopenharmony_ci    }
2471cb0ef41Sopenharmony_ci
2481cb0ef41Sopenharmony_ci    repl.once('close', () => {
2491cb0ef41Sopenharmony_ci      if (repl._flushing) {
2501cb0ef41Sopenharmony_ci        repl.once('flushHistory', onClose);
2511cb0ef41Sopenharmony_ci        return;
2521cb0ef41Sopenharmony_ci      }
2531cb0ef41Sopenharmony_ci
2541cb0ef41Sopenharmony_ci      onClose();
2551cb0ef41Sopenharmony_ci    });
2561cb0ef41Sopenharmony_ci
2571cb0ef41Sopenharmony_ci    function onClose() {
2581cb0ef41Sopenharmony_ci      const cleaned = clean === false ? false : cleanupTmpFile();
2591cb0ef41Sopenharmony_ci
2601cb0ef41Sopenharmony_ci      try {
2611cb0ef41Sopenharmony_ci        // Ensure everything that we expected was output
2621cb0ef41Sopenharmony_ci        assert.strictEqual(expected.length, 0);
2631cb0ef41Sopenharmony_ci        setImmediate(runTestWrap, cleaned);
2641cb0ef41Sopenharmony_ci      } catch (err) {
2651cb0ef41Sopenharmony_ci        console.error(`Failed test # ${numtests - tests.length}`);
2661cb0ef41Sopenharmony_ci        throw err;
2671cb0ef41Sopenharmony_ci      }
2681cb0ef41Sopenharmony_ci    }
2691cb0ef41Sopenharmony_ci
2701cb0ef41Sopenharmony_ci    repl.inputStream.run(test);
2711cb0ef41Sopenharmony_ci  });
2721cb0ef41Sopenharmony_ci}
273