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 Pipeline/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ partial class Build : NukeBuild
/// <para />
/// Afterward, you can update the package reference in `Directory.Packages.props` and reset this flag.
/// </summary>
readonly BuildScope BuildScope = BuildScope.Default;
readonly BuildScope BuildScope = BuildScope.CoreOnly;

[Parameter("Github Token")] readonly string GithubToken;

Expand Down
11 changes: 11 additions & 0 deletions Source/aweXpect.Core/Core/ExpectationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ protected ExpectationBuilder(string subjectExpression, ExpectationGrammars gramm
ExpectationGrammars = grammars;
}

/// <summary>
/// Initializes the <see cref="ExpectationBuilder" /> with an empty <see cref="Subject"/>.
/// </summary>
private protected ExpectationBuilder()
{
AweXpectInitialization.EnsureInitialized();
Subject = "";
_it = "";
ExpectationGrammars = ExpectationGrammars.None;
}

/// <summary>
/// The expected grammatical form of the expectation text.
/// </summary>
Expand Down
30 changes: 30 additions & 0 deletions Source/aweXpect.Core/Equivalency/EquivalencyComparison.Compare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Core.EvaluationContext;
using aweXpect.Core.Helpers;
using aweXpect.Results;

namespace aweXpect.Equivalency;

Expand Down Expand Up @@ -100,6 +105,31 @@ private static bool Compare<TActual, TExpected>(
MemberType memberType,
EquivalencyContext context)
{
if (expected is Expectation &&
expected is IOptionsProvider<ExpectationBuilder> { Options: EquivalencyExpectationBuilder equivalencyExpectationBuilder })
{
// Unfortunately this cannot be async, as it is used extensively in the `CollectionMatchOptions` :-(
var result = equivalencyExpectationBuilder.IsMetBy(actual, new EvaluationContext(), CancellationToken.None).GetAwaiter().GetResult();
if (result.Outcome == Outcome.Success)
{
return true;
}

if (result.Outcome == Outcome.Failure)
{
failureBuilder.AppendLine();
if (failureBuilder.Length > 2)
{
failureBuilder.AppendLine("and");
}

failureBuilder.Append(" ");
failureBuilder.Append(GetMemberPath(memberType, memberPath));
result.AppendResult(failureBuilder, " ");
return false;
}
}

if (actual is null || expected is null)
{
return CompareNulls(actual, expected, failureBuilder, memberPath, memberType);
Expand Down
124 changes: 124 additions & 0 deletions Source/aweXpect.Core/Equivalency/EquivalencyExpectationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Core.EvaluationContext;
using aweXpect.Core.Helpers;
using aweXpect.Core.Nodes;
using aweXpect.Core.TimeSystem;

namespace aweXpect.Equivalency;

internal class EquivalencyExpectationBuilder<T> : EquivalencyExpectationBuilder
{
private ConstraintResult? _result;

/// <inheritdoc cref="object.ToString()" />
public override string ToString()
{
StringBuilder sb = new();
sb.Append("is ");
Formatter.Format(sb, typeof(T));
sb.Append(" that ");
int lengthBefore = sb.Length;
_result?.AppendExpectation(sb);
if (sb.Length == lengthBefore)
{
// Remove " that ", when no expectations were added
sb.Length -= 6;
}

return sb.ToString();
}

public override async Task<ConstraintResult> IsMetBy(
object? value,
IEvaluationContext context,
CancellationToken cancellationToken)
{
if (value is T typedValue)
{
_result = await GetRootNode().IsMetBy(typedValue, context, cancellationToken);
}
else if (value is null)
{
T? typedDefault = default;
_result = new NotMatchingTypesResult(typedDefault,
await GetRootNode().IsMetBy(typedDefault, context, cancellationToken));
}
else
{
_result = new NotMatchingTypesResult(value, null);
}

return _result;
}

private sealed class NotMatchingTypesResult : ConstraintResult
{
private readonly ConstraintResult? _inner;
private readonly object? _value;

public NotMatchingTypesResult(object? value, ConstraintResult? inner) : base(FurtherProcessingStrategy.Continue)
{
_value = value;
_inner = inner;
Outcome = Outcome.Failure;
}

public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null)
=> _inner?.AppendExpectation(stringBuilder, indentation);

public override void AppendResult(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append(" was ");
Formatter.Format(stringBuilder, _value?.GetType());
}

public override bool TryGetValue<TValue>([NotNullWhen(true)] out TValue? value) where TValue : default
{
if (_value is TValue typedValue)
{
value = typedValue;
return true;
}

value = default;
return false;
}

public override ConstraintResult Negate() => this;
}
}

internal abstract class EquivalencyExpectationBuilder : ExpectationBuilder
{
/// <summary>
/// Appends the expectation of the root node to the <paramref name="stringBuilder" />.
/// </summary>
public void AppendExpectation(StringBuilder stringBuilder, string? indentation = null)
=> GetRootNode().AppendExpectation(stringBuilder, indentation);

/// <summary>
/// Evaluate if the expectations are met by the <paramref name="value" />.
/// </summary>
public abstract Task<ConstraintResult> IsMetBy(
object? value,
IEvaluationContext context,
CancellationToken cancellationToken);

/// <inheritdoc />
internal override Task<ConstraintResult> IsMet(Node rootNode,
EvaluationContext context,
ITimeSystem timeSystem,
TimeSpan? timeout,
CancellationToken cancellationToken)
=> throw new NotSupportedException($"Use {nameof(IsMetBy)} for EquivalencyExpectationBuilder!")
.LogTrace();

/// <inheritdoc cref="ExpectationBuilder.UpdateContexts(Action{ResultContexts})" />
public override ExpectationBuilder UpdateContexts(Action<ResultContexts> callback) => this;
}
43 changes: 43 additions & 0 deletions Source/aweXpect.Core/Equivalency/It.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using aweXpect.Core;
using aweXpect.Results;

namespace aweXpect.Equivalency;

/// <summary>
/// Static class to support equivalence checks on individual properties.
/// </summary>
public static class It
{
/// <summary>
/// Expects the property to be of type <typeparamref name="T" />.
/// </summary>
public static IsEquivalent<T> Is<T>() => new();

/// <summary>
/// Expectations on properties for equivalence.
/// </summary>
public class IsEquivalent<T> : ExpectationResult, IExpectThat<T>
{
/// <inheritdoc cref="IsEquivalent{T}" />
public IsEquivalent() : this(new EquivalencyExpectationBuilder<T>())
{
}

/// <inheritdoc cref="IsEquivalent{T}" />
private IsEquivalent(EquivalencyExpectationBuilder<T> expectationBuilder) : base(expectationBuilder)
{
ExpectationBuilder = expectationBuilder;
}

/// <summary>
/// …that…
/// </summary>
public IThat<T> That => this;

/// <inheritdoc cref="IExpectThat{T}.ExpectationBuilder" />
public ExpectationBuilder ExpectationBuilder { get; }

/// <inheritdoc cref="object.ToString()" />
public override string? ToString() => ExpectationBuilder.ToString();
}
}
21 changes: 19 additions & 2 deletions Source/aweXpect.Core/Results/ExpectationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ namespace aweXpect.Results;
/// The result of an expectation without an underlying value.
/// </summary>
[StackTraceHidden]
public class ExpectationResult(ExpectationBuilder expectationBuilder) : Expectation
public class ExpectationResult(ExpectationBuilder expectationBuilder)
: Expectation, IOptionsProvider<ExpectationBuilder>
{
/// <inheritdoc cref="IOptionsProvider{ExpectationBuilder}.Options" />
ExpectationBuilder IOptionsProvider<ExpectationBuilder>.Options => expectationBuilder;

/// <inheritdoc cref="object.ToString()" />
public override string? ToString()
=> expectationBuilder.ToString();

/// <summary>
/// Provide a <paramref name="reason" /> explaining why the constraint is needed.<br />
/// If the phrase does not start with the word <i>because</i>, it is prepended automatically.
Expand Down Expand Up @@ -133,9 +141,18 @@ public class ExpectationResult<TType>(ExpectationBuilder expectationBuilder)
/// The result of an expectation with an underlying value of type <typeparamref name="TType" />.
/// </summary>
[StackTraceHidden]
public class ExpectationResult<TType, TSelf>(ExpectationBuilder expectationBuilder) : Expectation
public class ExpectationResult<TType, TSelf>(ExpectationBuilder expectationBuilder)
: Expectation,
IOptionsProvider<ExpectationBuilder>
where TSelf : ExpectationResult<TType, TSelf>
{
/// <inheritdoc cref="IOptionsProvider{ExpectationBuilder}.Options" />
ExpectationBuilder IOptionsProvider<ExpectationBuilder>.Options => expectationBuilder;

/// <inheritdoc cref="object.ToString()" />
public override string? ToString()
=> expectationBuilder.ToString();

/// <summary>
/// Provide a <paramref name="reason" /> explaining why the constraint is needed.<br />
/// If the phrase does not start with the word <i>because</i>, it is prepended automatically.
Expand Down
1 change: 0 additions & 1 deletion Source/aweXpect/Equivalency/EquivalencyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ internal static ObjectEqualityOptions<TSubject> Equivalent<TSubject>(this Object
return options;
}


internal static void AddEquivalencyContext(this ExpectationBuilder expectationBuilder,
EquivalencyOptions equivalencyOptions)
=> expectationBuilder.UpdateContexts(contexts => contexts.Add(
Expand Down
17 changes: 15 additions & 2 deletions Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,17 @@ namespace aweXpect.Equivalency
Internal = 4,
Private = 8,
}
public static class It
{
public static aweXpect.Equivalency.It.IsEquivalent<T> Is<T>() { }
public class IsEquivalent<T> : aweXpect.Results.ExpectationResult, aweXpect.Core.IExpectThat<T>, aweXpect.Core.IThat<T>
{
public IsEquivalent() { }
public aweXpect.Core.ExpectationBuilder ExpectationBuilder { get; }
public aweXpect.Core.IThat<T> That { get; }
public override string? ToString() { }
}
}
}
namespace aweXpect
{
Expand Down Expand Up @@ -1076,11 +1087,12 @@ namespace aweXpect.Results
}
}
[System.Diagnostics.StackTraceHidden]
public class ExpectationResult : aweXpect.Results.Expectation
public class ExpectationResult : aweXpect.Results.Expectation, aweXpect.Core.IOptionsProvider<aweXpect.Core.ExpectationBuilder>
{
public ExpectationResult(aweXpect.Core.ExpectationBuilder expectationBuilder) { }
public aweXpect.Results.ExpectationResult Because(string reason) { }
public System.Runtime.CompilerServices.TaskAwaiter GetAwaiter() { }
public override string? ToString() { }
public aweXpect.Results.ExpectationResult WithCancellation(System.Threading.CancellationToken cancellationToken) { }
public aweXpect.Results.ExpectationResult WithTimeout(System.TimeSpan timeout) { }
}
Expand All @@ -1089,13 +1101,14 @@ namespace aweXpect.Results
public ExpectationResult(aweXpect.Core.ExpectationBuilder expectationBuilder) { }
}
[System.Diagnostics.StackTraceHidden]
public class ExpectationResult<TType, TSelf> : aweXpect.Results.Expectation
public class ExpectationResult<TType, TSelf> : aweXpect.Results.Expectation, aweXpect.Core.IOptionsProvider<aweXpect.Core.ExpectationBuilder>
where TSelf : aweXpect.Results.ExpectationResult<TType, TSelf>
{
public ExpectationResult(aweXpect.Core.ExpectationBuilder expectationBuilder) { }
public TSelf Because(string reason) { }
[System.Diagnostics.StackTraceHidden]
public System.Runtime.CompilerServices.TaskAwaiter<TType> GetAwaiter() { }
public override string? ToString() { }
public TSelf WithCancellation(System.Threading.CancellationToken cancellationToken) { }
public TSelf WithTimeout(System.TimeSpan timeout) { }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,17 @@ namespace aweXpect.Equivalency
Internal = 4,
Private = 8,
}
public static class It
{
public static aweXpect.Equivalency.It.IsEquivalent<T> Is<T>() { }
public class IsEquivalent<T> : aweXpect.Results.ExpectationResult, aweXpect.Core.IExpectThat<T>, aweXpect.Core.IThat<T>
{
public IsEquivalent() { }
public aweXpect.Core.ExpectationBuilder ExpectationBuilder { get; }
public aweXpect.Core.IThat<T> That { get; }
public override string? ToString() { }
}
}
}
namespace aweXpect
{
Expand Down Expand Up @@ -1059,11 +1070,12 @@ namespace aweXpect.Results
}
}
[System.Diagnostics.StackTraceHidden]
public class ExpectationResult : aweXpect.Results.Expectation
public class ExpectationResult : aweXpect.Results.Expectation, aweXpect.Core.IOptionsProvider<aweXpect.Core.ExpectationBuilder>
{
public ExpectationResult(aweXpect.Core.ExpectationBuilder expectationBuilder) { }
public aweXpect.Results.ExpectationResult Because(string reason) { }
public System.Runtime.CompilerServices.TaskAwaiter GetAwaiter() { }
public override string? ToString() { }
public aweXpect.Results.ExpectationResult WithCancellation(System.Threading.CancellationToken cancellationToken) { }
public aweXpect.Results.ExpectationResult WithTimeout(System.TimeSpan timeout) { }
}
Expand All @@ -1072,13 +1084,14 @@ namespace aweXpect.Results
public ExpectationResult(aweXpect.Core.ExpectationBuilder expectationBuilder) { }
}
[System.Diagnostics.StackTraceHidden]
public class ExpectationResult<TType, TSelf> : aweXpect.Results.Expectation
public class ExpectationResult<TType, TSelf> : aweXpect.Results.Expectation, aweXpect.Core.IOptionsProvider<aweXpect.Core.ExpectationBuilder>
where TSelf : aweXpect.Results.ExpectationResult<TType, TSelf>
{
public ExpectationResult(aweXpect.Core.ExpectationBuilder expectationBuilder) { }
public TSelf Because(string reason) { }
[System.Diagnostics.StackTraceHidden]
public System.Runtime.CompilerServices.TaskAwaiter<TType> GetAwaiter() { }
public override string? ToString() { }
public TSelf WithCancellation(System.Threading.CancellationToken cancellationToken) { }
public TSelf WithTimeout(System.TimeSpan timeout) { }
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/aweXpect.Core.Tests/Results/ExpectationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task GetType_ShouldForwardToBase()
}

[Fact]
public async Task ToString_ShouldForwardToBase()
public async Task ToString_ShouldForwardToExpectationBuilder()
{
#pragma warning disable aweXpect0001
Expectation sut = That(true).IsTrue();
Expand All @@ -52,6 +52,6 @@ public async Task ToString_ShouldForwardToBase()
string? result = sut.ToString();

await That(result)
.IsEqualTo("aweXpect.Results.AndOrResult`2[System.Boolean,aweXpect.Core.IThat`1[System.Boolean]]");
.IsEqualTo("aweXpect.Core.ExpectationBuilder`1[System.Boolean]");
}
}
Loading
Loading