Skip to content
Merged
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
31 changes: 28 additions & 3 deletions src/GalaxyCheck.Tests/RunnerTests/AssertTests/AboutExhaustion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using NebulaCheck;
using System;
using System.Linq;
using System.Threading.Tasks;
using static Tests.V2.DomainGenAttributes;
using static Tests.V2.Timeouts;

namespace Tests.V2.RunnerTests.AssertTests
{
Expand All @@ -14,11 +16,34 @@ public class Sync
[Property(Iterations = 1)]
public void ItCanExhaust([Seed] int seed, [Size] int size)
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAll(_ => true);
RunWithTimeout(
() =>
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAll(_ => true);

Action test = () => property.Assert(seed: seed, size: size, deepCheck: false);
Action test = () => property.Assert(seed: seed, size: size, deepCheck: false);

test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();
test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();
},
TimeSpan.FromSeconds(20));
}
}

public class Async
{
[Property(Iterations = 1)]
public void ItCanExhaustAsync([Seed] int seed, [Size] int size)
{
RunWithTimeoutAsync(
async () =>
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAllAsync(_ => Task.FromResult(true));

Func<Task> test = () => property.AssertAsync(seed: seed, size: size, deepCheck: false);

await test.Should().ThrowAsync<GalaxyCheck.Exceptions.GenExhaustionException>();
},
TimeSpan.FromSeconds(20));
}
}
}
Expand Down
75 changes: 65 additions & 10 deletions src/GalaxyCheck.Tests/RunnerTests/CheckTests/AboutExhaustion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,85 @@
using NebulaCheck;
using System;
using System.Linq;
using System.Threading.Tasks;
using static Tests.V2.DomainGenAttributes;
using static Tests.V2.Timeouts;

namespace Tests.V2.RunnerTests.CheckTests
{
public class AboutExhaustion
{
[Property(Iterations = 1)]
public void ItExhaustsWhenGenerationIsImpossible([Seed] int seed, [Size] int size)
public class Sync
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAll(_ => true);
[Property(Iterations = 1)]
public void ItExhaustsWhenGenerationIsImpossible([Seed] int seed, [Size] int size)
{
RunWithTimeout(
() =>
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAll(_ => true);

Action test = () => property.Check(seed: seed, size: size, deepCheck: false);
Action test = () => property.Check(seed: seed, size: size);

test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();
test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();

},
TimeSpan.FromSeconds(20));
}

[Property(Iterations = 1)]
public void ItExhaustsWhenPreconditionIsImpossible([Seed] int seed, [Size] int size)
{
RunWithTimeout(
() =>
{
var property = GalaxyCheck.Gen.Int32().ForAll(_ => GalaxyCheck.Property.Precondition(false));

Action test = () => property.Check(seed: seed, size: size);

test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();
},
TimeSpan.FromSeconds(20));
}
}

[Property(Iterations = 1)]
public void ItExhaustsWhenPreconditionIsImpossible([Seed] int seed, [Size] int size)
public class Async
{
var property = GalaxyCheck.Gen.Int32().ForAll(_ => GalaxyCheck.Property.Precondition(false));
[Property(Iterations = 1)]
public void ItExhaustsWhenGenerationIsImpossible([Seed] int seed, [Size] int size)
{
RunWithTimeoutAsync(
async () =>
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAllAsync(_ => Task.CompletedTask);

Func<Task> test = () => property.CheckAsync(seed: seed, size: size);

await test.Should().ThrowAsync<GalaxyCheck.Exceptions.GenExhaustionException>();

},
TimeSpan.FromSeconds(20));
}

[Property(Iterations = 1)]
public void ItExhaustsWhenPreconditionIsImpossible([Seed] int seed, [Size] int size)
{
RunWithTimeoutAsync(
async () =>
{
var property = GalaxyCheck.Gen.Int32().ForAllAsync(async _ =>
{
await Task.CompletedTask;
GalaxyCheck.Property.Precondition(false);
});

Func<Task> test = () => property.CheckAsync(seed: seed, size: size);

Action test = () => property.Check(seed: seed, size: size, deepCheck: false);
await test.Should().ThrowAsync<GalaxyCheck.Exceptions.GenExhaustionException>();
},
TimeSpan.FromSeconds(20));
}

test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();
}
}
}
38 changes: 33 additions & 5 deletions src/GalaxyCheck.Tests/RunnerTests/PrintTests/AboutExhaustion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,48 @@
using NebulaCheck;
using System;
using System.Linq;
using System.Threading.Tasks;
using static Tests.V2.DomainGenAttributes;
using static Tests.V2.Timeouts;

namespace Tests.V2.RunnerTests.PrintTests
{
public class AboutExhaustion
{
[Property(Iterations = 1)]
public void ItCanExhaust([Seed] int seed, [Size] int size)
public class Sync
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAll(_ => true);
[Property(Iterations = 1)]
public void ItCanExhaust([Seed] int seed, [Size] int size)
{
RunWithTimeout(
() =>
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAll(_ => true);

Action test = () => property.Advanced.Print(seed: seed, size: size);
Action test = () => property.Print(seed: seed, size: size);

test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();
test.Should().Throw<GalaxyCheck.Exceptions.GenExhaustionException>();
},
TimeSpan.FromSeconds(20));
}
}

public class Async
{
[Property(Iterations = 1)]
public void ItCanExhaustAsync([Seed] int seed, [Size] int size)
{
RunWithTimeoutAsync(
async () =>
{
var property = GalaxyCheck.Gen.Int32().Where(x => false).ForAllAsync(_ => Task.FromResult(true));

Func<Task> test = () => property.PrintAsync(seed: seed, size: size);

await test.Should().ThrowAsync<GalaxyCheck.Exceptions.GenExhaustionException>();
},
TimeSpan.FromSeconds(20));
}
}
}
}
43 changes: 43 additions & 0 deletions src/GalaxyCheck.Tests/Timeouts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;

namespace Tests.V2
{
public static class Timeouts
{
public static void RunWithTimeout(Action action, TimeSpan timeout)
{
var task = Task.Run(action);
try
{
var success = task.Wait(timeout);
if (!success)
{
throw new TimeoutException();
}
}
catch (AggregateException ex) when (ex.InnerException != null)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
}

public static void RunWithTimeoutAsync(Func<Task> action, TimeSpan timeout)
{
var task = Task.Run(action);
try
{
var success = task.Wait(timeout);
if (!success)
{
throw new TimeoutException();
}
}
catch (AggregateException ex) when (ex.InnerException != null)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
}
}
}
56 changes: 56 additions & 0 deletions src/GalaxyCheck/Internal/AsyncEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,62 @@ public static async IAsyncEnumerable<T> Unfold<T>(T seed, Func<T, Task<Option<T>
}
}

public static async IAsyncEnumerable<(TSource element, TAccumulator state)> ScanInParallel<TSource, TAccumulator>(
this IAsyncEnumerable<TSource> input,
TAccumulator seed,
Func<TAccumulator, TSource, TAccumulator> next)
{
var state = seed;
await foreach (var item in input)
{
state = next(state, item);
yield return (item, state);
}
}

public static IAsyncEnumerable<(T element, int consecutiveDiscardCount)> WithConsecutiveDiscardCount<T>(
this IAsyncEnumerable<T> source,
Func<T, bool> isCounted,
Func<T, bool> isDiscard)
{
return source.ScanInParallel(0, (consecutiveDiscardCount, element) =>
{
if (isCounted(element) == false)
{
// The element is considered neither a discard or a non-discard, don't increment or reset the
// consecutive discard count.
return consecutiveDiscardCount;
}

if (isDiscard(element))
{
return consecutiveDiscardCount + 1;
}

return 0;
});
}

public static IAsyncEnumerable<T> WithDiscardCircuitBreaker<T>(
this IAsyncEnumerable<T> source,
Func<T, bool> isCounted,
Func<T, bool> isDiscard)
{
return source
.WithConsecutiveDiscardCount(isCounted, isDiscard)
.Select((x) =>
{
var (element, consecutiveDiscardCount) = x;

if (consecutiveDiscardCount > 500)
{
throw new Exceptions.GenExhaustionException();
}

return element;
});
}

public static async Task<(T? head, IAsyncEnumerable<T> tail)> Deconstruct<T>(this IAsyncEnumerable<T> source)
{
var head = await source.FirstOrDefaultAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;

namespace GalaxyCheck.Runners.Check.AsyncAutomata
{
Expand Down Expand Up @@ -54,10 +55,10 @@ private static CheckStateTransition<T> DecorateTransition<T>(
.Select(x => x.nextTransition)
.Last();

private static async System.Threading.Tasks.Task<TransitionAggregation<T>> AggregateTransitionsAsync<T>(IAsyncEnumerable<CheckStateTransition<T>> transitions)
private static async Task<TransitionAggregation<T>> AggregateTransitionsAsync<T>(IAsyncEnumerable<CheckStateTransition<T>> transitions)
{
var mappedTransitions = await transitions
//.WithDiscardCircuitBreaker(isTransitionCountedInConsecutiveDiscardCount, isTransitionDiscard)
.WithDiscardCircuitBreaker(isTransitionCountedInConsecutiveDiscardCount, isTransitionDiscard)
.Select(x => (
state: x.State,
check: MapStateToIterationOrIgnore(x.State),
Expand Down
16 changes: 16 additions & 0 deletions src/GalaxyCheck/Runners/Print.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ namespace GalaxyCheck
{
public static partial class Extensions
{
// TODO: Delete this once C# supports extension methods after implicit conversions
public static void Print<T>(
this Property<T> property,
int? iterations = null,
int? seed = null,
int? size = null,
Action<string>? stdout = null) => Print((Property)property, iterations, seed, size, stdout);

public static void Print(
this Property property,
int? iterations = null,
Expand All @@ -23,6 +31,14 @@ public static void Print(
PrintHelpers.Print(sample.Values, sample.Discards, stdout);
}

// TODO: Delete this once C# supports extension methods after implicit conversions
public static Task PrintAsync<T>(
this AsyncProperty<T> property,
int? iterations = null,
int? seed = null,
int? size = null,
Action<string>? stdout = null) => PrintAsync((AsyncProperty)property, iterations, seed, size, stdout);

public static async Task PrintAsync(
this AsyncProperty property,
int? iterations = null,
Expand Down