diff --git a/Source/aweXpect.Core/Core/Nodes/WhichNode.cs b/Source/aweXpect.Core/Core/Nodes/WhichNode.cs index 677cefdf4..f6d52bf6d 100644 --- a/Source/aweXpect.Core/Core/Nodes/WhichNode.cs +++ b/Source/aweXpect.Core/Core/Nodes/WhichNode.cs @@ -71,6 +71,10 @@ public override async Task IsMetBy( if (_parent != null) { parentResult = await _parent.IsMetBy(value, context, cancellationToken); + if (parentResult.FurtherProcessingStrategy == FurtherProcessingStrategy.IgnoreCompletely) + { + return parentResult; + } } if (_inner == null) diff --git a/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs b/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs index c2590a619..85d608668 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs @@ -18,9 +18,10 @@ public ThatDelegateThrows Throws() { ThrowsOption throwOptions = new(); return new ThatDelegateThrows(ExpectationBuilder - .AddConstraint((it, grammars) => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) + .AddConstraint((it, grammars) + => new DelegateThrowsWithinTimeoutConstraint(it, grammars, throwOptions)) .ForWhich(d => d.Exception as TException) - .AddConstraint((it, grammars) => new ThrowsConstraint(it, grammars, typeof(TException), throwOptions)) + .AddConstraint((_, _) => new DoNothingConstraint()) .And(" "), throwOptions); } @@ -32,13 +33,156 @@ public ThatDelegateThrows Throws(Type exceptionType) { ThrowsOption throwOptions = new(); return new ThatDelegateThrows(ExpectationBuilder - .AddConstraint((it, grammars) => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) + .AddConstraint((it, grammars) + => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) .ForWhich(d => d.Exception) .AddConstraint((it, grammars) => new ThrowsConstraint(it, grammars, exceptionType, throwOptions)) .And(" "), throwOptions); } + private sealed class DoNothingConstraint() + : ConstraintResult.WithValue(ExpectationGrammars.None), IValueConstraint + { + public ConstraintResult IsMetBy(T actual) + { + Outcome = Outcome.Success; + return this; + } + + protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) + { + // Do nothing + } + + protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) + { + // Do nothing + } + + protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) + { + // Do nothing + } + + protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) + { + // Do nothing + } + } + + private sealed class DelegateThrowsWithinTimeoutConstraint( + string it, + ExpectationGrammars grammars, + ThrowsOption options) + : ConstraintResult(grammars), + IValueConstraint + where TException : Exception + { + private DelegateValue? _actual; + private bool _tookTooLong; + + public ConstraintResult IsMetBy(DelegateValue value) + { + _actual = value; + if (value.IsNull) + { + Outcome = Outcome.Failure; + return this; + } + + if (options.ExecutionTimeOptions is not null && + !options.ExecutionTimeOptions.IsWithinLimit(value.Duration)) + { + _tookTooLong = true; + Outcome = Outcome.Failure; + return this; + } + + if (!options.DoCheckThrow) + { + FurtherProcessingStrategy = FurtherProcessingStrategy.IgnoreCompletely; + Outcome = value.Exception is null ? Outcome.Success : Outcome.Failure; + return this; + } + + if (value.Exception is null) + { + FurtherProcessingStrategy = FurtherProcessingStrategy.IgnoreResult; + } + else if (typeof(TException).IsAssignableFrom(value.Exception.GetType())) + { + Outcome = Outcome.Success; + return this; + } + + Outcome = Outcome.Failure; + return this; + } + + public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null) + { + if (!options.DoCheckThrow) + { + stringBuilder.Append("does not throw any exception"); + } + else if (typeof(TException) == typeof(Exception)) + { + stringBuilder.Append("throws an exception"); + } + else + { + stringBuilder.Append("throws ").Append(Formatter.Format(typeof(TException)).PrependAOrAn()); + } + + if (options.ExecutionTimeOptions is not null) + { + stringBuilder.Append(' '); + options.ExecutionTimeOptions.AppendTo(stringBuilder, "in "); + } + } + + public override void AppendResult(StringBuilder stringBuilder, string? indentation = null) + { + if (_actual?.IsNull != false) + { + stringBuilder.ItWasNull(it); + } + else if (_tookTooLong) + { + stringBuilder.Append(it).Append(" took "); + options.ExecutionTimeOptions?.AppendFailureResult(stringBuilder, _actual.Duration); + } + else if (options.DoCheckThrow && _actual.Exception is null) + { + stringBuilder.Append(it).Append(" did not throw any exception"); + } + else + { + stringBuilder.Append(it).Append(" did throw "); + stringBuilder.Append(FormatForMessage(_actual.Exception)); + } + } + + public override bool TryGetValue([NotNullWhen(true)] out TValue? value) where TValue : default + { + if (_actual is TValue typedValue) + { + value = typedValue; + return true; + } + + value = default; + return typeof(TValue).IsAssignableFrom(typeof(TException)); + } + + public override ConstraintResult Negate() + { + options.DoCheckThrow = !options.DoCheckThrow; + return this; + } + } + private sealed class ThrowsConstraint( string it, ExpectationGrammars grammars,