|
@@ -5,6 +5,7 @@
|
|
using System;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Linq;
|
|
|
|
+using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
using Xunit;
|
|
using Xunit;
|
|
|
|
|
|
@@ -162,6 +163,128 @@ namespace Tests
|
|
Assert.Equal(new[] { 1, 2, 3, 4, 5 }, (await res.ToListAsync()).OrderBy(x => x));
|
|
Assert.Equal(new[] { 1, 2, 3, 4, 5 }, (await res.ToListAsync()).OrderBy(x => x));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+ [Fact]
|
|
|
|
+ public async Task Union_DisposesNotEmpty()
|
|
|
|
+ {
|
|
|
|
+ var e1 = new DisposalDetectingEnumerable(10, 2);
|
|
|
|
+ var e2 = new DisposalDetectingEnumerable(20, 2);
|
|
|
|
+ var res = e1.Union(e2).OrderBy(x => x);
|
|
|
|
+
|
|
|
|
+ var e = res.GetAsyncEnumerator();
|
|
|
|
+ await HasNextAsync(e, 10);
|
|
|
|
+ await HasNextAsync(e, 11);
|
|
|
|
+ await HasNextAsync(e, 20);
|
|
|
|
+ await HasNextAsync(e, 21);
|
|
|
|
+ await NoNextAsync(e);
|
|
|
|
+
|
|
|
|
+ Assert.Single(e1.Enumerators);
|
|
|
|
+ Assert.Single(e2.Enumerators);
|
|
|
|
+ Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Fact]
|
|
|
|
+ public async Task Union_DisposesFirstEmpty()
|
|
|
|
+ {
|
|
|
|
+ var e1 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var e2 = new DisposalDetectingEnumerable(1, 1);
|
|
|
|
+ var res = e1.Union(e2);
|
|
|
|
+
|
|
|
|
+ var e = res.GetAsyncEnumerator();
|
|
|
|
+ await HasNextAsync(e, 1);
|
|
|
|
+ await NoNextAsync(e);
|
|
|
|
+
|
|
|
|
+ Assert.Single(e1.Enumerators);
|
|
|
|
+ Assert.Single(e2.Enumerators);
|
|
|
|
+ Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Fact]
|
|
|
|
+ public async Task Union_DisposesSecondOfTwoEmpty()
|
|
|
|
+ {
|
|
|
|
+ var e1 = new DisposalDetectingEnumerable(1, 1);
|
|
|
|
+ var e2 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var res = e1.Union(e2);
|
|
|
|
+
|
|
|
|
+ var e = res.GetAsyncEnumerator();
|
|
|
|
+ await HasNextAsync(e, 1);
|
|
|
|
+ await NoNextAsync(e);
|
|
|
|
+
|
|
|
|
+ Assert.Single(e1.Enumerators);
|
|
|
|
+ Assert.Single(e2.Enumerators);
|
|
|
|
+ Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Fact]
|
|
|
|
+ public async Task Union_DisposesSecondOfThreeEmpty()
|
|
|
|
+ {
|
|
|
|
+ var e1 = new DisposalDetectingEnumerable(10, 1);
|
|
|
|
+ var e2 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var e3 = new DisposalDetectingEnumerable(30, 1);
|
|
|
|
+ var res = e1.Union(e2).Union(e3);
|
|
|
|
+
|
|
|
|
+ var e = res.GetAsyncEnumerator();
|
|
|
|
+ await HasNextAsync(e, 10);
|
|
|
|
+ await HasNextAsync(e, 30);
|
|
|
|
+ await NoNextAsync(e);
|
|
|
|
+
|
|
|
|
+ Assert.Single(e1.Enumerators);
|
|
|
|
+ Assert.Single(e2.Enumerators);
|
|
|
|
+ Assert.Single(e3.Enumerators);
|
|
|
|
+ Assert.Equal([true, true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed, e3.Enumerators[0].Disposed]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Fact]
|
|
|
|
+ public async Task Union_DisposesThirdOfThreeEmpty()
|
|
|
|
+ {
|
|
|
|
+ var e1 = new DisposalDetectingEnumerable(10, 1);
|
|
|
|
+ var e2 = new DisposalDetectingEnumerable(20, 1);
|
|
|
|
+ var e3 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var res = e1.Union(e2).Union(e3);
|
|
|
|
+
|
|
|
|
+ var e = res.GetAsyncEnumerator();
|
|
|
|
+ await HasNextAsync(e, 10);
|
|
|
|
+ await HasNextAsync(e, 20);
|
|
|
|
+ await NoNextAsync(e);
|
|
|
|
+
|
|
|
|
+ Assert.Single(e1.Enumerators);
|
|
|
|
+ Assert.Single(e2.Enumerators);
|
|
|
|
+ Assert.Single(e3.Enumerators);
|
|
|
|
+ Assert.Equal([true, true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed, e3.Enumerators[0].Disposed]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Fact]
|
|
|
|
+ public async Task Union_DisposesAllOfTwoEmpty()
|
|
|
|
+ {
|
|
|
|
+ var e1 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var e2 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var res = e1.Union(e2);
|
|
|
|
+
|
|
|
|
+ var e = res.GetAsyncEnumerator();
|
|
|
|
+ await NoNextAsync(e);
|
|
|
|
+
|
|
|
|
+ Assert.Single(e1.Enumerators);
|
|
|
|
+ Assert.Single(e2.Enumerators);
|
|
|
|
+ Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Fact]
|
|
|
|
+ public async Task Union_DisposesAllOfThreeEmpty()
|
|
|
|
+ {
|
|
|
|
+ var e1 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var e2 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var e3 = new DisposalDetectingEnumerable(0, 0);
|
|
|
|
+ var res = e1.Union(e2).Union(e3);
|
|
|
|
+
|
|
|
|
+ var e = res.GetAsyncEnumerator();
|
|
|
|
+ await NoNextAsync(e);
|
|
|
|
+
|
|
|
|
+ Assert.Single(e1.Enumerators);
|
|
|
|
+ Assert.Single(e2.Enumerators);
|
|
|
|
+ Assert.Single(e3.Enumerators);
|
|
|
|
+ Assert.Equal([true, true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed, e3.Enumerators[0].Disposed]);
|
|
|
|
+ }
|
|
|
|
+
|
|
private sealed class Eq : IEqualityComparer<int>
|
|
private sealed class Eq : IEqualityComparer<int>
|
|
{
|
|
{
|
|
public bool Equals(int x, int y)
|
|
public bool Equals(int x, int y)
|
|
@@ -174,5 +297,61 @@ namespace Tests
|
|
return EqualityComparer<int>.Default.GetHashCode(Math.Abs(obj));
|
|
return EqualityComparer<int>.Default.GetHashCode(Math.Abs(obj));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ private class DisposalDetectingEnumerable : IAsyncEnumerable<int>
|
|
|
|
+ {
|
|
|
|
+ private readonly int _start;
|
|
|
|
+ private readonly int _count;
|
|
|
|
+
|
|
|
|
+ public DisposalDetectingEnumerable(int start, int count)
|
|
|
|
+ {
|
|
|
|
+ _start = start;
|
|
|
|
+ _count = count;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public List<Enumerator> Enumerators { get; } = new List<Enumerator>();
|
|
|
|
+
|
|
|
|
+ public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
|
|
|
+ {
|
|
|
|
+ Enumerator r = new(_start, _count);
|
|
|
|
+ Enumerators.Add(r);
|
|
|
|
+ return r;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public class Enumerator : IAsyncEnumerator<int>
|
|
|
|
+ {
|
|
|
|
+ private readonly int _max;
|
|
|
|
+
|
|
|
|
+ public Enumerator(int start, int count)
|
|
|
|
+ {
|
|
|
|
+ Current = start - 1;
|
|
|
|
+ _max = start + count;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public int Current { get; private set; }
|
|
|
|
+
|
|
|
|
+ public bool Disposed { get; private set; }
|
|
|
|
+
|
|
|
|
+ public void Dispose()
|
|
|
|
+ {
|
|
|
|
+ Disposed = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public ValueTask DisposeAsync()
|
|
|
|
+ {
|
|
|
|
+ Disposed = true;
|
|
|
|
+ return new ValueTask();
|
|
|
|
+ }
|
|
|
|
+ public ValueTask<bool> MoveNextAsync()
|
|
|
|
+ {
|
|
|
|
+ if (++Current < _max)
|
|
|
|
+ {
|
|
|
|
+ return new ValueTask<bool>(true);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return new ValueTask<bool>(false);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|