| // Copyright (c) 2016, 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. | |
| import 'package:_fe_analyzer_shared/src/util/dependency_walker.dart'; | |
| import 'package:test/test.dart'; | |
| main() { | |
| late Map<String, TestNode> nodes; | |
| setUp(() { | |
| nodes = {}; | |
| }); | |
| TestNode getNode(String name) => | |
| nodes.putIfAbsent(name, () => TestNode(name)); | |
| void makeGraph(Map<String, List<String>> graph) { | |
| graph.forEach((name, deps) { | |
| var node = getNode(name); | |
| for (var dep in deps) { | |
| node._dependencies.add(getNode(dep)); | |
| } | |
| }); | |
| } | |
| TestWalker walk(String startingNodeName) => | |
| TestWalker()..walk(getNode(startingNodeName)); | |
| void checkGraph( | |
| Map<String, List<String>> graph, | |
| String startingNodeName, | |
| List<List<String>> expectedEvaluations, | |
| List<bool> expectedSccFlags, | |
| ) { | |
| makeGraph(graph); | |
| var walker = walk(startingNodeName); | |
| expect(walker._evaluations, expectedEvaluations.map((x) => x.toSet())); | |
| expect(walker._sccFlags, expectedSccFlags); | |
| } | |
| test('Complex graph', () { | |
| checkGraph( | |
| { | |
| 'a': ['b', 'c'], | |
| 'b': ['c', 'd'], | |
| 'c': [], | |
| 'd': ['c', 'e'], | |
| 'e': ['b', 'f'], | |
| 'f': ['c', 'd'], | |
| }, | |
| 'a', | |
| [ | |
| ['c'], | |
| ['b', 'd', 'e', 'f'], | |
| ['a'], | |
| ], | |
| [false, true, false], | |
| ); | |
| }); | |
| test('Diamond', () { | |
| checkGraph( | |
| { | |
| 'a': ['b', 'c'], | |
| 'b': ['d'], | |
| 'c': ['d'], | |
| 'd': [], | |
| }, | |
| 'a', | |
| [ | |
| ['d'], | |
| ['b'], | |
| ['c'], | |
| ['a'], | |
| ], | |
| [false, false, false, false], | |
| ); | |
| }); | |
| test('Single node', () { | |
| checkGraph( | |
| {'a': []}, | |
| 'a', | |
| [ | |
| ['a'], | |
| ], | |
| [false], | |
| ); | |
| }); | |
| test('Single node with trivial cycle', () { | |
| checkGraph( | |
| { | |
| 'a': ['a'], | |
| }, | |
| 'a', | |
| [ | |
| ['a'], | |
| ], | |
| [true], | |
| ); | |
| }); | |
| test('Three nodes with circular dependency', () { | |
| checkGraph( | |
| { | |
| 'a': ['b'], | |
| 'b': ['c'], | |
| 'c': ['a'], | |
| }, | |
| 'a', | |
| [ | |
| ['a', 'b', 'c'], | |
| ], | |
| [true], | |
| ); | |
| }); | |
| group('Two backlinks:', () { | |
| test('earlier first', () { | |
| // Test a graph A->B->C->D, where D points back to B and then C. | |
| checkGraph( | |
| { | |
| 'a': ['b'], | |
| 'b': ['c'], | |
| 'c': ['d'], | |
| 'd': ['b', 'c'], | |
| }, | |
| 'a', | |
| [ | |
| ['b', 'c', 'd'], | |
| ['a'], | |
| ], | |
| [true, false], | |
| ); | |
| }); | |
| test('later first', () { | |
| // Test a graph A->B->C->D, where D points back to C and then B. | |
| checkGraph( | |
| { | |
| 'a': ['b'], | |
| 'b': ['c'], | |
| 'c': ['d'], | |
| 'd': ['c', 'b'], | |
| }, | |
| 'a', | |
| [ | |
| ['b', 'c', 'd'], | |
| ['a'], | |
| ], | |
| [true, false], | |
| ); | |
| }); | |
| }); | |
| test('Two nodes with circular dependency', () { | |
| checkGraph( | |
| { | |
| 'a': ['b'], | |
| 'b': ['a'], | |
| }, | |
| 'a', | |
| [ | |
| ['a', 'b'], | |
| ], | |
| [true], | |
| ); | |
| }); | |
| test('Two nodes with simple dependency', () { | |
| checkGraph( | |
| { | |
| 'a': ['b'], | |
| 'b': [], | |
| }, | |
| 'a', | |
| [ | |
| ['b'], | |
| ['a'], | |
| ], | |
| [false, false], | |
| ); | |
| }); | |
| test('Do not revisit already-evaluated nodes', () { | |
| makeGraph({ | |
| 'a': ['b'], | |
| 'b': [], | |
| }); | |
| var walker = TestWalker(); | |
| var a = getNode('a'); | |
| walker.walk(a); | |
| expect(walker._evaluations, hasLength(2)); | |
| walker.walk(a); | |
| expect(walker._evaluations, hasLength(2)); | |
| }); | |
| } | |
| class TestNode extends Node<TestNode> { | |
| final String _name; | |
| @override | |
| bool isEvaluated = false; | |
| bool _computeDependenciesCalled = false; | |
| final _dependencies = <TestNode>[]; | |
| TestNode(this._name); | |
| @override | |
| List<TestNode> computeDependencies() { | |
| expect(_computeDependenciesCalled, false); | |
| _computeDependenciesCalled = true; | |
| return _dependencies; | |
| } | |
| } | |
| class TestWalker extends DependencyWalker<TestNode> { | |
| final _evaluations = <Set<String>>[]; | |
| final _sccFlags = <bool>[]; | |
| @override | |
| void evaluate(TestNode v) { | |
| v.isEvaluated = true; | |
| _evaluations.add({v._name}); | |
| _sccFlags.add(false); | |
| } | |
| @override | |
| void evaluateScc(List<TestNode> scc) { | |
| for (var v in scc) { | |
| v.isEvaluated = true; | |
| } | |
| var sccNames = scc.map((node) => node._name).toSet(); | |
| // Make sure there were no duplicates | |
| expect(sccNames.length, scc.length); | |
| _evaluations.add(sccNames); | |
| _sccFlags.add(true); | |
| } | |
| } |