Skip to content

Commit dca54e9

Browse files
Merge pull request #872 from polyadic/integrate-funcky-async
Integrate funcky async
2 parents bcf4740 + 8f587ac commit dca54e9

File tree

199 files changed

+7838
-151
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

199 files changed

+7838
-151
lines changed

Funcky.Async/Funcky.Async.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
<ItemGroup>
4747
<PackageReference Include="PolySharp" PrivateAssets="all" />
4848
<PackageReference Include="Polyadic.Build.SemanticVersioning" PrivateAssets="all" />
49-
<PackageReference Include="System.Linq.Async" Condition="'$(TargetFramework)' != 'net10.0'" />
49+
<PackageReference Include="System.Linq.Async" />
5050
</ItemGroup>
5151
<ItemGroup>
5252
<ProjectReference Include="..\Funcky\Funcky.csproj" />

Funcky.Test/AsyncGenerator.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#if INTEGRATED_ASYNC
2+
using FsCheck;
3+
using FsCheck.Fluent;
4+
5+
namespace Funcky.Async.Test;
6+
7+
internal static class AsyncGenerator
8+
{
9+
public static Arbitrary<IAsyncEnumerable<T>> GenerateAsyncEnumerable<T>(IArbMap map)
10+
=> map.GeneratorFor<List<T>>().Select(list => list.ToAsyncEnumerable()).ToArbitrary();
11+
12+
public static Arbitrary<AwaitSelector<T>> GenerateAwaitSelector<T>(IArbMap map)
13+
=> map.GeneratorFor<Func<T, T>>().Select(ResultToValueTask).ToArbitrary();
14+
15+
public static Arbitrary<AwaitSelectorWithCancellation<T>> GenerateAwaitWithCancellationSelector<T>(IArbMap map)
16+
=> map.GeneratorFor<Func<T, T>>().Select(ResultToValueTaskX).ToArbitrary();
17+
18+
private static AwaitSelector<T> ResultToValueTask<T>(Func<T, T> f)
19+
=> new(value => ValueTask.FromResult(f(value)));
20+
21+
private static AwaitSelectorWithCancellation<T> ResultToValueTaskX<T>(Func<T, T> f)
22+
=> new((value, _) => ValueTask.FromResult(f(value)));
23+
}
24+
25+
public sealed record AwaitSelector<T>(Func<T, ValueTask<T>> Get);
26+
27+
public sealed record AwaitSelectorWithCancellation<T>(Func<T, CancellationToken, ValueTask<T>> Get);
28+
#endif
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#if INTEGRATED_ASYNC
2+
using System.Collections.Immutable;
3+
using FsCheck;
4+
using FsCheck.Fluent;
5+
using FsCheck.Xunit;
6+
using Funcky.Async.Test.TestUtilities;
7+
8+
namespace Funcky.Async.Test;
9+
10+
public sealed class ConcatTest
11+
{
12+
[Fact]
13+
public async Task ConcatenatedSequenceIsEmptyWhenNoSourcesAreProvidedAsync()
14+
{
15+
await AsyncAssert.Empty(AsyncSequence.Concat<object>());
16+
}
17+
18+
[Fact]
19+
public async Task ConcatenatedSequenceIsEmptyWhenAllSourcesAreEmptyAsync()
20+
{
21+
await AsyncAssert.Empty(AsyncSequence.Concat(Enumerable.Empty<object>().ToAsyncEnumerable(), Enumerable.Empty<object>().ToAsyncEnumerable(), Enumerable.Empty<object>().ToAsyncEnumerable()));
22+
}
23+
24+
[Property]
25+
public Property ConcatenatedSequenceContainsElementsFromAllSourcesInOrder(int[][] sources)
26+
{
27+
var expected = sources.Aggregate(ImmutableArray<int>.Empty, (l, s) => l.AddRange(s)).ToAsyncEnumerable();
28+
29+
var innerOuterAsync = sources.Select(source => source.ToAsyncEnumerable()).ToAsyncEnumerable();
30+
var innerAsync = sources.Select(source => source.ToAsyncEnumerable());
31+
IAsyncEnumerable<IEnumerable<int>> outerAsync = sources.ToAsyncEnumerable();
32+
33+
var result = expected.SequenceEqualAsync(AsyncSequence.Concat(innerOuterAsync)).Result
34+
&& expected.SequenceEqualAsync(AsyncSequence.Concat(innerAsync)).Result
35+
&& expected.SequenceEqualAsync(AsyncSequence.Concat(outerAsync)).Result;
36+
37+
return result.ToProperty();
38+
}
39+
}
40+
#endif
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#if INTEGRATED_ASYNC
2+
using FsCheck;
3+
using FsCheck.Fluent;
4+
using FsCheck.Xunit;
5+
using Funcky.Async.Test.TestUtilities;
6+
using Funcky.Test.TestUtilities;
7+
8+
namespace Funcky.Async.Test;
9+
10+
public sealed class CycleRangeTest
11+
{
12+
[Fact]
13+
public async Task CycleRangeIsEnumeratedLazilyAsync()
14+
{
15+
var doNotEnumerate = new FailOnEnumerateAsyncSequence<object>();
16+
17+
await using var cycleRange = AsyncSequence.CycleRange(doNotEnumerate);
18+
}
19+
20+
[Fact]
21+
public async Task CyclingAnEmptySetThrowsAnArgumentException()
22+
=> await Assert.ThrowsAsync<InvalidOperationException>(CycleEmptySequenceAsync);
23+
24+
[Property]
25+
public Property CycleRangeCanProduceArbitraryManyItemsAsync(NonEmptySet<int> sequence, PositiveInt arbitraryElements)
26+
=> (GetArbitraryManyItemsAsync(sequence.Get, arbitraryElements.Get).Result == arbitraryElements.Get)
27+
.ToProperty();
28+
29+
[Property(Skip = "Tofix")]
30+
public Property CycleRangeRepeatsTheElementsArbitraryManyTimes(NonEmptySet<int> sequence, PositiveInt arbitraryElements)
31+
=> CycleRangeRepeatsTheElementsArbitraryManyTimesAsync(sequence.Get.ToAsyncEnumerable(), arbitraryElements.Get)
32+
.Result.ToProperty();
33+
34+
[Fact]
35+
public async Task CycleRangeEnumeratesUnderlyingEnumerableOnlyOnceAsync()
36+
{
37+
var sequence = Sequence.Return("Test", "Hello", "Do", "Wait");
38+
var enumerateOnce = AsyncEnumerateOnce.Create(sequence);
39+
40+
await using var cycleRange = AsyncSequence.CycleRange(enumerateOnce);
41+
42+
_ = await cycleRange
43+
.Take(sequence.Count * 3)
44+
.ToListAsync();
45+
}
46+
47+
private static async Task<int> GetArbitraryManyItemsAsync(IEnumerable<int> sequence, int arbitraryElements)
48+
{
49+
await using var cycleRange = AsyncSequence.CycleRange(sequence.ToAsyncEnumerable());
50+
51+
return await cycleRange.Take(arbitraryElements).CountAsync();
52+
}
53+
54+
private static async Task CycleEmptySequenceAsync()
55+
{
56+
await using var cycledRange = AsyncSequence.CycleRange(AsyncSequence.Return<string>());
57+
await using var enumerator = cycledRange.GetAsyncEnumerator();
58+
59+
await enumerator.MoveNextAsync();
60+
}
61+
62+
private static async Task<bool> CycleRangeRepeatsTheElementsArbitraryManyTimesAsync(IAsyncEnumerable<int> asyncEnumerable, int arbitraryElements)
63+
{
64+
await using var cycleRange = AsyncSequence.CycleRange(asyncEnumerable);
65+
66+
return await cycleRange
67+
.IsSequenceRepeating(asyncEnumerable)
68+
.NTimes(arbitraryElements);
69+
}
70+
}
71+
#endif
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#if INTEGRATED_ASYNC
2+
using FsCheck;
3+
using FsCheck.Fluent;
4+
using FsCheck.Xunit;
5+
using Funcky.Test.TestUtilities;
6+
7+
namespace Funcky.Async.Test;
8+
9+
public sealed class CycleTest
10+
{
11+
[Property]
12+
public Property CycleCanProduceArbitraryManyItems(int value, PositiveInt arbitraryElements)
13+
=> (AsyncSequence.Cycle(value).Take(arbitraryElements.Get).CountAsync().Result == arbitraryElements.Get)
14+
.ToProperty();
15+
16+
[Property]
17+
public Property CycleRepeatsTheElementArbitraryManyTimes(int value, PositiveInt arbitraryElements)
18+
=> AsyncSequence
19+
.Cycle(value)
20+
.IsSequenceRepeating(AsyncSequence.Return(value))
21+
.NTimes(arbitraryElements.Get)
22+
.Result
23+
.ToProperty();
24+
}
25+
#endif
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#if INTEGRATED_ASYNC
2+
using FsCheck;
3+
using FsCheck.Fluent;
4+
using FsCheck.Xunit;
5+
using Funcky.Async.Test.TestUtilities;
6+
using Funcky.Test.TestUtilities;
7+
8+
namespace Funcky.Async.Test;
9+
10+
public sealed class RepeatRangeTest
11+
{
12+
[Fact]
13+
public async Task RepeatRangeIsEnumeratedLazily()
14+
{
15+
var doNotEnumerate = new FailOnEnumerateAsyncSequence<object>();
16+
17+
await using var repeatRange = AsyncSequence.RepeatRange(doNotEnumerate, 2);
18+
}
19+
20+
[Fact]
21+
public async Task RepeatRangeThrowsWhenAlreadyDisposedAsync()
22+
{
23+
var repeatRange = AsyncSequence.RepeatRange(AsyncSequence.Return(1337), 5);
24+
25+
#pragma warning disable IDISP016 // we test behaviour after Dispose
26+
#pragma warning disable IDISP017 // we test behaviour after Dispose
27+
await repeatRange.DisposeAsync();
28+
#pragma warning restore IDISP016
29+
#pragma warning restore IDISP017
30+
31+
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await repeatRange.ToListAsync());
32+
}
33+
34+
[Fact]
35+
public async Task RepeatRangeThrowsWhenAlreadyDisposedEvenIfYouDisposeBetweenMoveNextAsync()
36+
{
37+
var list = AsyncSequence.Return(1337, 2, 5);
38+
39+
const int repeats = 5;
40+
41+
foreach (var i in Enumerable.Range(0, await list.CountAsync() * repeats))
42+
{
43+
var repeatRange = AsyncSequence.RepeatRange(list, repeats);
44+
await using var enumerator = repeatRange.GetAsyncEnumerator();
45+
46+
Assert.True(await AsyncEnumerable.Range(0, i).AllAsync(async (_, _) => await enumerator.MoveNextAsync()));
47+
48+
#pragma warning disable IDISP016 // we test behaviour after Dispose
49+
#pragma warning disable IDISP017 // we test behaviour after Dispose
50+
await repeatRange.DisposeAsync();
51+
#pragma warning restore IDISP016
52+
#pragma warning restore IDISP017
53+
54+
await Assert.ThrowsAnyAsync<ObjectDisposedException>(async () => await enumerator.MoveNextAsync());
55+
}
56+
}
57+
58+
[Property]
59+
public Property TheLengthOfTheGeneratedRepeatRangeIsCorrect(List<int> list, NonNegativeInt count)
60+
=> TheLengthOfTheGeneratedRepeatRangeIsCorrectAsync(list, count.Get)
61+
.Result
62+
.ToProperty();
63+
64+
[Property(Skip = "Tofix")]
65+
public Property TheSequenceRepeatsTheGivenNumberOfTimes(List<int> list, NonNegativeInt count)
66+
=> TheSequenceRepeatsTheGivenNumberOfTimesAsync(list.ToAsyncEnumerable(), count.Get)
67+
.Result
68+
.ToProperty();
69+
70+
[Fact]
71+
public async Task RepeatRangeEnumeratesUnderlyingEnumerableOnlyOnceAsync()
72+
{
73+
var sequence = Sequence.Return("Test", "Hello", "Do", "Wait");
74+
var enumerateOnce = AsyncEnumerateOnce.Create(sequence);
75+
76+
await using var repeatRange = AsyncSequence.RepeatRange(enumerateOnce, 3);
77+
78+
await foreach (var dummy in repeatRange)
79+
{
80+
}
81+
}
82+
83+
private static async Task<bool> TheLengthOfTheGeneratedRepeatRangeIsCorrectAsync(List<int> list, int count)
84+
{
85+
await using var repeatRange = AsyncSequence.RepeatRange(list.ToAsyncEnumerable(), count);
86+
87+
var materialized = await repeatRange.ToListAsync();
88+
89+
return materialized.Count == list.Count * count;
90+
}
91+
92+
private static async Task<bool> TheSequenceRepeatsTheGivenNumberOfTimesAsync(IAsyncEnumerable<int> asyncEnumerable, int count)
93+
{
94+
await using var repeatRange = AsyncSequence.RepeatRange(asyncEnumerable, count);
95+
96+
return await repeatRange
97+
.IsSequenceRepeating(asyncEnumerable)
98+
.NTimes(count);
99+
}
100+
}
101+
#endif
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#if INTEGRATED_ASYNC
2+
using FsCheck;
3+
using FsCheck.Fluent;
4+
using FsCheck.Xunit;
5+
using Funcky.Async.Test.TestUtilities;
6+
7+
namespace Funcky.Async.Test;
8+
9+
public sealed class ReturnTest
10+
{
11+
[Property]
12+
public Property ReturnOfASingleItemElevatesThatItemIntoASingleItemedEnumerable(int item)
13+
{
14+
var sequence = AsyncSequence.Return(item);
15+
16+
return (sequence.SingleOrNoneAsync().Result == item).ToProperty();
17+
}
18+
19+
[Fact]
20+
public async Task SequenceReturnCreatesAnEnumerableFromAnArbitraryNumberOfParameters()
21+
{
22+
const string one = "Alpha";
23+
const string two = "Beta";
24+
const string three = "Gamma";
25+
26+
var sequence = AsyncSequence.Return(one, two, three);
27+
28+
await AsyncAssert.Collection(
29+
sequence,
30+
element1 => Assert.Equal(one, element1),
31+
element2 => Assert.Equal(two, element2),
32+
element3 => Assert.Equal(three, element3));
33+
}
34+
}
35+
#endif
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#if INTEGRATED_ASYNC
2+
using Funcky.Async.Test.TestUtilities;
3+
4+
namespace Funcky.Async.Test;
5+
6+
public sealed class SuccessorsTest
7+
{
8+
[Fact]
9+
public async Task ReturnsEmptySequenceWhenFirstItemIsNoneAsync()
10+
{
11+
await AsyncAssert.Empty(AsyncSequence.Successors(Option<int>.None, ValueTask.FromResult));
12+
}
13+
14+
[Fact]
15+
public async Task ReturnsOnlyTheFirstItemWhenSuccessorFunctionImmediatelyReturnsNoneAsync()
16+
{
17+
var first = await AsyncAssert.Single(AsyncSequence.Successors(10, _ => ValueTask.FromResult(Option<int>.None)));
18+
Assert.Equal(10, first);
19+
}
20+
21+
[Fact]
22+
public async Task SuccessorsWithNonOptionFunctionReturnsEndlessEnumerableAsync()
23+
{
24+
const int count = 40;
25+
Assert.Equal(count, await AsyncSequence.Successors(0, ValueTask.FromResult).Take(count).CountAsync());
26+
}
27+
28+
[Fact]
29+
public async Task SuccessorsReturnsEnumerableThatReturnsValuesBasedOnSeedAsync()
30+
{
31+
await AsyncAssert.Equal(
32+
AsyncEnumerable.Range(0, 10),
33+
AsyncSequence.Successors(0, i => ValueTask.FromResult(i + 1)).Take(10));
34+
}
35+
36+
[Fact]
37+
public async Task SuccessorsReturnsEnumerableThatReturnsItemUntilNoneIsReturnedFromFuncAsync()
38+
{
39+
await AsyncAssert.Equal(
40+
AsyncEnumerable.Range(0, 11),
41+
AsyncSequence.Successors(0, i => ValueTask.FromResult(Option.FromBoolean(i < 10, i + 1))));
42+
}
43+
}
44+
#endif

0 commit comments

Comments
 (0)