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
47 changes: 36 additions & 11 deletions src/TestFramework/TestFramework/Assertions/Assert.IsNull.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.ComponentModel;
Expand All @@ -22,9 +22,11 @@ public sealed partial class Assert
public readonly struct AssertIsNullInterpolatedStringHandler
{
private readonly StringBuilder? _builder;
private readonly object? _value;

public AssertIsNullInterpolatedStringHandler(int literalLength, int formattedCount, object? value, out bool shouldAppend)
{
_value = value;
shouldAppend = IsNullFailing(value);
if (shouldAppend)
{
Expand All @@ -36,8 +38,7 @@ internal void ComputeAssertion(string valueExpression)
{
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " ");
ReportAssertIsNullFailed(_builder.ToString());
ReportAssertIsNullFailed(_value, _builder.ToString(), valueExpression);
}
}

Expand Down Expand Up @@ -90,8 +91,7 @@ internal void ComputeAssertion(string valueExpression)
{
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " ");
ReportAssertIsNotNullFailed(_builder.ToString());
ReportAssertIsNotNullFailed(_builder.ToString(), valueExpression, "value");
}
}

Expand Down Expand Up @@ -152,14 +152,27 @@ public static void IsNull(object? value, string? message = "", [CallerArgumentEx
{
if (IsNullFailing(value))
{
ReportAssertIsNullFailed(BuildUserMessageForValueExpression(message, valueExpression));
ReportAssertIsNullFailed(value, message, valueExpression);
}
}

private static bool IsNullFailing(object? value) => value is not null;

private static void ReportAssertIsNullFailed(string? message)
=> ReportAssertFailed("Assert.IsNull", message);
[DoesNotReturn]
private static void ReportAssertIsNullFailed(object? value, string? message, string valueExpression)
{
string actualValue = AssertionValueRenderer.RenderValue(value);
Comment thread
Evangelink marked this conversation as resolved.
EvidenceBlock evidence = EvidenceBlock.Create()
.AddLine("actual:", actualValue);

StructuredAssertionMessage structured = new(FrameworkMessages.IsNullFailedSummary);
structured.WithUserMessage(message);
structured.WithEvidence(evidence);
structured.WithExpectedAndActual(AssertionValueRenderer.RenderValue(null), actualValue);
structured.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsNull", valueExpression, nameof(value)));

ReportAssertFailed(structured);
}

/// <inheritdoc cref="IsNull(object?, string, string)" />
#pragma warning disable IDE0060 // Remove unused parameter - https://github.com/dotnet/roslyn/issues/76578
Expand Down Expand Up @@ -191,13 +204,25 @@ public static void IsNotNull([NotNull] object? value, string? message = "", [Cal
{
if (IsNotNullFailing(value))
{
ReportAssertIsNotNullFailed(BuildUserMessageForValueExpression(message, valueExpression));
ReportAssertIsNotNullFailed(message, valueExpression, nameof(value));
}
}

private static bool IsNotNullFailing([NotNullWhen(false)] object? value) => value is null;

[DoesNotReturn]
private static void ReportAssertIsNotNullFailed(string? message)
=> ReportAssertFailed("Assert.IsNotNull", message);
private static void ReportAssertIsNotNullFailed(string? message, string valueExpression, string paramName)
{
string actualValue = AssertionValueRenderer.RenderValue(null);
EvidenceBlock evidence = EvidenceBlock.Create()
.AddLine("actual:", actualValue);

StructuredAssertionMessage structured = new(FrameworkMessages.IsNotNullFailedSummary);
Comment thread
Evangelink marked this conversation as resolved.
structured.WithUserMessage(message);
structured.WithEvidence(evidence);
structured.WithExpectedAndActual("not null", actualValue);
structured.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsNotNull", valueExpression, paramName));

Comment thread
Evangelink marked this conversation as resolved.
ReportAssertFailed(structured);
}
}
49 changes: 38 additions & 11 deletions src/TestFramework/TestFramework/Assertions/Assert.IsTrue.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.ComponentModel;
Expand All @@ -22,9 +22,11 @@ public sealed partial class Assert
public readonly struct AssertIsTrueInterpolatedStringHandler
{
private readonly StringBuilder? _builder;
private readonly bool? _condition;

public AssertIsTrueInterpolatedStringHandler(int literalLength, int formattedCount, bool? condition, out bool shouldAppend)
{
_condition = condition;
shouldAppend = IsTrueFailing(condition);
if (shouldAppend)
{
Expand All @@ -36,8 +38,7 @@ internal void ComputeAssertion(string conditionExpression)
{
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "condition", conditionExpression) + " ");
ReportAssertIsTrueFailed(_builder.ToString());
ReportAssertIsTrueFailed(_condition, _builder.ToString(), conditionExpression);
}
}

Expand Down Expand Up @@ -74,9 +75,11 @@ internal void ComputeAssertion(string conditionExpression)
public readonly struct AssertIsFalseInterpolatedStringHandler
{
private readonly StringBuilder? _builder;
private readonly bool? _condition;

public AssertIsFalseInterpolatedStringHandler(int literalLength, int formattedCount, bool? condition, out bool shouldAppend)
{
_condition = condition;
shouldAppend = IsFalseFailing(condition);
if (shouldAppend)
{
Expand All @@ -88,8 +91,7 @@ internal void ComputeAssertion(string conditionExpression)
{
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "condition", conditionExpression) + " ");
ReportAssertIsFalseFailed(_builder.ToString());
ReportAssertIsFalseFailed(_condition, _builder.ToString(), conditionExpression);
}
}

Expand Down Expand Up @@ -150,15 +152,28 @@ public static void IsTrue([DoesNotReturnIf(false)] bool? condition, string? mess
{
if (IsTrueFailing(condition))
{
ReportAssertIsTrueFailed(BuildUserMessageForConditionExpression(message, conditionExpression));
ReportAssertIsTrueFailed(condition, message, conditionExpression);
}
}

private static bool IsTrueFailing(bool? condition)
=> condition is false or null;

private static void ReportAssertIsTrueFailed(string? message)
=> ReportAssertFailed("Assert.IsTrue", message);
[DoesNotReturn]
private static void ReportAssertIsTrueFailed(bool? condition, string? message, string conditionExpression)
{
string actualValue = AssertionValueRenderer.RenderValue(condition);
EvidenceBlock evidence = EvidenceBlock.Create()
.AddLine("actual:", actualValue);

StructuredAssertionMessage structured = new(FrameworkMessages.IsTrueFailedSummary);
structured.WithUserMessage(message);
structured.WithEvidence(evidence);
structured.WithExpectedAndActual(AssertionValueRenderer.RenderValue(true), actualValue);
structured.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsTrue", conditionExpression, nameof(condition)));
Comment thread
Evangelink marked this conversation as resolved.

ReportAssertFailed(structured);
}

/// <inheritdoc cref="IsFalse(bool?, string, string)" />
#pragma warning disable IDE0060 // Remove unused parameter - https://github.com/dotnet/roslyn/issues/76578
Expand Down Expand Up @@ -188,14 +203,26 @@ public static void IsFalse([DoesNotReturnIf(true)] bool? condition, string? mess
{
if (IsFalseFailing(condition))
{
ReportAssertIsFalseFailed(BuildUserMessageForConditionExpression(message, conditionExpression));
ReportAssertIsFalseFailed(condition, message, conditionExpression);
}
}

private static bool IsFalseFailing(bool? condition)
=> condition is true or null;

[DoesNotReturn]
private static void ReportAssertIsFalseFailed(string userMessage)
=> ReportAssertFailed("Assert.IsFalse", userMessage);
private static void ReportAssertIsFalseFailed(bool? condition, string? message, string conditionExpression)
{
string actualValue = AssertionValueRenderer.RenderValue(condition);
EvidenceBlock evidence = EvidenceBlock.Create()
.AddLine("actual:", actualValue);

Comment thread
Evangelink marked this conversation as resolved.
StructuredAssertionMessage structured = new(FrameworkMessages.IsFalseFailedSummary);
structured.WithUserMessage(message);
structured.WithEvidence(evidence);
structured.WithExpectedAndActual(AssertionValueRenderer.RenderValue(false), actualValue);
structured.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsFalse", conditionExpression, nameof(condition)));

ReportAssertFailed(structured);
}
}
23 changes: 20 additions & 3 deletions src/TestFramework/TestFramework/Assertions/Assert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,26 @@ internal static void ThrowAssertFailed(StructuredAssertionMessage structuredMess
throw CreateAssertFailedException(structuredMessage);
}

/// <summary>
/// Formats a call-site expression for display at the bottom of a structured assertion message.
/// When the expression is empty, the call-site is omitted. When the expression contains newlines,
/// it is replaced with a <c>&lt;paramName&gt;</c> placeholder.
/// </summary>
internal static string? FormatCallSiteExpression(string assertionMethodName, string expression, string paramName)
{
if (string.IsNullOrWhiteSpace(expression))
{
return null;
}

// If expression contains newlines (multiline constant), replace with placeholder per RFC
string arg = expression.IndexOf('\n') >= 0 || expression.IndexOf('\r') >= 0
? $"<{paramName}>"
: expression;

return $"{assertionMethodName}({arg})";
}

private static string FormatAssertionFailed(string assertionName, string? message)
{
string failedMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.AssertionFailed, assertionName);
Expand Down Expand Up @@ -240,9 +260,6 @@ private static string BuildUserMessageForThreeExpressions(string? format, string
: $"{callerArgMessagePart} {userMessage}";
}

private static string BuildUserMessageForConditionExpression(string? format, string conditionExpression)
=> BuildUserMessageForSingleExpression(format, conditionExpression, "condition");

private static string BuildUserMessageForValueExpression(string? format, string valueExpression)
=> BuildUserMessageForSingleExpression(format, valueExpression, "value");

Expand Down
14 changes: 13 additions & 1 deletion src/TestFramework/TestFramework/Resources/FrameworkMessages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,16 @@ Actual: {2}</value>
<data name="STATestMethodNonWindowsNotSupported" xml:space="preserve">
<value>[STATestMethod] is not supported on non-Windows platforms. STA (Single Threaded Apartment) is a Windows-only COM threading concept. Use [OSCondition(OperatingSystems.Windows)] to skip this test on non-Windows platforms.</value>
</data>
</root>
<data name="IsTrueFailedSummary" xml:space="preserve">
<value>Expected condition to be true.</value>
</data>
<data name="IsFalseFailedSummary" xml:space="preserve">
<value>Expected condition to be false.</value>
</data>
<data name="IsNullFailedSummary" xml:space="preserve">
<value>Expected value to be null.</value>
</data>
<data name="IsNotNullFailedSummary" xml:space="preserve">
<value>Expected value to not be null.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ Skutečnost: {2}</target>
<target state="translated">Neplatná adresa URL lístku GitHubu</target>
<note />
</trans-unit>
<trans-unit id="IsFalseFailedSummary">
<source>Expected condition to be false.</source>
<target state="new">Expected condition to be false.</target>
<note />
</trans-unit>
<trans-unit id="IsInRangeFail">
<source>Value '{0}' is not within the expected range [{1}..{2}]. {3}</source>
<target state="translated">Hodnota {0} není v očekávaném rozsahu [{1}..{2}]. {3}</target>
Expand Down Expand Up @@ -317,6 +322,21 @@ Skutečnost: {2}</target>
<target state="translated">Řetězec „{0}“ odpovídá vzoru „{1}“. {2}</target>
<note />
</trans-unit>
<trans-unit id="IsNotNullFailedSummary">
<source>Expected value to not be null.</source>
<target state="new">Expected value to not be null.</target>
<note />
</trans-unit>
<trans-unit id="IsNullFailedSummary">
<source>Expected value to be null.</source>
<target state="new">Expected value to be null.</target>
<note />
</trans-unit>
<trans-unit id="IsTrueFailedSummary">
<source>Expected condition to be true.</source>
<target state="new">Expected condition to be true.</target>
<note />
</trans-unit>
<trans-unit id="PrivateAccessorMemberNotFound">
<source>
The member specified ({0}) could not be found. You might need to regenerate your private accessor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ Tatsächlich: {2}</target>
<target state="translated">Ungültige GitHub-Ticket-URL.</target>
<note />
</trans-unit>
<trans-unit id="IsFalseFailedSummary">
<source>Expected condition to be false.</source>
<target state="new">Expected condition to be false.</target>
<note />
</trans-unit>
<trans-unit id="IsInRangeFail">
<source>Value '{0}' is not within the expected range [{1}..{2}]. {3}</source>
<target state="translated">Der Wert „{0}“ liegt nicht im erwarteten Bereich [{1}..{2}]. {3}</target>
Expand Down Expand Up @@ -317,6 +322,21 @@ Tatsächlich: {2}</target>
<target state="translated">Die Zeichenfolge „{0}“ stimmt mit dem Muster „{1}“ überein. {2}</target>
<note />
</trans-unit>
<trans-unit id="IsNotNullFailedSummary">
<source>Expected value to not be null.</source>
<target state="new">Expected value to not be null.</target>
<note />
</trans-unit>
<trans-unit id="IsNullFailedSummary">
<source>Expected value to be null.</source>
<target state="new">Expected value to be null.</target>
<note />
</trans-unit>
<trans-unit id="IsTrueFailedSummary">
<source>Expected condition to be true.</source>
<target state="new">Expected condition to be true.</target>
<note />
</trans-unit>
<trans-unit id="PrivateAccessorMemberNotFound">
<source>
The member specified ({0}) could not be found. You might need to regenerate your private accessor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ Real: {2}</target>
<target state="translated">Dirección URL de vale de GitHub no válida</target>
<note />
</trans-unit>
<trans-unit id="IsFalseFailedSummary">
<source>Expected condition to be false.</source>
<target state="new">Expected condition to be false.</target>
<note />
</trans-unit>
<trans-unit id="IsInRangeFail">
<source>Value '{0}' is not within the expected range [{1}..{2}]. {3}</source>
<target state="translated">El valor "{0}" no está dentro del rango esperado [{1}..{2}]. {3}</target>
Expand Down Expand Up @@ -317,6 +322,21 @@ Real: {2}</target>
<target state="translated">La cadena "{0}" coincide con el patrón "{1}". {2}</target>
<note />
</trans-unit>
<trans-unit id="IsNotNullFailedSummary">
<source>Expected value to not be null.</source>
<target state="new">Expected value to not be null.</target>
<note />
</trans-unit>
<trans-unit id="IsNullFailedSummary">
<source>Expected value to be null.</source>
<target state="new">Expected value to be null.</target>
<note />
</trans-unit>
<trans-unit id="IsTrueFailedSummary">
<source>Expected condition to be true.</source>
<target state="new">Expected condition to be true.</target>
<note />
</trans-unit>
<trans-unit id="PrivateAccessorMemberNotFound">
<source>
The member specified ({0}) could not be found. You might need to regenerate your private accessor,
Expand Down
Loading