Skip to content

Commit

Permalink
SAVEPOINT
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisdoomen committed Sep 15, 2024
1 parent 4e57658 commit 54c60ed
Show file tree
Hide file tree
Showing 21 changed files with 322 additions and 187 deletions.
9 changes: 9 additions & 0 deletions Src/FluentAssertions/Common/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using FluentAssertions.Formatting;

namespace FluentAssertions.Common;

Expand Down Expand Up @@ -77,4 +78,12 @@ ushort or
uint or
ulong);
}

/// <summary>
/// Convenience method to format an object to a string using the <see cref="Formatter"/> class.
/// </summary>
public static string ToFormattedString(this object source)
{
return Formatter.ToString(source);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using FluentAssertions.Common;
using FluentAssertions.Execution;
using FluentAssertions.Formatting;
using static System.FormattableString;
Expand Down Expand Up @@ -35,8 +36,7 @@ protected override EquivalencyResult OnHandle(Comparands comparands,
Invariant(
$"Comparing dictionary item {key} at {member.Description} between subject and expectation"));

assertionChain.ReuseOnce();
assertionChain.WithCallerPostfix($"[{Formatter.ToString(key)}]");
assertionChain.WithCallerPostfix($"[{key.ToFormattedString()}]").ReuseOnce();
subject[key].Should().Be(expectation[key], context.Reason.FormattedMessage, context.Reason.Arguments);
}
}
Expand Down
21 changes: 17 additions & 4 deletions Src/FluentAssertions/Execution/AssertionChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public sealed class AssertionChain

// We need to keep track of this because we don't want the second successful assertion hide the first unsuccessful assertion
private Func<string> expectation;
private string callerPostfix = string.Empty;
private string callerPrefix = string.Empty;

private static readonly AsyncLocal<AssertionChain> Instance = new();

Expand Down Expand Up @@ -50,13 +52,13 @@ private AssertionChain(Func<AssertionScope> getCurrentScope, Func<string> getCal
this.getCallerIdentifier = () =>
{
var scopeName = getCurrentScope().Name();
var callerIdentifier = getCallerIdentifier();
var callerIdentifier = callerPrefix + getCallerIdentifier() + callerPostfix;
if (scopeName is null)
{
return callerIdentifier;
}
else if (callerIdentifier is null)
else if (callerIdentifier.Length == 0)
{
return scopeName;
}
Expand Down Expand Up @@ -253,12 +255,21 @@ private Continuation FailWith(Func<string> getFailureReason)
public void OverrideCallerIdentifier(Func<string> getCallerIdentifier)
{
this.getCallerIdentifier = getCallerIdentifier;
HasOverriddenCallerIdentifier = true;
}

public AssertionChain WithCallerPostfix(string postfix)
{
var originalCallerIdentifier = getCallerIdentifier;
getCallerIdentifier = () => originalCallerIdentifier() + postfix;
callerPostfix = postfix;
HasOverriddenCallerIdentifier = true;

return this;
}

public AssertionChain WithCallerPrefix(string prefix)
{
callerPrefix = prefix;
HasOverriddenCallerIdentifier = true;

return this;
}
Expand Down Expand Up @@ -305,4 +316,6 @@ public AssertionChain UsingLineBreaks
return this;
}
}

public bool HasOverriddenCallerIdentifier { get; private set; }
}
1 change: 1 addition & 0 deletions Src/FluentAssertions/Formatting/Formatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static class Formatter
new XElementValueFormatter(),
new XAttributeValueFormatter(),
new PropertyInfoFormatter(),
new MethodInfoFormatter(),
new NullValueFormatter(),
new GuidValueFormatter(),
new DateTimeOffsetValueFormatter(),
Expand Down
31 changes: 31 additions & 0 deletions Src/FluentAssertions/Formatting/MethodInfoFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Reflection;

namespace FluentAssertions.Formatting;

public class MethodInfoFormatter : IValueFormatter
{
/// <summary>
/// Indicates whether the current <see cref="IValueFormatter"/> can handle the specified <paramref name="value"/>.
/// </summary>
/// <param name="value">The value for which to create a <see cref="string"/>.</param>
/// <returns>
/// <see langword="true"/> if the current <see cref="IValueFormatter"/> can handle the specified value; otherwise, <see langword="false"/>.
/// </returns>
public bool CanHandle(object value)
{
return value is MethodInfo;
}

public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild)
{
var method = (MethodInfo)value;
if (method is null)
{
formattedGraph.AddFragment("<null>");
}
else
{
formattedGraph.AddFragment($"{method!.DeclaringType!.Name + "." + method.Name}");
}
}
}
11 changes: 10 additions & 1 deletion Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ public bool CanHandle(object value)

public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild)
{
formattedGraph.AddFragment(((PropertyInfo)value).Name);
var property = (PropertyInfo)value;

if (property is null)
{
formattedGraph.AddFragment("<null>");
}
else
{
formattedGraph.AddFragment($"{property.DeclaringType?.Name ?? string.Empty}.{property.Name}");
}
}
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Types/ConstructorInfoAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal static string GetDescriptionFor(ConstructorInfo constructorInfo)
return $"{constructorInfo.DeclaringType}({GetParameterString(constructorInfo)})";
}

internal override string SubjectDescription => GetDescriptionFor(Subject);
protected override string SubjectDescription => GetDescriptionFor(Subject);

protected override string Identifier => "constructor";
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Types/MemberInfoAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,5 @@ public AndConstraint<TAssertions> NotBeDecoratedWith<TAttribute>(

protected override string Identifier => "member";

internal virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}";
protected virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}";
}
15 changes: 11 additions & 4 deletions Src/FluentAssertions/Types/MethodBaseAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,20 @@ public AndConstraint<TAssertions> HaveAccessModifier(
assertionChain
.BecauseOf(because, becauseArgs)
.ForCondition(Subject is not null)
.FailWith($"Expected method to be {accessModifier}{{reason}}, but {{context:member}} is <null>.");
.FailWith($"Expected method to be {accessModifier}{{reason}}, but {{context:method}} is <null>.");

if (assertionChain.Succeeded)
{
CSharpAccessModifier subjectAccessModifier = Subject.GetCSharpAccessModifier();

var subject = assertionChain.HasOverriddenCallerIdentifier
? assertionChain.CallerIdentifier
: "method " + Subject.ToFormattedString();

assertionChain
.ForCondition(accessModifier == subjectAccessModifier)
.BecauseOf(because, becauseArgs)
.FailWith(
$"Expected method {Subject!.Name} to be {accessModifier}{{reason}}, but it is {subjectAccessModifier}.");
.FailWith($"Expected {subject} to be {accessModifier}{{reason}}, but it is {subjectAccessModifier}.");
}

return new AndConstraint<TAssertions>((TAssertions)this);
Expand Down Expand Up @@ -90,10 +93,14 @@ public AndConstraint<TAssertions> NotHaveAccessModifier(CSharpAccessModifier acc
{
CSharpAccessModifier subjectAccessModifier = Subject.GetCSharpAccessModifier();

var subject = assertionChain.HasOverriddenCallerIdentifier
? assertionChain.CallerIdentifier
: "method " + Subject.ToFormattedString();

assertionChain
.ForCondition(accessModifier != subjectAccessModifier)
.BecauseOf(because, becauseArgs)
.FailWith($"Expected method {Subject!.Name} not to be {accessModifier}{{reason}}, but it is.");
.FailWith($"Expected {subject} not to be {accessModifier}{{reason}}, but it is.");
}

return new AndConstraint<TAssertions>((TAssertions)this);
Expand Down
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Types/MethodInfoAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ internal static string GetDescriptionFor(MethodInfo method)
return $"{returnTypeName} {method.DeclaringType}.{method.Name}";
}

internal override string SubjectDescription => GetDescriptionFor(Subject);
protected override string SubjectDescription => GetDescriptionFor(Subject);

protected override string Identifier => "method";
}
Loading

0 comments on commit 54c60ed

Please sign in to comment.