зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1248085 - Compute shortest paths in the HeapAnalysesWorker; r=jimb
This commit is contained in:
Родитель
e23e94e333
Коммит
2d9d0a66d0
|
@ -3,11 +3,13 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Visitor, walk } = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
|
||||
const { immutableUpdate } = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
|
||||
const { Visitor, walk } = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
|
||||
const { deduplicatePaths } = require("resource://devtools/shared/heapsnapshot/shortest-paths");
|
||||
|
||||
const DEFAULT_MAX_DEPTH = 4;
|
||||
const DEFAULT_MAX_SIBLINGS = 15;
|
||||
const DEFAULT_MAX_NUM_PATHS = 5;
|
||||
|
||||
/**
|
||||
* A single node in a dominator tree.
|
||||
|
@ -34,6 +36,10 @@ function DominatorTreeNode(nodeId, label, shallowSize, retainedSize) {
|
|||
// An array of immediately dominated child `DominatorTreeNode`s, or undefined.
|
||||
this.children = undefined;
|
||||
|
||||
// An object of the form returned by `deduplicatePaths`, encoding the set of
|
||||
// the N shortest retaining paths for this node as a graph.
|
||||
this.shortestPaths = undefined;
|
||||
|
||||
// True iff the `children` property does not contain every immediately
|
||||
// dominated node.
|
||||
//
|
||||
|
@ -289,3 +295,42 @@ DominatorTreeNode.getNodeByIdAlongPath = function (id, tree, path) {
|
|||
|
||||
return find(tree, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the shortest retaining paths for the given set of DominatorTreeNodes,
|
||||
* and populate each node's `shortestPaths` property with them in place.
|
||||
*
|
||||
* @param {HeapSnapshot} snapshot
|
||||
* @param {Object} breakdown
|
||||
* @param {NodeId} start
|
||||
* @param {Array<DominatorTreeNode>} treeNodes
|
||||
* @param {Number} maxNumPaths
|
||||
*/
|
||||
DominatorTreeNode.attachShortestPaths = function (snapshot,
|
||||
breakdown,
|
||||
start,
|
||||
treeNodes,
|
||||
maxNumPaths = DEFAULT_MAX_NUM_PATHS) {
|
||||
const idToTreeNode = new Map();
|
||||
const targets = [];
|
||||
for (let node of treeNodes) {
|
||||
const id = node.nodeId;
|
||||
idToTreeNode.set(id, node);
|
||||
targets.push(id);
|
||||
}
|
||||
|
||||
const shortestPaths = snapshot.computeShortestPaths(start,
|
||||
targets,
|
||||
maxNumPaths);
|
||||
|
||||
for (let [target, paths] of shortestPaths) {
|
||||
const deduped = deduplicatePaths(target, paths);
|
||||
deduped.nodes = deduped.nodes.map(id => {
|
||||
const { label } =
|
||||
DominatorTreeNode.getLabelAndShallowSize(id, snapshot, breakdown);
|
||||
return { id, label };
|
||||
});
|
||||
|
||||
idToTreeNode.get(target).shortestPaths = deduped;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -226,6 +226,8 @@ HeapAnalysesClient.prototype.getDominatorTree = function (opts) {
|
|||
* by greatest to least retained size.
|
||||
* - {Number} maxCount
|
||||
* The maximum number of children to return.
|
||||
* - {Number} maxRetainingPaths
|
||||
* The maximum number of retaining paths to find for each node.
|
||||
*
|
||||
* @returns {Promise<Object>}
|
||||
* A promise of an object with the following properties:
|
||||
|
|
|
@ -157,7 +157,8 @@ workerHelper.createTask(self, "getDominatorTree", request => {
|
|||
dominatorTreeId,
|
||||
breakdown,
|
||||
maxDepth,
|
||||
maxSiblings
|
||||
maxSiblings,
|
||||
maxRetainingPaths,
|
||||
} = request;
|
||||
|
||||
if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
|
||||
|
@ -168,11 +169,29 @@ workerHelper.createTask(self, "getDominatorTree", request => {
|
|||
const dominatorTree = dominatorTrees[dominatorTreeId];
|
||||
const snapshot = dominatorTreeSnapshots[dominatorTreeId];
|
||||
|
||||
return DominatorTreeNode.partialTraversal(dominatorTree,
|
||||
snapshot,
|
||||
breakdown,
|
||||
maxDepth,
|
||||
maxSiblings);
|
||||
const tree = DominatorTreeNode.partialTraversal(dominatorTree,
|
||||
snapshot,
|
||||
breakdown,
|
||||
maxDepth,
|
||||
maxSiblings);
|
||||
|
||||
const nodes = [];
|
||||
(function getNodes(node) {
|
||||
nodes.push(node);
|
||||
if (node.children) {
|
||||
for (let i = 0, length = node.children.length; i < length; i++) {
|
||||
getNodes(node.children[i]);
|
||||
}
|
||||
}
|
||||
}(tree));
|
||||
|
||||
DominatorTreeNode.attachShortestPaths(snapshot,
|
||||
breakdown,
|
||||
dominatorTree.root,
|
||||
nodes,
|
||||
maxRetainingPaths);
|
||||
|
||||
return tree;
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -184,7 +203,8 @@ workerHelper.createTask(self, "getImmediatelyDominated", request => {
|
|||
nodeId,
|
||||
breakdown,
|
||||
startIndex,
|
||||
maxCount
|
||||
maxCount,
|
||||
maxRetainingPaths,
|
||||
} = request;
|
||||
|
||||
if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
|
||||
|
@ -228,5 +248,11 @@ workerHelper.createTask(self, "getImmediatelyDominated", request => {
|
|||
|
||||
const moreChildrenAvailable = childIds.length > end;
|
||||
|
||||
DominatorTreeNode.attachShortestPaths(snapshot,
|
||||
breakdown,
|
||||
dominatorTree.root,
|
||||
nodes,
|
||||
maxRetainingPaths);
|
||||
|
||||
return { nodes, moreChildrenAvailable, path };
|
||||
});
|
||||
|
|
|
@ -55,4 +55,5 @@ DevToolsModules(
|
|||
'HeapAnalysesClient.js',
|
||||
'HeapAnalysesWorker.js',
|
||||
'HeapSnapshotFileUtils.js',
|
||||
'shortest-paths.js',
|
||||
)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Compress a set of paths leading to `target` into a single graph, returned as
|
||||
* a set of nodes and a set of edges.
|
||||
*
|
||||
* @param {NodeId} target
|
||||
* The target node passed to `HeapSnapshot.computeShortestPaths`.
|
||||
*
|
||||
* @param {Array<Path>} paths
|
||||
* An array of paths to `target`, as returned by
|
||||
* `HeapSnapshot.computeShortestPaths`.
|
||||
*
|
||||
* @returns {Object}
|
||||
* An object with two properties:
|
||||
* - edges: An array of unique objects of the form:
|
||||
* {
|
||||
* from: <node ID>,
|
||||
* to: <node ID>,
|
||||
* name: <string or null>
|
||||
* }
|
||||
* - nodes: An array of unique node IDs. Every `from` and `to` id is
|
||||
* guaranteed to be in this array exactly once.
|
||||
*/
|
||||
exports.deduplicatePaths = function (target, paths) {
|
||||
// Use this structure to de-duplicate edges among many retaining paths from
|
||||
// start to target.
|
||||
//
|
||||
// Map<FromNodeId, Map<ToNodeId, Set<EdgeName>>>
|
||||
const deduped = new Map();
|
||||
|
||||
function insert(from, to, name) {
|
||||
let toMap = deduped.get(from);
|
||||
if (!toMap) {
|
||||
toMap = new Map();
|
||||
deduped.set(from, toMap);
|
||||
}
|
||||
|
||||
let nameSet = toMap.get(to);
|
||||
if (!nameSet) {
|
||||
nameSet = new Set();
|
||||
toMap.set(to, nameSet);
|
||||
}
|
||||
|
||||
nameSet.add(name);
|
||||
}
|
||||
|
||||
for (let path of paths) {
|
||||
const pathLength = path.length;
|
||||
for (let i = 0; i < pathLength - 1; i++) {
|
||||
insert(path[i].predecessor, path[i + 1].predecessor, path[i].edge);
|
||||
}
|
||||
|
||||
insert(path[pathLength - 1].predecessor, target, path[pathLength - 1].edge);
|
||||
}
|
||||
|
||||
const nodes = [target];
|
||||
const edges = [];
|
||||
|
||||
for (let [from, toMap] of deduped) {
|
||||
// If the second/third/etc shortest path contains the `target` anywhere
|
||||
// other than the very last node, we could accidentally put the `target` in
|
||||
// `nodes` more than once.
|
||||
if (from !== target) {
|
||||
nodes.push(from);
|
||||
}
|
||||
|
||||
for (let [to, edgeNameSet] of toMap) {
|
||||
for (let name of edgeNameSet) {
|
||||
edges.push({ from, to, name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { nodes, edges };
|
||||
};
|
|
@ -23,6 +23,7 @@ const Services = require("Services");
|
|||
const { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
|
||||
const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
|
||||
const DominatorTreeNode = require("devtools/shared/heapsnapshot/DominatorTreeNode");
|
||||
const { deduplicatePaths } = require("devtools/shared/heapsnapshot/shortest-paths");
|
||||
const { LabelAndShallowSizeVisitor } = DominatorTreeNode;
|
||||
|
||||
|
||||
|
@ -375,3 +376,51 @@ function assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenA
|
|||
|
||||
assertStructurallyEquivalent(actual, expected);
|
||||
}
|
||||
|
||||
function assertDeduplicatedPaths({ target, paths, expectedNodes, expectedEdges }) {
|
||||
dumpn("Deduplicating paths:");
|
||||
dumpn("target = " + target);
|
||||
dumpn("paths = " + JSON.stringify(paths, null, 2));
|
||||
dumpn("expectedNodes = " + expectedNodes);
|
||||
dumpn("expectedEdges = " + JSON.stringify(expectedEdges, null, 2));
|
||||
|
||||
const { nodes, edges } = deduplicatePaths(target, paths);
|
||||
|
||||
dumpn("Actual nodes = " + nodes);
|
||||
dumpn("Actual edges = " + JSON.stringify(edges, null, 2));
|
||||
|
||||
equal(nodes.length, expectedNodes.length,
|
||||
"actual number of nodes is equal to the expected number of nodes");
|
||||
|
||||
equal(edges.length, expectedEdges.length,
|
||||
"actual number of edges is equal to the expected number of edges");
|
||||
|
||||
const expectedNodeSet = new Set(expectedNodes);
|
||||
const nodeSet = new Set(nodes);
|
||||
ok(nodeSet.size === nodes.length,
|
||||
"each returned node should be unique");
|
||||
|
||||
for (let node of nodes) {
|
||||
ok(expectedNodeSet.has(node), `the ${node} node was expected`);
|
||||
}
|
||||
|
||||
for (let expectedEdge of expectedEdges) {
|
||||
let count = 0;
|
||||
for (let edge of edges) {
|
||||
if (edge.from === expectedEdge.from &&
|
||||
edge.to === expectedEdge.to &&
|
||||
edge.name === expectedEdge.name) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
equal(count, 1,
|
||||
"should have exactly one matching edge for the expected edge = " + JSON.stringify(edge));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock path entry for the given predecessor and edge.
|
||||
*/
|
||||
function pathEntry(predecessor, edge) {
|
||||
return { predecessor, edge };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that the DominatorTreeNode.attachShortestPaths function can correctly
|
||||
// attach the deduplicated shortest retaining paths for each node it is given.
|
||||
|
||||
const startNodeId = 9999;
|
||||
const maxNumPaths = 2;
|
||||
|
||||
// Mock data mapping node id to shortest paths to that node id.
|
||||
const shortestPaths = new Map([
|
||||
[1000, [
|
||||
[pathEntry(1100, "a"), pathEntry(1200, "b")],
|
||||
[pathEntry(1100, "c"), pathEntry(1300, "d")],
|
||||
]],
|
||||
[2000, [
|
||||
[pathEntry(2100, "e"), pathEntry(2200, "f"), pathEntry(2300, "g")]
|
||||
]],
|
||||
[3000, [
|
||||
[pathEntry(3100, "h")],
|
||||
[pathEntry(3100, "i")],
|
||||
[pathEntry(3100, "j")],
|
||||
[pathEntry(3200, "k")],
|
||||
[pathEntry(3300, "l")],
|
||||
[pathEntry(3400, "m")],
|
||||
]],
|
||||
]);
|
||||
|
||||
const actual = [
|
||||
makeTestDominatorTreeNode({ nodeId: 1000 }),
|
||||
makeTestDominatorTreeNode({ nodeId: 2000 }),
|
||||
makeTestDominatorTreeNode({ nodeId: 3000 }),
|
||||
];
|
||||
|
||||
const expected = [
|
||||
makeTestDominatorTreeNode({
|
||||
nodeId: 1000,
|
||||
shortestPaths: {
|
||||
nodes: [
|
||||
{ id: 1000, label: ["SomeType-1000"] },
|
||||
{ id: 1100, label: ["SomeType-1100"] },
|
||||
{ id: 1200, label: ["SomeType-1200"] },
|
||||
{ id: 1300, label: ["SomeType-1300"] },
|
||||
],
|
||||
edges: [
|
||||
{ from: 1100, to: 1200, name: "a" },
|
||||
{ from: 1100, to: 1300, name: "c" },
|
||||
{ from: 1200, to: 1000, name: "b" },
|
||||
{ from: 1300, to: 1000, name: "d" },
|
||||
]
|
||||
}
|
||||
}),
|
||||
|
||||
makeTestDominatorTreeNode({
|
||||
nodeId: 2000,
|
||||
shortestPaths: {
|
||||
nodes: [
|
||||
{ id: 2000, label: ["SomeType-2000"] },
|
||||
{ id: 2100, label: ["SomeType-2100"] },
|
||||
{ id: 2200, label: ["SomeType-2200"] },
|
||||
{ id: 2300, label: ["SomeType-2300"] },
|
||||
],
|
||||
edges: [
|
||||
{ from: 2100, to: 2200, name: "e" },
|
||||
{ from: 2200, to: 2300, name: "f" },
|
||||
{ from: 2300, to: 2000, name: "g" },
|
||||
]
|
||||
}
|
||||
}),
|
||||
|
||||
makeTestDominatorTreeNode({ nodeId: 3000,
|
||||
shortestPaths: {
|
||||
nodes: [
|
||||
{ id: 3000, label: ["SomeType-3000"] },
|
||||
{ id: 3100, label: ["SomeType-3100"] },
|
||||
{ id: 3200, label: ["SomeType-3200"] },
|
||||
{ id: 3300, label: ["SomeType-3300"] },
|
||||
{ id: 3400, label: ["SomeType-3400"] },
|
||||
],
|
||||
edges: [
|
||||
{ from: 3100, to: 3000, name: "h" },
|
||||
{ from: 3100, to: 3000, name: "i" },
|
||||
{ from: 3100, to: 3000, name: "j" },
|
||||
{ from: 3200, to: 3000, name: "k" },
|
||||
{ from: 3300, to: 3000, name: "l" },
|
||||
{ from: 3400, to: 3000, name: "m" },
|
||||
]
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
const breakdown = {
|
||||
by: "internalType",
|
||||
then: { by: "count", count: true, bytes: true }
|
||||
};
|
||||
|
||||
const mockSnapshot = {
|
||||
computeShortestPaths: (start, nodeIds, max) => {
|
||||
equal(start, startNodeId);
|
||||
equal(max, maxNumPaths);
|
||||
|
||||
return new Map(nodeIds.map(nodeId => {
|
||||
const paths = shortestPaths.get(nodeId);
|
||||
ok(paths, "Expected computeShortestPaths call for node id = " + nodeId);
|
||||
return [nodeId, paths];
|
||||
}));
|
||||
},
|
||||
|
||||
describeNode: (bd, nodeId) => {
|
||||
equal(bd, breakdown);
|
||||
return {
|
||||
["SomeType-" + nodeId]: {
|
||||
count: 1,
|
||||
bytes: 10,
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
DominatorTreeNode.attachShortestPaths(mockSnapshot,
|
||||
breakdown,
|
||||
startNodeId,
|
||||
actual,
|
||||
maxNumPaths);
|
||||
|
||||
dumpn("Expected = " + JSON.stringify(expected, null, 2));
|
||||
dumpn("Actual = " + JSON.stringify(actual, null, 2));
|
||||
|
||||
assertStructurallyEquivalent(expected, actual);
|
||||
}
|
|
@ -60,6 +60,7 @@ const expected = {
|
|||
],
|
||||
shallowSize: 10,
|
||||
retainedSize: 10,
|
||||
shortestPaths: undefined,
|
||||
children: [
|
||||
{
|
||||
nodeId: 200,
|
||||
|
@ -70,6 +71,7 @@ const expected = {
|
|||
shallowSize: 10,
|
||||
retainedSize: 10,
|
||||
parentId: 100,
|
||||
shortestPaths: undefined,
|
||||
children: [
|
||||
{
|
||||
nodeId: 500,
|
||||
|
@ -81,6 +83,7 @@ const expected = {
|
|||
retainedSize: 10,
|
||||
parentId: 200,
|
||||
moreChildrenAvailable: false,
|
||||
shortestPaths: undefined,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
|
@ -93,6 +96,7 @@ const expected = {
|
|||
retainedSize: 10,
|
||||
parentId: 200,
|
||||
moreChildrenAvailable: false,
|
||||
shortestPaths: undefined,
|
||||
children: undefined
|
||||
}
|
||||
],
|
||||
|
@ -107,6 +111,7 @@ const expected = {
|
|||
shallowSize: 10,
|
||||
retainedSize: 10,
|
||||
parentId: 100,
|
||||
shortestPaths: undefined,
|
||||
children: [
|
||||
{
|
||||
nodeId: 800,
|
||||
|
@ -118,6 +123,7 @@ const expected = {
|
|||
retainedSize: 10,
|
||||
parentId: 300,
|
||||
moreChildrenAvailable: false,
|
||||
shortestPaths: undefined,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
|
@ -130,6 +136,7 @@ const expected = {
|
|||
retainedSize: 10,
|
||||
parentId: 300,
|
||||
moreChildrenAvailable: false,
|
||||
shortestPaths: undefined,
|
||||
children: undefined
|
||||
}
|
||||
],
|
||||
|
|
|
@ -51,6 +51,13 @@ add_task(function* () {
|
|||
equal(typeof node.moreChildrenAvailable, "boolean",
|
||||
"each node should indicate if there are more children available or not");
|
||||
|
||||
equal(typeof node.shortestPaths, "object",
|
||||
"Should have shortest paths");
|
||||
equal(typeof node.shortestPaths.nodes, "object",
|
||||
"Should have shortest paths' nodes");
|
||||
equal(typeof node.shortestPaths.edges, "object",
|
||||
"Should have shortest paths' edges");
|
||||
|
||||
if (node.children) {
|
||||
node.children.forEach(checkTree);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,15 @@ add_task(function* () {
|
|||
equal(response.path.length, 1);
|
||||
equal(response.path[0], partialTree.nodeId);
|
||||
|
||||
for (let node of response.nodes) {
|
||||
equal(typeof node.shortestPaths, "object",
|
||||
"Should have shortest paths");
|
||||
equal(typeof node.shortestPaths.nodes, "object",
|
||||
"Should have shortest paths' nodes");
|
||||
equal(typeof node.shortestPaths.edges, "object",
|
||||
"Should have shortest paths' edges");
|
||||
}
|
||||
|
||||
// Next, test getting a subset of children available.
|
||||
const secondResponse = yield client.getImmediatelyDominated({
|
||||
dominatorTreeId,
|
||||
|
@ -59,5 +68,14 @@ add_task(function* () {
|
|||
equal(secondResponse.path.length, 1);
|
||||
equal(secondResponse.path[0], partialTree.nodeId);
|
||||
|
||||
for (let node of secondResponse.nodes) {
|
||||
equal(typeof node.shortestPaths, "object",
|
||||
"Should have shortest paths");
|
||||
equal(typeof node.shortestPaths.nodes, "object",
|
||||
"Should have shortest paths' nodes");
|
||||
equal(typeof node.shortestPaths.edges, "object",
|
||||
"Should have shortest paths' edges");
|
||||
}
|
||||
|
||||
client.destroy();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test the behavior of the deduplicatePaths utility function.
|
||||
|
||||
function edge(from, to, name) {
|
||||
return { from, to, name };
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
const a = 1;
|
||||
const b = 2;
|
||||
const c = 3;
|
||||
const d = 4;
|
||||
const e = 5;
|
||||
const f = 6;
|
||||
const g = 7;
|
||||
|
||||
dumpn("Single long path");
|
||||
assertDeduplicatedPaths({
|
||||
target: g,
|
||||
paths: [
|
||||
[
|
||||
pathEntry(a, "e1"),
|
||||
pathEntry(b, "e2"),
|
||||
pathEntry(c, "e3"),
|
||||
pathEntry(d, "e4"),
|
||||
pathEntry(e, "e5"),
|
||||
pathEntry(f, "e6"),
|
||||
]
|
||||
],
|
||||
expectedNodes: [a, b, c, d, e, f, g],
|
||||
expectedEdges: [
|
||||
edge(a, b, "e1"),
|
||||
edge(b, c, "e2"),
|
||||
edge(c, d, "e3"),
|
||||
edge(d, e, "e4"),
|
||||
edge(e, f, "e5"),
|
||||
edge(f, g, "e6"),
|
||||
]
|
||||
});
|
||||
|
||||
dumpn("Multiple edges from and to the same nodes");
|
||||
assertDeduplicatedPaths({
|
||||
target: a,
|
||||
paths: [
|
||||
[pathEntry(b, "x")],
|
||||
[pathEntry(b, "y")],
|
||||
[pathEntry(b, "z")],
|
||||
],
|
||||
expectedNodes: [a, b],
|
||||
expectedEdges: [
|
||||
edge(b, a, "x"),
|
||||
edge(b, a, "y"),
|
||||
edge(b, a, "z"),
|
||||
]
|
||||
});
|
||||
|
||||
dumpn("Multiple paths sharing some nodes and edges");
|
||||
assertDeduplicatedPaths({
|
||||
target: g,
|
||||
paths: [
|
||||
[
|
||||
pathEntry(a, "a->b"),
|
||||
pathEntry(b, "b->c"),
|
||||
pathEntry(c, "foo"),
|
||||
],
|
||||
[
|
||||
pathEntry(a, "a->b"),
|
||||
pathEntry(b, "b->d"),
|
||||
pathEntry(d, "bar"),
|
||||
],
|
||||
[
|
||||
pathEntry(a, "a->b"),
|
||||
pathEntry(b, "b->e"),
|
||||
pathEntry(e, "baz"),
|
||||
],
|
||||
],
|
||||
expectedNodes: [a, b, c, d, e, g],
|
||||
expectedEdges: [
|
||||
edge(a, b, "a->b"),
|
||||
edge(b, c, "b->c"),
|
||||
edge(b, d, "b->d"),
|
||||
edge(b, e, "b->e"),
|
||||
edge(c, g, "foo"),
|
||||
edge(d, g, "bar"),
|
||||
edge(e, g, "baz"),
|
||||
]
|
||||
});
|
||||
|
||||
dumpn("Second shortest path contains target itself");
|
||||
assertDeduplicatedPaths({
|
||||
target: g,
|
||||
paths: [
|
||||
[
|
||||
pathEntry(a, "a->b"),
|
||||
pathEntry(b, "b->g"),
|
||||
],
|
||||
[
|
||||
pathEntry(a, "a->b"),
|
||||
pathEntry(b, "b->g"),
|
||||
pathEntry(g, "g->f"),
|
||||
pathEntry(f, "f->g"),
|
||||
],
|
||||
],
|
||||
expectedNodes: [a, b, f, g],
|
||||
expectedEdges: [
|
||||
edge(a, b, "a->b"),
|
||||
edge(b, g, "b->g"),
|
||||
edge(g, f, "g->f"),
|
||||
edge(f, g, "f->g"),
|
||||
]
|
||||
});
|
||||
}
|
|
@ -29,12 +29,14 @@ support-files =
|
|||
[test_census-tree-node-06.js]
|
||||
[test_census-tree-node-07.js]
|
||||
[test_census-tree-node-08.js]
|
||||
[test_deduplicatePaths_01.js]
|
||||
[test_DominatorTree_01.js]
|
||||
[test_DominatorTree_02.js]
|
||||
[test_DominatorTree_03.js]
|
||||
[test_DominatorTree_04.js]
|
||||
[test_DominatorTree_05.js]
|
||||
[test_DominatorTree_06.js]
|
||||
[test_DominatorTreeNode_attachShortestPaths_01.js]
|
||||
[test_DominatorTreeNode_getNodeByIdAlongPath_01.js]
|
||||
[test_DominatorTreeNode_insert_01.js]
|
||||
[test_DominatorTreeNode_insert_02.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче