mardi 10 décembre 2019

Testing Node CLI tool that listens to stdin.isTTY?

How can I implement a test that verifies the different behaviours of stdin.isTTY in a Node CLI tool?

I have implemented a CLI tool in Node that expects data to be either piped through the terminal or passed as command line args:

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;
      // eslint-disable-next-line no-cond-assign
      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);
  });

The tool works as expected when used from the terminal, e.g. piping data to the CLI script:

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

And passing data as command line arguments also works as expected:

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

I have implemented a test that attempts to verify these behaviours:

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);
  });
});

The pipe data to CLI tool test works as expected (and gives the same output as when executed from command line).

The pass data as CLI args test hangs indefinitely. Looking at the output, I find that

isTTY:  undefined
Read from stdin

Based on this observation I draw the conclusion that stdin.isTTY is not handled correctly (which causes the promise returned from the readFromStdin() function to remain unresolved, thus hanging the test).

Aucun commentaire:

Enregistrer un commentaire