• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2const common = require('../common');
3const assert = require('assert');
4const fs = require('fs');
5const path = require('path');
6const cp = require('child_process');
7const { spawnSync } = require('child_process');
8
9// This is a sibling test to test/addons/uv-handle-leak.
10
11const bindingPath = path.resolve(
12  __dirname, '..', 'addons', 'uv-handle-leak', 'build',
13  `${common.buildType}/binding.node`);
14
15if (!fs.existsSync(bindingPath))
16  common.skip('binding not built yet');
17
18if (process.argv[2] === 'child') {
19
20  const { Worker } = require('worker_threads');
21
22  // The worker thread loads and then unloads `bindingPath`. Because of this the
23  // symbols in `bindingPath` are lost when the worker thread quits, but the
24  // number of open handles in the worker thread's event loop is assessed in the
25  // main thread afterwards, and the names of the callbacks associated with the
26  // open handles is retrieved at that time as well. Thus, we require
27  // `bindingPath` here so that the symbols and their names survive the life
28  // cycle of the worker thread.
29  require(bindingPath);
30
31  new Worker(`
32  const binding = require(${JSON.stringify(bindingPath)});
33
34  binding.leakHandle();
35  binding.leakHandle(0);
36  binding.leakHandle(0x42);
37  `, { eval: true });
38} else {
39  const child = cp.spawnSync(process.execPath, [__filename, 'child']);
40  const stderr = child.stderr.toString();
41
42  assert.strictEqual(child.stdout.toString(), '');
43
44  const lines = stderr.split('\n');
45
46  let state = 'initial';
47
48  // Parse output that is formatted like this:
49
50  // uv loop at [0x559b65ed5770] has open handles:
51  // [0x7f2de0018430] timer (active)
52  //         Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
53  //         Data: 0x7f2df33df140 example_instance [...]
54  //         (First field): 0x7f2df33dedc0 vtable for ExampleOwnerClass [...]
55  // [0x7f2de000b870] timer
56  //         Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
57  //         Data: (nil)
58  // [0x7f2de000b910] timer
59  //         Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
60  //         Data: 0x42
61  // uv loop at [0x559b65ed5770] has 3 open handles in total
62
63  function isGlibc() {
64    try {
65      const lddOut = spawnSync('ldd', [process.execPath]).stdout;
66      const libcInfo = lddOut.toString().split('\n').map(
67        (line) => line.match(/libc\.so.+=>\s*(\S+)\s/)).filter((info) => info);
68      if (libcInfo.length === 0)
69        return false;
70      const nmOut = spawnSync('nm', ['-D', libcInfo[0][1]]).stdout;
71      if (/gnu_get_libc_version/.test(nmOut))
72        return true;
73    } catch {
74      return false;
75    }
76  }
77
78
79  if (!(common.isFreeBSD ||
80        common.isAIX ||
81        common.isIBMi ||
82        (common.isLinux && !isGlibc()) ||
83        common.isWindows)) {
84    assert(stderr.includes('ExampleOwnerClass'), stderr);
85    assert(stderr.includes('CloseCallback'), stderr);
86    assert(stderr.includes('example_instance'), stderr);
87  }
88
89  while (lines.length > 0) {
90    const line = lines.shift().trim();
91
92    switch (state) {
93      case 'initial':
94        assert.match(line, /^uv loop at \[.+\] has open handles:$/);
95        state = 'handle-start';
96        break;
97      case 'handle-start':
98        if (/^uv loop at \[.+\] has \d+ open handles in total$/.test(line)) {
99          state = 'assertion-failure';
100          break;
101        }
102        assert.match(line, /^\[.+\] timer( \(active\))?$/);
103        state = 'close-callback';
104        break;
105      case 'close-callback':
106        assert.match(line, /^Close callback:/);
107        state = 'data';
108        break;
109      case 'data':
110        assert.match(line, /^Data: .+$/);
111        state = 'maybe-first-field';
112        break;
113      case 'maybe-first-field':
114        if (!/^\(First field\)/.test(line)) {
115          lines.unshift(line);
116        }
117        state = 'handle-start';
118        break;
119      case 'assertion-failure':
120        assert.match(line, /Assertion .+ failed/);
121        state = 'done';
122        break;
123      case 'done':
124        break;
125    }
126  }
127
128  assert.strictEqual(state, 'done');
129}
130