• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3// Testcase to check reporting of uv handles.
4const common = require('../common');
5const tmpdir = require('../common/tmpdir');
6const path = require('path');
7if (common.isIBMi)
8  common.skip('IBMi does not support fs.watch()');
9
10// This is quite similar to common.PIPE except that it uses an extended prefix
11// of "\\?\pipe" on windows.
12const PIPE = (() => {
13  const localRelative = path.relative(process.cwd(), `${tmpdir.path}/`);
14  const pipePrefix = common.isWindows ? '\\\\?\\pipe\\' : localRelative;
15  const pipeName = `node-test.${process.pid}.sock`;
16  return path.join(pipePrefix, pipeName);
17})();
18
19function createFsHandle(childData) {
20  const fs = require('fs');
21  // Watching files should result in fs_event/fs_poll uv handles.
22  let watcher;
23  try {
24    watcher = fs.watch(__filename);
25  } catch {
26    // fs.watch() unavailable
27  }
28  fs.watchFile(__filename, () => {});
29  childData.skip_fs_watch = watcher === undefined;
30
31  return () => {
32    if (watcher) watcher.close();
33    fs.unwatchFile(__filename);
34  };
35}
36
37function createChildProcessHandle(childData) {
38  const spawn = require('child_process').spawn;
39  // Child should exist when this returns as child_process.pid must be set.
40  const cp = spawn(process.execPath,
41                   ['-e', "process.stdin.on('data', (x) => " +
42          'console.log(x.toString()));']);
43  childData.pid = cp.pid;
44
45  return () => {
46    cp.kill();
47  };
48}
49
50function createTimerHandle() {
51  const timeout = setInterval(() => {}, 1000);
52  // Make sure the timer doesn't keep the test alive and let
53  // us check we detect unref'd handles correctly.
54  timeout.unref();
55  return () => {
56    clearInterval(timeout);
57  };
58}
59
60function createTcpHandle(childData) {
61  const http = require('http');
62
63  return new Promise((resolve) => {
64    // Simple server/connection to create tcp uv handles.
65    const server = http.createServer((req, res) => {
66      req.on('end', () => {
67        resolve(() => {
68          res.writeHead(200, { 'Content-Type': 'text/plain' });
69          res.end();
70          server.close();
71        });
72      });
73      req.resume();
74    });
75    server.listen(() => {
76      childData.tcp_address = server.address();
77      http.get({ port: server.address().port });
78    });
79  });
80}
81
82function createUdpHandle(childData) {
83  // Datagram socket for udp uv handles.
84  const dgram = require('dgram');
85  const udpSocket = dgram.createSocket('udp4');
86  const connectedUdpSocket = dgram.createSocket('udp4');
87
88  return new Promise((resolve) => {
89    udpSocket.bind({}, common.mustCall(() => {
90      connectedUdpSocket.connect(udpSocket.address().port);
91
92      childData.udp_address = udpSocket.address();
93      resolve(() => {
94        connectedUdpSocket.close();
95        udpSocket.close();
96      });
97    }));
98  });
99}
100
101function createNamedPipeHandle(childData) {
102  const net = require('net');
103  const sockPath = PIPE;
104  return new Promise((resolve) => {
105    const server = net.createServer((socket) => {
106      childData.pipe_sock_path = server.address();
107      resolve(() => {
108        socket.end();
109        server.close();
110      });
111    });
112    server.listen(
113      sockPath,
114      () => {
115        net.connect(sockPath, (socket) => {});
116      });
117  });
118}
119
120async function child() {
121  // Exit on loss of parent process
122  const exit = () => process.exit(2);
123  process.on('disconnect', exit);
124
125  const childData = {};
126  const disposes = await Promise.all([
127    createFsHandle(childData),
128    createChildProcessHandle(childData),
129    createTimerHandle(childData),
130    createTcpHandle(childData),
131    createUdpHandle(childData),
132    createNamedPipeHandle(childData),
133  ]);
134  process.send(childData);
135
136  // Generate the report while the connection is active.
137  console.log(JSON.stringify(process.report.getReport(), null, 2));
138
139  // Tidy up to allow process to exit cleanly.
140  disposes.forEach((it) => {
141    it();
142  });
143  process.removeListener('disconnect', exit);
144}
145
146if (process.argv[2] === 'child') {
147  child();
148} else {
149  const helper = require('../common/report.js');
150  const fork = require('child_process').fork;
151  const assert = require('assert');
152  tmpdir.refresh();
153  const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
154  const child = fork(__filename, ['child'], options);
155  let child_data;
156  child.on('message', (data) => { child_data = data; });
157  let stderr = '';
158  child.stderr.on('data', (chunk) => { stderr += chunk; });
159  let stdout = '';
160  const report_msg = 'Report files were written: unexpectedly';
161  child.stdout.on('data', (chunk) => { stdout += chunk; });
162  child.on('exit', common.mustCall((code, signal) => {
163    assert.strictEqual(stderr.trim(), '');
164    assert.deepStrictEqual(code, 0, 'Process exited unexpectedly with code: ' +
165                           `${code}`);
166    assert.deepStrictEqual(signal, null, 'Process should have exited cleanly,' +
167                            ` but did not: ${signal}`);
168
169    const reports = helper.findReports(child.pid, tmpdir.path);
170    assert.deepStrictEqual(reports, [], report_msg, reports);
171
172    // Test libuv handle key order
173    {
174      const get_libuv = /"libuv":\s\[([\s\S]*?)\]/g;
175      const get_handle_inner = /{([\s\S]*?),*?}/g;
176      const libuv_handles_str = get_libuv.exec(stdout)[1];
177      const libuv_handles_array = libuv_handles_str.match(get_handle_inner);
178      for (const i of libuv_handles_array) {
179        // Exclude nested structure
180        if (i.includes('type')) {
181          const handle_keys = i.match(/(".*"):/g);
182          assert(handle_keys[0], 'type');
183          assert(handle_keys[1], 'is_active');
184        }
185      }
186    }
187
188    const report = JSON.parse(stdout);
189    const prefix = common.isWindows ? '\\\\?\\' : '';
190    const expected_filename = `${prefix}${__filename}`;
191    const found_tcp = [];
192    const found_udp = [];
193    const found_named_pipe = [];
194    // Functions are named to aid debugging when they are not called.
195    const validators = {
196      fs_event: common.mustCall(function fs_event_validator(handle) {
197        if (!child_data.skip_fs_watch) {
198          assert.strictEqual(handle.filename, expected_filename);
199          assert(handle.is_referenced);
200        }
201      }),
202      fs_poll: common.mustCall(function fs_poll_validator(handle) {
203        assert.strictEqual(handle.filename, expected_filename);
204        assert(handle.is_referenced);
205      }),
206      loop: common.mustCall(function loop_validator(handle) {
207        assert.strictEqual(typeof handle.loopIdleTimeSeconds, 'number');
208      }),
209      pipe: common.mustCallAtLeast(function pipe_validator(handle) {
210        assert(handle.is_referenced);
211        // Pipe handles. The report should contain three pipes:
212        // 1. The server's listening pipe.
213        // 2. The inbound pipe making the request.
214        // 3. The outbound pipe sending the response.
215        //
216        // There is no way to distinguish inbound and outbound in a cross
217        // platform manner, so we just check inbound here.
218        const sockPath = child_data.pipe_sock_path;
219        if (handle.localEndpoint === sockPath) {
220          if (handle.writable === false) {
221            found_named_pipe.push('listening');
222          }
223        } else if (handle.remoteEndpoint === sockPath) {
224          found_named_pipe.push('inbound');
225        }
226      }),
227      process: common.mustCall(function process_validator(handle) {
228        assert.strictEqual(handle.pid, child_data.pid);
229        assert(handle.is_referenced);
230      }),
231      tcp: common.mustCall(function tcp_validator(handle) {
232        // TCP handles. The report should contain three sockets:
233        // 1. The server's listening socket.
234        // 2. The inbound socket making the request.
235        // 3. The outbound socket sending the response.
236        const port = child_data.tcp_address.port;
237        if (handle.localEndpoint.port === port) {
238          if (handle.remoteEndpoint === null) {
239            found_tcp.push('listening');
240          } else {
241            found_tcp.push('inbound');
242          }
243        } else if (handle.remoteEndpoint.port === port) {
244          found_tcp.push('outbound');
245        }
246        assert(handle.is_referenced);
247      }, 3),
248      timer: common.mustCallAtLeast(function timer_validator(handle) {
249        assert(!handle.is_referenced);
250        assert.strictEqual(handle.repeat, 0);
251      }),
252      udp: common.mustCall(function udp_validator(handle) {
253        if (handle.remoteEndpoint === null) {
254          assert.strictEqual(handle.localEndpoint.port,
255                             child_data.udp_address.port);
256          found_udp.push('unconnected');
257        } else {
258          assert.strictEqual(handle.remoteEndpoint.port,
259                             child_data.udp_address.port);
260          found_udp.push('connected');
261        }
262        assert(handle.is_referenced);
263      }, 2),
264    };
265
266    for (const entry of report.libuv) {
267      if (validators[entry.type]) validators[entry.type](entry);
268    }
269    for (const socket of ['listening', 'inbound', 'outbound']) {
270      assert(found_tcp.includes(socket), `${socket} TCP socket was not found`);
271    }
272    for (const socket of ['connected', 'unconnected']) {
273      assert(found_udp.includes(socket), `${socket} UDP socket was not found`);
274    }
275    for (const socket of ['listening', 'inbound']) {
276      assert(found_named_pipe.includes(socket),
277             `${socket} named pipe socket was not found`);
278    }
279
280    // Common report tests.
281    helper.validateContent(stdout);
282  }));
283}
284