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