зеркало из https://github.com/microsoft/p-graph.git
Update cyclic dependancy error message to show the chain of the cycle (#15)
* update cyclic dependancy error to inlude the cycle * update tests and cyclic dependancy error output * Change files * upadte cyclic dep error message * chore(test): add test to check overlapping cycles * chore(test): update overlapping cycles test * chore(refactor): rename hasCycleDFS func to searchForCycleDFS
This commit is contained in:
Родитель
64f3ac7280
Коммит
1e42bb7ba4
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"type": "patch",
|
||||||
|
"comment": "update cyclic dependancy error to inlude the cycle",
|
||||||
|
"packageName": "p-graph",
|
||||||
|
"email": "cheruiyotbryan@gmail.com",
|
||||||
|
"dependentChangeType": "patch",
|
||||||
|
"date": "2021-09-23T13:29:58.510Z"
|
||||||
|
}
|
|
@ -37,11 +37,10 @@ export class PGraph {
|
||||||
throw new Error("We could not find a node in the graph with no dependencies, this likely means there is a cycle including all nodes");
|
throw new Error("We could not find a node in the graph with no dependencies, this likely means there is a cycle including all nodes");
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasCycles = graphHasCycles(this.pGraphDependencyMap);
|
const graph = graphHasCycles(this.pGraphDependencyMap);
|
||||||
|
|
||||||
if (hasCycles.hasCycle) {
|
if (graph.hasCycle) {
|
||||||
const { nodeId, dependsOn, dependedOnBy } = hasCycles.details;
|
throw new Error(`A cycle has been detected including the following nodes:\n${graph.cycle.join("\n")}`);
|
||||||
throw new Error(`The dependency graph has a cycle at ${nodeId} which depends on ${dependsOn} and is depended on by ${dependedOnBy}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,7 +202,39 @@ describe("Public API", () => {
|
||||||
["C", "D"],
|
["C", "D"],
|
||||||
["D", "B"],
|
["D", "B"],
|
||||||
];
|
];
|
||||||
const expectedErrorMessage = "The dependency graph has a cycle at B which depends on A,D and is depended on by C";
|
const expectedErrorMessage = `A cycle has been detected including the following nodes:
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D`;
|
||||||
|
expect(() => pGraph(nodeMap, dependencies)).toThrow(expectedErrorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an exception in the first instance of a cycle that has been detected when there are overlapped cycles", async () => {
|
||||||
|
// This is almost the same as the last test, except the root node is not a part of the cycle
|
||||||
|
const nodeMap: PGraphNodeMap = new Map([
|
||||||
|
["A", { run: () => Promise.resolve() }],
|
||||||
|
["B", { run: () => Promise.resolve() }],
|
||||||
|
["C", { run: () => Promise.resolve() }],
|
||||||
|
["D", { run: () => Promise.resolve() }],
|
||||||
|
["E", { run: () => Promise.resolve() }],
|
||||||
|
["F", { run: () => Promise.resolve() }],
|
||||||
|
]);
|
||||||
|
// B -> C -> E -> F -> D is the first cycle detected
|
||||||
|
const dependencies: DependencyList = [
|
||||||
|
["A", "B"],
|
||||||
|
["B", "C"],
|
||||||
|
["C", "D"],
|
||||||
|
["D", "B"],
|
||||||
|
["C", "E"],
|
||||||
|
["E", "F"],
|
||||||
|
["F", "D"],
|
||||||
|
];
|
||||||
|
const expectedErrorMessage = `A cycle has been detected including the following nodes:
|
||||||
|
B
|
||||||
|
C
|
||||||
|
E
|
||||||
|
F
|
||||||
|
D`
|
||||||
expect(() => pGraph(nodeMap, dependencies)).toThrow(expectedErrorMessage);
|
expect(() => pGraph(nodeMap, dependencies)).toThrow(expectedErrorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { PGraphNodeWithCyclicDependency, PGraphNodeWithNoCyclicDependency, PGrap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for any cycles in the dependency graph, returning `{ hasCycle: false }` if no cycles were detected.
|
* Checks for any cycles in the dependency graph, returning `{ hasCycle: false }` if no cycles were detected.
|
||||||
* Otherwise it returns the details of where the cycle was detected.
|
* Otherwise it returns the chain of nodes where the cycle was detected.
|
||||||
*/
|
*/
|
||||||
export function graphHasCycles(pGraphDependencyMap: Map<string, PGraphNodeWithDependencies>): PGraphNodeWithCyclicDependency | PGraphNodeWithNoCyclicDependency {
|
export function graphHasCycles(pGraphDependencyMap: Map<string, PGraphNodeWithDependencies>): PGraphNodeWithCyclicDependency | PGraphNodeWithNoCyclicDependency {
|
||||||
/**
|
/**
|
||||||
|
@ -13,7 +13,7 @@ export function graphHasCycles(pGraphDependencyMap: Map<string, PGraphNodeWithDe
|
||||||
*/
|
*/
|
||||||
const visitMap = new Map<string, boolean>();
|
const visitMap = new Map<string, boolean>();
|
||||||
|
|
||||||
for (const [nodeId, nodes] of pGraphDependencyMap.entries()) {
|
for (const [nodeId] of pGraphDependencyMap.entries()) {
|
||||||
/**
|
/**
|
||||||
* Test whether this node has already been visited or not.
|
* Test whether this node has already been visited or not.
|
||||||
*/
|
*/
|
||||||
|
@ -21,15 +21,9 @@ export function graphHasCycles(pGraphDependencyMap: Map<string, PGraphNodeWithDe
|
||||||
/**
|
/**
|
||||||
* Test whether the sub-graph of this node has cycles.
|
* Test whether the sub-graph of this node has cycles.
|
||||||
*/
|
*/
|
||||||
if (hasCycleDFS(pGraphDependencyMap, visitMap, nodeId)) {
|
const cycle = searchForCycleDFS(pGraphDependencyMap, visitMap, nodeId);
|
||||||
return {
|
if (cycle.length) {
|
||||||
hasCycle: true,
|
return { hasCycle: true, cycle };
|
||||||
details: {
|
|
||||||
nodeId,
|
|
||||||
dependsOn: Array.from(nodes.dependsOn),
|
|
||||||
dependedOnBy: Array.from(nodes.dependedOnBy)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +48,7 @@ interface StackElement {
|
||||||
traversing: boolean;
|
traversing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasCycleDFS = (graph: Map<string, PGraphNodeWithDependencies>, visitMap: Map<string, boolean>, nodeId: string): boolean => {
|
const searchForCycleDFS = (graph: Map<string, PGraphNodeWithDependencies>, visitMap: Map<string, boolean>, nodeId: string): string[] => {
|
||||||
const stack: StackElement[] = [{ node: nodeId, traversing: false }];
|
const stack: StackElement[] = [{ node: nodeId, traversing: false }];
|
||||||
while (stack.length > 0) {
|
while (stack.length > 0) {
|
||||||
const current = stack[stack.length - 1];
|
const current = stack[stack.length - 1];
|
||||||
|
@ -65,7 +59,8 @@ const hasCycleDFS = (graph: Map<string, PGraphNodeWithDependencies>, visitMap: M
|
||||||
* The current node has already been visited,
|
* The current node has already been visited,
|
||||||
* hence there is a cycle.
|
* hence there is a cycle.
|
||||||
*/
|
*/
|
||||||
return true;
|
const listOfCycle = stack.filter(i => i.traversing).map(a => a.node);
|
||||||
|
return listOfCycle.slice(listOfCycle.indexOf(current.node));
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* The current node has already been fully traversed
|
* The current node has already been fully traversed
|
||||||
|
@ -90,9 +85,9 @@ const hasCycleDFS = (graph: Map<string, PGraphNodeWithDependencies>, visitMap: M
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the current node's dependencies to the stack
|
* Add the current node's dependents to the stack
|
||||||
*/
|
*/
|
||||||
stack.push(...[...node.dependsOn].map((n) => ({ node: n, traversing: false })));
|
stack.push(...[...node.dependedOnBy].map((n) => ({ node: n, traversing: false })));
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* The current node has now been fully traversed.
|
* The current node has now been fully traversed.
|
||||||
|
@ -101,5 +96,5 @@ const hasCycleDFS = (graph: Map<string, PGraphNodeWithDependencies>, visitMap: M
|
||||||
stack.pop();
|
stack.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return [];
|
||||||
};
|
};
|
||||||
|
|
18
src/types.ts
18
src/types.ts
|
@ -66,21 +66,7 @@ export interface PGraphNodeWithCyclicDependency {
|
||||||
*/
|
*/
|
||||||
hasCycle: true;
|
hasCycle: true;
|
||||||
/**
|
/**
|
||||||
* Details on where the cyclic dependency was detected.
|
* Chain of node where the cyclic dependency was detected.
|
||||||
*/
|
*/
|
||||||
details: {
|
cycle: string[]
|
||||||
/**
|
|
||||||
* The identifier of this node, where the cyclic dependency was detected
|
|
||||||
*/
|
|
||||||
nodeId: string,
|
|
||||||
/**
|
|
||||||
* The set of nodes that this node depends on.
|
|
||||||
*/
|
|
||||||
dependsOn: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The set of nodes that depend on this node.
|
|
||||||
*/
|
|
||||||
dependedOnBy: string[];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче