Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
19a1f23
Add `Assert.Scope()` for soft assertions
Evangelink Feb 6, 2026
312a4a4
Add public API
Evangelink Feb 6, 2026
d860b27
Merge branch 'main' into dev/amauryleve/soft-assertion
Evangelink Feb 9, 2026
b0208b3
Address code review
Evangelink Feb 9, 2026
6133a25
Fixes
Evangelink Feb 9, 2026
63dceb7
Merge branch 'main' into dev/amauryleve/soft-assertion
Evangelink Feb 9, 2026
12d69af
Update
Evangelink Feb 9, 2026
f9133c3
Merge branch 'main' into dev/amauryleve/soft-assertion
Evangelink Feb 12, 2026
61f218e
Update logic to ignore compiler diags
Evangelink Feb 12, 2026
888a8a6
More refactoring
Evangelink Feb 12, 2026
c68b24e
Cleanup
Evangelink Feb 12, 2026
ceb625b
Update RFC
Evangelink Feb 12, 2026
61899b1
Add back removed NotNull attributes
Evangelink Feb 12, 2026
bb40e99
Update src/TestFramework/TestFramework/Assertions/Assert.cs
Evangelink Feb 16, 2026
328b1fb
Address review comments
Evangelink Feb 16, 2026
e84eadc
Final refactoring
Evangelink Feb 16, 2026
b9d6aff
Rely on UpdateXlf instead of manually modifying xlf
Youssef1313 Feb 20, 2026
bc227a8
Improve tests
Evangelink Feb 20, 2026
13ccc3b
Fix
Evangelink Feb 20, 2026
c462a15
Merge branch 'main' into dev/amauryleve/soft-assertion
Evangelink Feb 20, 2026
086a072
Address review comments
Evangelink Feb 20, 2026
2f512e2
Merge main into dev/amauryleve/soft-assertion and resolve conflicts
Evangelink Apr 20, 2026
86e62e1
Fix
Evangelink Apr 20, 2026
6f6e49c
Move ExceptionDispatchInfo.Capture to AddError for cleaner stack trac…
Evangelink Apr 20, 2026
206a81f
Move ExceptionDispatchInfo.Capture to AddError for cleaner stack trac…
Evangelink Apr 20, 2026
d6290ec
Fix test
Evangelink Apr 20, 2026
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
263 changes: 263 additions & 0 deletions docs/RFCs/011-Soft-Assertions-Nullability-Design.md

Large diffs are not rendered by default.

70 changes: 35 additions & 35 deletions src/TestFramework/TestFramework/Assertions/Assert.AreEqual.cs

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions src/TestFramework/TestFramework/Assertions/Assert.AreSame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal void ComputeAssertion(string expectedExpression, string actualExpressio
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionTwoParametersMessage, "expected", expectedExpression, "actual", actualExpression) + " ");
ThrowAssertAreSameFailed(_expected, _actual, _builder.ToString());
ReportAssertAreSameFailed(_expected, _actual, _builder.ToString());
}
}

Expand Down Expand Up @@ -99,7 +99,7 @@ internal void ComputeAssertion(string notExpectedExpression, string actualExpres
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionTwoParametersMessage, "notExpected", notExpectedExpression, "actual", actualExpression) + " ");
ThrowAssertAreNotSameFailed(_notExpected, _actual, _builder.ToString());
ReportAssertAreNotSameFailed(_notExpected, _actual, _builder.ToString());
}
}

Expand Down Expand Up @@ -182,14 +182,14 @@ public static void AreSame<T>(T? expected, T? actual, string? message = "", [Cal
}

string userMessage = BuildUserMessageForExpectedExpressionAndActualExpression(message, expectedExpression, actualExpression);
ThrowAssertAreSameFailed(expected, actual, userMessage);
ReportAssertAreSameFailed(expected, actual, userMessage);
}

private static bool IsAreSameFailing<T>(T? expected, T? actual)
=> !object.ReferenceEquals(expected, actual);

[DoesNotReturn]
private static void ThrowAssertAreSameFailed<T>(T? expected, T? actual, string userMessage)
private static void ReportAssertAreSameFailed<T>(T? expected, T? actual, string userMessage)
{
string finalMessage = expected is null
? string.Format(
Expand All @@ -208,7 +208,7 @@ private static void ThrowAssertAreSameFailed<T>(T? expected, T? actual, string u
userMessage)
: userMessage;

ThrowAssertFailed("Assert.AreSame", finalMessage);
ReportAssertFailed("Assert.AreSame", finalMessage);
}

/// <inheritdoc cref="AreNotSame{T}(T, T, string?, string, string)" />
Expand Down Expand Up @@ -252,15 +252,15 @@ public static void AreNotSame<T>(T? notExpected, T? actual, string? message = ""
{
if (IsAreNotSameFailing(notExpected, actual))
{
ThrowAssertAreNotSameFailed(notExpected, actual, BuildUserMessageForNotExpectedExpressionAndActualExpression(message, notExpectedExpression, actualExpression));
ReportAssertAreNotSameFailed(notExpected, actual, BuildUserMessageForNotExpectedExpressionAndActualExpression(message, notExpectedExpression, actualExpression));
}
}

private static bool IsAreNotSameFailing<T>(T? notExpected, T? actual)
=> object.ReferenceEquals(notExpected, actual);

[DoesNotReturn]
private static void ThrowAssertAreNotSameFailed<T>(T? notExpected, T? actual, string userMessage)
private static void ReportAssertAreNotSameFailed<T>(T? notExpected, T? actual, string userMessage)
{
string finalMessage = notExpected is null && actual is null
? string.Format(
Expand All @@ -269,6 +269,6 @@ private static void ThrowAssertAreNotSameFailed<T>(T? notExpected, T? actual, st
userMessage)
: userMessage;

ThrowAssertFailed("Assert.AreNotSame", finalMessage);
ReportAssertFailed("Assert.AreNotSame", finalMessage);
}
}
64 changes: 32 additions & 32 deletions src/TestFramework/TestFramework/Assertions/Assert.Contains.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal TItem ComputeAssertion(string collectionExpression)
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "collection", collectionExpression) + " ");
ThrowAssertContainsSingleFailed(_actualCount, _builder.ToString());
ReportAssertContainsSingleFailed(_actualCount, _builder.ToString());
}

return _item!;
Expand Down Expand Up @@ -180,12 +180,12 @@ public static T ContainsSingle<T>(Func<T, bool> predicate, IEnumerable<T> collec
if (string.IsNullOrEmpty(predicateExpression))
{
string userMessage = BuildUserMessageForCollectionExpression(message, collectionExpression);
ThrowAssertContainsSingleFailed(matchCount, userMessage);
ReportAssertContainsSingleFailed(matchCount, userMessage);
}
else
{
string userMessage = BuildUserMessageForPredicateExpressionAndCollectionExpression(message, predicateExpression, collectionExpression);
ThrowAssertSingleMatchFailed(matchCount, userMessage);
ReportAssertSingleMatchFailed(matchCount, userMessage);
}

// Unreachable code but compiler cannot work it out
Expand Down Expand Up @@ -241,12 +241,12 @@ public static T ContainsSingle<T>(Func<T, bool> predicate, IEnumerable<T> collec
if (string.IsNullOrEmpty(predicateExpression))
{
string userMessage = BuildUserMessageForCollectionExpression(message, collectionExpression);
ThrowAssertContainsSingleFailed(matchCount, userMessage);
ReportAssertContainsSingleFailed(matchCount, userMessage);
}
else
{
string userMessage = BuildUserMessageForPredicateExpressionAndCollectionExpression(message, predicateExpression, collectionExpression);
ThrowAssertSingleMatchFailed(matchCount, userMessage);
ReportAssertSingleMatchFailed(matchCount, userMessage);
}

return default;
Expand Down Expand Up @@ -276,7 +276,7 @@ public static void Contains<T>(T expected, IEnumerable<T> collection, string? me
if (!collection.Contains(expected))
{
string userMessage = BuildUserMessageForExpectedExpressionAndCollectionExpression(message, expectedExpression, collectionExpression);
ThrowAssertContainsItemFailed(userMessage);
ReportAssertContainsItemFailed(userMessage);
}
}

Expand Down Expand Up @@ -307,7 +307,7 @@ public static void Contains(object? expected, IEnumerable collection, string? me
}

string userMessage = BuildUserMessageForExpectedExpressionAndCollectionExpression(message, expectedExpression, collectionExpression);
ThrowAssertContainsItemFailed(userMessage);
ReportAssertContainsItemFailed(userMessage);
}

/// <summary>
Expand All @@ -331,7 +331,7 @@ public static void Contains<T>(T expected, IEnumerable<T> collection, IEqualityC
if (!collection.Contains(expected, comparer))
{
string userMessage = BuildUserMessageForExpectedExpressionAndCollectionExpression(message, expectedExpression, collectionExpression);
ThrowAssertContainsItemFailed(userMessage);
ReportAssertContainsItemFailed(userMessage);
}
}

Expand Down Expand Up @@ -364,7 +364,7 @@ public static void Contains(object? expected, IEnumerable collection, IEqualityC
}

string userMessage = BuildUserMessageForExpectedExpressionAndCollectionExpression(message, expectedExpression, collectionExpression);
ThrowAssertContainsItemFailed(userMessage);
ReportAssertContainsItemFailed(userMessage);
}

/// <summary>
Expand All @@ -387,7 +387,7 @@ public static void Contains<T>(Func<T, bool> predicate, IEnumerable<T> collectio
if (!collection.Any(predicate))
{
string userMessage = BuildUserMessageForPredicateExpressionAndCollectionExpression(message, predicateExpression, collectionExpression);
ThrowAssertContainsPredicateFailed(userMessage);
ReportAssertContainsPredicateFailed(userMessage);
}
}

Expand Down Expand Up @@ -419,7 +419,7 @@ public static void Contains(Func<object?, bool> predicate, IEnumerable collectio
}

string userMessage = BuildUserMessageForPredicateExpressionAndCollectionExpression(message, predicateExpression, collectionExpression);
ThrowAssertContainsPredicateFailed(userMessage);
ReportAssertContainsPredicateFailed(userMessage);
}

/// <summary>
Expand Down Expand Up @@ -497,7 +497,7 @@ public static void Contains(string substring, string value, StringComparison com
{
string userMessage = BuildUserMessageForSubstringExpressionAndValueExpression(message, substringExpression, valueExpression);
string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.ContainsFail, value, substring, userMessage);
ThrowAssertFailed("Assert.Contains", finalMessage);
ReportAssertFailed("Assert.Contains", finalMessage);
}
}

Expand Down Expand Up @@ -525,7 +525,7 @@ public static void DoesNotContain<T>(T notExpected, IEnumerable<T> collection, s
if (collection.Contains(notExpected))
{
string userMessage = BuildUserMessageForNotExpectedExpressionAndCollectionExpression(message, notExpectedExpression, collectionExpression);
ThrowAssertDoesNotContainItemFailed(userMessage);
ReportAssertDoesNotContainItemFailed(userMessage);
}
}

Expand All @@ -552,7 +552,7 @@ public static void DoesNotContain(object? notExpected, IEnumerable collection, s
if (object.Equals(notExpected, item))
{
string userMessage = BuildUserMessageForNotExpectedExpressionAndCollectionExpression(message, notExpectedExpression, collectionExpression);
ThrowAssertDoesNotContainItemFailed(userMessage);
ReportAssertDoesNotContainItemFailed(userMessage);
}
}
}
Expand All @@ -578,7 +578,7 @@ public static void DoesNotContain<T>(T notExpected, IEnumerable<T> collection, I
if (collection.Contains(notExpected, comparer))
{
string userMessage = BuildUserMessageForNotExpectedExpressionAndCollectionExpression(message, notExpectedExpression, collectionExpression);
ThrowAssertDoesNotContainItemFailed(userMessage);
ReportAssertDoesNotContainItemFailed(userMessage);
}
}

Expand Down Expand Up @@ -607,7 +607,7 @@ public static void DoesNotContain(object? notExpected, IEnumerable collection, I
if (comparer.Equals(item, notExpected))
{
string userMessage = BuildUserMessageForNotExpectedExpressionAndCollectionExpression(message, notExpectedExpression, collectionExpression);
ThrowAssertDoesNotContainItemFailed(userMessage);
ReportAssertDoesNotContainItemFailed(userMessage);
}
}
}
Expand All @@ -632,7 +632,7 @@ public static void DoesNotContain<T>(Func<T, bool> predicate, IEnumerable<T> col
if (collection.Any(predicate))
{
string userMessage = BuildUserMessageForPredicateExpressionAndCollectionExpression(message, predicateExpression, collectionExpression);
ThrowAssertDoesNotContainPredicateFailed(userMessage);
ReportAssertDoesNotContainPredicateFailed(userMessage);
}
}

Expand Down Expand Up @@ -660,7 +660,7 @@ public static void DoesNotContain(Func<object?, bool> predicate, IEnumerable col
if (predicate(item))
{
string userMessage = BuildUserMessageForPredicateExpressionAndCollectionExpression(message, predicateExpression, collectionExpression);
ThrowAssertDoesNotContainPredicateFailed(userMessage);
ReportAssertDoesNotContainPredicateFailed(userMessage);
}
}
}
Expand Down Expand Up @@ -740,7 +740,7 @@ public static void DoesNotContain(string substring, string value, StringComparis
{
string userMessage = BuildUserMessageForSubstringExpressionAndValueExpression(message, substringExpression, valueExpression);
string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.DoesNotContainFail, value, substring, userMessage);
ThrowAssertFailed("Assert.DoesNotContain", finalMessage);
ReportAssertFailed("Assert.DoesNotContain", finalMessage);
}
}

Expand Down Expand Up @@ -781,71 +781,71 @@ public static void IsInRange<T>(T minValue, T maxValue, T value, string? message
{
string userMessage = BuildUserMessageForMinValueExpressionAndMaxValueExpressionAndValueExpression(message, minValueExpression, maxValueExpression, valueExpression);
string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.IsInRangeFail, value, minValue, maxValue, userMessage);
ThrowAssertFailed("IsInRange", finalMessage);
ReportAssertFailed("IsInRange", finalMessage);
}
}

#endregion // IsInRange

[DoesNotReturn]
private static void ThrowAssertSingleMatchFailed(int actualCount, string userMessage)
private static void ReportAssertSingleMatchFailed(int actualCount, string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.ContainsSingleMatchFailMsg,
userMessage,
actualCount);
ThrowAssertFailed("Assert.ContainsSingle", finalMessage);
ReportAssertFailed("Assert.ContainsSingle", finalMessage);
}

[DoesNotReturn]
private static void ThrowAssertContainsSingleFailed(int actualCount, string userMessage)
private static void ReportAssertContainsSingleFailed(int actualCount, string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.ContainsSingleFailMsg,
userMessage,
actualCount);
ThrowAssertFailed("Assert.ContainsSingle", finalMessage);
ReportAssertFailed("Assert.ContainsSingle", finalMessage);
}

[DoesNotReturn]
private static void ThrowAssertContainsItemFailed(string userMessage)
private static void ReportAssertContainsItemFailed(string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.ContainsItemFailMsg,
userMessage);
ThrowAssertFailed("Assert.Contains", finalMessage);
ReportAssertFailed("Assert.Contains", finalMessage);
}

[DoesNotReturn]
private static void ThrowAssertContainsPredicateFailed(string userMessage)
private static void ReportAssertContainsPredicateFailed(string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.ContainsPredicateFailMsg,
userMessage);
ThrowAssertFailed("Assert.Contains", finalMessage);
ReportAssertFailed("Assert.Contains", finalMessage);
}

[DoesNotReturn]
private static void ThrowAssertDoesNotContainItemFailed(string userMessage)
private static void ReportAssertDoesNotContainItemFailed(string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.DoesNotContainItemFailMsg,
userMessage);
ThrowAssertFailed("Assert.DoesNotContain", finalMessage);
ReportAssertFailed("Assert.DoesNotContain", finalMessage);
}

[DoesNotReturn]
private static void ThrowAssertDoesNotContainPredicateFailed(string userMessage)
private static void ReportAssertDoesNotContainPredicateFailed(string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.DoesNotContainPredicateFailMsg,
userMessage);
ThrowAssertFailed("Assert.DoesNotContain", finalMessage);
ReportAssertFailed("Assert.DoesNotContain", finalMessage);
}
}
20 changes: 10 additions & 10 deletions src/TestFramework/TestFramework/Assertions/Assert.Count.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 Down Expand Up @@ -48,7 +48,7 @@ internal void ComputeAssertion(string assertionName, string collectionExpression
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "collection", collectionExpression) + " ");
ThrowAssertCountFailed(assertionName, _expectedCount, _actualCount, _builder.ToString());
ReportAssertCountFailed(assertionName, _expectedCount, _actualCount, _builder.ToString());
}
}

Expand Down Expand Up @@ -116,7 +116,7 @@ internal void ComputeAssertion(string collectionExpression)
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "collection", collectionExpression) + " ");
ThrowAssertIsNotEmptyFailed(_builder.ToString());
ReportAssertIsNotEmptyFailed(_builder.ToString());
}
}

Expand Down Expand Up @@ -203,7 +203,7 @@ public static void IsNotEmpty<T>(IEnumerable<T> collection, string? message = ""
}

string userMessage = BuildUserMessageForCollectionExpression(message, collectionExpression);
ThrowAssertIsNotEmptyFailed(userMessage);
ReportAssertIsNotEmptyFailed(userMessage);
}

/// <summary>
Expand All @@ -223,7 +223,7 @@ public static void IsNotEmpty(IEnumerable collection, string? message = "", [Cal
}

string userMessage = BuildUserMessageForCollectionExpression(message, collectionExpression);
ThrowAssertIsNotEmptyFailed(userMessage);
ReportAssertIsNotEmptyFailed(userMessage);
}
#endregion // IsNotEmpty

Expand Down Expand Up @@ -327,31 +327,31 @@ private static void HasCount<T>(string assertionName, int expected, IEnumerable<
}

string userMessage = BuildUserMessageForCollectionExpression(message, collectionExpression);
ThrowAssertCountFailed(assertionName, expected, actualCount, userMessage);
ReportAssertCountFailed(assertionName, expected, actualCount, userMessage);
}

private static void HasCount(string assertionName, int expected, IEnumerable collection, string? message, string collectionExpression)
=> HasCount(assertionName, expected, collection.Cast<object>(), message, collectionExpression);

[DoesNotReturn]
private static void ThrowAssertCountFailed(string assertionName, int expectedCount, int actualCount, string userMessage)
private static void ReportAssertCountFailed(string assertionName, int expectedCount, int actualCount, string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.HasCountFailMsg,
userMessage,
expectedCount,
actualCount);
ThrowAssertFailed($"Assert.{assertionName}", finalMessage);
ReportAssertFailed($"Assert.{assertionName}", finalMessage);
}

[DoesNotReturn]
private static void ThrowAssertIsNotEmptyFailed(string userMessage)
private static void ReportAssertIsNotEmptyFailed(string userMessage)
{
string finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.IsNotEmptyFailMsg,
userMessage);
ThrowAssertFailed("Assert.IsNotEmpty", finalMessage);
ReportAssertFailed("Assert.IsNotEmpty", finalMessage);
}
}
Loading
Loading