| <!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> |