Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions FrameworkFeatureConstants.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<DefineConstants>$(DefineConstants);REFLECTION_ASSEMBLY_NAME_INFO;REFLECTION_TYPE_NAME;ORDERED_DICTIONARY</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))">
<DefineConstants>$(DefineConstants);SHUFFLE_EXTENSION;INTEGRATED_ASYNC</DefineConstants>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>

<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
Expand Down
2 changes: 1 addition & 1 deletion Funcky.Async.Test/Funcky.Async.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net9.0;net8.0;net7.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
Expand Down
12 changes: 6 additions & 6 deletions Funcky.Async/Extensions/AsyncEnumerableExtensions/Scan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Funcky.Extensions;
public static partial class AsyncEnumerableExtensions
{
/// <summary>
/// Scan generates a sequence known as the the inclusive prefix sum.
/// Scan generates a sequence known as the inclusive prefix sum.
/// </summary>
/// <typeparam name="TSource">The type of the source elements.</typeparam>
/// <typeparam name="TAccumulate">The seed and target type.</typeparam>
Expand All @@ -17,7 +17,7 @@ public static IAsyncEnumerable<TAccumulate> InclusiveScan<TSource, TAccumulate>(
=> InclusiveScanEnumerable(source, seed, accumulator);

/// <summary>
/// Scan generates a sequence known as the the inclusive prefix sum.
/// Scan generates a sequence known as the inclusive prefix sum.
/// </summary>
/// <typeparam name="TSource">The type of the source elements.</typeparam>
/// <typeparam name="TAccumulate">The seed and target type.</typeparam>
Expand All @@ -29,7 +29,7 @@ public static IAsyncEnumerable<TAccumulate> InclusiveScanAwait<TSource, TAccumul
=> InclusiveScanAwaitEnumerable(source, seed, accumulator);

/// <summary>
/// Scan generates a sequence known as the the inclusive prefix sum.
/// Scan generates a sequence known as the inclusive prefix sum.
/// </summary>
/// <typeparam name="TSource">The type of the source elements.</typeparam>
/// <typeparam name="TAccumulate">The seed and target type.</typeparam>
Expand All @@ -41,7 +41,7 @@ public static IAsyncEnumerable<TAccumulate> InclusiveScanAwaitWithCancellation<T
=> InclusiveScanAwaitWithCancellationEnumerable(source, seed, accumulator);

/// <summary>
/// Scan generates a sequence known as the the exclusive prefix sum.
/// Scan generates a sequence known as the exclusive prefix sum.
/// </summary>
/// <typeparam name="TSource">The type of the source elements.</typeparam>
/// <typeparam name="TAccumulate">The seed and target type.</typeparam>
Expand All @@ -53,7 +53,7 @@ public static IAsyncEnumerable<TAccumulate> ExclusiveScan<TSource, TAccumulate>(
=> ExclusiveScanEnumerable(source, seed, accumulator);

/// <summary>
/// Scan generates a sequence known as the the exclusive prefix sum.
/// Scan generates a sequence known as the exclusive prefix sum.
/// </summary>
/// <typeparam name="TSource">The type of the source elements.</typeparam>
/// <typeparam name="TAccumulate">The seed and target type.</typeparam>
Expand All @@ -65,7 +65,7 @@ public static IAsyncEnumerable<TAccumulate> ExclusiveScanAwait<TSource, TAccumul
=> ExclusiveScanAwaitEnumerable(source, seed, accumulator);

/// <summary>
/// Scan generates a sequence known as the the exclusive prefix sum.
/// Scan generates a sequence known as the exclusive prefix sum.
/// </summary>
/// <typeparam name="TSource">The type of the source elements.</typeparam>
/// <typeparam name="TAccumulate">The seed and target type.</typeparam>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>Funcky.SourceGenerator.Test</AssemblyName>
<RootNamespace>Funcky.SourceGenerator.Test</RootNamespace>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
Expand Down
28 changes: 28 additions & 0 deletions Funcky.Test/AsyncGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#if INTEGRATED_ASYNC
using FsCheck;
using FsCheck.Fluent;

namespace Funcky.Async.Test;

internal static class AsyncGenerator
{
public static Arbitrary<IAsyncEnumerable<T>> GenerateAsyncEnumerable<T>(IArbMap map)
=> map.GeneratorFor<List<T>>().Select(list => list.ToAsyncEnumerable()).ToArbitrary();

public static Arbitrary<AwaitSelector<T>> GenerateAwaitSelector<T>(IArbMap map)
=> map.GeneratorFor<Func<T, T>>().Select(ResultToValueTask).ToArbitrary();

public static Arbitrary<AwaitSelectorWithCancellation<T>> GenerateAwaitWithCancellationSelector<T>(IArbMap map)
=> map.GeneratorFor<Func<T, T>>().Select(ResultToValueTaskX).ToArbitrary();

private static AwaitSelector<T> ResultToValueTask<T>(Func<T, T> f)
=> new(value => ValueTask.FromResult(f(value)));

private static AwaitSelectorWithCancellation<T> ResultToValueTaskX<T>(Func<T, T> f)
=> new((value, _) => ValueTask.FromResult(f(value)));
}

public sealed record AwaitSelector<T>(Func<T, ValueTask<T>> Get);

public sealed record AwaitSelectorWithCancellation<T>(Func<T, CancellationToken, ValueTask<T>> Get);
#endif
40 changes: 40 additions & 0 deletions Funcky.Test/AsyncSequence/ConcatTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#if INTEGRATED_ASYNC
using System.Collections.Immutable;
using FsCheck;
using FsCheck.Fluent;
using FsCheck.Xunit;
using Funcky.Async.Test.TestUtilities;

namespace Funcky.Async.Test;

public sealed class ConcatTest
{
[Fact]
public async Task ConcatenatedSequenceIsEmptyWhenNoSourcesAreProvidedAsync()
{
await AsyncAssert.Empty(AsyncSequence.Concat<object>());
}

[Fact]
public async Task ConcatenatedSequenceIsEmptyWhenAllSourcesAreEmptyAsync()
{
await AsyncAssert.Empty(AsyncSequence.Concat(Enumerable.Empty<object>().ToAsyncEnumerable(), Enumerable.Empty<object>().ToAsyncEnumerable(), Enumerable.Empty<object>().ToAsyncEnumerable()));
}

[Property]
public Property ConcatenatedSequenceContainsElementsFromAllSourcesInOrder(int[][] sources)
{
var expected = sources.Aggregate(ImmutableArray<int>.Empty, (l, s) => l.AddRange(s)).ToAsyncEnumerable();

var innerOuterAsync = sources.Select(source => source.ToAsyncEnumerable()).ToAsyncEnumerable();
var innerAsync = sources.Select(source => source.ToAsyncEnumerable());
IAsyncEnumerable<IEnumerable<int>> outerAsync = sources.ToAsyncEnumerable();

var result = expected.SequenceEqualAsync(AsyncSequence.Concat(innerOuterAsync)).Result
&& expected.SequenceEqualAsync(AsyncSequence.Concat(innerAsync)).Result
&& expected.SequenceEqualAsync(AsyncSequence.Concat(outerAsync)).Result;

return result.ToProperty();
}
}
#endif
71 changes: 71 additions & 0 deletions Funcky.Test/AsyncSequence/CycleRangeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#if INTEGRATED_ASYNC
using FsCheck;
using FsCheck.Fluent;
using FsCheck.Xunit;
using Funcky.Async.Test.TestUtilities;
using Funcky.Test.TestUtilities;

namespace Funcky.Async.Test;

public sealed class CycleRangeTest
{
[Fact]
public async Task CycleRangeIsEnumeratedLazilyAsync()
{
var doNotEnumerate = new FailOnEnumerateAsyncSequence<object>();

await using var cycleRange = AsyncSequence.CycleRange(doNotEnumerate);
}

[Fact]
public async Task CyclingAnEmptySetThrowsAnArgumentException()
=> await Assert.ThrowsAsync<InvalidOperationException>(CycleEmptySequenceAsync);

[Property]
public Property CycleRangeCanProduceArbitraryManyItemsAsync(NonEmptySet<int> sequence, PositiveInt arbitraryElements)
=> (GetArbitraryManyItemsAsync(sequence.Get, arbitraryElements.Get).Result == arbitraryElements.Get)
.ToProperty();

[Property(Skip = "Tofix")]
public Property CycleRangeRepeatsTheElementsArbitraryManyTimes(NonEmptySet<int> sequence, PositiveInt arbitraryElements)
=> CycleRangeRepeatsTheElementsArbitraryManyTimesAsync(sequence.Get.ToAsyncEnumerable(), arbitraryElements.Get)
.Result.ToProperty();

[Fact]
public async Task CycleRangeEnumeratesUnderlyingEnumerableOnlyOnceAsync()
{
var sequence = Sequence.Return("Test", "Hello", "Do", "Wait");
var enumerateOnce = AsyncEnumerateOnce.Create(sequence);

await using var cycleRange = AsyncSequence.CycleRange(enumerateOnce);

_ = await cycleRange
.Take(sequence.Count * 3)
.ToListAsync();
}

private static async Task<int> GetArbitraryManyItemsAsync(IEnumerable<int> sequence, int arbitraryElements)
{
await using var cycleRange = AsyncSequence.CycleRange(sequence.ToAsyncEnumerable());

return await cycleRange.Take(arbitraryElements).CountAsync();
}

private static async Task CycleEmptySequenceAsync()
{
await using var cycledRange = AsyncSequence.CycleRange(AsyncSequence.Return<string>());
await using var enumerator = cycledRange.GetAsyncEnumerator();

await enumerator.MoveNextAsync();
}

private static async Task<bool> CycleRangeRepeatsTheElementsArbitraryManyTimesAsync(IAsyncEnumerable<int> asyncEnumerable, int arbitraryElements)
{
await using var cycleRange = AsyncSequence.CycleRange(asyncEnumerable);

return await cycleRange
.IsSequenceRepeating(asyncEnumerable)
.NTimes(arbitraryElements);
}
}
#endif
25 changes: 25 additions & 0 deletions Funcky.Test/AsyncSequence/CycleTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if INTEGRATED_ASYNC
using FsCheck;
using FsCheck.Fluent;
using FsCheck.Xunit;
using Funcky.Test.TestUtilities;

namespace Funcky.Async.Test;

public sealed class CycleTest
{
[Property]
public Property CycleCanProduceArbitraryManyItems(int value, PositiveInt arbitraryElements)
=> (AsyncSequence.Cycle(value).Take(arbitraryElements.Get).CountAsync().Result == arbitraryElements.Get)
.ToProperty();

[Property]
public Property CycleRepeatsTheElementArbitraryManyTimes(int value, PositiveInt arbitraryElements)
=> AsyncSequence
.Cycle(value)
.IsSequenceRepeating(AsyncSequence.Return(value))
.NTimes(arbitraryElements.Get)
.Result
.ToProperty();
}
#endif
101 changes: 101 additions & 0 deletions Funcky.Test/AsyncSequence/RepeatRangeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#if INTEGRATED_ASYNC
using FsCheck;
using FsCheck.Fluent;
using FsCheck.Xunit;
using Funcky.Async.Test.TestUtilities;
using Funcky.Test.TestUtilities;

namespace Funcky.Async.Test;

public sealed class RepeatRangeTest
{
[Fact]
public async Task RepeatRangeIsEnumeratedLazily()
{
var doNotEnumerate = new FailOnEnumerateAsyncSequence<object>();

await using var repeatRange = AsyncSequence.RepeatRange(doNotEnumerate, 2);
}

[Fact]
public async Task RepeatRangeThrowsWhenAlreadyDisposedAsync()
{
var repeatRange = AsyncSequence.RepeatRange(AsyncSequence.Return(1337), 5);

#pragma warning disable IDISP016 // we test behaviour after Dispose
#pragma warning disable IDISP017 // we test behaviour after Dispose
await repeatRange.DisposeAsync();
#pragma warning restore IDISP016
#pragma warning restore IDISP017

await Assert.ThrowsAsync<ObjectDisposedException>(async () => await repeatRange.ToListAsync());
}

[Fact]
public async Task RepeatRangeThrowsWhenAlreadyDisposedEvenIfYouDisposeBetweenMoveNextAsync()
{
var list = AsyncSequence.Return(1337, 2, 5);

const int repeats = 5;

foreach (var i in Enumerable.Range(0, await list.CountAsync() * repeats))
{
var repeatRange = AsyncSequence.RepeatRange(list, repeats);
await using var enumerator = repeatRange.GetAsyncEnumerator();

Assert.True(await AsyncEnumerable.Range(0, i).AllAsync(async (_, _) => await enumerator.MoveNextAsync()));

#pragma warning disable IDISP016 // we test behaviour after Dispose
#pragma warning disable IDISP017 // we test behaviour after Dispose
await repeatRange.DisposeAsync();
#pragma warning restore IDISP016
#pragma warning restore IDISP017

await Assert.ThrowsAnyAsync<ObjectDisposedException>(async () => await enumerator.MoveNextAsync());
}
}

[Property]
public Property TheLengthOfTheGeneratedRepeatRangeIsCorrect(List<int> list, NonNegativeInt count)
=> TheLengthOfTheGeneratedRepeatRangeIsCorrectAsync(list, count.Get)
.Result
.ToProperty();

[Property(Skip = "Tofix")]
public Property TheSequenceRepeatsTheGivenNumberOfTimes(List<int> list, NonNegativeInt count)
=> TheSequenceRepeatsTheGivenNumberOfTimesAsync(list.ToAsyncEnumerable(), count.Get)
.Result
.ToProperty();

[Fact]
public async Task RepeatRangeEnumeratesUnderlyingEnumerableOnlyOnceAsync()
{
var sequence = Sequence.Return("Test", "Hello", "Do", "Wait");
var enumerateOnce = AsyncEnumerateOnce.Create(sequence);

await using var repeatRange = AsyncSequence.RepeatRange(enumerateOnce, 3);

await foreach (var dummy in repeatRange)
{
}
}

private static async Task<bool> TheLengthOfTheGeneratedRepeatRangeIsCorrectAsync(List<int> list, int count)
{
await using var repeatRange = AsyncSequence.RepeatRange(list.ToAsyncEnumerable(), count);

var materialized = await repeatRange.ToListAsync();

return materialized.Count == list.Count * count;
}

private static async Task<bool> TheSequenceRepeatsTheGivenNumberOfTimesAsync(IAsyncEnumerable<int> asyncEnumerable, int count)
{
await using var repeatRange = AsyncSequence.RepeatRange(asyncEnumerable, count);

return await repeatRange
.IsSequenceRepeating(asyncEnumerable)
.NTimes(count);
}
}
#endif
35 changes: 35 additions & 0 deletions Funcky.Test/AsyncSequence/ReturnTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#if INTEGRATED_ASYNC
using FsCheck;
using FsCheck.Fluent;
using FsCheck.Xunit;
using Funcky.Async.Test.TestUtilities;

namespace Funcky.Async.Test;

public sealed class ReturnTest
{
[Property]
public Property ReturnOfASingleItemElevatesThatItemIntoASingleItemedEnumerable(int item)
{
var sequence = AsyncSequence.Return(item);

return (sequence.SingleOrNoneAsync().Result == item).ToProperty();
}

[Fact]
public async Task SequenceReturnCreatesAnEnumerableFromAnArbitraryNumberOfParameters()
{
const string one = "Alpha";
const string two = "Beta";
const string three = "Gamma";

var sequence = AsyncSequence.Return(one, two, three);

await AsyncAssert.Collection(
sequence,
element1 => Assert.Equal(one, element1),
element2 => Assert.Equal(two, element2),
element3 => Assert.Equal(three, element3));
}
}
#endif
Loading