public static void Select_Indexed_Unordered(int count) { // For unordered collections, which element is at which index isn't actually guaranteed, but an effect of the implementation. // If this test starts failing it should be updated, and possibly mentioned in release notes. IntegerRangeSet seen = new IntegerRangeSet(0, count); foreach (var p in UnorderedSources.Default(count).Select((x, index) => KeyValuePair.Create(x, index))) { seen.Add(p.Key); Assert.Equal(p.Key, p.Value); } seen.AssertComplete(); }
public static void SelectMany_Unordered_ResultSelector_NotPipelined(int count, Labeled <Func <int, int, IEnumerable <int> > > expander, int expansion) { Func <int, int, IEnumerable <int> > expand = expander.Item; IntegerRangeSet seen = new IntegerRangeSet(0, count * expansion); Assert.All(UnorderedSources.Default(count).SelectMany(x => expand(x, expansion), (original, expanded) => KeyValuePair.Create(original, expanded)).ToList(), p => { seen.Add(p.Value); Assert.Equal(p.Key, p.Value / expansion); }); seen.AssertComplete(); }
public static IEnumerable <object[]> SingleSpecificData(int[] counts) { Func <int, IEnumerable <int> > positions = x => new[] { 0, x / 2, Math.Max(0, x - 1) }.Distinct(); foreach (object[] results in UnorderedSources.Ranges(counts.Cast <int>(), positions)) { yield return(results); } foreach (object[] results in Sources.Ranges(counts.Cast <int>(), positions)) { yield return(results); } }
public static IEnumerable <object[]> SingleData(int[] elements, int[] counts) { foreach (int element in elements) { foreach (object[] results in UnorderedSources.Ranges(element, counts.Cast <int>())) { yield return(new object[] { results[0], results[1], element }); } foreach (object[] results in Sources.Ranges(element, counts.Cast <int>())) { yield return(new object[] { results[0], results[1], element }); } } }
public static IEnumerable<object[]> EmptyData() { foreach (object[] query in UnorderedSources.Ranges(new[] { 0 })) { yield return new object[] { query[0], 1 }; } foreach (object[] query in Sources.Ranges(new[] { 0 })) { yield return new object[] { query[0], 1 }; } yield return new object[] { Labeled.Label("Empty-Int", ParallelEnumerable.Empty<int>()), 1 }; yield return new object[] { Labeled.Label("Empty-Decimal", ParallelEnumerable.Empty<decimal>()), 1.5M }; yield return new object[] { Labeled.Label("Empty-String", ParallelEnumerable.Empty<string>()), "default" }; }
public static void GroupBy_Unordered_ElementSelector_ResultSelector(int count) { IntegerRangeSet groupsSeen = new IntegerRangeSet(0, Math.Min(count, GroupFactor)); foreach (var group in UnorderedSources.Default(count).GroupBy(x => x % GroupFactor, x => - x, (key, elements) => KeyValuePair.Create(key, elements))) { groupsSeen.Add(group.Key); int expected = 1 + (count - (group.Key + 1)) / GroupFactor; IntegerRangeSet elementsSeen = new IntegerRangeSet(1 - expected, expected); Assert.All(group.Value, x => { Assert.Equal(group.Key, -x % GroupFactor); elementsSeen.Add(x / GroupFactor); }); elementsSeen.AssertComplete(); } groupsSeen.AssertComplete(); }
/// <summary> /// Run through all sources, ensuring 64k elements for each core (to saturate buffers in producers/consumers). /// </summary> /// Data returned is in the format of the underlying sources. /// <returns>Rows of sourced data to check.</returns> public static IEnumerable <object[]> ProducerBlocked_Data() { // Provide enough elements to ensure all the cores get >64K ints. int elements = 64 * 1024 * Environment.ProcessorCount; foreach (object[] data in Sources.Ranges(new[] { elements })) { yield return(data); } foreach (object[] data in UnorderedSources.Ranges(new[] { elements })) { yield return(data); } }
public static void SelectMany_Indexed_Unordered(int count, Labeled <Func <int, int, IEnumerable <int> > > expander, int expansion) { // For unordered collections, which element is at which index isn't actually guaranteed, but an effect of the implementation. // If this test starts failing it should be updated, and possibly mentioned in release notes. Func <int, int, IEnumerable <int> > expand = expander.Item; IntegerRangeSet seen = new IntegerRangeSet(0, count * expansion); foreach (var pIndex in UnorderedSources.Default(count).SelectMany((x, index) => expand(x, expansion).Select(y => KeyValuePair.Create(index, y)))) { seen.Add(pIndex.Value); Assert.Equal(pIndex.Key, pIndex.Value / expansion); } seen.AssertComplete(); }
public static void Except_Unordered_Distinct_NotPipelined(int leftCount, int rightStart, int rightCount, int start, int count) { ParallelQuery <int> leftQuery = UnorderedSources.Default(leftCount); ParallelQuery <int> rightQuery = UnorderedSources.Default(rightStart, rightCount); leftCount = Math.Min(DuplicateFactor * 2, leftCount); rightCount = Math.Min(DuplicateFactor, (rightCount + 1) / 2); int expectedCount = Math.Max(0, leftCount - rightCount); IntegerRangeSet seen = new IntegerRangeSet(leftCount - expectedCount, expectedCount); Assert.All(leftQuery.Except(rightQuery.Select(x => Math.Abs(x) % DuplicateFactor), new ModularCongruenceComparer(DuplicateFactor * 2)).ToList(), x => seen.Add(x % (DuplicateFactor * 2))); seen.AssertComplete(); }
public static IEnumerable <object[]> ExceptData(int[] leftCounts) { foreach (int leftCount in leftCounts.DefaultIfEmpty(Sources.OuterLoopCount / 4)) { foreach (int rightCount in RightCounts(leftCount)) { int rightStart = 0 - rightCount / 2; foreach (object[] left in Sources.Ranges(new[] { leftCount })) { yield return(left.Concat(new object[] { UnorderedSources.Default(rightStart, rightCount), rightCount, rightStart + rightCount, Math.Max(0, leftCount - (rightCount + 1) / 2) }).ToArray()); } } } }
public static void GroupBy_Unordered_NotPipelined(int count) { IntegerRangeSet groupsSeen = new IntegerRangeSet(0, Math.Min(count, GroupFactor)); foreach (IGrouping <int, int> group in UnorderedSources.Default(count).GroupBy(x => x % GroupFactor).ToList()) { groupsSeen.Add(group.Key); IntegerRangeSet elementsSeen = new IntegerRangeSet(0, 1 + (count - (group.Key + 1)) / GroupFactor); Assert.All(group, x => { Assert.Equal(group.Key, x % GroupFactor); elementsSeen.Add(x / GroupFactor); }); elementsSeen.AssertComplete(); } groupsSeen.AssertComplete(); }
public static void Union_Unordered_Distinct_NotPipelined(int leftCount, int rightCount) { ParallelQuery <int> leftQuery = UnorderedSources.Default(leftCount); ParallelQuery <int> rightQuery = UnorderedSources.Default(leftCount, rightCount); leftCount = Math.Min(DuplicateFactor, leftCount); rightCount = Math.Min(DuplicateFactor, rightCount); int offset = leftCount - Math.Min(leftCount, rightCount) / 2; int expectedCount = Math.Max(leftCount, rightCount) + (Math.Min(leftCount, rightCount) + 1) / 2; IntegerRangeSet seen = new IntegerRangeSet(0, expectedCount); Assert.All(leftQuery.Select(x => x % DuplicateFactor).Union(rightQuery.Select(x => (x - leftCount) % DuplicateFactor + offset), new ModularCongruenceComparer(DuplicateFactor + DuplicateFactor / 2)).ToList(), x => seen.Add(x)); seen.AssertComplete(); }
public static void Zip_Unordered(int leftCount, int rightCount) { ParallelQuery <int> leftQuery = UnorderedSources.Default(leftCount); ParallelQuery <int> rightQuery = UnorderedSources.Default(leftCount, rightCount); IntegerRangeSet seen = new IntegerRangeSet(0, Math.Min(leftCount, rightCount)); foreach (var pair in leftQuery.Zip(rightQuery, (x, y) => KeyValuePair.Create(x, y))) { // For unordered collections the pairing isn't actually guaranteed, but an effect of the implementation. // If this test starts failing it should be updated, and possibly mentioned in release notes. Assert.Equal(pair.Key + leftCount, pair.Value); seen.Add(pair.Key); } seen.AssertComplete(); }
public static void Intersect_Unordered_Distinct(int leftCount, int rightStart, int rightCount, int count) { ParallelQuery <int> leftQuery = UnorderedSources.Default(leftCount); ParallelQuery <int> rightQuery = UnorderedSources.Default(rightStart, rightCount); leftCount = Math.Min(DuplicateFactor * 2, leftCount); rightCount = Math.Min(DuplicateFactor, (rightCount + 1) / 2); IntegerRangeSet seen = new IntegerRangeSet(0, Math.Min(leftCount, rightCount)); foreach (int i in leftQuery.Intersect(rightQuery.Select(x => Math.Abs(x) % DuplicateFactor), new ModularCongruenceComparer(DuplicateFactor * 2))) { seen.Add(i % (DuplicateFactor * 2)); } seen.AssertComplete(); }
private static IEnumerable <LabeledOperation> UnorderedRangeSources() { // The difference between this and the existing sources is more control is needed over the range creation. // Specifically, start/count won't be known until the nesting level is resolved at runtime. yield return(Label("ParallelEnumerable.Range", (start, count, ignore) => ParallelEnumerable.Range(start, count))); yield return(Label("Enumerable.Range", (start, count, ignore) => Enumerable.Range(start, count).AsParallel())); yield return(Label("Array", (start, count, ignore) => UnorderedSources.GetRangeArray(start, count).AsParallel())); yield return(Label("Partitioner", (start, count, ignore) => Partitioner.Create(UnorderedSources.GetRangeArray(start, count)).AsParallel())); yield return(Label("List", (start, count, ignore) => UnorderedSources.GetRangeArray(start, count).ToList().AsParallel())); yield return(Label("ReadOnlyCollection", (start, count, ignore) => new ReadOnlyCollection <int>(UnorderedSources.GetRangeArray(start, count).ToList()).AsParallel())); }
public static void ToLookup_DuplicateKeys_ElementSelector(int count) { IntegerRangeSet seenOuter = new IntegerRangeSet(0, Math.Min(count, 2)); ILookup <int, int> lookup = UnorderedSources.Default(count).ToLookup(x => x % 2, y => - y); Assert.All(lookup, group => { seenOuter.Add(group.Key); IntegerRangeSet seenInner = new IntegerRangeSet(0, (count + ((1 + group.Key) % 2)) / 2); Assert.All(group, y => { Assert.Equal(group.Key, -y % 2); seenInner.Add(-y / 2); }); seenInner.AssertComplete(); }); seenOuter.AssertComplete(); Assert.Empty(lookup[-1]); }
/// <summary> /// Get a a combination of partitioned data sources, degree of parallelism, expected resulting dop, /// query to execute on the data source, and mode of execution. /// </summary> /// <param name="dop">A set of the desired degrees of parallelism to be employed.</param> /// <returns>Entries for test data. /// The first element is the Labeled{ParallelQuery{int}} data source, /// the second is the desired dop, /// the third is the expected resulting dop, /// the fourth is the query to execute on the data source, /// and the fifth is the execution mode.</returns> public static IEnumerable <object[]> WithExecutionModeQueryData(int[] dops) { foreach (int dop in dops) { // Use data sources that have a fixed set of elements in each partition (no load balancing between the partitions). // PLINQ will assign a Task to each partition, and no other task will process that partition. As a result, we can // verify that we get a known number of tasks doing the processing. (This doesn't guarantee that such tasks are // running in parallel, but it's "good enough". If PLINQ's implementation is ever changed to proactively exit // tasks and spawn replicas to continue the processing, ala Parallel.For*, this test will need to be updated.) int count = 3 * dop; // 3 chosen arbitrarily as a small value; any positive value will do var partitionedRanges = new Labeled <ParallelQuery <int> >[] { Labeled.Label("ParallelEnumerable.Range", ParallelEnumerable.Range(0, count)), Labeled.Label("Partitioner.Create", Partitioner.Create(UnorderedSources.GetRangeArray(0, count), loadBalance: false).AsParallel()) }; // For each source and mode, get both unordered and ordered queries that should easily parallelize for all execution modes foreach (ParallelExecutionMode mode in new[] { ParallelExecutionMode.Default, ParallelExecutionMode.ForceParallelism }) { foreach (Labeled <ParallelQuery <int> > source in partitionedRanges) { foreach (var query in EasyUnorderedQueries(count)) { yield return new object[] { source, dop, dop, query, mode } } ; foreach (var query in EasyOrderedQueries(count)) { yield return new object[] { source.Order(), dop, dop, query, mode } } ; } } // For each source, get queries that are difficult to parallelize and thus only do so with ForceParallelism. foreach (Labeled <ParallelQuery <int> > source in partitionedRanges) { foreach (var query in HardQueries(count)) { yield return(new object[] { source, dop, dop, query, ParallelExecutionMode.ForceParallelism }); // should parallelize, thus expected DOP of > 1 yield return(new object[] { source, dop, 1, query, ParallelExecutionMode.Default }); // won't parallelize, thus expected DOP of 1 } } } }
public static void Aggregate_AggregateException() { AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate((i, j) => { throw new DeliberateTestException(); })); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate(0, (i, j) => { throw new DeliberateTestException(); })); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate(0, (i, j) => { throw new DeliberateTestException(); }, i => i)); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate <int, int, int>(0, (i, j) => i, i => { throw new DeliberateTestException(); })); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate(0, (i, j) => { throw new DeliberateTestException(); }, (i, j) => i, i => i)); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate <int, int, int>(0, (i, j) => i, (i, j) => i, i => { throw new DeliberateTestException(); })); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate <int, int, int>(() => { throw new DeliberateTestException(); }, (i, j) => i, (i, j) => i, i => i)); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate(() => 0, (i, j) => { throw new DeliberateTestException(); }, (i, j) => i, i => i)); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate <int, int, int>(() => 0, (i, j) => i, (i, j) => i, i => { throw new DeliberateTestException(); })); if (Environment.ProcessorCount >= 2) { AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate(0, (i, j) => i, (i, j) => { throw new DeliberateTestException(); }, i => i)); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(2).Aggregate(() => 0, (i, j) => i, (i, j) => { throw new DeliberateTestException(); }, i => i)); } }
public static void Average_AggregateException() { AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, int>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, int?>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, long>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, long?>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, float>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, float?>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, double>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, double?>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, decimal>)(x => { throw new DeliberateTestException(); }))); AssertThrows.Wrapped <DeliberateTestException>(() => UnorderedSources.Default(1).Average((Func <int, decimal?>)(x => { throw new DeliberateTestException(); }))); }
public static void GroupBy_Unordered_CustomComparator(int count) { IntegerRangeSet groupsSeen = new IntegerRangeSet(0, Math.Min(count, GroupFactor)); foreach (IGrouping <int, int> group in UnorderedSources.Default(count).GroupBy(x => x, new ModularCongruenceComparer(GroupFactor))) { groupsSeen.Add(group.Key % GroupFactor); IntegerRangeSet elementsSeen = new IntegerRangeSet(0, 1 + (count - (group.Key % GroupFactor + 1)) / GroupFactor); foreach (int i in group) { Assert.Equal(group.Key % GroupFactor, i % GroupFactor); elementsSeen.Add(i / GroupFactor); } elementsSeen.AssertComplete(); } groupsSeen.AssertComplete(); }
public static void GroupBy_Unordered(int count) { IntegerRangeSet groupsSeen = new IntegerRangeSet(0, Math.Min(count, GroupFactor)); foreach (IGrouping <int, int> group in UnorderedSources.Default(count).GroupBy(x => x % GroupFactor)) { groupsSeen.Add(group.Key); IntegerRangeSet elementsSeen = new IntegerRangeSet(0, 1 + (count - (group.Key + 1)) / GroupFactor); foreach (int i in group) { Assert.Equal(group.Key, i % GroupFactor); elementsSeen.Add(i / GroupFactor); } elementsSeen.AssertComplete(); } groupsSeen.AssertComplete(); }
public static void ToLookup_DuplicateKeys_CustomComparator(int count) { IntegerRangeSet seenOuter = new IntegerRangeSet(0, Math.Min(count, 2)); ILookup <int, int> lookup = UnorderedSources.Default(count).ToLookup(x => x, new ModularCongruenceComparer(2)); Assert.All(lookup, group => { seenOuter.Add(group.Key % 2); IntegerRangeSet seenInner = new IntegerRangeSet(0, (count + ((1 + group.Key) % 2)) / 2); Assert.All(group, y => { Assert.Equal(group.Key % 2, y % 2); seenInner.Add(y / 2); }); seenInner.AssertComplete(); }); seenOuter.AssertComplete(); if (count < 2) { Assert.Empty(lookup[-1]); } }
public static void GroupJoin(Labeled <ParallelQuery <int> > left, int leftCount, int rightCount) { ParallelQuery <int> leftQuery = left.Item; int seen = 0; foreach (var p in leftQuery.GroupJoin(UnorderedSources.Default(rightCount), x => x * KeyFactor, y => y, (x, y) => KeyValuePair.Create(x, y))) { Assert.Equal(seen++, p.Key); if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor) { Assert.Equal(p.Key * KeyFactor, Assert.Single(p.Value)); } else { Assert.Empty(p.Value); } } Assert.Equal(leftCount, seen); }
public static void Join_InnerJoin_Ordered(Labeled <ParallelQuery <int> > left, int leftCount, int rightCount) { ParallelQuery <int> leftQuery = left.Item; ParallelQuery <int> rightQuery = UnorderedSources.Default(rightCount); ParallelQuery <int> middleQuery = ParallelEnumerable.Range(0, leftCount).AsOrdered(); int seen = 0; Assert.All(leftQuery.Join(middleQuery.Join(rightQuery, x => x * KeyFactor / 2, y => y, (x, y) => KeyValuePair.Create(x, y)), z => z * 2, p => p.Key, (x, p) => KeyValuePair.Create(x, p)), pOuter => { KeyValuePair <int, int> pInner = pOuter.Value; Assert.Equal(seen++, pOuter.Key); Assert.Equal(pOuter.Key * 2, pInner.Key); Assert.Equal(pOuter.Key * KeyFactor, pInner.Value); }); Assert.Equal(Math.Min((leftCount + 1) / 2, (rightCount + (KeyFactor - 1)) / KeyFactor), seen); }
public static IEnumerable <object[]> AggregateExceptionData(int[] counts) { foreach (object[] results in UnorderedSources.Ranges(counts.Cast <int>())) { Labeled <ParallelQuery <int> > query = (Labeled <ParallelQuery <int> >)results[0]; if (query.ToString().StartsWith("Partitioner")) { yield return(new object[] { Labeled.Label(query.ToString(), Partitioner.Create(UnorderedSources.GetRangeArray(0, (int)results[1]), false).AsParallel()), results[1] }); } else if (query.ToString().StartsWith("Enumerable.Range")) { yield return(new object[] { Labeled.Label(query.ToString(), new StrictPartitioner <int>(Partitioner.Create(Enumerable.Range(0, (int)results[1]), EnumerablePartitionerOptions.None), (int)results[1]).AsParallel()), results[1] }); } else { yield return(results); } } }
public static void GroupJoin_Unordered(int leftCount, int rightCount) { ParallelQuery <int> leftQuery = UnorderedSources.Default(leftCount); ParallelQuery <int> rightQuery = UnorderedSources.Default(rightCount); IntegerRangeSet seen = new IntegerRangeSet(0, leftCount); foreach (var p in leftQuery.GroupJoin(rightQuery, x => x * KeyFactor, y => y, (x, y) => KeyValuePair.Create(x, y))) { seen.Add(p.Key); if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor) { Assert.Equal(p.Key * KeyFactor, Assert.Single(p.Value)); } else { Assert.Empty(p.Value); } } seen.AssertComplete(); }
public static void Join_Unordered_Multiple(int leftCount, int rightCount) { ParallelQuery <int> leftQuery = UnorderedSources.Default(leftCount); ParallelQuery <int> rightQuery = UnorderedSources.Default(rightCount); IntegerRangeSet seenOuter = new IntegerRangeSet(0, Math.Min(leftCount, (rightCount + (KeyFactor - 1)) / KeyFactor)); IntegerRangeSet seenInner = new IntegerRangeSet(0, Math.Min(leftCount * KeyFactor, rightCount)); Assert.All(leftQuery.Join(rightQuery, x => x, y => y / KeyFactor, (x, y) => KeyValuePair.Create(x, y)), p => { Assert.Equal(p.Key, p.Value / KeyFactor); seenInner.Add(p.Value); if (p.Value % KeyFactor == 0) { seenOuter.Add(p.Key); } }); seenOuter.AssertComplete(); seenInner.AssertComplete(); }
public static void ToDictionary_ElementSelector_UniqueKeys_CustomComparator(int count) { if (count > 2) { AssertThrows.Wrapped <ArgumentException>(() => UnorderedSources.Default(count).ToDictionary(x => x, y => y, new ModularCongruenceComparer(2))); } else if (count == 1 || count == 2) { IntegerRangeSet seen = new IntegerRangeSet(0, count); foreach (KeyValuePair <int, int> entry in UnorderedSources.Default(count).ToDictionary(x => x, y => y, new ModularCongruenceComparer(2))) { seen.Add(entry.Key); Assert.Equal(entry.Key, entry.Value); } seen.AssertComplete(); } else { Assert.Empty(UnorderedSources.Default(count).ToDictionary(x => x, y => y, new ModularCongruenceComparer(2))); } }
// Get a set of ranges from 0 to each count, having an extra parameter describing the maximum (count - 1) public static IEnumerable <object[]> MaxData(int[] counts) { Func <int, int> max = x => x - 1; foreach (object[] results in UnorderedSources.Ranges(counts.Cast <int>(), max)) { yield return(results); } // A source with data explicitly created out of order foreach (int count in counts) { int[] data = Enumerable.Range(0, count).ToArray(); for (int i = 0; i < count / 2; i += 2) { int tmp = data[i]; data[i] = data[count - i - 1]; data[count - i - 1] = tmp; } yield return(new object[] { new Labeled <ParallelQuery <int> >("Out-of-order input", data.AsParallel()), count, max(count) }); } }
public static void GroupJoin_Unordered_CustomComparator(int leftCount, int rightCount) { ParallelQuery <int> leftQuery = UnorderedSources.Default(leftCount); ParallelQuery <int> rightQuery = UnorderedSources.Default(rightCount); IntegerRangeSet seenOuter = new IntegerRangeSet(0, leftCount); Assert.All(leftQuery.GroupJoin(rightQuery, x => x, y => y % ElementFactor, (x, y) => KeyValuePair.Create(x, y), new ModularCongruenceComparer(KeyFactor)), p => { seenOuter.Add(p.Key); if (p.Key % KeyFactor < Math.Min(ElementFactor, rightCount)) { IntegerRangeSet seenInner = new IntegerRangeSet(0, (rightCount + (ElementFactor - 1) - p.Key % ElementFactor) / ElementFactor); Assert.All(p.Value, y => { Assert.Equal(p.Key % KeyFactor, y % ElementFactor); seenInner.Add(y / ElementFactor); }); seenInner.AssertComplete(); } else { Assert.Empty(p.Value); } }); seenOuter.AssertComplete(); }