Skip to content

Commit

Permalink
Merge pull request #1797 from dennisdoomen/ImproveTracing
Browse files Browse the repository at this point in the history
Fix and improve tracing for nested AssertionScopes
  • Loading branch information
dennisdoomen committed Feb 8, 2022
2 parents 1cb52cb + b8c6fa7 commit 0aafd31
Show file tree
Hide file tree
Showing 11 changed files with 63 additions and 6 deletions.
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

0 comments on commit 0aafd31

Please sign in to comment.