• 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/*
51 * grep and sed hang if the spawn function leaks file descriptors to child
52 * processes.
53 * This happens when calling pipe(2) and then forgetting to set the
54 * FD_CLOEXEC flag on the resulting file descriptors.
55 *
56 * This test checks child processes exit, meaning they don't hang like
57 * explained above.
58 */
59
60
61// pipe echo | grep
62echo.stdout.on('data', mustCallAtLeast((data) => {
63  debug(`grep stdin write ${data.length}`);
64  if (!grep.stdin.write(data)) {
65    echo.stdout.pause();
66  }
67}));
68
69// TODO(@jasnell): This does not appear to ever be
70// emitted. It's not clear if it is necessary.
71grep.stdin.on('drain', (data) => {
72  echo.stdout.resume();
73});
74
75// Propagate end from echo to grep
76echo.stdout.on('end', mustCall((code) => {
77  grep.stdin.end();
78}));
79
80echo.on('exit', mustCall(() => {
81  debug('echo exit');
82}));
83
84grep.on('exit', mustCall(() => {
85  debug('grep exit');
86}));
87
88sed.on('exit', mustCall(() => {
89  debug('sed exit');
90}));
91
92
93// pipe grep | sed
94grep.stdout.on('data', mustCallAtLeast((data) => {
95  debug(`grep stdout ${data.length}`);
96  if (!sed.stdin.write(data)) {
97    grep.stdout.pause();
98  }
99}));
100
101// TODO(@jasnell): This does not appear to ever be
102// emitted. It's not clear if it is necessary.
103sed.stdin.on('drain', (data) => {
104  grep.stdout.resume();
105});
106
107// Propagate end from grep to sed
108grep.stdout.on('end', mustCall((code) => {
109  debug('grep stdout end');
110  sed.stdin.end();
111}));
112
113
114let result = '';
115
116// print sed's output
117sed.stdout.on('data', mustCallAtLeast((data) => {
118  result += data.toString('utf8', 0, data.length);
119  debug(data);
120}));
121
122sed.stdout.on('end', mustCall((code) => {
123  assert.strictEqual(result, `hellO${os.EOL}nOde${os.EOL}wOrld${os.EOL}`);
124}));
125