1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
/* 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;
function inspectNode(snapshot) {
return util.inspect(snapshot, { depth: 4 });
}
function isEdge(edge, { node_name, edge_name }) {
if (edge.name !== edge_name) {
return false;
}
// From our internal embedded graph
if (edge.to.value) {
if (edge.to.value.constructor.name !== node_name) {
return false;
}
} else if (edge.to.name !== node_name) {
return false;
}
return true;
}
class State {
constructor() {
this.snapshot = createJSHeapDump();
this.embedderGraph = buildEmbedderGraph();
}
// Validate the v8 heap snapshot
validateSnapshot(rootName, expected, { loose = false } = {}) {
const rootNodes = this.snapshot.filter(
(node) => node.name === rootName && node.type !== 'string');
if (loose) {
assert(rootNodes.length >= expected.length,
`Expect to find at least ${expected.length} '${rootName}', ` +
`found ${rootNodes.length}`);
} else {
assert.strictEqual(
rootNodes.length, expected.length,
`Expect to find ${expected.length} '${rootName}', ` +
`found ${rootNodes.length}`);
}
for (const expectation of expected) {
if (expectation.children) {
for (const expectedEdge of expectation.children) {
const check = typeof expectedEdge === 'function' ? expectedEdge :
(edge) => (isEdge(edge, expectedEdge));
const hasChild = rootNodes.some(
(node) => node.outgoingEdges.some(check)
);
// Don't use assert with a custom message here. Otherwise the
// inspection in the message is done eagerly and wastes a lot of CPU
// time.
if (!hasChild) {
throw new Error(
'expected to find child ' +
`${util.inspect(expectedEdge)} in ${inspectNode(rootNodes)}`);
}
}
}
}
}
// Validate our internal embedded graph representation
validateGraph(rootName, expected, { loose = false } = {}) {
const rootNodes = this.embedderGraph.filter(
(node) => node.name === rootName
);
if (loose) {
assert(rootNodes.length >= expected.length,
`Expect to find at least ${expected.length} '${rootName}', ` +
`found ${rootNodes.length}`);
} else {
assert.strictEqual(
rootNodes.length, expected.length,
`Expect to find ${expected.length} '${rootName}', ` +
`found ${rootNodes.length}`);
}
for (const expectation of expected) {
if (expectation.children) {
for (const expectedEdge of expectation.children) {
const check = typeof expectedEdge === 'function' ? expectedEdge :
(edge) => (isEdge(edge, expectedEdge));
// Don't use assert with a custom message here. Otherwise the
// inspection in the message is done eagerly and wastes a lot of CPU
// time.
const hasChild = rootNodes.some(
(node) => node.edges.some(check)
);
if (!hasChild) {
throw new Error(
'expected to find child ' +
`${util.inspect(expectedEdge)} in ${inspectNode(rootNodes)}`);
}
}
}
}
}
validateSnapshotNodes(rootName, expected, { loose = false } = {}) {
this.validateSnapshot(rootName, expected, { loose });
this.validateGraph(rootName, expected, { loose });
}
}
function recordState() {
return new State();
}
function validateSnapshotNodes(...args) {
return recordState().validateSnapshotNodes(...args);
}
module.exports = {
recordState,
validateSnapshotNodes
};
|