【问题标题】:Testing Node CLI tool that listens to stdin.isTTY?测试侦听 stdin.isTTY 的 Node CLI 工具?
【发布时间】:2021-07-07 21:13:17
【问题描述】:

如何在 Node CLI 工具中实现验证 stdin.isTTY 不同行为的测试?

我在 Node 中实现了一个 CLI 工具,它期望数据通过终端管道传输或作为命令行参数传递:

cli.js

#!/usr/bin/env node

const { stdin, exit } = require('process');

const parseCliArgs = (argv) => {
  // parse args...
  return argv;
};

const readFromStdin = async () => {
  stdin.setEncoding('utf-8');

  return new Promise(((resolve, reject) => {
    let data = '';

    stdin.on('readable', () => {
      let chunk;
      while ((chunk = stdin.read()) !== null) {
        data += chunk;
      }
    });

    stdin.on('end', () => {
      resolve(data);
    });

    stdin.on('error', err => reject(err));
  }));
};


const main = async (argv) => {
  let args;

  console.info('isTTY: ', stdin.isTTY);

  if (stdin.isTTY) {
    console.info('Parse arguments');
    args = parseCliArgs(argv);
  } else {
    console.info('Read from stdin');
    args = await readFromStdin();
  }
  console.info(args);
};


main(process.argv)
  .catch((err) => {
    console.error(err);
    exit(1);
  });

从终端使用时,该工具按预期工作,例如管道数据到 CLI 脚本:

$ echo "hello" | ./cli.js
isTTY:  undefined
Read from stdin
hello

并且将数据作为命令行参数传递也可以按预期工作:

$ ./cli.js hello
isTTY:  true
Parse arguments
[
  '/usr/local/Cellar/node/13.2.0/bin/node',
  '[my local path]/cli.js',
  'hello'
]

现在,我尝试编写一个测试来验证这些行为:

cli.spec.js

const { execSync } = require('child_process');

// pipe stdio and stdout from child process to node's stdio and stdout
const CHILD_PROCESS_OPTIONS = { stdio: 'inherit' };

describe('cli test', () => {
  it('pipe data to CLI tool', () => {
    const command = `echo "hello" | ${__dirname}/cli.js`;
    execSync(command, CHILD_PROCESS_OPTIONS);
  });

  it('pass data as CLI args', () => {
    const command = `${__dirname}/cli.js "hello"`;
    execSync(command, CHILD_PROCESS_OPTIONS);
  });
});

pipe data to CLI tool 测试按预期工作(并提供与从命令行执行时相同的输出)。

pass data as CLI args 测试无限期挂起。查看输出,我发现

isTTY:  undefined
Read from stdin

基于此观察,我得出结论,stdin.isTTY 未正确处理(这导致从 readFromStdin() 函数返回的承诺仍未解决,从而挂起测试)。


  • 如何使测试pass data as CLI args 通过?
  • 是否可以在子进程中模拟process.stdin.isTTY

【问题讨论】:

    标签: javascript node.js testing command-line command-line-interface


    【解决方案1】:

    对于遇到这种情况的人来说,这与 Node.js 如何产生子进程有关。具体来说,它是 child_process API 支持的stdio option。与此相关的issue 在 Node.js 核心中打开。

    当您在 Node.js 中生成子进程时,它与父进程之间有管道/流来处理 stdio by default。因此,父级可以调用child.stdin.write() 将数据发送到子级的标准输入,也可以读取child.stderrchild.stdout 流。

    使用同步 child_process API 时,标准输入 (AFAICT) 非常令人困惑,因为您根本无法将 write() 发送给孩子。您可以根据文档传递 Stream 引用,但我无法使用 tty.Read/WriteStream 弄清楚。

    我遇到了这个问题,发现将options.stdio[0] 设置为inherit 解决了这个问题。这是因为我的父(测试)进程没有通过标准输入传递任何东西,因此设置了 TTY 标志,子进程将继承它并按预期工作。我认为ignore 可能有效,但它没有。

    TLDR;这是我的complete test case,下面是一个示例。要测试 Node.js CLI,您的 CLI 通过isTTY 检测管道但也支持文件参数,请尝试以下操作:

    try {
      const stdout = execSync('your-cli.js some-file.txt', {
        stdio: [
          // Child will inherit process.stdin of
          // this parent process. This has isTTY
          // set to true, so the child will also
          // be set to true and parse the "some-file.txt"
          // arg instead of waiting on stdin
          'inherit',
          // Capture stdout from child. This is returned from
          // the call, and attached to an exception if one is
          // thrown, i.e error.stdout
          'pipe',
          // Capture stderr from child. This will be attached
          // as a "stderr" property if an exception is raised
          'pipe'
        ]
      });
      console.log('Child Process stdout was:', stdout)
    } catch (e) {
      console.log('Error in child. Child stderr was:', e.stderr) 
    }
    

    【讨论】:

      【解决方案2】:

      事实证明,这些测试按预期工作,但并非在所有环境中都有效。从终端执行时,测试按预期通过(例如npm test 和朋友)。但是,最初的实现没有通过,从那时起,我尝试使用我的 IDE 中的调试器来寻找解决方案,但它仍然无法正常工作。如果有适用于所有环境的另一种解决方案,请提交另一个答案。

      【讨论】:

      • 我添加了一个答案,更深入地解释了为什么会这样。它并不完美,但增加了更多信息:)
      猜你喜欢
      • 1970-01-01
      • 2011-01-16
      • 1970-01-01
      • 2021-07-14
      • 2022-01-25
      • 1970-01-01
      • 1970-01-01
      • 2018-01-08
      相关资源
      最近更新 更多