Apply structured assertion messages to IsTrue, IsFalse, IsNull, IsNotNull#8187
Apply structured assertion messages to IsTrue, IsFalse, IsNull, IsNotNull#8187Evangelink wants to merge 7 commits into
Conversation
Introduce the foundational types and helpers for structured multi-line assertion failure messages as described in RFC 012: - EvidenceLine: labeled line record struct for evidence blocks - EvidenceBlock: collection of labeled lines with automatic alignment - StructuredAssertionMessage: builder producing the new multi-line format (prefix + summary + user message + evidence block + call-site) - AssertionValueRenderer: renders values per RFC 012 rules (null, quoted strings with escape sequences, booleans, collections as JSON arrays) - AssertFailedException: add ExpectedText/ActualText public properties - Assert: add ReportAssertFailed/ThrowAssertFailed overloads accepting StructuredAssertionMessage No existing assertion methods are changed yet - this PR only introduces the infrastructure that subsequent PRs will use to migrate each assertion method to the new format.
…Null - Update Assert.IsTrue/IsFalse to use StructuredAssertionMessage with evidence block showing actual value and call-site expression - Update Assert.IsNull to use StructuredAssertionMessage with evidence block showing actual value - Update Assert.IsNotNull to use StructuredAssertionMessage without evidence block (actual is always null per RFC) - Update interpolated string handlers to store condition/value for passing to the new reporting methods - Add FormatCallSiteExpression helper to Assert.cs for formatting call-site display lines - Remove unused BuildUserMessageForConditionExpression method - Update all related test expectations to match new message format
# Conflicts: # src/TestFramework/TestFramework/Assertions/Assert.cs # src/TestFramework/TestFramework/Assertions/AssertionValueRenderer.cs # src/TestFramework/TestFramework/Assertions/EvidenceBlock.cs # src/TestFramework/TestFramework/Assertions/StructuredAssertionMessage.cs # test/UnitTests/TestFramework.UnitTests/Assertions/AssertFailedExceptionTests.cs # test/UnitTests/TestFramework.UnitTests/Assertions/AssertionValueRendererTests.cs # test/UnitTests/TestFramework.UnitTests/Assertions/StructuredAssertionMessageTests.cs
There was a problem hiding this comment.
Pull request overview
Extends the RFC 012 structured assertion message infrastructure to the core boolean and null assertions (IsTrue, IsFalse, IsNull, IsNotNull) in MSTest’s TestFramework, and updates unit tests to validate the new multi-line message format.
Changes:
- Updated
Assert.IsTrue/Assert.IsFalseto emitStructuredAssertionMessagewith anactual:evidence line and a reconstructed call-site expression. - Updated
Assert.IsNull/Assert.IsNotNullto emitStructuredAssertionMessage(withIsNotNullomitting evidence per RFC guidance). - Added
Assert.FormatCallSiteExpression(...)helper(s) and updated unit tests to assert the new message layouts.
Show a summary per file
| File | Description |
|---|---|
| test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ScopeTests.cs | Updates soft-assert scope tests to assert the new structured failure messages for IsTrue/IsNotNull. |
| test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsTrueTests.cs | Updates IsTrue/IsFalse tests to validate structured message formatting, including user message behavior. |
| test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsNull.cs | Updates IsNull/IsNotNull tests to validate structured message formatting. |
| src/TestFramework/TestFramework/Assertions/Assert.IsTrue.cs | Implements structured assertion messages for IsTrue/IsFalse, including evidence and call-site expression output. |
| src/TestFramework/TestFramework/Assertions/Assert.IsNull.cs | Implements structured assertion messages for IsNull/IsNotNull, including the RFC-mandated omission of evidence for IsNotNull. |
| src/TestFramework/TestFramework/Assertions/Assert.cs | Adds call-site expression formatting helper methods used by the updated assertions. |
Copilot's findings
- Files reviewed: 6/6 changed files
- Comments generated: 1
| EvidenceBlock evidence = EvidenceBlock.Create() | ||
| .AddLine("actual:", actualValue); | ||
|
|
||
| StructuredAssertionMessage structured = new("Expected value to be null."); |
There was a problem hiding this comment.
THis text should be localized
There was a problem hiding this comment.
Fixed. Moved to FrameworkMessages.IsNullFailedSummary.
| private static void ReportAssertIsNotNullFailed(string? message, string valueExpression) | ||
| { | ||
| // RFC: IsNotNull omits the evidence block since actual is always null | ||
| StructuredAssertionMessage structured = new("Expected value to not be null."); |
There was a problem hiding this comment.
This should be localized
There was a problem hiding this comment.
Fixed. Moved to FrameworkMessages.IsNotNullFailedSummary.
| EvidenceBlock evidence = EvidenceBlock.Create() | ||
| .AddLine("actual:", actualValue); | ||
|
|
||
| StructuredAssertionMessage structured = new("Expected condition to be true."); |
There was a problem hiding this comment.
This should be localized
There was a problem hiding this comment.
Fixed. Moved to FrameworkMessages.IsTrueFailedSummary.
| EvidenceBlock evidence = EvidenceBlock.Create() | ||
| .AddLine("actual:", actualValue); | ||
|
|
||
| StructuredAssertionMessage structured = new("Expected condition to be false."); |
There was a problem hiding this comment.
This should be localized
There was a problem hiding this comment.
Fixed. Moved to FrameworkMessages.IsFalseFailedSummary.
Code Review: PR #8187 — Design-Level Concerns🔴 Backward Compatibility (BLOCKING)The assertion failure message format changes completely and unconditionally:
Concrete breakages for existing consumers upgrading to this version:
There is no opt-in/opt-out mechanism and RFC 012 (docs/RFCs/012-Structured-Assertion-Messages.md) is marked Recommendation: Before merging, either:
i️ Localization Note (Known Tradeoff)Strings like
|
|
[MAJOR] Localization regression — structured message strings are hardcoded English The PR hardcodes four user-facing summary strings directly in C# source:
Plus the evidence label The existing assertion messages ( The repo guidelines explicitly require: "Add a corresponding entry in the resource file (.resx). NEVER manually modify Recommendation: Either:
|
Evangelink
left a comment
There was a problem hiding this comment.
Review Summary
| # | Dimension | Verdict |
|---|---|---|
| 1 | Algorithmic Correctness | 🟡 1 MODERATE |
| 2 | Threading & Concurrency | ✅ LGTM |
| 3 | Security & IPC | ✅ LGTM |
| 4 | Public API & Binary Compatibility | ✅ LGTM (struct field additions are safe in .NET) |
| 5 | Performance & Allocations | ✅ LGTM (all new alloc on failure path only) |
| 6 | Cross-TFM Compatibility | ✅ LGTM |
| 7 | Resource & IDisposable | ✅ LGTM |
| 8 | Defensive Coding | ✅ LGTM |
| 9 | Localization | |
| 10 | Backward Compatibility | 🔴 BLOCKING |
| 11 | Assertion Quality | ✅ LGTM |
| 12 | Test Completeness | ✅ LGTM |
| 13 | Data-Driven Test Coverage | ✅ LGTM |
| 14 | Code Structure | ✅ LGTM (minor duplication is acceptable) |
| 15 | Naming & Conventions | ✅ LGTM |
| 16 | Documentation Accuracy | ✅ LGTM |
| 17 | Scope & PR Discipline | ✅ LGTM |
✅ 15/17 dimensions clean.
Blocking
- Backward Compatibility: The assertion failure message format changes completely and unconditionally — no opt-in, no feature flag, RFC 012 not yet formally approved. Consumers doing message matching (
Contains("Assert.IsNull failed")), log parsers, or snapshot tests will break silently on upgrade. Needs either an opt-in mechanism or explicit RFC approval + release-notes breaking-change documentation.
Moderate
-
WithExpectedAndActual(null, actualValue)in IsTrue/IsFalse: SetsAssertFailedException.ExpectedText = null, incorrectly signaling "no expected value." The expected values aretrueandfalse. Fix: passAssertionValueRenderer.RenderValue(true/false)as the first argument.
Generated by Expert Code Review (on open) for issue #8187 · ● 100M
…r multiline expressions
| private static void ReportAssertIsNotNullFailed(string? message, string valueExpression) | ||
| { | ||
| // RFC: IsNotNull omits the evidence block since actual is always null | ||
| StructuredAssertionMessage structured = new(FrameworkMessages.IsNotNullFailedSummary); | ||
| structured.WithUserMessage(message); | ||
| #pragma warning disable CA1507 // Use nameof - 'value' is the parameter name of the public IsNotNull method, not available in this scope | ||
| structured.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsNotNull", valueExpression, "value")); | ||
| #pragma warning restore CA1507 | ||
|
|
| StructuredAssertionMessage structured = new(FrameworkMessages.IsNotNullFailedSummary); | ||
| structured.WithUserMessage(message); | ||
| #pragma warning disable CA1507 // Use nameof - 'value' is the parameter name of the public IsNotNull method, not available in this scope | ||
| structured.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsNotNull", valueExpression, "value")); | ||
| #pragma warning restore CA1507 |
| { | ||
| // RFC: IsNotNull omits the evidence block since actual is always null | ||
| StructuredAssertionMessage structured = new(FrameworkMessages.IsNotNullFailedSummary); |
Summary
Applies the structured assertion message infrastructure (RFC 012) to the boolean and null assertion methods:
IsTrue,IsFalse,IsNull, andIsNotNull.Changes
StructuredAssertionMessagewith evidence blocks showing expected/actual boolean values and call-site expressionsStructuredAssertionMessagewith evidence blocks showing the actual value and call-site expressionsFormatCallSiteExpressionhelper methods for formatting call-site expressions in structured messagesContext
This builds on the structured assertion message infrastructure merged in the parent PR, extending it to the first set of assertion methods (boolean and null checks).