blob: 197c0b520935c118aee06178f84ee8486cb40c00 [file] [log] [blame]
<!doctype html>
<html>
<head>
<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
<script>
function test()
{
// Simple HeapSnapshot representation.
WI.TestHeapSnapshotNode = class TestHeapSnapshotNode
{
constructor(identifier, className, size, flags)
{
this.id = identifier;
this.className = className;
this.size = size;
this.internal = flags & (1 << 0) ? true : false;
this.isObjectType = flags & (1 << 1) ? true : false;
this.gcRoot = false;
this.outgoingEdges = [];
this.incomingEdges = [];
}
}
WI.TestHeapSnapshotEdge = class TestHeapSnapshotEdge
{
constructor(from, to, type, data)
{
this.from = from;
this.to = to;
this.type = type;
this.data = data;
}
};
WI.TestHeapSnapshot = class TestHeapSnapshot
{
constructor(rootNode, nodes, nodeMap)
{
console.assert(rootNode instanceof WI.TestHeapSnapshotNode);
console.assert(nodes.every((n) => n instanceof WI.TestHeapSnapshotNode));
this.rootNode = rootNode;
this.nodes = nodes;
this.nodeMap = nodeMap;
this.totalSize = nodes.reduce((sum, node) => sum += node.size, 0);
this.totalObjectCount = nodes.length - 1; // <root>.
}
static fromPayload(payload)
{
let {version, nodes, nodeClassNames, edges, edgeTypes, edgeNames} = payload;
console.assert(version === 1, "Only know how to handle JavaScriptCore Heap Snapshot Format Version 1");
let nodeMap = new Map;
// Turn nodes into real nodes.
let processedNodes = [];
for (let i = 0, length = nodes.length; i < length;) {
let id = nodes[i++];
let size = nodes[i++];
let classNameIndex = nodes[i++];
let flags = nodes[i++];
let node = new WI.TestHeapSnapshotNode(id, nodeClassNames[classNameIndex], size, flags);
nodeMap.set(id, node);
processedNodes.push(node);
}
// Turn edges into real edges and set them on the nodes.
for (let i = 0, length = edges.length; i < length;) {
let fromIdentifier = edges[i++];
let toIdentifier = edges[i++];
let edgeTypeIndex = edges[i++];
let data = edges[i++];
let from = nodeMap.get(fromIdentifier);
let to = nodeMap.get(toIdentifier);
let type = edgeTypes[edgeTypeIndex];
if (type === "Property" || type === "Variable")
data = edgeNames[data];
let edge = new WI.TestHeapSnapshotEdge(from, to, type, data);
from.outgoingEdges.push(edge);
to.incomingEdges.push(edge);
}
// Root node.
let rootNode = nodeMap.get(0);
console.assert(rootNode, "Node with identifier 0 is the synthetic <root> node.");
console.assert(rootNode.outgoingEdges.length > 0, "This had better have children!");
console.assert(rootNode.incomingEdges.length === 0, "This had better not have back references!");
// Mark GC roots.
let rootNodeEdges = rootNode.outgoingEdges;
for (let i = 0, length = rootNodeEdges.length; i < length; ++i)
rootNodeEdges[i].to.gcRoot = true;
return new WI.TestHeapSnapshot(rootNode, processedNodes, nodeMap);
}
// Public
instancesWithClassName(className)
{
let results = [];
// Skip <root>.
for (let i = 1; i < this.nodes.length; ++i) {
let node = this.nodes[i];
if (node.className === className)
results.push(node);
}
return results;
}
};
// ------
let suite = InspectorTest.createAsyncSuite("HeapSnapshot");
let snapshot = null;
let snapshotNodeForWindowObject = null;
let testSnapshot = null;
let testSnapshotNodeForWindowObject = null;
function compareNodes(node1, node2) {
return node1.id === node2.id
&& node1.size === node2.size
&& node1.className === node2.className
&& node1.internal === node2.internal
&& node1.isObjectType === node2.isObjectType
&& node1.gcRoot === node2.gcRoot;
}
suite.addTestCase({
name: "HeapSnapshotProxy data",
test(resolve, reject) {
HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
InspectorTest.expectThat(!error, "Should not have an error creating a snapshot.");
testSnapshot = WI.TestHeapSnapshot.fromPayload(JSON.parse(snapshotStringData));
let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
InspectorTest.assert(testSnapshot, "Created TestHeapSnapshot");
InspectorTest.assert(snapshot, "Created HeapSnapshotProxy");
InspectorTest.expectThat(snapshot.totalSize === testSnapshot.totalSize, "Snapshots totalSize should match.");
InspectorTest.expectThat(snapshot.totalObjectCount === testSnapshot.totalObjectCount, "Snapshots totalObjectCount should match.");
resolve();
});
});
}
});
suite.addTestCase({
name: "HeapSnapshotProxy.prototype.instancesWithClassName",
test(resolve, reject) {
let windowObjects = testSnapshot.instancesWithClassName("Window")
let windowObjectCount = windowObjects.length;
let functionObjectCount = testSnapshot.instancesWithClassName("Function").length;
let stringCount = testSnapshot.instancesWithClassName("string").length;
snapshot.instancesWithClassName("Window", (windows) => {
testSnapshotNodeForWindowObject = windowObjects[0]; // Used by later tests.
snapshotNodeForWindowObject = windows[0]; // Used by later tests.
InspectorTest.expectThat(windows.length > 0, "Should be at least 1 Window.");
InspectorTest.expectThat(windows.length === windowObjectCount, "Window object count is expected.");
InspectorTest.expectThat(windows.every((node) => node.className === "Window"), "Every className should be 'Window'.");
});
snapshot.instancesWithClassName("Function", (functions) => {
InspectorTest.expectThat(functions.length > 0, "Should be at least 1 Function.");
InspectorTest.expectThat(functions.length === functionObjectCount, "Function object count is expected.");
InspectorTest.expectThat(functions.every((node) => node.className === "Function"), "Every className should be 'Function'.");
});
snapshot.instancesWithClassName("string", (strings) => {
InspectorTest.expectThat(strings.length > 0, "Should be at least 1 string.");
InspectorTest.expectThat(strings.length === stringCount, "string count is expected.");
InspectorTest.expectThat(strings.every((node) => node.className === "string"), "Every className should be 'string'.");
resolve();
});
}
});
suite.addTestCase({
name: "HeapSnapshotProxy.prototype.nodeWithIdentifier and HeapSnapshotNodeProxy data",
test(resolve, reject) {
snapshot.nodeWithIdentifier(testSnapshotNodeForWindowObject.id, (heapSnapshotNode) => {
InspectorTest.expectThat(heapSnapshotNode.className === "Window", "Node className should be 'Window'.");
InspectorTest.expectThat(heapSnapshotNode.id === testSnapshotNodeForWindowObject.id, "Node identifier should match.")
InspectorTest.expectThat(heapSnapshotNode.size === testSnapshotNodeForWindowObject.size, "Node size should match.");
InspectorTest.expectThat(heapSnapshotNode.internal === testSnapshotNodeForWindowObject.internal, "Node internal state should match.");
InspectorTest.expectThat(heapSnapshotNode.isObjectType === testSnapshotNodeForWindowObject.isObjectType, "Node isObjectType state should match.");
InspectorTest.expectThat(heapSnapshotNode.gcRoot === testSnapshotNodeForWindowObject.gcRoot, "Node gcRoot state should match.");
InspectorTest.expectThat(heapSnapshotNode.retainedSize >= heapSnapshotNode.size, "Node retainedSize should at least be the size.");
resolve();
});
}
});
suite.addTestCase({
name: "HeapSnapshotProxy.prototype.allocationBucketCounts",
test(resolve, reject) {
let testSmall = 0, testMedium = 0, testLarge = 0;
const smallSize = 32, mediumSize = 128;
// Skip <root>.
for (let i = 1; i < testSnapshot.nodes.length; ++i) {
let {size} = testSnapshot.nodes[i];
if (size < smallSize)
testSmall++;
else if (size < mediumSize)
testMedium++;
else
testLarge++;
}
snapshot.allocationBucketCounts([smallSize, mediumSize], (results) => {
let [small, medium, large] = results;
InspectorTest.expectThat(results.length === 3, "Result should have 3 buckets, for small/medium/large.");
InspectorTest.expectThat(small === testSmall, "Small count should match.");
InspectorTest.expectThat(medium === testMedium, "Medium count should match.");
InspectorTest.expectThat(large === testLarge, "Large count should match.");
resolve();
});
}
});
suite.addTestCase({
name: "HeapSnapshotNodeProxy.prototype.retainedNodes",
test(resolve, reject) {
let expectedNodes = testSnapshotNodeForWindowObject.outgoingEdges.map((edge) => edge.to);
expectedNodes.sort((a, b) => a.id - b.id);
snapshotNodeForWindowObject.retainedNodes((nodes, edges) => {
nodes.sort((a, b) => a.id - b.id);
InspectorTest.assert(nodes.length > 0, "Test only makes since if there are retained nodes");
InspectorTest.expectThat(nodes.length === expectedNodes.length, "Number of retained nodes should match.");
InspectorTest.expectThat(nodes.length === edges.length, "Number of edges should match the number of nodes.");
InspectorTest.expectThat(nodes.every((node, i) => compareNodes(node, expectedNodes[i])), "Node values should match.");
resolve();
});
}
});
suite.addTestCase({
name: "HeapSnapshotNodeProxy.prototype.retainers",
test(resolve, reject) {
let expectedNodes = testSnapshotNodeForWindowObject.incomingEdges.map((edge) => edge.from);
expectedNodes.sort((a, b) => a.id - b.id);
snapshotNodeForWindowObject.retainers((nodes, edges) => {
nodes.sort((a, b) => a.id - b.id);
InspectorTest.assert(nodes.length > 0, "Test only makes since if there are retainer nodes");
InspectorTest.expectThat(nodes.length === expectedNodes.length, "Number of retainer nodes should match.");
InspectorTest.expectThat(nodes.length === edges.length, "Number of edges should match the number of nodes.");
InspectorTest.expectThat(nodes.every((node, i) => compareNodes(node, expectedNodes[i])), "Node values should match.");
resolve();
});
}
});
suite.runTestCasesAndFinish();
}
</script>
</head>
<body onload="runTest()">
<p>Testing HeapSnapshot Worker and Proxy objects.</p>
</body>
</html>