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