Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ private async Task WriteExceptionAsync(string testDisplayName, string testName,
logger.LogTrace($"Using relative path '{relativePath}'.");
}
}
else if (file.StartsWith(repoRoot, StringComparison.CurrentCultureIgnoreCase))
else if (file.StartsWith(repoRoot, StringComparison.OrdinalIgnoreCase))
{
if (logger.IsEnabled(LogLevel.Trace))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// </summary>
public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -86,6 +87,7 @@ internal void ComputeAssertion(string expectedExpression, string actualExpressio
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertAreNotEqualInterpolatedStringHandler<TArgument>
Expand Down Expand Up @@ -152,6 +154,7 @@ internal void ComputeAssertion(string notExpectedExpression, string actualExpres
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertNonGenericAreEqualInterpolatedStringHandler
Expand Down Expand Up @@ -263,6 +266,7 @@ internal void ComputeAssertion(string expectedExpression, string actualExpressio
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertNonGenericAreNotEqualInterpolatedStringHandler
Expand Down
2 changes: 2 additions & 0 deletions src/TestFramework/TestFramework/Assertions/Assert.AreSame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// </summary>
public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -74,6 +75,7 @@ internal void ComputeAssertion(string expectedExpression, string actualExpressio
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertAreNotSameInterpolatedStringHandler<TArgument>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// </summary>
public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down
2 changes: 2 additions & 0 deletions src/TestFramework/TestFramework/Assertions/Assert.Count.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// </summary>
public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -95,6 +96,7 @@ public void AppendFormatted(object? value, int alignment = 0, string? format = n
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertIsNotEmptyInterpolatedStringHandler<TItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// </summary>
public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -76,6 +77,7 @@ internal void ComputeAssertion(string valueExpression)
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertGenericIsExactInstanceOfTypeInterpolatedStringHandler<TArg>
Expand Down Expand Up @@ -135,6 +137,7 @@ internal void ComputeAssertion(string valueExpression)
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertIsNotExactInstanceOfTypeInterpolatedStringHandler
Expand Down Expand Up @@ -196,6 +199,7 @@ internal void ComputeAssertion(string valueExpression)
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertGenericIsNotExactInstanceOfTypeInterpolatedStringHandler<TArg>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// </summary>
public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -76,6 +77,7 @@ internal void ComputeAssertion(string valueExpression)
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertGenericIsInstanceOfTypeInterpolatedStringHandler<TArg>
Expand Down Expand Up @@ -135,6 +137,7 @@ internal void ComputeAssertion(string valueExpression)
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertIsNotInstanceOfTypeInterpolatedStringHandler
Expand Down Expand Up @@ -196,6 +199,7 @@ internal void ComputeAssertion(string valueExpression)
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertGenericIsNotInstanceOfTypeInterpolatedStringHandler<TArg>
Expand Down
2 changes: 2 additions & 0 deletions src/TestFramework/TestFramework/Assertions/Assert.IsNull.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;

public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -71,6 +72,7 @@ internal void ComputeAssertion(string valueExpression)
}
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down
2 changes: 2 additions & 0 deletions src/TestFramework/TestFramework/Assertions/Assert.IsTrue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;

public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -70,6 +71,7 @@ internal void ComputeAssertion(string conditionExpression)
public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _builder!.AppendFormat(null, $"{{0,{alignment}:{format}}}", value);
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertIsFalseInterpolatedStringHandler
Expand Down
1 change: 1 addition & 0 deletions src/TestFramework/TestFramework/Assertions/Assert.That.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// <summary>
/// Provides That extension to Assert class.
/// </summary>
[StackTraceHidden]
public static partial class AssertExtensions
{
// Constants for standardized display values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// </summary>
public sealed partial class Assert
{
[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Expand Down Expand Up @@ -85,6 +86,7 @@ internal TException ComputeAssertion(string actionExpression)
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
}

[StackTraceHidden]
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly struct AssertThrowsExactlyInterpolatedStringHandler<TException>
Expand Down
1 change: 1 addition & 0 deletions src/TestFramework/TestFramework/Assertions/Assert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// unit tests. If the condition being tested is not met, an exception
/// is thrown.
/// </summary>
[StackTraceHidden]
public sealed partial class Assert
{
private Assert()
Expand Down
1 change: 1 addition & 0 deletions src/TestFramework/TestFramework/Assertions/AssertScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// Represents a scope in which assertion failures are collected instead of thrown immediately.
/// When the scope is disposed, all collected failures are thrown as a single <see cref="AssertFailedException"/>.
/// </summary>
[StackTraceHidden]
internal sealed class AssertScope : IDisposable
{
private static readonly AsyncLocal<AssertScope?> CurrentScope = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// <summary>
/// Renders values for display in structured assertion messages following the RFC 012 value rendering rules.
/// </summary>
[StackTraceHidden]
internal static class AssertionValueRenderer
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// with collections within unit tests. If the condition being tested is not
/// met, an exception is thrown.
/// </summary>
[StackTraceHidden]
public sealed partial class CollectionAssert
{
#region Singleton constructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// Represents the evidence block of a structured assertion message, containing labeled value lines
/// such as expected/actual values and assertion-specific details.
/// </summary>
[StackTraceHidden]
internal sealed class EvidenceBlock
{
private readonly List<EvidenceLine> _lines = [];
Expand Down
1 change: 1 addition & 0 deletions src/TestFramework/TestFramework/Assertions/EvidenceLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// <summary>
/// Represents a single labeled line in the evidence block of a structured assertion message.
/// </summary>
[StackTraceHidden]
internal readonly record struct EvidenceLine(string Label, string Value);
1 change: 1 addition & 0 deletions src/TestFramework/TestFramework/Assertions/StringAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// <summary>
/// The string assert.
/// </summary>
[StackTraceHidden]
public sealed class StringAssert
{
#region Singleton constructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
/// &lt;call-site expression&gt;
/// </code>
/// </summary>
[StackTraceHidden]
internal sealed class StructuredAssertionMessage
{
private const string AssertionPrefix = "Assertion failed.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public async Task ScopeWithMultipleFailures_TestFailsWithAggregatedMessage()
// Validate the output includes the aggregate message and that inner exception stack traces
// point to the test method (assertion call site).
testHostResult.AssertOutputMatchesRegex(
"""failed ScopeWithMultipleFailures \(\d+ms\)[\s\S]+2 assertion\(s\) failed within the assert scope\.[\s\S]+at Microsoft\.VisualStudio\.TestTools\.UnitTesting\.AssertScope\.Dispose\(\)[\s\S]+at UnitTest1\.ScopeWithMultipleFailures\(\)""");
"""failed ScopeWithMultipleFailures \(\d+ms\)[\s\S]+2 assertion\(s\) failed within the assert scope\.[\s\S]+at UnitTest1\.ScopeWithMultipleFailures\(\)""");
}

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,81 @@ public void DoesNotSkipUserFrameWhoseTypeNameStartsWithAssert()
$"\nLogs:\n{string.Join("\n", logger.Logs)}");
}

[TestMethod]
public void RealAssertFailure_AnnotationPointsAtTestMethodAndNotAtFrameworkFile()
{
// End-to-end acceptance for https://github.com/microsoft/testfx/issues/8277.
//
// Trigger a *real* MSTest assertion failure (no synthetic stack trace) and ensure the
// AzDO reporter annotates the test method's source file rather than the framework
// partial-class file that actually holds the failing implementation.
//
// This regression is double-guarded:
// * On .NET 6+: every public Assert/CollectionAssert/StringAssert method carries
// [StackTraceHidden], so the framework frame should not even appear in the
// captured stack trace.
// * On older runtimes (.NET Framework / netstandard2.0 consumers): the attribute is
// a no-op, but the reporter's type-prefix heuristic still skips the framework
// frame.
// Either way, the annotation must point at this test method's source file.
(string testFile, _) = GetCurrentLocation();

AssertFailedException error;
try
{
#pragma warning disable MSTEST0025 // Use 'Assert.Fail' instead of an always-failing assert - we deliberately want a real Assert.AreEqual failure so the captured stack trace reflects what users see.
Assert.AreEqual(1, 2);
#pragma warning restore MSTEST0025
throw new InvalidOperationException("Expected AssertFailedException was not thrown.");
}
catch (AssertFailedException ex)
{
error = ex;
}

var logger = new TextLogger();
// Trim ##. If we keep it, then when the test fails, the assertion failure will get printed to screen and picked up incorrectly by AzDO, because it scans all output for the ##vso... pattern.
string? text = AzureDevOpsReporter.GetErrorText(
"MyTestDisplayName",
explanation: null,
error,
"severity",
new SystemFileSystem(),
logger,
"net9.0")?.TrimStart('#');

// Also neutralize any '##vso[' that the trace logger captured (the reporter logs the full
// command line at trace level) so the assertion failure messages below cannot smuggle a
// real AzDO command into the build output when this test fails on CI.
string safeLogs = string.Join("\n", logger.Logs).Replace("##vso[", "vso[");

Assert.IsNotNull(text, $"AzDO reporter should have produced an annotation. Logs:\n{safeLogs}");

// Mirror the path normalization performed by AzureDevOpsReporter.GetErrorText:
// * Strip the deterministic-build root '/_/' (set by <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
// in CI), then fall back to stripping the runtime repo root for local builds.
// * Normalize separators to '/'.
// Without this, the test breaks in CI where [CallerFilePath] returns '/_/test/...'
// but the reporter strips '/_/' and emits 'sourcepath=test/...'.
const string DeterministicBuildRoot = "/_/";
string normalizedTestFile = testFile.Replace('\\', '/');
string repoRoot = RootFinder.Find().Replace('\\', '/');
string expectedRelativeFile = normalizedTestFile.StartsWith(DeterministicBuildRoot, StringComparison.OrdinalIgnoreCase)
? normalizedTestFile.Substring(DeterministicBuildRoot.Length)
: normalizedTestFile.StartsWith(repoRoot, StringComparison.OrdinalIgnoreCase)
Comment thread
Evangelink marked this conversation as resolved.
? normalizedTestFile.Substring(repoRoot.Length).TrimStart('/')
: normalizedTestFile;

Assert.Contains(
$"sourcepath={expectedRelativeFile};",
text,
$"AzDO annotation must point at the test method's source file. Got: {text}\nLogs:\n{safeLogs}");
Assert.DoesNotContain(
"/src/TestFramework/TestFramework/Assertions/",
text,
$"AzDO annotation must not point at a framework Assert partial-class file. Got: {text}\nLogs:\n{safeLogs}");
}

[TestMethod]
public void FormatErrorMessage_PlacesTestNameAsTitleOnFirstLine()
{
Expand Down
Loading
Loading