1'use strict';
2const common = require('../common');
3
4if (common.isWindows) {
5  // https://github.com/nodejs/node/issues/48300
6  common.skip('Does not work with cygwin quirks on Windows');
7}
8
9const assert = require('assert');
10const path = require('path');
11const fs = require('fs');
12const spawn = require('child_process').spawn;
13const tmpdir = require('../common/tmpdir');
14
15let cat, grep, wc;
16
17const KB = 1024;
18const MB = KB * KB;
19
20
21// Make sure process chaining allows desired data flow:
22// check cat <file> | grep 'x' | wc -c === 1MB
23// This helps to make sure no data is lost between pipes.
24
25{
26  tmpdir.refresh();
27  const file = path.resolve(tmpdir.path, 'data.txt');
28  const buf = Buffer.alloc(MB).fill('x');
29
30  // Most OS commands that deal with data, attach special meanings to new line -
31  // for example, line buffering. So cut the buffer into lines at some points,
32  // forcing data flow to be split in the stream. Do not use os.EOL for \n as
33  // that is 2 characters on Windows and is sometimes converted to 1 character
34  // which causes the test to fail.
35  for (let i = 1; i < KB; i++)
36    buf.write('\n', i * KB);
37  fs.writeFileSync(file, buf.toString());
38
39  cat = spawn('cat', [file]);
40  grep = spawn('grep', ['x'], { stdio: [cat.stdout, 'pipe', 'pipe'] });
41  wc = spawn('wc', ['-c'], { stdio: [grep.stdout, 'pipe', 'pipe'] });
42
43  // Extra checks: We never try to start reading data ourselves.
44  cat.stdout._handle.readStart = common.mustNotCall();
45  grep.stdout._handle.readStart = common.mustNotCall();
46
47  // Keep an array of error codes and assert on them during process exit. This
48  // is because stdio can still be open when a child process exits, and we don't
49  // want to lose information about what caused the error.
50  const errors = [];
51  process.on('exit', () => {
52    assert.deepStrictEqual(errors, []);
53  });
54
55  [cat, grep, wc].forEach((child, index) => {
56    const errorHandler = (thing, type) => {
57      // Don't want to assert here, as we might miss error code info.
58      console.error(`unexpected ${type} from child #${index}:\n${thing}`);
59    };
60
61    child.stderr.on('data', (d) => { errorHandler(d, 'data'); });
62    child.on('error', (err) => { errorHandler(err, 'error'); });
63    child.on('exit', common.mustCall((code) => {
64      if (code !== 0) {
65        errors.push(`child ${index} exited with code ${code}`);
66      }
67    }));
68  });
69
70  let wcBuf = '';
71  wc.stdout.on('data', common.mustCall((data) => {
72    wcBuf += data;
73  }));
74
75  process.on('exit', () => {
76    // Grep always adds one extra byte at the end.
77    assert.strictEqual(wcBuf.trim(), (MB + 1).toString());
78  });
79}
80