// 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; new(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); }