blob: f73257391f7ee99170cc009573bcb4de3da8250c [file] [log] [blame]
// Copyright (c) 2023, 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 'dart:collection';
import 'package:graphs/graphs.dart';
import 'package:test/test.dart';
import 'utils/utils.dart';
void main() {
group('for an acyclic graph', () {
for (final acyclic in [true, false]) {
group('with acyclic: $acyclic', () {
group('returns the transitive closure for a graph', () {
test('with no nodes', () {
expect(_transitiveClosure<int>({}, acyclic: acyclic), isEmpty);
});
test('with only one node', () {
expect(
_transitiveClosure<int>({1: []}, acyclic: acyclic),
equals({1: <int>{}}),
);
});
test('with no edges', () {
expect(
_transitiveClosure<int>(
{1: [], 2: [], 3: [], 4: []},
acyclic: acyclic,
),
equals(<int, Set<int>>{1: {}, 2: {}, 3: {}, 4: {}}),
);
});
test('with single edges', () {
expect(
_transitiveClosure<int>(
{
1: [2],
2: [3],
3: [4],
4: [],
},
acyclic: acyclic,
),
equals({
1: {2, 3, 4},
2: {3, 4},
3: {4},
4: <int>{},
}),
);
});
test('with many edges from one node', () {
expect(
_transitiveClosure<int>(
{
1: [2, 3, 4],
2: [],
3: [],
4: [],
},
acyclic: acyclic,
),
equals(<int, Set<int>>{
1: {2, 3, 4},
2: {},
3: {},
4: {},
}),
);
});
test('with transitive edges', () {
expect(
_transitiveClosure<int>(
{
1: [2, 4],
2: [],
3: [],
4: [3],
},
acyclic: acyclic,
),
equals(<int, Set<int>>{
1: {2, 3, 4},
2: {},
3: {},
4: {3},
}),
);
});
test('with diamond edges', () {
expect(
_transitiveClosure<int>(
{
1: [2, 3],
2: [4],
3: [4],
4: [],
},
acyclic: acyclic,
),
equals(<int, Set<int>>{
1: {2, 3, 4},
2: {4},
3: {4},
4: {},
}),
);
});
test('with disjoint subgraphs', () {
expect(
_transitiveClosure<int>(
{
1: [2],
2: [3],
3: [],
4: [5],
5: [6],
6: [],
},
acyclic: acyclic,
),
equals(<int, Set<int>>{
1: {2, 3},
2: {3},
3: {},
4: {5, 6},
5: {6},
6: {},
}),
);
});
});
test('respects custom equality and hash functions', () {
final result = _transitiveClosure<int>(
{
0: [2],
3: [4],
5: [6],
7: [],
},
equals: (i, j) => (i ~/ 2) == (j ~/ 2),
hashCode: (i) => (i ~/ 2).hashCode,
);
expect(
result.keys,
unorderedMatches([
0,
anyOf([2, 3]),
anyOf([4, 5]),
anyOf([6, 7]),
]),
);
expect(
result[0],
equals({
anyOf([2, 3]),
anyOf([4, 5]),
anyOf([6, 7]),
}),
);
expect(
result[2],
equals({
anyOf([4, 5]),
anyOf([6, 7]),
}),
);
expect(
result[4],
equals({
anyOf([6, 7]),
}),
);
expect(result[6], isEmpty);
});
});
}
});
group('for a cyclic graph', () {
group('with acyclic: true throws a CycleException for a graph with', () {
test('a one-node cycle', () {
expect(
() => _transitiveClosure<int>(
{
1: [1],
},
acyclic: true,
),
throwsCycleException([1]),
);
});
test('a multi-node cycle', () {
expect(
() => _transitiveClosure<int>(
{
1: [2],
2: [3],
3: [4],
4: [1],
},
acyclic: true,
),
throwsCycleException([4, 1, 2, 3]),
);
});
});
group('returns the transitive closure for a graph', () {
test('with a single one-node component', () {
expect(
_transitiveClosure<int>({
1: [1],
}),
equals({
1: {1},
}),
);
});
test('with a single multi-node component', () {
expect(
_transitiveClosure<int>({
1: [2],
2: [3],
3: [4],
4: [1],
}),
equals({
1: {1, 2, 3, 4},
2: {1, 2, 3, 4},
3: {1, 2, 3, 4},
4: {1, 2, 3, 4},
}),
);
});
test('with a series of multi-node components', () {
expect(
_transitiveClosure<int>({
1: [2],
2: [1, 3],
3: [4],
4: [3, 5],
5: [6],
6: [5, 7],
7: [8],
8: [7],
}),
equals({
1: {1, 2, 3, 4, 5, 6, 7, 8},
2: {1, 2, 3, 4, 5, 6, 7, 8},
3: {3, 4, 5, 6, 7, 8},
4: {3, 4, 5, 6, 7, 8},
5: {5, 6, 7, 8},
6: {5, 6, 7, 8},
7: {7, 8},
8: {7, 8},
}),
);
});
test('with a diamond of multi-node components', () {
expect(
_transitiveClosure<int>({
1: [2],
2: [1, 3, 5],
3: [4],
4: [3, 7],
5: [6],
6: [5, 7],
7: [8],
8: [7],
}),
equals({
1: {1, 2, 3, 4, 5, 6, 7, 8},
2: {1, 2, 3, 4, 5, 6, 7, 8},
3: {3, 4, 7, 8},
4: {3, 4, 7, 8},
5: {5, 6, 7, 8},
6: {5, 6, 7, 8},
7: {7, 8},
8: {7, 8},
}),
);
});
test('mixed single- and multi-node components', () {
expect(
_transitiveClosure<int>({
1: [2],
2: [1, 3],
3: [4],
4: [5],
5: [4, 6],
6: [7],
7: [8],
8: [7],
}),
equals({
1: {1, 2, 3, 4, 5, 6, 7, 8},
2: {1, 2, 3, 4, 5, 6, 7, 8},
3: {4, 5, 6, 7, 8},
4: {4, 5, 6, 7, 8},
5: {4, 5, 6, 7, 8},
6: {7, 8},
7: {7, 8},
8: {7, 8},
}),
);
});
});
});
}
/// Returns the transitive closure of a graph represented a map from keys to
/// edges.
Map<T, Set<T>> _transitiveClosure<T extends Object>(
Map<T, List<T>> graph, {
bool Function(T, T)? equals,
int Function(T)? hashCode,
bool acyclic = false,
}) {
assert((equals == null) == (hashCode == null));
if (equals != null) {
graph = LinkedHashMap(equals: equals, hashCode: hashCode)..addAll(graph);
}
return transitiveClosure(
graph.keys,
(node) {
expect(graph, contains(node));
return graph[node]!;
},
equals: equals,
hashCode: hashCode,
acyclic: acyclic,
);
}