// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. library kernel.test.graph_test; import 'package:expect/expect.dart' show Expect; import 'package:kernel/util/graph.dart'; const String A = 'A'; const String B = 'B'; const String C = 'C'; const String D = 'D'; const String E = 'E'; const String F = 'F'; class TestGraph implements Graph { final Map> graph; TestGraph(this.graph); @override Iterable get vertices => graph.keys; @override Iterable neighborsOf(String vertex) => graph[vertex]!; } void test( {required List> expectedStrongComponents, List expectedSortedVertices = const [], List expectedCyclicVertices = const [], List> expectedLayers = const [], List>> expectedStrongLayers = const [], required Map> graphData}) { Graph graph = new TestGraph(graphData); List> strongComponentResult = computeStrongComponents(graph); Expect.equals( expectedStrongComponents.length, strongComponentResult.length, "Unexpected strongly connected components count. " "Expected ${expectedStrongComponents}, " "actual ${strongComponentResult}"); for (int index = 0; index < expectedStrongComponents.length; index++) { Expect.listEquals( expectedStrongComponents[index], strongComponentResult[index], "Unexpected strongly connected components. " "Expected $expectedStrongComponents, actual $strongComponentResult."); } TopologicalSortResult topologicalResult = topologicalSort(graph); Set sortedAndCyclicVertices = topologicalResult.sortedVertices .toSet() .intersection(topologicalResult.cyclicVertices.toSet()); Expect.isTrue(sortedAndCyclicVertices.isEmpty, "Found vertices both sorted and cyclic: $sortedAndCyclicVertices"); List sortedOrCyclicVertices = [ ...topologicalResult.sortedVertices, ...topologicalResult.cyclicVertices ]; Expect.equals( graphData.length, sortedOrCyclicVertices.length, "Unexpected vertex count. Expected ${graphData.length}, " "found ${sortedOrCyclicVertices.length}."); Expect.listEquals( expectedSortedVertices, topologicalResult.sortedVertices, "Unexpected sorted vertices. " "Expected $expectedSortedVertices, " "actual ${topologicalResult.sortedVertices}."); Expect.listEquals( expectedCyclicVertices, topologicalResult.cyclicVertices, "Unexpected cyclic vertices. " "Expected $expectedCyclicVertices, " "actual ${topologicalResult.cyclicVertices}."); Expect.equals( expectedLayers.length, topologicalResult.layers.length, "Unexpected topological layer count. " "Expected ${expectedLayers}, " "actual ${topologicalResult.layers}"); for (int index = 0; index < expectedLayers.length; index++) { Expect.listEquals( expectedLayers[index], topologicalResult.layers[index], "Unexpected topological layers. " "Expected $expectedLayers, " "actual ${topologicalResult.layers}."); for (String vertex in topologicalResult.layers[index]) { int actualIndex = topologicalResult.indexMap[vertex]!; Expect.equals( index, actualIndex, "Unexpected topological index for $vertex. " "Expected $index, found $actualIndex."); } } StrongComponentGraph strongComponentGraph = new StrongComponentGraph(graph, strongComponentResult); TopologicalSortResult> strongTopologicalResult = topologicalSort(strongComponentGraph); Expect.equals( expectedStrongLayers.length, strongTopologicalResult.layers.length, "Unexpected strong topological layer count. " "Expected ${expectedStrongLayers}, " "actual ${strongTopologicalResult.layers}"); for (int index = 0; index < expectedStrongLayers.length; index++) { List> expectedStrongLayer = expectedStrongLayers[index]; List> strongLayer = strongTopologicalResult.layers[index]; Expect.equals( expectedStrongLayer.length, strongLayer.length, "Unexpected strong topological layer $index count. " "Expected ${expectedStrongLayers}, " "actual ${strongTopologicalResult.layers}"); for (int subIndex = 0; subIndex < expectedStrongLayer.length; subIndex++) { Expect.listEquals( expectedStrongLayer[subIndex], strongLayer[subIndex], "Unexpected strong topological layer $index. " "Expected $expectedStrongLayer, " "actual $strongLayer."); } for (List vertex in strongTopologicalResult.layers[index]) { int actualIndex = strongTopologicalResult.indexMap[vertex]!; Expect.equals( index, actualIndex, "Unexpected strong topological index for $vertex. " "Expected $index, found $actualIndex."); } } } void main() { test(graphData: { A: [B], B: [A], C: [A], D: [C], }, expectedStrongComponents: [ [B, A], [C], [D] ], expectedCyclicVertices: [ B, A, C, D ], expectedStrongLayers: [ [ [B, A] ], [ [C] ], [ [D] ] ]); test(graphData: {}, expectedStrongComponents: []); test(graphData: { A: [], B: [], C: [], D: [], }, expectedStrongComponents: [ [A], [B], [C], [D] ], expectedSortedVertices: [ A, B, C, D ], expectedLayers: [ [A, B, C, D] ], expectedStrongLayers: [ [ [A], [B], [C], [D] ] ]); test(graphData: { D: [C], C: [A], B: [A], A: [B], }, expectedStrongComponents: [ [B, A], [C], [D] ], expectedCyclicVertices: [ B, A, C, D ], expectedStrongLayers: [ [ [B, A] ], [ [C] ], [ [D] ] ]); test(graphData: { A: [B], B: [C], C: [D], D: [], }, expectedStrongComponents: [ [D], [C], [B], [A] ], expectedSortedVertices: [ D, C, B, A ], expectedLayers: [ [D], [C], [B], [A] ], expectedStrongLayers: [ [ [D] ], [ [C] ], [ [B] ], [ [A] ] ]); test(graphData: { D: [], C: [D], B: [C], A: [B], }, expectedStrongComponents: [ [D], [C], [B], [A] ], expectedSortedVertices: [ D, C, B, A ], expectedLayers: [ [D], [C], [B], [A] ], expectedStrongLayers: [ [ [D] ], [ [C] ], [ [B] ], [ [A] ] ]); test(graphData: { A: [], B: [A], C: [A], D: [B, C], }, expectedStrongComponents: [ [A], [B], [C], [D] ], expectedSortedVertices: [ A, B, C, D ], expectedLayers: [ [A], [B, C], [D] ], expectedStrongLayers: [ [ [A] ], [ [B], [C] ], [ [D] ] ]); // Test a complex graph. test(graphData: { A: [B, C], B: [C, D], C: [], D: [C, E], E: [B, F], F: [C, D] }, expectedStrongComponents: [ [C], [F, E, D, B], [A], ], expectedSortedVertices: [ C ], expectedCyclicVertices: [ E, D, B, A, F ], expectedLayers: [ [C] ], expectedStrongLayers: [ [ [C] ], [ [F, E, D, B] ], [ [A] ] ]); // Test a diamond-shaped graph. test(graphData: { A: [B, C], B: [D], C: [D], D: [] }, expectedStrongComponents: [ [D], [B], [C], [A], ], expectedSortedVertices: [ D, B, C, A ], expectedLayers: [ [D], [B, C], [A] ], expectedStrongLayers: [ [ [D] ], [ [B], [C] ], [ [A] ] ]); // Test a graph with a single node. test(graphData: { A: [] }, expectedStrongComponents: [ [A] ], expectedSortedVertices: [ A ], expectedLayers: [ [A] ], expectedStrongLayers: [ [ [A] ] ]); // Test a graph with a single node and a trivial cycle. test(graphData: { A: [A] }, expectedStrongComponents: [ [A] ], expectedCyclicVertices: [ A ], expectedStrongLayers: [ [ [A] ] ]); // Test a graph with three nodes with circular dependencies. test(graphData: { A: [B], B: [C], C: [A], }, expectedStrongComponents: [ [C, B, A] ], expectedCyclicVertices: [ C, B, A ], expectedStrongLayers: [ [ [C, B, A] ] ]); // Test a graph A->B->C->D, where D points back to B and then C. test(graphData: { A: [B], B: [C], C: [D], D: [B, C] }, expectedStrongComponents: [ [D, C, B], [A] ], expectedCyclicVertices: [ D, C, B, A ], expectedStrongLayers: [ [ [D, C, B] ], [ [A] ] ]); // Test a graph A->B->C->D, where D points back to C and then B. test(graphData: { A: [B], B: [C], C: [D], D: [C, B] }, expectedStrongComponents: [ [D, C, B], [A] ], expectedCyclicVertices: [ D, C, B, A ], expectedStrongLayers: [ [ [D, C, B] ], [ [A] ] ]); // Test a graph with two nodes with circular dependencies. test(graphData: { A: [B], B: [A] }, expectedStrongComponents: [ [B, A] ], expectedCyclicVertices: [ B, A ], expectedStrongLayers: [ [ [B, A] ] ]); // Test a graph with two nodes and a single dependency. test(graphData: { A: [B], B: [] }, expectedStrongComponents: [ [B], [A] ], expectedSortedVertices: [ B, A ], expectedLayers: [ [B], [A] ], expectedStrongLayers: [ [ [B] ], [ [A] ] ]); test(graphData: { A: [], B: [A], C: [B, D], D: [C], E: [A], F: [B], }, expectedStrongComponents: [ [A], [B], [D, C], [E], [F], ], expectedSortedVertices: [ A, B, E, F, ], expectedCyclicVertices: [ D, C, ], expectedLayers: [ [A], [B, E], [F], ], expectedStrongLayers: [ [ [A] ], [ [B], [E] ], [ [D, C], [F] ] ]); testTransitiveDependencies(graphData: { A: [B], B: [A], }, of: { A }, expected: { A, B, }); testTransitiveDependencies(graphData: {}, of: { A, B, C, D, E, F, }, expected: {}); { String MAIN = "MAIN"; String DARTFFI = "DARTFFI"; testTransitiveDependencies(graphData: { MAIN: [DARTFFI], }, of: { DARTFFI }, expected: { MAIN, }); } testTransitiveDependencies(graphData: { A: [B], B: [C], C: [B, D], D: [C], E: [A], F: [B], }, of: { A }, expected: { A, E }); { String MAIN = "MAIN"; String TARGET = "TARGET"; testTransitiveDependencies(graphData: { MAIN: [A, E], A: [B], B: [A, C], C: [TARGET], D: [], E: [D], }, of: { TARGET, }, expected: { C, B, A, MAIN, }); testTransitiveDependencies(graphData: { MAIN: [A, E], A: [B], B: [A, C], C: [], D: [], E: [D], }, of: { TARGET, }, expected: {}); testTransitiveDependencies(graphData: { MAIN: [A, C, E, TARGET], A: [B], B: [A, C], C: [], D: [], E: [D], }, of: { TARGET, }, expected: { MAIN, }); } } void testTransitiveDependencies( {required Set of, required Set expected, required Map> graphData}) { Graph graph = new TestGraph(graphData); Set actual = calculateTransitiveDependenciesOf(graph, of); Expect.setEquals(expected, actual); }