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
2 changes: 1 addition & 1 deletion JustDummies/AnyBoolean.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public bool Generate() {

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)}.");
throw ConflictingAnyConstraintException.AlreadyPinned(applying, _pinnedConstraint, V(_pinned!.Value));
}

return new AnyBoolean(_source, value, applying);
Expand Down
8 changes: 4 additions & 4 deletions JustDummies/AnyChar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public AnyChar OneOf(params char[] values) {
// Re-declaring the SAME constraint is not a contradiction, so it is a no-op rather than a
// conflict: the second declaration asks for exactly what the first already guarantees.
if (string.Equals(_allowedConstraint, constraint, StringComparison.Ordinal)) { return this; }
if (_allowedConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {constraint} because {_allowedConstraint} is already defined."); }
if (_allowedConstraint is not null) { throw ConflictingAnyConstraintException.AlreadyDefined(constraint, _allowedConstraint); }

return Validated(new AnyChar(_source, _charset, _charsetConstraint, _casing, _casingConstraint, values.Distinct().ToArray(), constraint, _excluded), constraint);
}
Expand Down Expand Up @@ -162,7 +162,7 @@ private AnyChar WithCharset(CharacterSet charset, string applying) {
// Re-declaring the SAME constraint is not a contradiction, so it is a no-op rather than a
// conflict: the second declaration asks for exactly what the first already guarantees.
if (string.Equals(_charsetConstraint, applying, StringComparison.Ordinal)) { return this; }
if (_charsetConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {_charsetConstraint} is already defined."); }
if (_charsetConstraint is not null) { throw ConflictingAnyConstraintException.AlreadyDefined(applying, _charsetConstraint); }

return Validated(new AnyChar(_source, charset, applying, _casing, _casingConstraint, _allowed, _allowedConstraint, _excluded), applying);
}
Expand All @@ -171,7 +171,7 @@ private AnyChar WithCasing(LetterCasing casing, string applying) {
// Re-declaring the SAME constraint is not a contradiction, so it is a no-op rather than a
// conflict: the second declaration asks for exactly what the first already guarantees.
if (string.Equals(_casingConstraint, applying, StringComparison.Ordinal)) { return this; }
if (_casingConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {_casingConstraint} is already defined."); }
if (_casingConstraint is not null) { throw ConflictingAnyConstraintException.AlreadyDefined(applying, _casingConstraint); }

return Validated(new AnyChar(_source, _charset, _charsetConstraint, casing, applying, _allowed, _allowedConstraint, _excluded), applying);
}
Expand Down Expand Up @@ -203,7 +203,7 @@ private AnyChar Validated(AnyChar candidate, string applying) {
? "no character remains in the pool the declared constraints allow"
: $"no character {candidate._allowedConstraint} allows satisfies the constraints already defined";

throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {pool}.");
throw ConflictingAnyConstraintException.NoValueRemains(applying, pool);
}

private bool MatchesCharset(char character) {
Expand Down
2 changes: 1 addition & 1 deletion JustDummies/AnyDateTimeOffset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private AnyDateTimeOffset WithOffsetRange(int minMinutes, int maxMinutes, string
if (_offsetDeclared) {
if (_offsetMinMinutes == minMinutes && _offsetMaxMinutes == maxMinutes) { return this; }

throw new ConflictingAnyConstraintException($"Cannot apply {applying} because an offset constraint is already defined.");
throw ConflictingAnyConstraintException.AlreadyDefined(applying, "an offset constraint");
}

// Tighten the instant so local ticks = UtcTicks + offset stay in [0, MaxTicks] for every offset in the range;
Expand Down
8 changes: 1 addition & 7 deletions JustDummies/AnyDerivation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,7 @@ internal static T Invoke<T>(Func<T> invoke, RandomSource? source, bool reproduci
} catch (DummyException) {
throw;
} catch (Exception exception) {
int? seed = source?.Current.Seed;
string message = $"Generation failed: {failure()} ({exception.GetType().Name}: {exception.Message}).";
if (source is not null) {
message += $" {(reproducible ? source.ReplayGuidance(seed!.Value) : source.PartialReplayGuidance(seed!.Value))}";
}

throw new AnyGenerationException(message, seed, exception);
throw AnyGenerationException.FactoryFailed(failure, exception, source, reproducible);
}
}

Expand Down
13 changes: 5 additions & 8 deletions JustDummies/AnyEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public sealed class AnyEnum<TEnum> : IAny<TEnum>, IHasRandomSource, ICardinality
internal static AnyEnum<TEnum> Create(RandomSource source) {
if (source is null) { throw new ArgumentNullException(nameof(source)); }
if (Declared.Length == 0) {
throw new AnyGenerationException($"Cannot generate an arbitrary {typeof(TEnum).Name} value because the enum declares no members.");
throw AnyGenerationException.EnumDeclaresNoMembers(typeof(TEnum).Name);
}

return new AnyEnum<TEnum>(source, Declared, false, null, null, []);
Expand Down Expand Up @@ -174,15 +174,12 @@ public AnyEnum<TEnum> AllowingCombinations() {
if (_combinable) { return this; }

if (!IsFlags) {
throw new ConflictingAnyConstraintException(
$"Cannot apply {constraint} because {typeof(TEnum).Name} is not declared [Flags]: OR-ing its members would produce values the type does not define.");
throw ConflictingAnyConstraintException.EnumIsNotFlags(constraint, typeof(TEnum).Name);
}

int generators = Declared.Count(value => ToUInt64(value) != 0UL);
if (generators > MaxCombinableMembers) {
throw new ConflictingAnyConstraintException(
$"Cannot apply {constraint} because {typeof(TEnum).Name} declares {V(generators)} non-zero members, more than the {V(MaxCombinableMembers)} whose combinations can be enumerated. " +
$"Draw from an explicit set with OneOf(...) instead.");
throw ConflictingAnyConstraintException.TooManyCombinableMembers(constraint, typeof(TEnum).Name, V(generators), V(MaxCombinableMembers));
}

return Validated(new AnyEnum<TEnum>(_source, Combinations, true, _allowed, _allowedConstraint, _excluded), constraint);
Expand Down Expand Up @@ -213,7 +210,7 @@ public AnyEnum<TEnum> OneOf(params TEnum[] values) {
// Re-declaring the SAME constraint is not a contradiction, so it is a no-op rather than a
// conflict: the second declaration asks for exactly what the first already guarantees.
if (string.Equals(_allowedConstraint, constraint, StringComparison.Ordinal)) { return this; }
if (_allowedConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {constraint} because {_allowedConstraint} is already defined."); }
if (_allowedConstraint is not null) { throw ConflictingAnyConstraintException.AlreadyDefined(constraint, _allowedConstraint); }

return Validated(new AnyEnum<TEnum>(_source, _universe, _combinable, values.Distinct().ToArray(), constraint, _excluded), constraint);
}
Expand Down Expand Up @@ -292,7 +289,7 @@ private AnyEnum<TEnum> Validated(AnyEnum<TEnum> candidate, string applying) {
else if (candidate._combinable) { pool = $"no {typeof(TEnum).Name} combination remains available"; }
else { pool = $"no declared {typeof(TEnum).Name} member remains available"; }

throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {pool}.");
throw ConflictingAnyConstraintException.NoValueRemains(applying, pool);
}

}
55 changes: 55 additions & 0 deletions JustDummies/AnyGenerationException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,61 @@ internal static AnyGenerationException ExclusionNudgeExhausted(string typeName,
return NearTheCandidate(typeName, replayGuidance, seed, "The exclusion nudge could not leave the excluded point within the allowed range.");
}

/// <summary>
/// Builds the exception for an enum with no member to draw from — nothing was constrained, the type simply
/// offers nothing.
/// </summary>
internal static AnyGenerationException EnumDeclaresNoMembers(string enumName) {
return new AnyGenerationException($"Cannot generate an arbitrary {enumName} value because the enum declares no members.");
}

/// <summary>
/// Builds the exception for a relative URI whose every component was declared away — no path segment, no query,
/// no fragment, no root — leaving the empty string, which is not a valid URI reference.
/// </summary>
internal static AnyGenerationException EmptyRelativeReference(string replayGuidance, int seed) {
return new AnyGenerationException("A relative URI with exactly 0 path segments and no query, fragment or root is empty, which is not a valid URI reference. " +
$"Add a query, a fragment, Rooted(), or a positive segment count. {replayGuidance}",
seed);
}

/// <summary>
/// Builds the exception for a pattern whose expansion outgrew the generation ceiling, which exists so no
/// pattern can grow the buffer without bound.
/// </summary>
internal static AnyGenerationException PatternExceedsGenerationLimit(int limit) {
return new AnyGenerationException($"The pattern produced a string longer than the {limit}-character generation limit. This ceiling guards against runaway expansion; a pattern can reach it " +
"either through a nested unbounded quantifier (such as \"(a+)+\") or through bounded quantifiers whose product is very large (such as \"(a{1000}){1000}\").");
}

/// <summary>
/// Builds the exception for a pattern every draw of which the .NET engine refused to match — the generator and
/// the engine disagree about the same pattern, which only a degenerate empty-match shape provokes.
/// </summary>
internal static AnyGenerationException PatternVerificationFailed(string attempts) {
return new AnyGenerationException($"Generation failed: after {attempts} attempts, every value the pattern generator built was rejected by the .NET engine for the same pattern. " +
"This happens only for a degenerate pattern whose empty-match behaviour the generator cannot mirror; rewrite it with the supported subset, or generate the value another way.");
}

/// <summary>
/// Builds the exception for a caller-supplied factory or composer that threw, naming what was being generated
/// and how to replay the run.
/// </summary>
/// <remarks>
/// <paramref name="failure" /> stays a thunk all the way in here, and is called once, on this path only:
/// rendering the generated values would run the caller's <c>ToString()</c> and allocate the sentence on every
/// successful draw otherwise — which is every draw a test actually makes.
/// </remarks>
internal static AnyGenerationException FactoryFailed(Func<string> failure, Exception cause, RandomSource? source, bool reproducible) {
int? seed = source?.Current.Seed;
string message = $"Generation failed: {failure()} ({cause.GetType().Name}: {cause.Message}).";
if (source is not null) {
message += $" {(reproducible ? source.ReplayGuidance(seed!.Value) : source.PartialReplayGuidance(seed!.Value))}";
}

return new AnyGenerationException(message, seed, cause);
}

/// <summary>
/// Writes the sentence every near-the-candidate failure shares, and wraps <paramref name="diagnostic" /> as the
/// inner failure so the developer-facing detail travels with the exception rather than in its message.
Expand Down
8 changes: 4 additions & 4 deletions JustDummies/AnyGuid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public AnyGuid OneOf(params Guid[] values) {
// Re-declaring the SAME constraint is not a contradiction, so it is a no-op rather than a
// conflict: the second declaration asks for exactly what the first already guarantees.
if (string.Equals(_allowedConstraint, constraint, StringComparison.Ordinal)) { return this; }
if (_allowedConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {constraint} because {_allowedConstraint} is already defined."); }
if (_allowedConstraint is not null) { throw ConflictingAnyConstraintException.AlreadyDefined(constraint, _allowedConstraint); }

return Validated(new AnyGuid(_source, _pinned, _pinnedConstraint, values.Distinct().ToArray(), constraint, _excluded), constraint);
}
Expand Down Expand Up @@ -183,17 +183,17 @@ private AnyGuid WithExcluded(Guid[] values, string applying) {
private AnyGuid Validated(AnyGuid candidate, string applying) {
if (candidate._pinned is Guid pinned) {
if (candidate._excluded.Contains(pinned)) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {candidate._pinnedConstraint} already pins the value to {V(pinned)}, which the exclusions forbid.");
throw ConflictingAnyConstraintException.PinnedValueExcluded(applying, candidate._pinnedConstraint!, V(pinned));
}
if (candidate._allowed is not null && !candidate._allowed.Contains(pinned)) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {candidate._pinnedConstraint} already pins the value to {V(pinned)}, which {candidate._allowedConstraint} does not allow.");
throw ConflictingAnyConstraintException.PinnedValueNotAllowed(applying, candidate._pinnedConstraint!, V(pinned), candidate._allowedConstraint!);
}

return candidate;
}

if (candidate._effectiveAllowed is not null && candidate._effectiveAllowed.Count == 0) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because no value {candidate._allowedConstraint} allows remains available.");
throw ConflictingAnyConstraintException.NoValueRemains(applying, $"no value {candidate._allowedConstraint} allows remains available");
}

return candidate;
Expand Down
2 changes: 1 addition & 1 deletion JustDummies/AnyOneOf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private AnyOneOf<T> Excluding(IReadOnlyList<T> excluded, string applying) {
? $"it forbids every value {_declaringConstraint} allows that the exclusions already declared leave"
: $"it forbids every value {_declaringConstraint} allows";

throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {emptied}.");
throw ConflictingAnyConstraintException.NoValueRemains(applying, emptied);
}

return new AnyOneOf<T>(_source, survivors, _declared, _declaringConstraint);
Expand Down
3 changes: 1 addition & 2 deletions JustDummies/AnyPattern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,7 @@ private string BuildVerified() {
}
}

throw new AnyGenerationException(
$"Generation failed: after {V(MatchAttemptLimit)} attempts, every value the pattern generator built was rejected by the .NET engine for the same pattern. This happens only for a degenerate pattern whose empty-match behaviour the generator cannot mirror; rewrite it with the supported subset, or generate the value another way.");
throw AnyGenerationException.PatternVerificationFailed(V(MatchAttemptLimit));
}

private AnyGenerationException Exhausted() {
Expand Down
6 changes: 3 additions & 3 deletions JustDummies/CollectionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ internal CollectionState<T> AsDistinct(IEqualityComparer<T>? comparer, string ap
// the same comparer again, or re-declaring distinctness without one, asks for the equality already in
// force and stays a no-op.
if (comparer is not null && _comparer is not null && !ReferenceEquals(comparer, _comparer)) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because a different comparer is already defined by an earlier {applying}.");
throw ConflictingAnyConstraintException.ComparerAlreadyDefined(applying);
}

return Rebuild(_count, true, comparer ?? _comparer, _fixedContaining, _generatedContaining, applying);
Expand Down Expand Up @@ -190,7 +190,7 @@ private void Validate(string applying) {
for (int left = 0; left < _fixedContaining.Count; left++) {
for (int right = left + 1; right < _fixedContaining.Count; right++) {
if (comparer.Equals(_fixedContaining[left], _fixedContaining[right])) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because a distinct collection cannot contain {AnyDerivation.Display(_fixedContaining[left])} more than once.");
throw ConflictingAnyConstraintException.DuplicateInDistinctCollection(applying, AnyDerivation.Display(_fixedContaining[left]));
}
}
}
Expand All @@ -204,7 +204,7 @@ private void Validate(string applying) {
int need = Math.Max(_count.Floor, required);
int fromGenerator = need - FixedOutsideCount() - _generatedContaining.Count;
if (fromGenerator > cardinality) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {Elements(fromGenerator)} required to be distinct exceed the {cardinality.ToString(CultureInfo.InvariantCulture)} distinct value(s) the element generator can produce.");
throw ConflictingAnyConstraintException.DistinctElementsExceedCardinality(applying, Elements(fromGenerator), cardinality.ToString(CultureInfo.InvariantCulture));
}
}
}
Expand Down
Loading
Loading