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