When executable is spawned from node if there is no stdin getStdin() never resolves
lukechilds opened this issue ยท 13 comments
If there is no file option set my CLI app falls back to getStdin()
.
I check if the result of the file read or getStdin()
is falsey and if so I show the help message.
This works fine when used for real in a shell but when I try to write a test for it, it just hangs. It seems if I spawn my CLI app from node, get-stdin()
returns a promise that never resolves. For example:
$ node dist/cli.js
Usage: htconvert [options]
Options:
-h, --help output usage information
-V, --version output the version number
-f, --file [.htaccess] File containing .htaccess redirects
But if I run this from node:
var exec = require('child_process').exec;
exec('node dist/cli.js').stdout.pipe(process.stdout);
I get nothing.
Not sure if possibly related to #1. Is this expected behaviour or a bug?
That's expected. Since you've executed getStdin()
, stdin is waiting to get written to. You're not doing that in your example.
But I'm not writing to stdin in the first example from the shell and it doesn't hang. It's only when it's spawned from node via exec
that it hangs.
Is there a way I can simulate the first example from exec so I can test for help output in AVA?
That's because in the first example the process is interactive, in the second, it's spawned, so it's not. get-stream
has a shortcut to resolve when it's interactive. When get-stdin
subscribe to stdin, it blocks the process until something is written to stdin. I don't really know all the details, but I think this is how the stdin works on a system level, not just Node.js. @Qix- can probably elaborate on all this.
Is there a way I can simulate the first example from exec so I can test for help output in AVA?
Write to stdin, or make the process interactive. The only way to make it interactive as far as I know is to childProcess.spawn('foo', {stdio: 'inherit'});
.
The thing is if I write to stdin it'll work with that data and won't show the help output ๐ข
I'll look in to making the process interactive, thanks for the explanation ๐
Without knowing exactly how get-stdin
works, my first suspicion is the isatty()
function returning false - which is the usual case.
isatty(int)
returns 1
or 0
(true or false, respectively) when you pass it a file descriptor. Without getting too much into details, essentially if the file descriptor is interactive then isatty()
returns 1. That means if stdin is interactive, isatty(0)
will return 1
(true). Same thing with piping stdout - if you're piping the output of process A to process B, isatty(1)
in process A will return 0
(false).
I'm assuming that's what's going on here: spawning a new process in Node has a high potential to munge the I/O descriptors of the child process (either turning them off or piping to them instead of forwarding them or marking them as interactive). If you're not inherit
ing I'm pretty sure they are marked as non-interactive, thus isatty(0)
will return 0
.
Since we can only "best guess" as to what's going on, we assume there is data going to be piped in.
Hopefully that helps.
@Qix- My question is. Why does stdin need to block? It's very annoying that you can't both have an stdin listener and write to stdout, without anything coming into stdin.
It's very annoying that you can't both have an stdin listener and write to stdout
You can; could you explain exactly what you mean? Writing to stdout while reading stdin is perfectly possible - at least, on the unix side of things. Not sure if listening in Node blocks stdout, but that definitely shouldn't be the case.
Just tried and it works fine: lukechilds@545a9fc want a PR?
AVA tests pass but the npm test
command fails because of an xo rule, nothing to do with what I've changed.
encountered same problem when using https://github.com/silverwind/oui
which use get-stdin but hang there if the stdin is not closed by parent process.
It seems that all async spawn methods need to explicitely close stdin
. This should work for OP:
var exec = require('child_process').exec;
var child = exec('node dist/cli.js');
child.stdout.pipe(process.stdout);
child.stdin.end();
Ongoing discussion in: nodejs/node#2339 (comment)
Re @ulion: yep, that's right; there's no way for a process to know when stdin is finished until the pipe itself is closed.
@silverwind Thanks so much, have a gold star! ๐
All working now: lukechilds/htconvert@a8f0476