Skip to content

Commit b987a88

Browse files
authored
Merge pull request #354 from DataObjects-NET/7.0-hTopologicalSorterTest-imp
Improves tests of Xtensive.Sorting.TopologicalSorter
2 parents b5e84de + 289ebb2 commit b987a88

File tree

3 files changed

+266
-165
lines changed

3 files changed

+266
-165
lines changed

Orm/Xtensive.Orm.Tests.Core/Helpers/TopologicalSorterTest.cs

Lines changed: 249 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -16,145 +16,262 @@
1616

1717
namespace Xtensive.Orm.Tests.Core.Helpers
1818
{
19-
[TestFixture]
20-
public class TopologicalSorterTest
19+
[TestFixture]
20+
public class TopologicalSorterTest
21+
{
22+
[Test, Explicit]
23+
public void PerformanceTest()
2124
{
22-
[Test, Explicit]
23-
public void PerformanceTest()
24-
{
25-
using (TestLog.InfoRegion("No loops")) {
26-
InternalPerformanceTest(10000, 10, false);
27-
InternalPerformanceTest(100, 10, false);
28-
InternalPerformanceTest(1000, 10, false);
29-
InternalPerformanceTest(10000, 10, false);
30-
InternalPerformanceTest(100000, 10, false);
31-
}
32-
TestLog.Info("");
33-
using (TestLog.InfoRegion("With loop removal")) {
34-
InternalPerformanceTest(10000, 10, true);
35-
InternalPerformanceTest(100, 10, true);
36-
InternalPerformanceTest(1000, 10, true);
37-
InternalPerformanceTest(10000, 10, true);
38-
InternalPerformanceTest(100000, 10, true);
39-
}
40-
}
25+
using (TestLog.InfoRegion("No loops")) {
26+
InternalPerformanceTest(10000, 10, false);
27+
InternalPerformanceTest(100, 10, false);
28+
InternalPerformanceTest(1000, 10, false);
29+
InternalPerformanceTest(10000, 10, false);
30+
InternalPerformanceTest(100000, 10, false);
31+
}
32+
TestLog.Info("");
33+
using (TestLog.InfoRegion("With loop removal")) {
34+
InternalPerformanceTest(10000, 10, true);
35+
InternalPerformanceTest(100, 10, true);
36+
InternalPerformanceTest(1000, 10, true);
37+
InternalPerformanceTest(10000, 10, true);
38+
InternalPerformanceTest(100000, 10, true);
39+
}
40+
}
4141

42-
private static void InternalPerformanceTest(int nodeCount, int averageConnectionCount, bool allowLoops)
43-
{
44-
TestLog.Info("Building graph: {0} nodes, {1} connections/node in average.", nodeCount, averageConnectionCount);
45-
var rnd = new Random();
46-
var nodes = new List<Node<int, int>>();
47-
for (int i = 0; i < nodeCount; i++)
48-
nodes.Add(new Node<int, int>(i));
49-
int connectionCount = 0;
50-
foreach (var from in nodes) {
51-
int outgoingConnectionCount = rnd.Next(averageConnectionCount);
52-
for (int i = 0; i < outgoingConnectionCount; i++) {
53-
var to = nodes[rnd.Next(allowLoops ? nodeCount : @from.Item)];
54-
if (from==to)
55-
continue;
56-
var c = new NodeConnection<int, int>(@from, to, connectionCount++);
57-
c.BindToNodes();
58-
}
59-
}
60-
61-
GC.GetTotalMemory(true);
62-
using (new Measurement("Sorting", nodeCount + connectionCount)) {
63-
List<Node<int, int>> removedEdges;
64-
var result = TopologicalSorter.Sort(nodes, out removedEdges);
65-
if (!allowLoops)
66-
Assert.AreEqual(nodeCount, result.Count);
67-
}
68-
GC.GetTotalMemory(true);
69-
}
42+
private static void InternalPerformanceTest(int nodeCount, int averageConnectionCount, bool allowLoops)
43+
{
44+
TestLog.Info("Building graph: {0} nodes, {1} connections/node in average.", nodeCount, averageConnectionCount);
45+
var rnd = new Random();
46+
var nodes = new List<Node<int, int>>();
47+
for (var i = 0; i < nodeCount; i++) {
48+
nodes.Add(new Node<int, int>(i));
49+
}
7050

71-
[Test]
72-
public void SelfReferenceTest()
73-
{
74-
var node = new Node<int, string>(1);
75-
var connection = new NodeConnection<int, string>(node, node, "ConnectionItem");
76-
connection.BindToNodes();
77-
78-
List<NodeConnection<int, string>> removedEdges;
79-
List<int> result = TopologicalSorter.Sort(EnumerableUtils.One(node), out removedEdges);
80-
Assert.AreEqual(1, result.Count);
81-
Assert.AreEqual(node.Item, result[0]);
82-
Assert.AreEqual(1, removedEdges.Count);
83-
Assert.AreEqual(connection, removedEdges[0]);
51+
int connectionCount = 0;
52+
foreach (var from in nodes) {
53+
var outgoingConnectionCount = rnd.Next(averageConnectionCount);
54+
for (var i = 0; i < outgoingConnectionCount; i++) {
55+
var to = nodes[rnd.Next(allowLoops ? nodeCount : @from.Item)];
56+
if (from == to)
57+
continue;
58+
var c = new NodeConnection<int, int>(@from, to, connectionCount++);
59+
c.BindToNodes();
8460
}
61+
}
8562

86-
[Test]
87-
public void RemoveWholeNodeTest()
88-
{
89-
var node1 = new Node<int, string>(1);
90-
var node2 = new Node<int, string>(2);
91-
var connection12_1 = new NodeConnection<int, string>(node1, node2, "ConnectionItem 1->2 1");
92-
connection12_1.BindToNodes();
93-
var connection12_2 = new NodeConnection<int, string>(node1, node2, "ConnectionItem 1->2 2");
94-
connection12_2.BindToNodes();
95-
var connection21_1 = new NodeConnection<int, string>(node2, node1, "ConnectionItem 2->1 1");
96-
connection21_1.BindToNodes();
97-
98-
// Remove edge by edge.
99-
100-
List<NodeConnection<int, string>> removedEdges;
101-
List<int> result = TopologicalSorter.Sort(new[] {node2, node1}, out removedEdges);
102-
Assert.AreEqual(2, result.Count);
103-
Assert.AreEqual(node1.Item, result[0]);
104-
Assert.AreEqual(node2.Item, result[1]);
105-
106-
Assert.AreEqual(1, removedEdges.Count);
107-
Assert.AreEqual(connection21_1, removedEdges[0]);
108-
109-
// Remove whole node
110-
connection12_1.BindToNodes();
111-
connection12_2.BindToNodes();
112-
connection21_1.BindToNodes();
113-
114-
result = TopologicalSorter.Sort(new[] {node2, node1}, out removedEdges, true);
115-
Assert.AreEqual(2, result.Count);
116-
Assert.AreEqual(node1.Item, result[1]);
117-
Assert.AreEqual(node2.Item, result[0]);
118-
119-
Assert.AreEqual(2, removedEdges.Count);
120-
Assert.AreEqual(0, removedEdges.Except(new[] {connection12_1, connection12_2}).Count());
121-
}
63+
_ = GC.GetTotalMemory(true);
64+
using (new Measurement("Sorting", nodeCount + connectionCount)) {
65+
var result = TopologicalSorter.Sort(nodes, out var _);
66+
if (!allowLoops)
67+
Assert.AreEqual(nodeCount, result.Count);
68+
}
69+
_ = GC.GetTotalMemory(true);
70+
}
12271

123-
[Test]
124-
public void CombinedTest()
125-
{
126-
TestSort(new[] {4, 3, 2, 1}, (i1, i2) => !(i1 == 3 || i2 == 3), null, new[] {4, 2, 1});
127-
TestSort(new[] {3, 2, 1}, (i1, i2) => i1 >= i2, new[] {1, 2, 3}, null);
128-
TestSort(new[] {3, 2, 1}, (i1, i2) => true, null, new[] {1, 2, 3});
129-
TestSort(new[] {3, 2, 1}, (i1, i2) => false, new[] {3, 2, 1}, null);
130-
}
72+
[Test]
73+
public void SelfReferenceTest()
74+
{
75+
var node = new Node<int, string>(1);
76+
var connection = new NodeConnection<int, string>(node, node, "ConnectionItem");
77+
connection.BindToNodes();
78+
79+
var result = TopologicalSorter.Sort(EnumerableUtils.One(node), out var removedEdges);
80+
Assert.AreEqual(1, result.Count);
81+
Assert.AreEqual(node.Item, result[0]);
82+
Assert.AreEqual(1, removedEdges.Count);
83+
Assert.AreEqual(connection, removedEdges[0]);
84+
}
85+
86+
[Test]
87+
public void NullNodeCollectionTest()
88+
{
89+
_ = Assert.Throws<ArgumentNullException>(() => TopologicalSorter.Sort((IEnumerable<Node<int, string>>) null, out var _));
90+
_ = Assert.Throws<ArgumentNullException>(() => TopologicalSorter.Sort((IEnumerable<Node<int, string>>) null, out var _, false));
91+
_ = Assert.Throws<ArgumentNullException>(() => TopologicalSorter.Sort((IEnumerable<Node<int, string>>) null, out var _, true));
92+
_ = Assert.Throws<ArgumentNullException>(() => TopologicalSorter.Sort((List<Node<int, int>>) null, out _));
93+
}
94+
95+
[Test]
96+
public void EmptyNodeCollectionTest()
97+
{
98+
_ = TopologicalSorter.Sort(Enumerable.Empty<Node<int, string>>(), out _);
99+
_ = TopologicalSorter.Sort(Enumerable.Empty<Node<int, string>>(), out _, false);
100+
_ = TopologicalSorter.Sort(Enumerable.Empty<Node<int, string>>(), out _, true);
101+
_ = TopologicalSorter.Sort(new List<Node<int, int>>(), out _);
102+
}
103+
104+
[Test]
105+
public void FullCircleTest()
106+
{
107+
var nodes = new List<Node<int, int>>();
108+
for (var i = 0; i < 3; i++) {
109+
nodes.Add(new Node<int, int>(i));
110+
}
111+
112+
var c = new NodeConnection<int, int>(nodes[0], nodes[1], 1);
113+
c.BindToNodes();
114+
c = new NodeConnection<int, int>(nodes[1], nodes[2], 2);
115+
c.BindToNodes();
116+
c = new NodeConnection<int, int>(nodes[2], nodes[0], 3);
117+
c.BindToNodes();
118+
119+
var result = TopologicalSorter.Sort(nodes, out var removedEdges);
120+
Assert.That(result, Is.Null);
121+
}
122+
123+
[Test]
124+
public void RemoveWholeNodeTest()
125+
{
126+
var node1 = new Node<int, string>(1);
127+
var node2 = new Node<int, string>(2);
128+
var connection12_1 = new NodeConnection<int, string>(node1, node2, "ConnectionItem 1->2 1");
129+
connection12_1.BindToNodes();
130+
var connection12_2 = new NodeConnection<int, string>(node1, node2, "ConnectionItem 1->2 2");
131+
connection12_2.BindToNodes();
132+
var connection21_1 = new NodeConnection<int, string>(node2, node1, "ConnectionItem 2->1 1");
133+
connection21_1.BindToNodes();
134+
135+
// Remove edge by edge.
136+
var result = TopologicalSorter.Sort(new[] { node2, node1 }, out var removedEdges);
137+
Assert.AreEqual(2, result.Count);
138+
Assert.AreEqual(node1.Item, result[0]);
139+
Assert.AreEqual(node2.Item, result[1]);
140+
141+
Assert.AreEqual(1, removedEdges.Count);
142+
Assert.AreEqual(connection21_1, removedEdges[0]);
143+
144+
// Remove whole node
145+
connection12_1.BindToNodes();
146+
connection12_2.BindToNodes();
147+
connection21_1.BindToNodes();
148+
149+
result = TopologicalSorter.Sort(new[] { node2, node1 }, out removedEdges, true);
150+
Assert.AreEqual(2, result.Count);
151+
Assert.AreEqual(node1.Item, result[1]);
152+
Assert.AreEqual(node2.Item, result[0]);
153+
154+
Assert.AreEqual(2, removedEdges.Count);
155+
Assert.AreEqual(0, removedEdges.Except(new[] { connection12_1, connection12_2 }).Count());
156+
}
157+
158+
[Test]
159+
public void CombinedTest()
160+
{
161+
TestSortLoopsCheck(new[] { 4, 3, 2, 1 }, (i1, i2) => !(i1 == 3 || i2 == 3), null, new[] { 4, 2, 1 });
162+
TestSortLoopsCheck(new[] { 3, 2, 1 }, (i1, i2) => i1 >= i2, new[] { 3, 2, 1 }, null);
163+
TestSortLoopsCheck(new[] { 3, 2, 1 }, (i1, i2) => true, null, new[] { 1, 2, 3 });
164+
TestSortLoopsCheck(new[] { 3, 2, 1 }, (i1, i2) => false, new[] { 3, 2, 1 }, null);
165+
TestSortLoopsCheck(Array.Empty<int>(), (i1, i2) => true, Array.Empty<int>(), null);
166+
TestSortLoopsCheck(Array.Empty<int>(), (i1, i2) => false, Array.Empty<int>(), null);
167+
_ = Assert.Throws<ArgumentNullException>(() => TestSortLoopsCheck<int>(null, (i1, i2) => true, null, null));
168+
_ = Assert.Throws<ArgumentNullException>(() => TestSortLoopsCheck<int>(null, (i1, i2) => false, null, null));
169+
170+
TestSortNoLoopsCheck(new[] { 4, 3, 2, 1 }, (i1, i2) => !(i1 == 3 || i2 == 3), null);
171+
TestSortNoLoopsCheck(new[] { 3, 2, 1 }, (i1, i2) => i1 >= i2, new[] { 3, 2, 1 });
172+
TestSortNoLoopsCheck(new[] { 3, 2, 1 }, (i1, i2) => true, null);
173+
TestSortNoLoopsCheck(new[] { 3, 2, 1 }, (i1, i2) => false, new[] { 3, 2, 1 });
174+
TestSortNoLoopsCheck(Array.Empty<int>(), (i1, i2) => true, Array.Empty<int>());
175+
TestSortNoLoopsCheck(Array.Empty<int>(), (i1, i2) => false, Array.Empty<int>());
176+
_ = Assert.Throws<ArgumentNullException>(() => TestSortNoLoopsCheck<int>(null, (i1, i2) => true, null));
177+
_ = Assert.Throws<ArgumentNullException>(() => TestSortNoLoopsCheck<int>(null, (i1, i2) => false, null));
178+
179+
TestEdgeRemoval(new[] { 4, 3, 2, 1 }, (i1, i2) => !(i1 == 3 || i2 == 3), new[] { 3, 1, 2, 4 }, new[] { (4, 2), (4, 1), (2, 1) });
180+
TestEdgeRemoval(new[] { 3, 2, 1 }, (i1, i2) => i1 >= i2, new[] { 3, 2, 1 }, null);
181+
TestEdgeRemoval(new[] { 3, 2, 1 }, (i1, i2) => true, new[] { 1, 2, 3 }, new[] { (3, 2), (2, 1), (3, 1) });
182+
TestEdgeRemoval(new[] { 3, 2, 1 }, (i1, i2) => false, new[] { 3, 2, 1 }, null);
183+
184+
TestEdgeRemovalWithNode(new[] { 4, 3, 2, 1 }, (i1, i2) => !(i1 == 3 || i2 == 3), new[] { 3, 1, 2, 4 }, new[] { (4, 2), (4, 1), (2, 1) });
185+
TestEdgeRemovalWithNode(new[] { 3, 2, 1 }, (i1, i2) => i1 >= i2, new[] { 3, 2, 1 }, null);
186+
TestEdgeRemovalWithNode(new[] { 3, 2, 1 }, (i1, i2) => true, new[] { 1, 2, 3 }, new[] { (3, 2), (2, 1), (3, 1) });
187+
TestEdgeRemovalWithNode(new[] { 3, 2, 1 }, (i1, i2) => false, new[] { 3, 2, 1 }, null);
188+
}
131189

132-
private void TestSort<T>(T[] data, Predicate<T, T> connector, T[] expected, T[] loops)
133-
{
134-
List<Node<T, object>> actualLoopNodes;
135-
List<T> actual = TopologicalSorter.Sort(data, connector, out actualLoopNodes);
136-
T[] actualLoops = null;
137-
if (actualLoopNodes != null)
138-
actualLoops = actualLoopNodes
139-
.Where(n => n.OutgoingConnectionCount != 0)
140-
.Select(n => n.Item)
141-
.ToArray();
142-
143-
AssertEx.HasSameElements(expected, actual);
144-
AssertEx.HasSameElements(loops, actualLoops);
145-
146-
List<NodeConnection<T, object>> removedEdges;
147-
List<T> sortWithRemove = TopologicalSorter.Sort(data, connector, out removedEdges);
148-
Assert.AreEqual(sortWithRemove.Count, data.Length);
149-
if (loops == null) {
150-
Assert.AreEqual(sortWithRemove.Count, actual.Count);
151-
for (int i = 0; i < actual.Count; i++) {
152-
Assert.AreEqual(sortWithRemove[i], actual[i]);
153-
}
154-
}
155-
else {
156-
TestLog.Debug("Loops detected");
157-
}
190+
private void TestSortLoopsCheck<T>(T[] data, Predicate<T, T> connector, T[] expected, T[] loops)
191+
{
192+
var actual = TopologicalSorter.Sort(data, connector, out List<Node<T, object>> actualLoopNodes);
193+
194+
if (expected == null)
195+
Assert.That(actual, Is.Null);
196+
else if (data.Length == 0)
197+
Assert.That(actual, Is.Empty);
198+
else
199+
Assert.That(expected.SequenceEqual(actual));
200+
201+
var actualLoops = actualLoopNodes != null
202+
? actualLoopNodes
203+
.Where(n => n.OutgoingConnectionCount != 0)
204+
.Select(n => n.Item)
205+
.ToArray()
206+
: null;
207+
208+
AssertEx.HasSameElements(loops, actualLoops);
209+
210+
var sortWithRemove = TopologicalSorter.Sort(data, connector, out List<NodeConnection<T, object>> removedEdges);
211+
Assert.AreEqual(sortWithRemove.Count, data.Length);
212+
213+
if (loops == null) {
214+
Assert.AreEqual(sortWithRemove.Count, actual.Count);
215+
for (var i = 0; i < actual.Count; i++) {
216+
Assert.AreEqual(sortWithRemove[i], actual[i]);
158217
}
218+
}
219+
else {
220+
TestLog.Debug("Loops detected");
221+
}
222+
}
223+
224+
private void TestSortNoLoopsCheck<T>(T[] data, Predicate<T, T> connector, T[] expected)
225+
{
226+
var actual = TopologicalSorter.Sort(data, connector);
227+
228+
if (expected == null)
229+
Assert.That(actual, Is.Null);
230+
else if (data.Length == 0)
231+
Assert.That(actual, Is.Empty);
232+
else
233+
Assert.That(expected.SequenceEqual(actual));
234+
235+
var sortWithRemove = TopologicalSorter.Sort(data, connector, out List<NodeConnection<T, object>> _);
236+
Assert.AreEqual(sortWithRemove.Count, data.Length);
237+
}
238+
239+
private void TestEdgeRemoval<T>(T[] data, Predicate<T, T> connector, T[] expected, (T source, T target)[] expectedRemovedEdges)
240+
{
241+
var sortWithRemove = TopologicalSorter.Sort(data, connector, out List<NodeConnection<T, object>> removedEdges);
242+
Assert.That(sortWithRemove, Is.Not.Null);
243+
Assert.AreEqual(sortWithRemove.Count, data.Length);
244+
Assert.That(sortWithRemove.SequenceEqual(expected), Is.True);
245+
246+
if (expectedRemovedEdges == null) {
247+
Assert.That(removedEdges, Is.Empty);
248+
}
249+
250+
foreach (var removedEdge in removedEdges) {
251+
var s = removedEdge.Source.Item;
252+
var t = removedEdge.Destination.Item;
253+
(T source, T target) expectedTuple = (s, t);
254+
Assert.That(expectedRemovedEdges.Contains(expectedTuple), Is.True, $"({s} -> {t}) is not represented in expected edges");
255+
}
256+
}
257+
258+
private void TestEdgeRemovalWithNode<T>(T[] data, Predicate<T, T> connector, T[] expected, (T source, T target)[] expectedRemovedEdges)
259+
{
260+
var sortWithRemove = TopologicalSorter.Sort(data, connector, out var removedEdges, true);
261+
Assert.That(sortWithRemove, Is.Not.Null);
262+
Assert.AreEqual(sortWithRemove.Count, data.Length);
263+
Assert.That(sortWithRemove.SequenceEqual(expected), Is.True);
264+
265+
if (expectedRemovedEdges == null) {
266+
Assert.That(removedEdges, Is.Empty);
267+
}
268+
269+
foreach (var removedEdge in removedEdges) {
270+
var s = removedEdge.Source.Item;
271+
var t = removedEdge.Destination.Item;
272+
(T source, T target) expectedTuple = (s, t);
273+
Assert.That(expectedRemovedEdges.Contains(expectedTuple), Is.True, $"({s} -> {t}) is not represented in expected edges");
274+
}
159275
}
276+
}
160277
}

0 commit comments

Comments
 (0)