• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3// Testcase to check reporting of uv handles.
4const common = require('../common');
5if (common.isIBMi)
6  common.skip('IBMi does not support fs.watch()');
7
8if (process.argv[2] === 'child') {
9  // Exit on loss of parent process
10  const exit = () => process.exit(2);
11  process.on('disconnect', exit);
12
13  const fs = require('fs');
14  const http = require('http');
15  const spawn = require('child_process').spawn;
16
17  // Watching files should result in fs_event/fs_poll uv handles.
18  let watcher;
19  try {
20    watcher = fs.watch(__filename);
21  } catch {
22    // fs.watch() unavailable
23  }
24  fs.watchFile(__filename, () => {});
25
26  // Child should exist when this returns as child_process.pid must be set.
27  const child_process = spawn(process.execPath,
28                              ['-e', "process.stdin.on('data', (x) => " +
29                                     'console.log(x.toString()));']);
30
31  const timeout = setInterval(() => {}, 1000);
32  // Make sure the timer doesn't keep the test alive and let
33  // us check we detect unref'd handles correctly.
34  timeout.unref();
35
36  // Datagram socket for udp uv handles.
37  const dgram = require('dgram');
38  const udp_socket = dgram.createSocket('udp4');
39  const connected_udp_socket = dgram.createSocket('udp4');
40  udp_socket.bind({}, common.mustCall(() => {
41    connected_udp_socket.connect(udp_socket.address().port);
42  }));
43
44  // Simple server/connection to create tcp uv handles.
45  const server = http.createServer((req, res) => {
46    req.on('end', () => {
47      // Generate the report while the connection is active.
48      console.log(JSON.stringify(process.report.getReport(), null, 2));
49      child_process.kill();
50
51      res.writeHead(200, { 'Content-Type': 'text/plain' });
52      res.end();
53
54      // Tidy up to allow process to exit cleanly.
55      server.close(() => {
56        if (watcher) watcher.close();
57        fs.unwatchFile(__filename);
58        connected_udp_socket.close();
59        udp_socket.close();
60        process.removeListener('disconnect', exit);
61      });
62    });
63    req.resume();
64  });
65  server.listen(() => {
66    const data = { pid: child_process.pid,
67                   tcp_address: server.address(),
68                   udp_address: udp_socket.address(),
69                   skip_fs_watch: (watcher === undefined) };
70    process.send(data);
71    http.get({ port: server.address().port });
72  });
73} else {
74  const helper = require('../common/report.js');
75  const fork = require('child_process').fork;
76  const assert = require('assert');
77  const tmpdir = require('../common/tmpdir');
78  tmpdir.refresh();
79  const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
80  const child = fork(__filename, ['child'], options);
81  let child_data;
82  child.on('message', (data) => { child_data = data; });
83  let stderr = '';
84  child.stderr.on('data', (chunk) => { stderr += chunk; });
85  let stdout = '';
86  const report_msg = 'Report files were written: unexpectedly';
87  child.stdout.on('data', (chunk) => { stdout += chunk; });
88  child.on('exit', common.mustCall((code, signal) => {
89    assert.deepStrictEqual(code, 0, 'Process exited unexpectedly with code: ' +
90                           `${code}`);
91    assert.deepStrictEqual(signal, null, 'Process should have exited cleanly,' +
92                            ` but did not: ${signal}`);
93    assert.strictEqual(stderr.trim(), '');
94
95    const reports = helper.findReports(child.pid, tmpdir.path);
96    assert.deepStrictEqual(reports, [], report_msg, reports);
97
98    // Test libuv handle key order
99    {
100      const get_libuv = /"libuv":\s\[([\s\S]*?)\]/gm;
101      const get_handle_inner = /{([\s\S]*?),*?}/gm;
102      const libuv_handles_str = get_libuv.exec(stdout)[1];
103      const libuv_handles_array = libuv_handles_str.match(get_handle_inner);
104      for (const i of libuv_handles_array) {
105        // Exclude nested structure
106        if (i.includes('type')) {
107          const handle_keys = i.match(/(".*"):/gm);
108          assert(handle_keys[0], 'type');
109          assert(handle_keys[1], 'is_active');
110        }
111      }
112    }
113
114    const report = JSON.parse(stdout);
115    const prefix = common.isWindows ? '\\\\?\\' : '';
116    const expected_filename = `${prefix}${__filename}`;
117    const found_tcp = [];
118    const found_udp = [];
119    // Functions are named to aid debugging when they are not called.
120    const validators = {
121      fs_event: common.mustCall(function fs_event_validator(handle) {
122        if (!child_data.skip_fs_watch) {
123          assert.strictEqual(handle.filename, expected_filename);
124          assert(handle.is_referenced);
125        }
126      }),
127      fs_poll: common.mustCall(function fs_poll_validator(handle) {
128        assert.strictEqual(handle.filename, expected_filename);
129        assert(handle.is_referenced);
130      }),
131      pipe: common.mustCallAtLeast(function pipe_validator(handle) {
132        assert(handle.is_referenced);
133      }),
134      process: common.mustCall(function process_validator(handle) {
135        assert.strictEqual(handle.pid, child_data.pid);
136        assert(handle.is_referenced);
137      }),
138      tcp: common.mustCall(function tcp_validator(handle) {
139        // TCP handles. The report should contain three sockets:
140        // 1. The server's listening socket.
141        // 2. The inbound socket making the request.
142        // 3. The outbound socket sending the response.
143        const port = child_data.tcp_address.port;
144        if (handle.localEndpoint.port === port) {
145          if (handle.remoteEndpoint === null) {
146            found_tcp.push('listening');
147          } else {
148            found_tcp.push('inbound');
149          }
150        } else if (handle.remoteEndpoint.port === port) {
151          found_tcp.push('outbound');
152        }
153        assert(handle.is_referenced);
154      }, 3),
155      timer: common.mustCallAtLeast(function timer_validator(handle) {
156        assert(!handle.is_referenced);
157        assert.strictEqual(handle.repeat, 0);
158      }),
159      udp: common.mustCall(function udp_validator(handle) {
160        if (handle.remoteEndpoint === null) {
161          assert.strictEqual(handle.localEndpoint.port,
162                             child_data.udp_address.port);
163          found_udp.push('unconnected');
164        } else {
165          assert.strictEqual(handle.remoteEndpoint.port,
166                             child_data.udp_address.port);
167          found_udp.push('connected');
168        }
169        assert(handle.is_referenced);
170      }, 2),
171    };
172    console.log(report.libuv);
173    for (const entry of report.libuv) {
174      if (validators[entry.type]) validators[entry.type](entry);
175    }
176    for (const socket of ['listening', 'inbound', 'outbound']) {
177      assert(found_tcp.includes(socket), `${socket} TCP socket was not found`);
178    }
179    for (const socket of ['connected', 'unconnected']) {
180      assert(found_udp.includes(socket), `${socket} UDP socket was not found`);
181    }
182
183    // Common report tests.
184    helper.validateContent(stdout);
185  }));
186}
187