private static void Test() { // Make a Random instance with a fixed seed so that tests are repeatable. Random random = new Random(0); DisjointSet test = null; // Add a single range. DisjointSet.Add(ref test, new Range(5, 10)); // Verify that DisjointSet.Contains works for each value in the range. for (ulong i = 0; i <= 15; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (5 <= i && i <= 10)), "DisjointSet.Contains does not work (simple case)."); } // Verify that it works even when the set is splayed randomly. for (ulong j = 0; j <= 100; j++) { ulong i = (ulong)random.Next(0, 15); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (5 <= i && i <= 10)), "DisjointSet.Contains does not work (simple case, random splaying)."); } // Add new ranges that overlap the existing range(s). DisjointSet.Add(ref test, new Range(4, 9)); DisjointSet.Add(ref test, new Range(15, 20)); DisjointSet.Add(ref test, new Range(10, 15)); // Verify that the ranges were merged. Debug.Assert(test.Left == null && test.Right == null, "DisjointSet.Add did not merge overlapping ranges."); Debug.Assert(test.Value == new Range(4, 20), "DisjointSet.Add did not merge overlapping ranges."); // Add ranges adjacent to the ends of the existing range. DisjointSet.Add(ref test, new Range(21, 25)); DisjointSet.Add(ref test, 3ul); // Verify that they were also merged. Debug.Assert(test.Left == null && test.Right == null, "DisjointSet.Add did not merge adjacent ranges."); Debug.Assert(test.Value == new Range(3, 25), "DisjointSet.Add did not merge adjacent ranges."); // Remove a range. DisjointSet.Remove(ref test, new Range(10, 15)); // Verify that the ranges still work. for (ulong i = 0; i <= 30; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((3 <= i && i <= 9) || (16 <= i && i <= 25))), "DisjointSet.Contains does not work after removal."); } // Verify that it works even when the set is splayed randomly. for (ulong j = 0; j <= 300; j++) { ulong i = (ulong)random.Next(0, 30); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((3 <= i && i <= 9) || (16 <= i && i <= 25))), "DisjointSet.Contains does not work after removal (random splaying)."); } // Remove the entire split ranges (on a cloned copy so we can do it several times). DisjointSet empty; empty = test.Clone(); DisjointSet.Remove(ref empty, Range.UNIVERSE); Debug.Assert(empty == null, "DisjointSet.Remove(UNIVERSE) didn't result in an empty set."); empty = test.Clone(); DisjointSet.Remove(ref empty, new Range(3, 25)); Debug.Assert(empty == null, "DisjointSet.Remove of entire range didn't result in an empty set."); empty = test.Clone(); DisjointSet.Remove(ref empty, new Range(3, 26)); Debug.Assert(empty == null, "DisjointSet.Remove of entire range didn't result in an empty set."); empty = test.Clone(); DisjointSet.Remove(ref empty, new Range(2, 25)); Debug.Assert(empty == null, "DisjointSet.Remove of entire range didn't result in an empty set."); ///////////////////////////////////////////////////////////////////////// // Now reset the set so we can do some stress tests (mostly to make // sure the splaying code works correctly). test = null; // Insert all integers divisible by 5, 7, or 11. This makes the ranges // easily testable, and results in a set with some singletons as well // as some contiguous ranges. for (ulong i = 0; i < 1000; i++) { if (i % 5 == 0 || i % 7 == 0 || i % 11 == 0) { DisjointSet.Add(ref test, i); } } // Verify the contents of the set. for (ulong i = 0; i < 1000; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (i % 5 == 0 || i % 7 == 0 || i % 11 == 0)), "DisjointSet.Contains failed the stress test."); } // Verify the contents of the set, even with random splaying. for (ulong j = 0; j < 10000; j++) { ulong i = (ulong)random.Next(0, 1000); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (i % 5 == 0 || i % 7 == 0 || i % 11 == 0)), "DisjointSet.Contains failed the stress test (random splaying)."); } // Now remove all singletons divisible by 7. for (ulong i = 0; i < 1000; i++) { if (i % 7 == 0) { DisjointSet.Remove(ref test, i); } } // Verify the contents of the set. for (ulong i = 0; i < 1000; i++) { Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))), "DisjointSet.Contains failed the stress test."); } // Verify the contents of the set, even with random splaying. for (ulong j = 0, i; j < 10000; j++) { i = (ulong)random.Next(0, 1000); Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))), "DisjointSet.Contains failed the stress test (random splaying)."); } // Test the IEnumerator implementation. ArrayList enums = new ArrayList(); ulong prev = 0; foreach (ulong i in test) { Debug.Assert(i >= prev, "The enumeration is not well ordered."); enums.Add(prev = i); } // Verify that the output of the enumerator is exactly what should be in the set. for (ulong i = 0; i < 1000; i++) { Debug.Assert(!(enums.Contains(i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))), "The enumeration did not properly enumerate the set."); } // Test removing from a big tree. DisjointSet.Remove(ref test, Range.UNIVERSE); Debug.Assert(test == null, "DisjointSet.Remove(UNIVERSE) didn't yield an empty set."); ///////////////////////////////////////////////////////////////////////// // Reset the test and add a lot of small random ranges. This simulates // the kind of behavior experienced by RTPNetworkReceiver. test = null; ArrayList values = new ArrayList(10000); for (ulong i = 0; i < 10000; i++) { Range r = i; // Expand the range by some random number of indices. while (random.Next(2) == 1) { r = new Range(r.Min, ++i); } // Add all the indices in the range to both sets. DisjointSet.Add(ref test, r); for (ulong v = r.Min; v <= r.Max; v++) { values.Add(v); } // Skip some random number of indices. while (random.Next(2) == 1) { i++; } } // Test removing all of the values from the set in randomized ascending order, // just like what happens when receiving responses to a NACK (ie., some frames // might be dropped twice (or more times), and then NACKed again). DisjointSet copy = test.Clone(); for (ArrayList compare = (ArrayList)values.Clone(); compare.Count > 0;) { for (int i = 0; i < compare.Count;) { Debug.Assert(copy != null, "DisjointSet is empty but not all entries have been removed."); // Skip some random number of entries. while (random.Next(2) == 1) { i++; } if (i >= compare.Count) { break; } // Remove the entries one at a time. ulong v = (ulong)compare[i]; DisjointSet.Remove(ref copy, v); compare.RemoveAt(i); } } Debug.Assert(copy == null, "All entries were removed but DisjointSet is not empty."); // Test that all of the entries were actually added to the set. foreach (ulong v in test) { Debug.Assert(values.Contains(v), "DisjointSet contains a value that it shouldn't."); } foreach (ulong v in values) { Debug.Assert(DisjointSet.Contains(ref test, v), "DisjointSet doesn't contain a value that it should."); } ///////////////////////////////////////////////////////////////////////// // Simulate an error-case discovered during actual testing. // See Bug 821, attachment #45 (comment #3). DisjointSet current = null; DisjointSet.Add(ref current, new Range(1, 4)); DisjointSet.Add(ref current, new Range(6, 16)); DisjointSet.Add(ref current, new Range(18, 20)); DisjointSet.Add(ref current, new Range(22, 33)); DisjointSet.Add(ref current, new Range(35, 55)); DisjointSet.Add(ref current, new Range(57, 61)); DisjointSet.Add(ref current, 64); DisjointSet.Add(ref current, 67); DisjointSet stable = null; DisjointSet.Add(ref stable, new Range(1, 16)); DisjointSet.Add(ref stable, new Range(18, 61)); DisjointSet.Add(ref stable, 64); DisjointSet.Add(ref stable, 67); copy = stable.Clone(); test = new Range(1, 100); DisjointSet.Remove(ref test, current); foreach (ulong i in test) { DisjointSet.Remove(ref copy, i); } DisjointSet.Remove(ref copy, 0); DisjointSet.Remove(ref copy, stable); Debug.Assert(copy == null, "Stable set is not a superset of the Current set."); }