1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Threading;
7 using Xunit;
8 
9 namespace System.Linq.Parallel.Tests
10 {
11     public static class GroupJoinTests
12     {
13         private const int KeyFactor = 8;
14         private const int ElementFactor = 4;
15 
GroupJoinUnorderedData(int[] counts)16         public static IEnumerable<object[]> GroupJoinUnorderedData(int[] counts)
17         {
18             foreach (int leftCount in counts)
19             {
20                 foreach (int rightCount in counts)
21                 {
22                     yield return new object[] { leftCount, rightCount };
23                 }
24             }
25         }
26 
GroupJoinData(int[] counts)27         public static IEnumerable<object[]> GroupJoinData(int[] counts)
28         {
29             counts = counts.DefaultIfEmpty(Sources.OuterLoopCount / 64).ToArray();
30             // When dealing with joins, if there aren't multiple matches the ordering of the second operand is immaterial.
31             foreach (object[] parms in Sources.Ranges(counts, i => counts))
32             {
33                 yield return parms;
34             }
35         }
36 
GroupJoinMultipleData(int[] counts)37         public static IEnumerable<object[]> GroupJoinMultipleData(int[] counts)
38         {
39             counts = counts.DefaultIfEmpty(Sources.OuterLoopCount / 64).ToArray();
40             foreach (object[] parms in UnorderedSources.BinaryRanges(counts, counts))
41             {
42                 yield return new object[] { ((Labeled<ParallelQuery<int>>)parms[0]).Order(), parms[1], ((Labeled<ParallelQuery<int>>)parms[2]).Order(), parms[3] };
43             }
44         }
45 
46         //
47         // GroupJoin
48         //
49         [Theory]
50         [MemberData(nameof(GroupJoinUnorderedData), new[] { 0, 1, 2, KeyFactor * 2 })]
GroupJoin_Unordered(int leftCount, int rightCount)51         public static void GroupJoin_Unordered(int leftCount, int rightCount)
52         {
53             ParallelQuery<int> leftQuery = UnorderedSources.Default(leftCount);
54             ParallelQuery<int> rightQuery = UnorderedSources.Default(rightCount);
55             IntegerRangeSet seen = new IntegerRangeSet(0, leftCount);
56             foreach (var p in leftQuery.GroupJoin(rightQuery, x => x * KeyFactor, y => y, (x, y) => KeyValuePair.Create(x, y)))
57             {
58                 seen.Add(p.Key);
59                 if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor)
60                 {
61                     Assert.Equal(p.Key * KeyFactor, Assert.Single(p.Value));
62                 }
63                 else
64                 {
65                     Assert.Empty(p.Value);
66                 }
67             }
68             seen.AssertComplete();
69         }
70 
71         [Fact]
72         [OuterLoop]
GroupJoin_Unordered_Longrunning()73         public static void GroupJoin_Unordered_Longrunning()
74         {
75             GroupJoin_Unordered(Sources.OuterLoopCount / 64, Sources.OuterLoopCount / 64);
76         }
77 
78         [Theory]
79         [MemberData(nameof(GroupJoinData), new[] { 0, 1, 2, KeyFactor * 2 })]
GroupJoin(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)80         public static void GroupJoin(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)
81         {
82             ParallelQuery<int> leftQuery = left.Item;
83             int seen = 0;
84             foreach (var p in leftQuery.GroupJoin(UnorderedSources.Default(rightCount), x => x * KeyFactor, y => y, (x, y) => KeyValuePair.Create(x, y)))
85             {
86                 Assert.Equal(seen++, p.Key);
87                 if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor)
88                 {
89                     Assert.Equal(p.Key * KeyFactor, Assert.Single(p.Value));
90                 }
91                 else
92                 {
93                     Assert.Empty(p.Value);
94                 }
95             }
96             Assert.Equal(leftCount, seen);
97         }
98 
99         [Theory]
100         [OuterLoop]
101         [MemberData(nameof(GroupJoinData), new int[] { /* Sources.OuterLoopCount */ })]
GroupJoin_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)102         public static void GroupJoin_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)
103         {
104             GroupJoin(left, leftCount, rightCount);
105         }
106 
107         [Theory]
108         [MemberData(nameof(GroupJoinUnorderedData), new[] { 0, 1, 2, KeyFactor * 2 })]
GroupJoin_Unordered_NotPipelined(int leftCount, int rightCount)109         public static void GroupJoin_Unordered_NotPipelined(int leftCount, int rightCount)
110         {
111             ParallelQuery<int> leftQuery = UnorderedSources.Default(leftCount);
112             ParallelQuery<int> rightQuery = UnorderedSources.Default(rightCount);
113             IntegerRangeSet seen = new IntegerRangeSet(0, leftCount);
114             Assert.All(leftQuery.GroupJoin(rightQuery, x => x * KeyFactor, y => y, (x, y) => KeyValuePair.Create(x, y)).ToList(),
115                 p =>
116                 {
117                     seen.Add(p.Key);
118                     if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor)
119                     {
120                         Assert.Equal(p.Key * KeyFactor, Assert.Single(p.Value));
121                     }
122                     else
123                     {
124                         Assert.Empty(p.Value);
125                     }
126                 });
127             seen.AssertComplete();
128         }
129 
130         [Fact]
131         [OuterLoop]
GroupJoin_Unordered_NotPipelined_Longrunning()132         public static void GroupJoin_Unordered_NotPipelined_Longrunning()
133         {
134             GroupJoin_Unordered_NotPipelined(Sources.OuterLoopCount / 64, Sources.OuterLoopCount / 64);
135         }
136 
137         [Theory]
138         [MemberData(nameof(GroupJoinData), new[] { 0, 1, 2, KeyFactor * 2 })]
GroupJoin_NotPipelined(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)139         public static void GroupJoin_NotPipelined(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)
140         {
141             ParallelQuery<int> leftQuery = left.Item;
142             int seen = 0;
143             Assert.All(leftQuery.GroupJoin(UnorderedSources.Default(rightCount), x => x * KeyFactor, y => y, (x, y) => KeyValuePair.Create(x, y)).ToList(),
144                 p =>
145                 {
146                     Assert.Equal(seen++, p.Key);
147                     if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor)
148                     {
149                         Assert.Equal(p.Key * KeyFactor, Assert.Single(p.Value));
150                     }
151                     else
152                     {
153                         Assert.Empty(p.Value);
154                     }
155                 });
156             Assert.Equal(leftCount, seen);
157         }
158 
159         [Theory]
160         [OuterLoop]
161         [MemberData(nameof(GroupJoinData), new int[] { /* Sources.OuterLoopCount */ })]
GroupJoin_NotPipelined_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)162         public static void GroupJoin_NotPipelined_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, int rightCount)
163         {
164             GroupJoin_NotPipelined(left, leftCount, rightCount);
165         }
166 
167         [Theory]
168         [MemberData(nameof(GroupJoinUnorderedData), new[] { 0, 1, 2, KeyFactor * 2 })]
GroupJoin_Unordered_Multiple(int leftCount, int rightCount)169         public static void GroupJoin_Unordered_Multiple(int leftCount, int rightCount)
170         {
171             ParallelQuery<int> leftQuery = UnorderedSources.Default(leftCount);
172             ParallelQuery<int> rightQuery = UnorderedSources.Default(rightCount);
173             IntegerRangeSet seenOuter = new IntegerRangeSet(0, leftCount);
174             Assert.All(leftQuery.GroupJoin(rightQuery, x => x, y => y / KeyFactor, (x, y) => KeyValuePair.Create(x, y)),
175                 p =>
176                 {
177                     seenOuter.Add(p.Key);
178                     if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor)
179                     {
180                         IntegerRangeSet seenInner = new IntegerRangeSet(p.Key * KeyFactor, Math.Min(rightCount - p.Key * KeyFactor, KeyFactor));
181                         Assert.All(p.Value, y => { Assert.Equal(p.Key, y / KeyFactor); seenInner.Add(y); });
182                         seenInner.AssertComplete();
183                     }
184                     else
185                     {
186                         Assert.Empty(p.Value);
187                     }
188                 });
189             seenOuter.AssertComplete();
190         }
191 
192         [Fact]
193         [OuterLoop]
GroupJoin_Unordered_Multiple_Longrunning()194         public static void GroupJoin_Unordered_Multiple_Longrunning()
195         {
196             GroupJoin_Unordered_Multiple(Sources.OuterLoopCount / 64, Sources.OuterLoopCount / 64);
197         }
198 
199         [Theory]
200         [ActiveIssue(1155)]
201         [MemberData(nameof(GroupJoinMultipleData), new[] { 0, 1, 2, KeyFactor * 2 - 1, KeyFactor * 2 })]
GroupJoin_Multiple(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)202         public static void GroupJoin_Multiple(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)
203         {
204             ParallelQuery<int> leftQuery = left.Item;
205             ParallelQuery<int> rightQuery = right.Item;
206             int seenOuter = 0;
207             Assert.All(leftQuery.GroupJoin(rightQuery, x => x, y => y / KeyFactor, (x, y) => KeyValuePair.Create(x, y)),
208                 p =>
209                 {
210                     Assert.Equal(seenOuter++, p.Key);
211                     if (p.Key < (rightCount + (KeyFactor - 1)) / KeyFactor)
212                     {
213                         int seenInner = p.Key * KeyFactor;
214                         Assert.All(p.Value, y =>
215                            {
216                                Assert.Equal(p.Key, y / KeyFactor);
217                                Assert.Equal(seenInner++, y);
218                            });
219                         Assert.Equal(Math.Min((p.Key + 1) * KeyFactor, rightCount), seenInner);
220                     }
221                     else
222                     {
223                         Assert.Empty(p.Value);
224                     }
225                 });
226             Assert.Equal(leftCount, seenOuter);
227         }
228 
229         [Theory]
230         [ActiveIssue(1155)]
231         [OuterLoop]
232         [MemberData(nameof(GroupJoinMultipleData), new int[] { /* Sources.OuterLoopCount */ })]
GroupJoin_Multiple_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)233         public static void GroupJoin_Multiple_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)
234         {
235             GroupJoin_Multiple(left, leftCount, right, rightCount);
236         }
237 
238         [Theory]
239         [MemberData(nameof(GroupJoinUnorderedData), new[] { 0, 1, 2, KeyFactor * 2 })]
GroupJoin_Unordered_CustomComparator(int leftCount, int rightCount)240         public static void GroupJoin_Unordered_CustomComparator(int leftCount, int rightCount)
241         {
242             ParallelQuery<int> leftQuery = UnorderedSources.Default(leftCount);
243             ParallelQuery<int> rightQuery = UnorderedSources.Default(rightCount);
244             IntegerRangeSet seenOuter = new IntegerRangeSet(0, leftCount);
245             Assert.All(leftQuery.GroupJoin(rightQuery, x => x, y => y % ElementFactor, (x, y) => KeyValuePair.Create(x, y), new ModularCongruenceComparer(KeyFactor)),
246                 p =>
247                 {
248                     seenOuter.Add(p.Key);
249                     if (p.Key % KeyFactor < Math.Min(ElementFactor, rightCount))
250                     {
251                         IntegerRangeSet seenInner = new IntegerRangeSet(0, (rightCount + (ElementFactor - 1) - p.Key % ElementFactor) / ElementFactor);
252                         Assert.All(p.Value, y => { Assert.Equal(p.Key % KeyFactor, y % ElementFactor); seenInner.Add(y / ElementFactor); });
253                         seenInner.AssertComplete();
254                     }
255                     else
256                     {
257                         Assert.Empty(p.Value);
258                     }
259                 });
260             seenOuter.AssertComplete();
261         }
262 
263         [Fact]
264         [OuterLoop]
GroupJoin_Unordered_CustomComparator_Longrunning()265         public static void GroupJoin_Unordered_CustomComparator_Longrunning()
266         {
267             GroupJoin_Unordered_CustomComparator(Sources.OuterLoopCount / 64, Sources.OuterLoopCount / 64);
268         }
269 
270         [Theory]
271         [ActiveIssue(1155)]
272         [MemberData(nameof(GroupJoinMultipleData), new[] { 0, 1, 2, KeyFactor * 2 })]
GroupJoin_CustomComparator(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)273         public static void GroupJoin_CustomComparator(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)
274         {
275             ParallelQuery<int> leftQuery = left.Item;
276             ParallelQuery<int> rightQuery = right.Item;
277             int seenOuter = 0;
278             Assert.All(leftQuery.GroupJoin(rightQuery, x => x, y => y % ElementFactor, (x, y) => KeyValuePair.Create(x, y), new ModularCongruenceComparer(KeyFactor)),
279                 p =>
280                 {
281                     Assert.Equal(seenOuter++, p.Key);
282                     if (p.Key % KeyFactor < Math.Min(ElementFactor, rightCount))
283                     {
284                         int seenInner = p.Key % (KeyFactor / 2) - (KeyFactor / 2);
285                         Assert.All(p.Value, y =>
286                             {
287                                 Assert.Equal(p.Key % KeyFactor, y % (KeyFactor / 2));
288                                 Assert.Equal(seenInner += (KeyFactor / 2), y);
289                             });
290                         Assert.Equal(Math.Max(p.Key % (KeyFactor / 2), rightCount + (p.Key % (KeyFactor / 2) - (KeyFactor / 2))), seenInner);
291                     }
292                     else
293                     {
294                         Assert.Empty(p.Value);
295                     }
296                 });
297             Assert.Equal(leftCount, seenOuter);
298         }
299 
300         [Theory]
301         [ActiveIssue(1155)]
302         [OuterLoop]
303         [MemberData(nameof(GroupJoinMultipleData), new int[] { /* Sources.OuterLoopCount */ })]
GroupJoin_CustomComparator_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)304         public static void GroupJoin_CustomComparator_Longrunning(Labeled<ParallelQuery<int>> left, int leftCount, Labeled<ParallelQuery<int>> right, int rightCount)
305         {
306             GroupJoin_CustomComparator(left, leftCount, right, rightCount);
307         }
308 
309         [Fact]
GroupJoin_NotSupportedException()310         public static void GroupJoin_NotSupportedException()
311         {
312 #pragma warning disable 618
313             Assert.Throws<NotSupportedException>(() => ParallelEnumerable.Range(0, 1).GroupJoin(Enumerable.Range(0, 1), i => i, i => i, (i, j) => i));
314             Assert.Throws<NotSupportedException>(() => ParallelEnumerable.Range(0, 1).GroupJoin(Enumerable.Range(0, 1), i => i, i => i, (i, j) => i, null));
315 #pragma warning restore 618
316         }
317 
318         [Fact]
319         // Should not get the same setting from both operands.
GroupJoin_NoDuplicateSettings()320         public static void GroupJoin_NoDuplicateSettings()
321         {
322             CancellationToken t = new CancellationTokenSource().Token;
323             Assert.Throws<InvalidOperationException>(() => ParallelEnumerable.Range(0, 1).WithCancellation(t).GroupJoin(ParallelEnumerable.Range(0, 1).WithCancellation(t), x => x, y => y, (x, e) => e));
324             Assert.Throws<InvalidOperationException>(() => ParallelEnumerable.Range(0, 1).WithDegreeOfParallelism(1).GroupJoin(ParallelEnumerable.Range(0, 1).WithDegreeOfParallelism(1), x => x, y => y, (x, e) => e));
325             Assert.Throws<InvalidOperationException>(() => ParallelEnumerable.Range(0, 1).WithExecutionMode(ParallelExecutionMode.Default).GroupJoin(ParallelEnumerable.Range(0, 1).WithExecutionMode(ParallelExecutionMode.Default), x => x, y => y, (x, e) => e));
326             Assert.Throws<InvalidOperationException>(() => ParallelEnumerable.Range(0, 1).WithMergeOptions(ParallelMergeOptions.Default).GroupJoin(ParallelEnumerable.Range(0, 1).WithMergeOptions(ParallelMergeOptions.Default), x => x, y => y, (x, e) => e));
327         }
328 
329         [Fact]
GroupJoin_ArgumentNullException()330         public static void GroupJoin_ArgumentNullException()
331         {
332             AssertExtensions.Throws<ArgumentNullException>("outer", () => ((ParallelQuery<int>)null).GroupJoin(ParallelEnumerable.Range(0, 1), i => i, i => i, (i, j) => i));
333             AssertExtensions.Throws<ArgumentNullException>("inner", () => ParallelEnumerable.Range(0, 1).GroupJoin((ParallelQuery<int>)null, i => i, i => i, (i, j) => i));
334             AssertExtensions.Throws<ArgumentNullException>("outerKeySelector", () => ParallelEnumerable.Range(0, 1).GroupJoin(ParallelEnumerable.Range(0, 1), (Func<int, int>)null, i => i, (i, j) => i));
335             AssertExtensions.Throws<ArgumentNullException>("innerKeySelector", () => ParallelEnumerable.Range(0, 1).GroupJoin(ParallelEnumerable.Range(0, 1), i => i, (Func<int, int>)null, (i, j) => i));
336             AssertExtensions.Throws<ArgumentNullException>("resultSelector", () => ParallelEnumerable.Range(0, 1).GroupJoin(ParallelEnumerable.Range(0, 1), i => i, i => i, (Func<int, IEnumerable<int>, int>)null));
337             AssertExtensions.Throws<ArgumentNullException>("outer", () => ((ParallelQuery<int>)null).GroupJoin(ParallelEnumerable.Range(0, 1), i => i, i => i, (i, j) => i, EqualityComparer<int>.Default));
338             AssertExtensions.Throws<ArgumentNullException>("inner", () => ParallelEnumerable.Range(0, 1).GroupJoin((ParallelQuery<int>)null, i => i, i => i, (i, j) => i, EqualityComparer<int>.Default));
339             AssertExtensions.Throws<ArgumentNullException>("outerKeySelector", () => ParallelEnumerable.Range(0, 1).GroupJoin(ParallelEnumerable.Range(0, 1), (Func<int, int>)null, i => i, (i, j) => i, EqualityComparer<int>.Default));
340             AssertExtensions.Throws<ArgumentNullException>("innerKeySelector", () => ParallelEnumerable.Range(0, 1).GroupJoin(ParallelEnumerable.Range(0, 1), i => i, (Func<int, int>)null, (i, j) => i, EqualityComparer<int>.Default));
341             AssertExtensions.Throws<ArgumentNullException>("resultSelector", () => ParallelEnumerable.Range(0, 1).GroupJoin(ParallelEnumerable.Range(0, 1), i => i, i => i, (Func<int, IEnumerable<int>, int>)null, EqualityComparer<int>.Default));
342         }
343     }
344 }
345