user6689821
user6689821

Reputation: 157

readline.write() does not arrive at stdout

I'm trying to write a test for a CLI that uses Node.js's readline module to print and capture info from the user, and I can't seem to capture anything from stdout. A simple version of the issue I'm facing follows.

app.js:

#!/usr/bin/env node

const readline = require('readline')
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

rl.write('hello\n')
process.exit()

runner.js:

const spawn = require('child_process').spawn

const rl = spawn(__dirname + '/app.js')

rl.stdout.on('data', chunk => {
  console.log('stdout says', chunk.toString())
})

running runner.js, I would expect to see the output stdout says hello, but nothing prints.

However, if I run app.js directly, hello prints out to the console. Additionally, if I use other readline methods such as question, the handler will fire with the expected data.

Why doesn't this code work as expected? How can it be changed to work?

Upvotes: 2

Views: 1798

Answers (2)

pqnet
pqnet

Reputation: 6588

The write method of readline interface does not write to the output stream, but it simulates user input. If you set the terminal option to true, you enable echoing the user input to stdout, which is why that option seems to do what you expect, however using rl.write also has (among other things) the effect of triggering line events on the interface (which can lead to written stuff being interpreted as command in certain configurations).

If you want to write something for the user to read, just write directly onto the output stream, for example in your case you can use process.stdout.write()

Upvotes: 0

shaochuancs
shaochuancs

Reputation: 16236

Relate to Readline output to file Node.js

To capture the output of rl.write(), one solution is: define "terminal" as true when creating readline interface instance.

Sample code:

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: true
});

Explanation: readline module in node.js only write data to the "output" stream when "terminal" is true. Otherwise, it just emit "line" event, and send data to the line event handler. According to the source code (https://github.com/nodejs/node/blob/master/lib/readline.js):

First, Check whether "terminal" is configured. If not, make it equal to output stream's isTTY property:

if (terminal === undefined && !(output === null || output === undefined)) {
  terminal = !!output.isTTY;
}
...
this.terminal = !!terminal;

Second, when rl.write() function is invoked, it will call _ttyWrite() or _normalWrite(), depending on whether "terminal" is true:

Interface.prototype.write = function(d, key) {
  ...
  this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d);
};

Finally, if _ttyWrite() is called, data will be sent to output stream. If _normalWrite() is called, output stream is ignored:

//Interface.prototype._ttyWrite will invoke Interface.prototype._insertString, which will call Interface.prototype._writeToOutput
Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
  ...
  if (this.output !== null && this.output !== undefined)
    this.output.write(stringToWrite);
};

Thus, when app.js is run in console directly, "hello" will be printed, as "terminal" is equal to process.stdout.isTTY, which is true. However, when executed in child process, "terminal" is false (if not configured), as process.stdout.isTTY is undefined now.

Upvotes: 5

Related Questions