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
19 changes: 19 additions & 0 deletions JustDummies.UnitTests/CompositionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ public void CombineOverMixedForeignAndLibraryQualifiesTheHint() {
Check.That(caught.Message).Not.Contains("The arbitrary values were seeded with");
}

[Fact(DisplayName = "A composer failure over a Combine mixing two different seeded sources does not promise a full replay from one seed.")]
public void CombineOverMixedSeededSourcesQualifiesTheHint() {
// The first operand draws from Any.WithSeed(4242); the second from the ambient source. The composed value
// depends on BOTH, so replaying WithSeed(4242) alone reproduces only the first — the hint must not promise a
// deterministic full replay from that one seed (issue #319).
IAny<string> generator = Any.Combine<int, int, string>(
Any.WithSeed(4242).Int32().Between(1, 3),
Any.Int32().Between(4, 6),
(first, second) => throw new InvalidOperationException($"rejected {first}/{second}"));

AnyGenerationException caught = Assert.Throws<AnyGenerationException>(() => generator.Generate());

Check.That(caught.Message).Contains("Combine(...)");
Check.WithCustomMessage($"The hint over-promised a full replay. Message: {caught.Message}")
.That(caught.Message).Not.Contains("already replays deterministically");
Check.WithCustomMessage($"The hint did not qualify the replay promise. Message: {caught.Message}")
.That(caught.Message).Contains("not reproducible from this seed alone");
}

[Fact(DisplayName = "Combine composes four through eight parts, passing every constrained part to the lambda.")]
public void CombineSupportsHigherArities() {
IAny<int> part = Any.Int32().Between(1, 9);
Expand Down
14 changes: 7 additions & 7 deletions JustDummies/Any.cs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second);
bool reproducible = AnyDerivation.IsReproducible(first) && AnyDerivation.IsReproducible(second);
bool reproducible = AnyDerivation.DrawsOnlyFrom(first, source) && AnyDerivation.DrawsOnlyFrom(second, source);

return new DerivedAny<TResult>(source, reproducible, () => {
T1 firstValue = first.Generate();
Expand All @@ -637,14 +637,14 @@
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, Func<T1, T2, T3, TResult> compose) {

Check warning on line 640 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 640 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third);
bool reproducible = AnyDerivation.IsReproducible(first) && AnyDerivation.IsReproducible(second) && AnyDerivation.IsReproducible(third);
bool reproducible = AnyDerivation.DrawsOnlyFrom(first, source) && AnyDerivation.DrawsOnlyFrom(second, source) && AnyDerivation.DrawsOnlyFrom(third, source);

return new DerivedAny<TResult>(source, reproducible, () => {
T1 firstValue = first.Generate();
Expand All @@ -670,7 +670,7 @@
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, T4, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, Func<T1, T2, T3, T4, TResult> compose) {

Check warning on line 673 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 673 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
Expand All @@ -678,7 +678,7 @@
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth);
bool reproducible = AnyDerivation.IsReproducible(first) && AnyDerivation.IsReproducible(second) && AnyDerivation.IsReproducible(third) && AnyDerivation.IsReproducible(fourth);
bool reproducible = AnyDerivation.DrawsOnlyFrom(first, source) && AnyDerivation.DrawsOnlyFrom(second, source) && AnyDerivation.DrawsOnlyFrom(third, source) && AnyDerivation.DrawsOnlyFrom(fourth, source);

return new DerivedAny<TResult>(source, reproducible, () => {
T1 firstValue = first.Generate();
Expand Down Expand Up @@ -707,7 +707,7 @@
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, Func<T1, T2, T3, T4, T5, TResult> compose) {

Check warning on line 710 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 710 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
Expand All @@ -716,7 +716,7 @@
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth);
bool reproducible = AnyDerivation.IsReproducible(first) && AnyDerivation.IsReproducible(second) && AnyDerivation.IsReproducible(third) && AnyDerivation.IsReproducible(fourth) && AnyDerivation.IsReproducible(fifth);
bool reproducible = AnyDerivation.DrawsOnlyFrom(first, source) && AnyDerivation.DrawsOnlyFrom(second, source) && AnyDerivation.DrawsOnlyFrom(third, source) && AnyDerivation.DrawsOnlyFrom(fourth, source) && AnyDerivation.DrawsOnlyFrom(fifth, source);

return new DerivedAny<TResult>(source, reproducible, () => {
T1 firstValue = first.Generate();
Expand Down Expand Up @@ -748,7 +748,7 @@
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, T6, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, IAny<T6> sixth, Func<T1, T2, T3, T4, T5, T6, TResult> compose) {

Check warning on line 751 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 751 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
Expand All @@ -758,7 +758,7 @@
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth) ?? AnyDerivation.SourceOf(sixth);
bool reproducible = AnyDerivation.IsReproducible(first) && AnyDerivation.IsReproducible(second) && AnyDerivation.IsReproducible(third) && AnyDerivation.IsReproducible(fourth) && AnyDerivation.IsReproducible(fifth) && AnyDerivation.IsReproducible(sixth);
bool reproducible = AnyDerivation.DrawsOnlyFrom(first, source) && AnyDerivation.DrawsOnlyFrom(second, source) && AnyDerivation.DrawsOnlyFrom(third, source) && AnyDerivation.DrawsOnlyFrom(fourth, source) && AnyDerivation.DrawsOnlyFrom(fifth, source) && AnyDerivation.DrawsOnlyFrom(sixth, source);

return new DerivedAny<TResult>(source, reproducible, () => {
T1 firstValue = first.Generate();
Expand Down Expand Up @@ -795,7 +795,7 @@
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters",
Justification = "Heterogeneous composition needs one generator parameter per part; the arity-8 ceiling is a deliberate ergonomic decision (ADR-0015), and a flat parameter list reads better at the call site than nested Combine calls.")]
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, T6, T7, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, IAny<T6> sixth, IAny<T7> seventh, Func<T1, T2, T3, T4, T5, T6, T7, TResult> compose) {

Check warning on line 798 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
Expand All @@ -806,7 +806,7 @@
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth) ?? AnyDerivation.SourceOf(sixth) ?? AnyDerivation.SourceOf(seventh);
bool reproducible = AnyDerivation.IsReproducible(first) && AnyDerivation.IsReproducible(second) && AnyDerivation.IsReproducible(third) && AnyDerivation.IsReproducible(fourth) && AnyDerivation.IsReproducible(fifth) && AnyDerivation.IsReproducible(sixth) && AnyDerivation.IsReproducible(seventh);
bool reproducible = AnyDerivation.DrawsOnlyFrom(first, source) && AnyDerivation.DrawsOnlyFrom(second, source) && AnyDerivation.DrawsOnlyFrom(third, source) && AnyDerivation.DrawsOnlyFrom(fourth, source) && AnyDerivation.DrawsOnlyFrom(fifth, source) && AnyDerivation.DrawsOnlyFrom(sixth, source) && AnyDerivation.DrawsOnlyFrom(seventh, source);

return new DerivedAny<TResult>(source, reproducible, () => {
T1 firstValue = first.Generate();
Expand Down Expand Up @@ -847,7 +847,7 @@
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters",
Justification = "Heterogeneous composition needs one generator parameter per part; the arity-8 ceiling is a deliberate ergonomic decision (ADR-0015), and a flat parameter list reads better at the call site than nested Combine calls.")]
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, IAny<T6> sixth, IAny<T7> seventh, IAny<T8> eighth, Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> compose) {

Check warning on line 850 in JustDummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
Expand All @@ -859,7 +859,7 @@
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth) ?? AnyDerivation.SourceOf(sixth) ?? AnyDerivation.SourceOf(seventh) ?? AnyDerivation.SourceOf(eighth);
bool reproducible = AnyDerivation.IsReproducible(first) && AnyDerivation.IsReproducible(second) && AnyDerivation.IsReproducible(third) && AnyDerivation.IsReproducible(fourth) && AnyDerivation.IsReproducible(fifth) && AnyDerivation.IsReproducible(sixth) && AnyDerivation.IsReproducible(seventh) && AnyDerivation.IsReproducible(eighth);
bool reproducible = AnyDerivation.DrawsOnlyFrom(first, source) && AnyDerivation.DrawsOnlyFrom(second, source) && AnyDerivation.DrawsOnlyFrom(third, source) && AnyDerivation.DrawsOnlyFrom(fourth, source) && AnyDerivation.DrawsOnlyFrom(fifth, source) && AnyDerivation.DrawsOnlyFrom(sixth, source) && AnyDerivation.DrawsOnlyFrom(seventh, source) && AnyDerivation.DrawsOnlyFrom(eighth, source);

return new DerivedAny<TResult>(source, reproducible, () => {
T1 firstValue = first.Generate();
Expand Down
12 changes: 12 additions & 0 deletions JustDummies/AnyDerivation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ internal static bool IsReproducible<T>(IAny<T> generator) {
return SourceOf(generator) is not null;
}

/// <summary>
/// Whether <paramref name="generator" /> is reproducible <b>and</b> draws from <paramref name="source" />
/// specifically — the per-operand condition for a <c>Combine</c>'s full-replay promise. An operand that is
/// individually reproducible but draws from a <i>different</i> seeded source (a second <see cref="Any.WithSeed" />
/// context, or the ambient source alongside a fixed one) leaves the reported seed covering only part of the run,
/// so naming it as a deterministic full replay would over-promise. When the operands do not all draw from the one
/// reported source, the hint is qualified instead — exactly as it is for a foreign operand.
/// </summary>
internal static bool DrawsOnlyFrom<T>(IAny<T> generator, RandomSource? source) {
return IsReproducible(generator) && ReferenceEquals(SourceOf(generator), source);
}

/// <summary>
/// A conservative upper bound on the number of distinct values <paramref name="generator" /> yields, when it
/// advertises one through <see cref="ICardinalityHint{T}" />; <c>null</c> when the domain is unbounded or unknown.
Expand Down