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
8 changes: 4 additions & 4 deletions Dummies.UnitTests/AnyCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ public void DistinctOverAWideDomain() {
[Fact(DisplayName = "Distinct: a count beyond the element cardinality conflicts eagerly, naming the shortfall.")]
public void DistinctCardinalityConflictsEagerly() {
ConflictingAnyConstraintException fromBool = Assert.Throws<ConflictingAnyConstraintException>(
() => Any.SetOf(Any.Bool()).WithCount(3));
() => Any.SetOf(Any.Boolean()).WithCount(3));
Check.That(fromBool.Message).Contains("2 distinct value");

Check.ThatCode(() => Any.SetOf(Any.Enum<Suit>()).WithMinCount(5)).Throws<ConflictingAnyConstraintException>();
Check.ThatCode(() => Any.SetOf(Any.Int32().Between(1, 3)).WithCount(5)).Throws<ConflictingAnyConstraintException>();
Check.ThatCode(() => Any.ListOf(Any.Int32().Between(1, 3)).WithCount(5).Distinct()).Throws<ConflictingAnyConstraintException>();
// Order-independent: turning distinct on after the count is set conflicts just the same.
Check.ThatCode(() => Any.ListOf(Any.Bool()).WithCount(3).Distinct()).Throws<ConflictingAnyConstraintException>();
Check.ThatCode(() => Any.ListOf(Any.Boolean()).WithCount(3).Distinct()).Throws<ConflictingAnyConstraintException>();
}

[Fact(DisplayName = "Distinct: an unknowable small domain cannot be detected early, so a shortfall surfaces at generation.")]
Expand Down Expand Up @@ -228,7 +228,7 @@ public void ContainingAnyDefersToGeneration() {
// When every source draws from the same two-value domain, three distinct values are impossible — but the
// overlap is opaque, so it is caught while drawing (a replayable AnyGenerationException) rather than as a
// false eager conflict.
Check.ThatCode(() => Any.SetOf(Any.Bool()).ContainingAny(Any.Bool()).ContainingAny(Any.Bool()).ContainingAny(Any.Bool()).Generate())
Check.ThatCode(() => Any.SetOf(Any.Boolean()).ContainingAny(Any.Boolean()).ContainingAny(Any.Boolean()).ContainingAny(Any.Boolean()).Generate())
.Throws<AnyGenerationException>();
}

Expand Down Expand Up @@ -315,7 +315,7 @@ public void DictionaryOfBehaves() {
Check.That(dictionary.Values).ContainsOnlyElementsThatMatch(value => value.Length > 0);
}

Check.ThatCode(() => Any.DictionaryOf(Any.Bool(), Any.Int32()).WithCount(3)).Throws<ConflictingAnyConstraintException>();
Check.ThatCode(() => Any.DictionaryOf(Any.Boolean(), Any.Int32()).WithCount(3)).Throws<ConflictingAnyConstraintException>();
Check.ThatCode(() => Any.DictionaryOf<int, int>(null!, Any.Int32())).Throws<ArgumentNullException>();
}

Expand Down
16 changes: 8 additions & 8 deletions Dummies.UnitTests/AnySetTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,24 @@ private enum OrderStatus {

}

[Fact(DisplayName = "Bool: unconstrained draws hit both values; pins pin; contradictory pins conflict.")]
public void BoolBehaves() {
[Fact(DisplayName = "Boolean: unconstrained draws hit both values; pins pin; contradictory pins conflict.")]
public void BooleanBehaves() {
HashSet<bool> seen = new();
for (int i = 0; i < SampleCount; i++) { seen.Add(Any.Bool().Generate()); }
for (int i = 0; i < SampleCount; i++) { seen.Add(Any.Boolean().Generate()); }
Check.That(seen.Count).IsEqualTo(2);

for (int i = 0; i < SampleCount; i++) {
Check.That(Any.Bool().True().Generate()).IsTrue();
Check.That(Any.Bool().False().Generate()).IsFalse();
Check.That(Any.Bool().DifferentFrom(true).Generate()).IsFalse();
Check.That(Any.Boolean().True().Generate()).IsTrue();
Check.That(Any.Boolean().False().Generate()).IsFalse();
Check.That(Any.Boolean().DifferentFrom(true).Generate()).IsFalse();
}

ConflictingAnyConstraintException conflict = Assert.Throws<ConflictingAnyConstraintException>(
() => Any.Bool().True().False());
() => Any.Boolean().True().False());
Check.That(conflict.Message).Contains("False()");
Check.That(conflict.Message).Contains("True()");

bool value = Any.Bool().True().Generate();
bool value = Any.Boolean().True().Generate();
Check.That(value).IsTrue();
}

Expand Down
56 changes: 56 additions & 0 deletions Dummies.UnitTests/FactoryNamingConventionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#region Usings declarations

using System.Reflection;

using NFluent;

#endregion

namespace Dummies.UnitTests;

/// <summary>
/// Locks the factory-naming rule recorded in ADR-0031: every parameterless, type-named scalar factory
/// on <see cref="Any" /> is named after the CLR type it produces — which is also the name of its
/// <c>Any{ClrType}</c> builder. This is the guard that would have caught the <c>Bool</c>/<c>AnyBool</c>
/// deviation before release. The <see cref="Any" />↔<see cref="AnyContext" /> mirror itself is guarded
/// separately by <c>SurfaceParityTests</c>.
/// </summary>
public sealed class FactoryNamingConventionTests {

// The type-named scalar factories are exactly Any's public, static, non-generic, parameterless methods
// whose return type is a builder (implements IAny<T>). StringMatching (parameters), Enum<T> (generic),
// the collection/composition factories (generic, parameterized) and WithSeed/Reproducibly (not builders)
// fall out by construction, so no hand-maintained allow-list can drift out of sync with the surface.
private static IEnumerable<MethodInfo> ScalarFactories() {
return typeof(Any).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(method => !method.IsGenericMethod
&& method.GetParameters().Length == 0
&& ElementTypeOf(method.ReturnType) is not null);
}

// The T of the single IAny<T> a builder implements, or null when the type is not a builder.
private static Type? ElementTypeOf(Type builder) {
return builder.GetInterfaces()
.FirstOrDefault(candidate => candidate.IsGenericType && candidate.GetGenericTypeDefinition() == typeof(IAny<>))
?.GetGenericArguments()[0];
}

[Fact(DisplayName = "Every type-named scalar factory, and its builder, is named after the CLR type it produces.")]
public void FactoriesAreNamedAfterTheirClrType() {
List<MethodInfo> factories = ScalarFactories().ToList();

// Guards the reflection itself: were the query ever to match nothing, every assertion below would pass vacuously.
Check.That(factories.Count).IsStrictlyGreaterThan(15);

foreach (MethodInfo factory in factories) {
Type builder = factory.ReturnType;
string clrName = ElementTypeOf(builder)!.Name;

Check.WithCustomMessage($"Any.{factory.Name}() returns {builder.Name} (IAny<{clrName}>); the factory must be named '{clrName}', after the CLR type it produces.")
.That(factory.Name).IsEqualTo(clrName);
Check.WithCustomMessage($"The builder for {clrName} is named '{builder.Name}'; it must be 'Any{clrName}' to match the CLR type.")
.That(builder.Name).IsEqualTo("Any" + clrName);
}
}

}
2 changes: 1 addition & 1 deletion Dummies.UnitTests/SeedReproducibilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private static string Batch() {
ulong unsigned = Any.UInt64().Generate();
double real = Any.Double().Between(0d, 1000d).Generate();
decimal exact = Any.Decimal().Between(0m, 1000m).Generate();
bool flag = Any.Bool().Generate();
bool flag = Any.Boolean().Generate();
Guid id = Any.Guid().Generate();
char letter = Any.Char().Generate();
TimeSpan span = Any.TimeSpan().Generate();
Expand Down
2 changes: 1 addition & 1 deletion Dummies.UnitTests/SurfaceParityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public static IEnumerable<object[]> Builders() {
yield return [typeof(AnyDateTimeOffset), InstantAlgebra];

// The remaining scalar builders each carry their own deliberate set.
yield return [typeof(AnyBool), new[] { "True", "False", "DifferentFrom" }];
yield return [typeof(AnyBoolean), new[] { "True", "False", "DifferentFrom" }];
yield return [typeof(AnyGuid), new[] { "NonEmpty", "Empty", "OneOf", "Except", "DifferentFrom" }];
yield return [typeof(AnyEnum<DayOfWeek>), new[] { "OneOf", "Except", "DifferentFrom" }];
yield return [typeof(AnyChar), new[] { "Alpha", "AlphaNumeric", "Numeric", "UpperCase", "LowerCase", "OneOf", "Except", "DifferentFrom" }];
Expand Down
4 changes: 2 additions & 2 deletions Dummies/Any.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,8 @@
/// flip unless pinned with <c>True()</c> or <c>False()</c>.
/// </summary>
/// <returns>A generator to constrain fluently.</returns>
public static AnyBool Bool() {
return AnyBool.Create(AmbientRandomSource.Instance);
public static AnyBoolean Boolean() {
return AnyBoolean.Create(AmbientRandomSource.Instance);
}

/// <summary>
Expand Down Expand Up @@ -467,7 +467,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, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, Func<T1, T2, T3, TResult> compose) {

Check warning on line 470 in Dummies/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 470 in Dummies/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 Down Expand Up @@ -499,7 +499,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 502 in Dummies/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 502 in Dummies/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 Down Expand Up @@ -535,7 +535,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 538 in Dummies/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 538 in Dummies/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 Down Expand Up @@ -575,7 +575,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 578 in Dummies/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 578 in Dummies/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 Down Expand Up @@ -621,7 +621,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 624 in Dummies/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 Down Expand Up @@ -672,7 +672,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 675 in Dummies/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 Down
18 changes: 9 additions & 9 deletions Dummies/AnyBool.cs → Dummies/AnyBoolean.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ namespace Dummies;
/// mostly useful for symmetry when a test sweeps cases — and contradictory pins fail eagerly with a
/// <see cref="ConflictingAnyConstraintException" /> naming both sides, like every other generator.
/// </summary>
public sealed class AnyBool : IAny<bool>, IHasRandomSource, ICardinalityHint<bool> {
public sealed class AnyBoolean : IAny<bool>, IHasRandomSource, ICardinalityHint<bool> {

#region Statics members declarations

internal static AnyBool Create(RandomSource source) {
return new AnyBool(source, null, null);
internal static AnyBoolean Create(RandomSource source) {
return new AnyBoolean(source, null, null);
}

private static string V(bool value) {
Expand All @@ -27,7 +27,7 @@ private static string V(bool value) {

#endregion

private AnyBool(RandomSource source, bool? pinned, string? pinnedConstraint) {
private AnyBoolean(RandomSource source, bool? pinned, string? pinnedConstraint) {
_source = source;
_pinned = pinned;
_pinnedConstraint = pinnedConstraint;
Expand All @@ -44,14 +44,14 @@ private AnyBool(RandomSource source, bool? pinned, string? pinnedConstraint) {
/// <summary>Pins the value to <c>true</c>.</summary>
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyBool True() {
public AnyBoolean True() {
return Pin(true, "True()");
}

/// <summary>Pins the value to <c>false</c>.</summary>
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyBool False() {
public AnyBoolean False() {
return Pin(false, "False()");
}

Expand All @@ -62,7 +62,7 @@ public AnyBool False() {
/// <param name="value">The value the generated value must differ from.</param>
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyBool DifferentFrom(bool value) {
public AnyBoolean DifferentFrom(bool value) {
return Pin(!value, $"DifferentFrom({V(value)})");
}

Expand All @@ -71,12 +71,12 @@ public bool Generate() {
return _pinned ?? _source.Current.Random.Next(2) == 0;
}

private AnyBool Pin(bool value, string applying) {
private AnyBoolean Pin(bool value, string applying) {
if (_pinnedConstraint is not null && _pinned != value) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {_pinnedConstraint} already pins the value to {V(_pinned!.Value)}.");
}

return new AnyBool(_source, value, applying);
return new AnyBoolean(_source, value, applying);
}

}
4 changes: 2 additions & 2 deletions Dummies/AnyContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ public AnyDecimal Decimal() {
/// flip unless pinned with <c>True()</c> or <c>False()</c>.
/// </summary>
/// <returns>A generator to constrain fluently.</returns>
public AnyBool Bool() {
return AnyBool.Create(_source);
public AnyBoolean Boolean() {
return AnyBoolean.Create(_source);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Dummies/README.nuget.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ matter — and that is the point.
`.Generate()`, across the .NET simple types: `String`, `Char`, every integer
width (`SByte`/`Byte`/`Int16`/`UInt16`/`Int32`/`UInt32`/`Int64`/`UInt64`),
`Double`/`Single`/`Decimal` (finite values only — never NaN or infinities),
`Bool`, `Guid`, `Enum<T>` (declared members only), `TimeSpan`, `DateTime` (UTC)
`Boolean`, `Guid`, `Enum<T>` (declared members only), `TimeSpan`, `DateTime` (UTC)
and `DateTimeOffset`. On modern targets (`net8.0`) the surface extends to
`DateOnly`, `TimeOnly`, `Int128`, `UInt128` and `Half`; the package also targets
`netstandard2.0` for the widest reach.
Expand Down
Loading