summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAnna Henningsen <anna@addaleax.net>2018-07-10 22:12:23 +0200
committerAnna Henningsen <anna@addaleax.net>2018-07-15 20:35:44 +0200
commit1009118d27b069411016df4ca6915aac3c36a41f (patch)
treeb7827619d21e534b9b8de6e9ac29928f5d64ec4d /test
parent45ad8df318390b54ea6cb54b4b4b320875f9c88f (diff)
downloadandroid-node-v8-1009118d27b069411016df4ca6915aac3c36a41f.tar.gz
android-node-v8-1009118d27b069411016df4ca6915aac3c36a41f.tar.bz2
android-node-v8-1009118d27b069411016df4ca6915aac3c36a41f.zip
test: add heap snapshot tests
Add a number of tests that validate that heap snapshots contain what we expect them to contain, and cross-check against a JS version of our own embedder graphs. PR-URL: https://github.com/nodejs/node/pull/21741 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com>
Diffstat (limited to 'test')
-rw-r--r--test/common/README.md37
-rw-r--r--test/common/heap.js80
-rw-r--r--test/parallel/test-heapdump-dns.js17
-rw-r--r--test/parallel/test-heapdump-fs-promise.js16
-rw-r--r--test/parallel/test-heapdump-http2.js76
-rw-r--r--test/parallel/test-heapdump-inspector.js21
-rw-r--r--test/parallel/test-heapdump-tls.js33
-rw-r--r--test/parallel/test-heapdump-worker.js27
-rw-r--r--test/parallel/test-heapdump-zlib.js17
9 files changed, 324 insertions, 0 deletions
diff --git a/test/common/README.md b/test/common/README.md
index 111ce45b00..c0051ad9f7 100644
--- a/test/common/README.md
+++ b/test/common/README.md
@@ -10,6 +10,7 @@ This directory contains modules used to test the Node.js implementation.
* [DNS module](#dns-module)
* [Duplex pair helper](#duplex-pair-helper)
* [Fixtures module](#fixtures-module)
+* [Heap dump checker module](#heap-dump-checker-module)
* [HTTP2 module](#http2-module)
* [Internet module](#internet-module)
* [tmpdir module](#tmpdir-module)
@@ -538,6 +539,42 @@ Returns the result of
Returns the result of
`fs.readFileSync(path.join(fixtures.fixturesDir, 'keys', arg), 'enc')`.
+## Heap dump checker module
+
+This provides utilities for checking the validity of heap dumps.
+This requires the usage of `--expose-internals`.
+
+### heap.recordState()
+
+Create a heap dump and an embedder graph copy for inspection.
+The returned object has a `validateSnapshotNodes` function similar to the
+one listed below. (`heap.validateSnapshotNodes(...)` is a shortcut for
+`heap.recordState().validateSnapshotNodes(...)`.)
+
+### heap.validateSnapshotNodes(name, expected, options)
+
+* `name` [&lt;string>] Look for this string as the name of heap dump nodes.
+* `expected` [&lt;Array>] A list of objects, possibly with an `children`
+ property that points to expected other adjacent nodes.
+* `options` [&lt;Array>]
+ * `loose` [&lt;boolean>] Do not expect an exact listing of occurrences
+ of nodes with name `name` in `expected`.
+
+Create a heap dump and an embedder graph copy and validate occurrences.
+
+<!-- eslint-disable no-undef, no-unused-vars, node-core/required-modules, strict -->
+```js
+validateSnapshotNodes('TLSWRAP', [
+ {
+ children: [
+ { name: 'enc_out' },
+ { name: 'enc_in' },
+ { name: 'TLSWrap' }
+ ]
+ }
+]);
+```
+
## HTTP/2 Module
The http2.js module provides a handful of utilities for creating mock HTTP/2
diff --git a/test/common/heap.js b/test/common/heap.js
new file mode 100644
index 0000000000..a02de9a606
--- /dev/null
+++ b/test/common/heap.js
@@ -0,0 +1,80 @@
+/* eslint-disable node-core/required-modules */
+'use strict';
+const assert = require('assert');
+const util = require('util');
+
+let internalTestHeap;
+try {
+ internalTestHeap = require('internal/test/heap');
+} catch (e) {
+ console.log('using `test/common/heap.js` requires `--expose-internals`');
+ throw e;
+}
+const { createJSHeapDump, buildEmbedderGraph } = internalTestHeap;
+
+class State {
+ constructor() {
+ this.snapshot = createJSHeapDump();
+ this.embedderGraph = buildEmbedderGraph();
+ }
+
+ validateSnapshotNodes(name, expected, { loose = false } = {}) {
+ const snapshot = this.snapshot.filter(
+ (node) => node.name === 'Node / ' + name && node.type !== 'string');
+ if (loose)
+ assert(snapshot.length >= expected.length);
+ else
+ assert.strictEqual(snapshot.length, expected.length);
+ for (const expectedNode of expected) {
+ if (expectedNode.children) {
+ for (const expectedChild of expectedNode.children) {
+ const check = typeof expectedChild === 'function' ?
+ expectedChild :
+ (node) => [expectedChild.name, 'Node / ' + expectedChild.name]
+ .includes(node.name);
+
+ assert(snapshot.some((node) => {
+ return node.outgoingEdges.map((edge) => edge.toNode).some(check);
+ }), `expected to find child ${util.inspect(expectedChild)} ` +
+ `in ${util.inspect(snapshot)}`);
+ }
+ }
+ }
+
+ const graph = this.embedderGraph.filter((node) => node.name === name);
+ if (loose)
+ assert(graph.length >= expected.length);
+ else
+ assert.strictEqual(graph.length, expected.length);
+ for (const expectedNode of expected) {
+ if (expectedNode.edges) {
+ for (const expectedChild of expectedNode.children) {
+ const check = typeof expectedChild === 'function' ?
+ expectedChild : (node) => {
+ return node.name === expectedChild.name ||
+ (node.value &&
+ node.value.constructor &&
+ node.value.constructor.name === expectedChild.name);
+ };
+
+ assert(graph.some((node) => node.edges.some(check)),
+ `expected to find child ${util.inspect(expectedChild)} ` +
+ `in ${util.inspect(snapshot)}`);
+ }
+ }
+ }
+ }
+}
+
+function recordState() {
+ return new State();
+}
+
+function validateSnapshotNodes(...args) {
+ return recordState().validateSnapshotNodes(...args);
+}
+
+module.exports = {
+ recordState,
+ validateSnapshotNodes
+};
diff --git a/test/parallel/test-heapdump-dns.js b/test/parallel/test-heapdump-dns.js
new file mode 100644
index 0000000000..011503f587
--- /dev/null
+++ b/test/parallel/test-heapdump-dns.js
@@ -0,0 +1,17 @@
+// Flags: --expose-internals
+'use strict';
+require('../common');
+const { validateSnapshotNodes } = require('../common/heap');
+
+validateSnapshotNodes('DNSCHANNEL', []);
+const dns = require('dns');
+validateSnapshotNodes('DNSCHANNEL', [{}]);
+dns.resolve('localhost', () => {});
+validateSnapshotNodes('DNSCHANNEL', [
+ {
+ children: [
+ { name: 'task list' },
+ { name: 'ChannelWrap' }
+ ]
+ }
+]);
diff --git a/test/parallel/test-heapdump-fs-promise.js b/test/parallel/test-heapdump-fs-promise.js
new file mode 100644
index 0000000000..be44b3d873
--- /dev/null
+++ b/test/parallel/test-heapdump-fs-promise.js
@@ -0,0 +1,16 @@
+// Flags: --expose-internals
+'use strict';
+require('../common');
+const { validateSnapshotNodes } = require('../common/heap');
+const fs = require('fs').promises;
+
+validateSnapshotNodes('FSREQPROMISE', []);
+fs.stat(__filename);
+validateSnapshotNodes('FSREQPROMISE', [
+ {
+ children: [
+ { name: 'FSReqPromise' },
+ { name: 'Float64Array' } // Stat array
+ ]
+ }
+]);
diff --git a/test/parallel/test-heapdump-http2.js b/test/parallel/test-heapdump-http2.js
new file mode 100644
index 0000000000..19a70d8c44
--- /dev/null
+++ b/test/parallel/test-heapdump-http2.js
@@ -0,0 +1,76 @@
+// Flags: --expose-internals
+'use strict';
+const common = require('../common');
+const { recordState } = require('../common/heap');
+const http2 = require('http2');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+{
+ const state = recordState();
+ state.validateSnapshotNodes('HTTP2SESSION', []);
+ state.validateSnapshotNodes('HTTP2STREAM', []);
+}
+
+const server = http2.createServer();
+server.on('stream', (stream) => {
+ stream.respondWithFile(__filename);
+});
+server.listen(0, () => {
+ const client = http2.connect(`http://localhost:${server.address().port}`);
+ const req = client.request();
+
+ req.on('response', common.mustCall(() => {
+ const state = recordState();
+ state.validateSnapshotNodes('HTTP2STREAM', [
+ {
+ children: [
+ { name: 'Http2Stream' }
+ ]
+ },
+ ], { loose: true });
+ state.validateSnapshotNodes('FILEHANDLE', [
+ {
+ children: [
+ { name: 'FileHandle' }
+ ]
+ }
+ ]);
+ state.validateSnapshotNodes('TCPWRAP', [
+ {
+ children: [
+ { name: 'TCP' }
+ ]
+ }
+ ], { loose: true });
+ state.validateSnapshotNodes('TCPSERVERWRAP', [
+ {
+ children: [
+ { name: 'TCP' }
+ ]
+ }
+ ], { loose: true });
+ state.validateSnapshotNodes('STREAMPIPE', [
+ {
+ children: [
+ { name: 'StreamPipe' }
+ ]
+ }
+ ]);
+ state.validateSnapshotNodes('HTTP2SESSION', [
+ {
+ children: [
+ { name: 'Http2Session' },
+ { name: 'streams' }
+ ]
+ }
+ ], { loose: true });
+ }));
+
+ req.resume();
+ req.on('end', common.mustCall(() => {
+ client.close();
+ server.close();
+ }));
+ req.end();
+});
diff --git a/test/parallel/test-heapdump-inspector.js b/test/parallel/test-heapdump-inspector.js
new file mode 100644
index 0000000000..355b8d0d0a
--- /dev/null
+++ b/test/parallel/test-heapdump-inspector.js
@@ -0,0 +1,21 @@
+// Flags: --expose-internals
+'use strict';
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const { validateSnapshotNodes } = require('../common/heap');
+const inspector = require('inspector');
+
+const session = new inspector.Session();
+validateSnapshotNodes('INSPECTORJSBINDING', []);
+session.connect();
+validateSnapshotNodes('INSPECTORJSBINDING', [
+ {
+ children: [
+ { name: 'session' },
+ { name: 'Connection' },
+ (node) => node.type === 'closure' || typeof node.value === 'function'
+ ]
+ }
+]);
diff --git a/test/parallel/test-heapdump-tls.js b/test/parallel/test-heapdump-tls.js
new file mode 100644
index 0000000000..be14b7b5f7
--- /dev/null
+++ b/test/parallel/test-heapdump-tls.js
@@ -0,0 +1,33 @@
+// Flags: --expose-internals
+'use strict';
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const { validateSnapshotNodes } = require('../common/heap');
+const net = require('net');
+const tls = require('tls');
+
+validateSnapshotNodes('TLSWRAP', []);
+
+const server = net.createServer(common.mustCall((c) => {
+ c.end();
+})).listen(0, common.mustCall(() => {
+ const c = tls.connect({ port: server.address().port });
+
+ c.on('error', common.mustCall(() => {
+ server.close();
+ }));
+ c.write('hello');
+
+ validateSnapshotNodes('TLSWRAP', [
+ {
+ children: [
+ { name: 'enc_out' },
+ { name: 'enc_in' },
+ { name: 'TLSWrap' }
+ ]
+ }
+ ]);
+}));
diff --git a/test/parallel/test-heapdump-worker.js b/test/parallel/test-heapdump-worker.js
new file mode 100644
index 0000000000..68d2ccd1ab
--- /dev/null
+++ b/test/parallel/test-heapdump-worker.js
@@ -0,0 +1,27 @@
+// Flags: --expose-internals --experimental-worker
+'use strict';
+require('../common');
+const { validateSnapshotNodes } = require('../common/heap');
+const { Worker } = require('worker_threads');
+
+validateSnapshotNodes('WORKER', []);
+const worker = new Worker('setInterval(() => {}, 100);', { eval: true });
+validateSnapshotNodes('WORKER', [
+ {
+ children: [
+ { name: 'thread_exit_async' },
+ { name: 'env' },
+ { name: 'MESSAGEPORT' },
+ { name: 'Worker' }
+ ]
+ }
+]);
+validateSnapshotNodes('MESSAGEPORT', [
+ {
+ children: [
+ { name: 'data' },
+ { name: 'MessagePort' }
+ ]
+ }
+], { loose: true });
+worker.terminate();
diff --git a/test/parallel/test-heapdump-zlib.js b/test/parallel/test-heapdump-zlib.js
new file mode 100644
index 0000000000..7a749902f5
--- /dev/null
+++ b/test/parallel/test-heapdump-zlib.js
@@ -0,0 +1,17 @@
+// Flags: --expose-internals
+'use strict';
+require('../common');
+const { validateSnapshotNodes } = require('../common/heap');
+const zlib = require('zlib');
+
+validateSnapshotNodes('ZLIB', []);
+// eslint-disable-next-line no-unused-vars
+const gunzip = zlib.createGunzip();
+validateSnapshotNodes('ZLIB', [
+ {
+ children: [
+ { name: 'Zlib' },
+ { name: 'zlib memory' }
+ ]
+ }
+]);