• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const {
24  isWindows,
25  mustCall,
26  mustCallAtLeast,
27} = require('../common');
28const assert = require('assert');
29const os = require('os');
30const spawn = require('child_process').spawn;
31const debug = require('util').debuglog('test');
32
33// We're trying to reproduce:
34// $ echo "hello\nnode\nand\nworld" | grep o | sed s/o/a/
35
36let grep, sed, echo;
37
38if (isWindows) {
39  grep = spawn('grep', ['--binary', 'o']);
40  sed = spawn('sed', ['--binary', 's/o/O/']);
41  echo = spawn('cmd.exe',
42               ['/c', 'echo', 'hello&&', 'echo',
43                'node&&', 'echo', 'and&&', 'echo', 'world']);
44} else {
45  grep = spawn('grep', ['o']);
46  sed = spawn('sed', ['s/o/O/']);
47  echo = spawn('echo', ['hello\nnode\nand\nworld\n']);
48}
49
50// If the spawn function leaks file descriptors to subprocesses, grep and sed
51// hang.
52// This happens when calling pipe(2) and then forgetting to set the
53// FD_CLOEXEC flag on the resulting file descriptors.
54//
55// This test checks child processes exit, meaning they don't hang like
56// explained above.
57
58
59// pipe echo | grep
60echo.stdout.on('data', mustCallAtLeast((data) => {
61  debug(`grep stdin write ${data.length}`);
62  if (!grep.stdin.write(data)) {
63    echo.stdout.pause();
64  }
65}));
66
67// TODO(@jasnell): This does not appear to ever be
68// emitted. It's not clear if it is necessary.
69grep.stdin.on('drain', (data) => {
70  echo.stdout.resume();
71});
72
73// Propagate end from echo to grep
74echo.stdout.on('end', mustCall((code) => {
75  grep.stdin.end();
76}));
77
78echo.on('exit', mustCall(() => {
79  debug('echo exit');
80}));
81
82grep.on('exit', mustCall(() => {
83  debug('grep exit');
84}));
85
86sed.on('exit', mustCall(() => {
87  debug('sed exit');
88}));
89
90
91// pipe grep | sed
92grep.stdout.on('data', mustCallAtLeast((data) => {
93  debug(`grep stdout ${data.length}`);
94  if (!sed.stdin.write(data)) {
95    grep.stdout.pause();
96  }
97}));
98
99// TODO(@jasnell): This does not appear to ever be
100// emitted. It's not clear if it is necessary.
101sed.stdin.on('drain', (data) => {
102  grep.stdout.resume();
103});
104
105// Propagate end from grep to sed
106grep.stdout.on('end', mustCall((code) => {
107  debug('grep stdout end');
108  sed.stdin.end();
109}));
110
111
112let result = '';
113
114// print sed's output
115sed.stdout.on('data', mustCallAtLeast((data) => {
116  result += data.toString('utf8', 0, data.length);
117  debug(data);
118}));
119
120sed.stdout.on('end', mustCall((code) => {
121  assert.strictEqual(result, `hellO${os.EOL}nOde${os.EOL}wOrld${os.EOL}`);
122}));
123