diff --git a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsReporter.cs b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsReporter.cs index 0471c3dae8..43c7f0421b 100644 --- a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsReporter.cs +++ b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsReporter.cs @@ -130,23 +130,24 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella EnsureEnabledConfigurationLoaded(); TestNodeStateProperty? nodeState = nodeUpdateMessage.TestNode.Properties.SingleOrDefault(); string testDisplayName = nodeUpdateMessage.TestNode.DisplayName; - string testName = GetTestName(nodeUpdateMessage.TestNode); + // Defer GetTestName() to failure branches only: for passing/skipped/in-progress tests + // nodeState falls through the switch with no match and testName is never needed. switch (nodeState) { case FailedTestNodeStateProperty failed: - await WriteExceptionAsync(testDisplayName, testName, failed.Explanation, failed.Exception, cancellationToken).ConfigureAwait(false); + await WriteExceptionAsync(testDisplayName, GetTestName(nodeUpdateMessage.TestNode), failed.Explanation, failed.Exception, cancellationToken).ConfigureAwait(false); break; case ErrorTestNodeStateProperty error: - await WriteExceptionAsync(testDisplayName, testName, error.Explanation, error.Exception, cancellationToken).ConfigureAwait(false); + await WriteExceptionAsync(testDisplayName, GetTestName(nodeUpdateMessage.TestNode), error.Explanation, error.Exception, cancellationToken).ConfigureAwait(false); break; #pragma warning disable CS0618, MTP0001 // Type or member is obsolete case CancelledTestNodeStateProperty cancelled: #pragma warning restore CS0618, MTP0001 // Type or member is obsolete - await WriteExceptionAsync(testDisplayName, testName, cancelled.Explanation, cancelled.Exception, cancellationToken).ConfigureAwait(false); + await WriteExceptionAsync(testDisplayName, GetTestName(nodeUpdateMessage.TestNode), cancelled.Explanation, cancelled.Exception, cancellationToken).ConfigureAwait(false); break; case TimeoutTestNodeStateProperty timeout: - await WriteExceptionAsync(testDisplayName, testName, timeout.Explanation, timeout.Exception, cancellationToken).ConfigureAwait(false); + await WriteExceptionAsync(testDisplayName, GetTestName(nodeUpdateMessage.TestNode), timeout.Explanation, timeout.Exception, cancellationToken).ConfigureAwait(false); break; } } diff --git a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/TestNodeIdentity.cs b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/TestNodeIdentity.cs index 4eb5d4c76e..9e9f263c55 100644 --- a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/TestNodeIdentity.cs +++ b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/TestNodeIdentity.cs @@ -15,8 +15,20 @@ internal static class TestNodeIdentity /// fully-qualified name property is unavailable. /// public static string GetTestName(TestNode testNode) - => testNode.Properties - .OfType() - .FirstOrDefault(static property => property.Key == FullyQualifiedNamePropertyKey)?.Value - ?? testNode.DisplayName; + { + // Walk the PropertyBag once with the zero-allocation struct enumerator and short-circuit + // on the first matching key, avoiding the LINQ iterator/boxed-enumerator allocations that + // OfType().FirstOrDefault(...) would incur. + using PropertyBag.PropertyBagEnumerator enumerator = testNode.Properties.GetStructEnumerator(); + while (enumerator.MoveNext()) + { + if (enumerator.Current is SerializableKeyValuePairStringProperty kvp + && kvp.Key == FullyQualifiedNamePropertyKey) + { + return kvp.Value; + } + } + + return testNode.DisplayName; + } }