Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and improve tracing for nested AssertionScopes #1797

Merged
merged 2 commits into from Feb 8, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -865,7 +865,6 @@ public AndConstraint<TAssertions> Contain(IEnumerable<T> expected, string becaus
new EquivalencyValidator().AssertEquality(comparands, context);

string[] failures = scope.Discard();

if (!failures.Any())
{
return new AndWhichConstraint<TAssertions, T>((TAssertions)this, actualItem);
Expand Down
7 changes: 4 additions & 3 deletions Src/FluentAssertions/Equivalency/EquivalencyValidator.cs
Expand Up @@ -22,7 +22,7 @@ public void AssertEquality(Comparands comparands, EquivalencyValidationContext c

if (context.TraceWriter is not null)
{
scope.AddReportable("trace", context.TraceWriter.ToString());
scope.AppendTracing(context.TraceWriter.ToString());
}
}

Expand Down Expand Up @@ -62,13 +62,14 @@ private static void UpdateScopeWithReportableContext(AssertionScope scope, Compa

private void RunStepsUntilEquivalencyIsProven(Comparands comparands, IEquivalencyValidationContext context)
{
using var _ = context.Tracer.WriteBlock(node => node.Description);

foreach (IEquivalencyStep step in AssertionOptions.EquivalencyPlan)
{
var result = step.Handle(comparands, context, this);
context.Tracer.WriteLine(_ => $"Step {step.GetType().Name} returned {result}");

if (result == EquivalencyResult.AssertionCompleted)
{
context.Tracer.WriteLine(_ => $"Equivalency was proven by {step.GetType().Name}");
return;
}
}
Expand Down
Expand Up @@ -18,7 +18,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon
context.Tracer.WriteLine(member =>
{
string strategyName = (strategy == EqualityStrategy.Equals)
? "Equals must be used" : "object overrides Equals";
? $"{expectationType} overrides Equals" : "we are forced to use Equals";

return $"Treating {member.Description} as a value type because {strategyName}.";
});
Expand Down
26 changes: 25 additions & 1 deletion Src/FluentAssertions/Execution/AssertionScope.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using FluentAssertions.Common;
using FluentAssertions.Formatting;
Expand Down Expand Up @@ -30,6 +32,7 @@ public sealed class AssertionScope : IAssertionScope
private Func<string> expectation;
private string fallbackIdentifier = "object";
private bool? succeeded;
private readonly StringBuilder tracing = new();

private sealed class DeferredReportable
{
Expand Down Expand Up @@ -324,6 +327,14 @@ public void AddPreFormattedFailure(string formattedFailureMessage)
assertionStrategy.HandleFailure(formattedFailureMessage);
}

/// <summary>
/// Adds a block of tracing to the scope for reporting when an assertion fails.
/// </summary>
public void AppendTracing(string tracingBlock)
{
tracing.Append(tracingBlock);
}

/// <summary>
/// Tracks a keyed object in the current scope that is excluded from the failure message in case an assertion fails.
/// </summary>
Expand All @@ -350,6 +361,10 @@ public void AddReportable(string key, Func<string> valueFunc)
contextData.Add(new ContextDataItems.DataItem(key, new DeferredReportable(valueFunc), reportable: true, requiresFormatting: false));
}

/// <summary>
/// Returns all failures that happened up to this point and ensures they will not cause
/// <see cref="Dispose"/> to fail the assertion.
/// </summary>
public string[] Discard()
{
return assertionStrategy.DiscardFailures().ToArray();
Expand Down Expand Up @@ -380,11 +395,20 @@ public void Dispose()
parent.assertionStrategy.HandleFailure(failureMessage);
}

parent.AppendTracing(tracing.ToString());

parent = null;
}
else
{
assertionStrategy.ThrowIfAny(contextData.GetReportable());
IDictionary<string,object> reportable = contextData.GetReportable();

if (tracing.Length > 0)
{
reportable.Add("trace", tracing.ToString());
}

assertionStrategy.ThrowIfAny(reportable);
}
}

Expand Down
Expand Up @@ -1255,6 +1255,7 @@ namespace FluentAssertions.Execution
public void AddPreFormattedFailure(string formattedFailureMessage) { }
public void AddReportable(string key, System.Func<string> valueFunc) { }
public void AddReportable(string key, string value) { }
public void AppendTracing(string tracingBlock) { }
public void AssumeSingleCaller() { }
public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { }
public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { }
Expand Down
Expand Up @@ -1255,6 +1255,7 @@ namespace FluentAssertions.Execution
public void AddPreFormattedFailure(string formattedFailureMessage) { }
public void AddReportable(string key, System.Func<string> valueFunc) { }
public void AddReportable(string key, string value) { }
public void AppendTracing(string tracingBlock) { }
public void AssumeSingleCaller() { }
public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { }
public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { }
Expand Down
Expand Up @@ -1255,6 +1255,7 @@ namespace FluentAssertions.Execution
public void AddPreFormattedFailure(string formattedFailureMessage) { }
public void AddReportable(string key, System.Func<string> valueFunc) { }
public void AddReportable(string key, string value) { }
public void AppendTracing(string tracingBlock) { }
public void AssumeSingleCaller() { }
public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { }
public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { }
Expand Down
Expand Up @@ -1207,6 +1207,7 @@ namespace FluentAssertions.Execution
public void AddPreFormattedFailure(string formattedFailureMessage) { }
public void AddReportable(string key, System.Func<string> valueFunc) { }
public void AddReportable(string key, string value) { }
public void AppendTracing(string tracingBlock) { }
public void AssumeSingleCaller() { }
public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { }
public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { }
Expand Down
Expand Up @@ -1255,6 +1255,7 @@ namespace FluentAssertions.Execution
public void AddPreFormattedFailure(string formattedFailureMessage) { }
public void AddReportable(string key, System.Func<string> valueFunc) { }
public void AddReportable(string key, string value) { }
public void AppendTracing(string tracingBlock) { }
public void AssumeSingleCaller() { }
public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { }
public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { }
Expand Down
Expand Up @@ -198,6 +198,33 @@ public void When_collection_does_contain_equivalent_by_including_single_property
collection.Should().ContainEquivalentOf(item, options => options.Including(x => x.Name));
}

[Fact]
public void Tracing_should_be_included_in_the_assertion_output()
{
// Arrange
var collection = new[]
{
new Customer
{
Name = "John",
Age = 18
},
new Customer
{
Name = "Jane",
Age = 18
}
};

var item = new Customer { Name = "John", Age = 21 };

// Act
Action act = () => collection.Should().ContainEquivalentOf(item, options => options.WithTracing());

// Assert
act.Should().Throw<XunitException>().WithMessage("*Equivalency was proven*");
}

[Fact]
public void When_injecting_a_null_config_to_ContainEquivalentOf_it_should_throw()
{
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Expand Up @@ -17,6 +17,7 @@ sidebar:
* Improved the documentation on `BeLowerCased` and `BeUpperCased` for strings with non-alphabetic characters - [#1792](https://github.com/fluentassertions/fluentassertions/pull/1792)
* Caller identification does not handle all arguments using `new` - [#1794](https://github.com/fluentassertions/fluentassertions/pull/1794)
* Resolve an issue preventing `HaveAccessModifier` from correctly recognizing internal interfaces and enums - [#1793](https://github.com/fluentassertions/fluentassertions/issues/1793)
* Improved tracing for nested `AssertionScope`s - [#1797](https://github.com/fluentassertions/fluentassertions/pull/1797)

### Fixes (Extensibility)
* Fixed a continuation issue when using `ClearExpectation` - [#1791](https://github.com/fluentassertions/fluentassertions/pull/1791)
Expand Down