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