diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs index 545531f35a..c5734197ab 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs @@ -68,6 +68,10 @@ internal TrxLogger(IFileHelper fileHelper, TrxFileHelper trxFileHelper) private ConcurrentDictionary? _innerResults; private ConcurrentDictionary? _innerTestEntries; + // Tracks which parent data-driven test results have already had their count contribution corrected. + // Using TryAdd ensures the decrement fires exactly once even under concurrent dispatch. + private ConcurrentDictionary? _parentResultsCorrected; + private readonly TrxFileHelper _trxFileHelper; /// @@ -93,11 +97,11 @@ internal TrxLogger(IFileHelper fileHelper, TrxFileHelper trxFileHelper) #region ITestLogger - [MemberNotNullWhen(true, nameof(_testResultsDirPath), nameof(_results), nameof(_innerResults), nameof(_testElements), nameof(_entries), nameof(_innerTestEntries), nameof(_runLevelErrorsAndWarnings), nameof(_runLevelStdOut))] + [MemberNotNullWhen(true, nameof(_testResultsDirPath), nameof(_results), nameof(_innerResults), nameof(_testElements), nameof(_entries), nameof(_innerTestEntries), nameof(_runLevelErrorsAndWarnings), nameof(_runLevelStdOut), nameof(_parentResultsCorrected))] private bool IsInitialized { get; set; } /// - [MemberNotNull(nameof(_testResultsDirPath), nameof(_results), nameof(_innerResults), nameof(_testElements), nameof(_entries), nameof(_innerTestEntries), nameof(_runLevelErrorsAndWarnings), nameof(_runLevelStdOut))] + [MemberNotNull(nameof(_testResultsDirPath), nameof(_results), nameof(_innerResults), nameof(_testElements), nameof(_entries), nameof(_innerTestEntries), nameof(_runLevelErrorsAndWarnings), nameof(_runLevelStdOut), nameof(_parentResultsCorrected))] public void Initialize(TestLoggerEvents events, string testResultsDirPath) { ValidateArg.NotNull(events, nameof(events)); @@ -120,6 +124,7 @@ public void Initialize(TestLoggerEvents events, string testResultsDirPath) PassedTestCount = 0; FailedTestCount = 0; _runLevelStdOut = new StringBuilder(); + _parentResultsCorrected = new ConcurrentDictionary(); TestRunStartTime = DateTime.UtcNow; IsInitialized = true; @@ -313,6 +318,22 @@ internal void TestResultHandler(object? sender, TestResultEventArgs e) { Interlocked.Increment(ref _passedTestCount); } + + // For data-driven tests, the parent result is a container whose counts should not be + // included in the summary — only the individual data row results should be counted. + // When the first inner data-driven result arrives, undo the parent's contribution exactly once. + // TryAdd is atomic, so this is safe under concurrent dispatch. + if (parentTestElement != null + && parentTestElement.TestType.Equals(TrxLoggerConstants.UnitTestType) + && parentTestResult is TestResultAggregation + && _parentResultsCorrected!.TryAdd(parentExecutionId, true)) + { + Interlocked.Decrement(ref _totalTestCount); + if (parentTestResult.Outcome == TrxLoggerObjectModel.TestOutcome.Failed) + Interlocked.Decrement(ref _failedTestCount); + else if (parentTestResult.Outcome == TrxLoggerObjectModel.TestOutcome.Passed) + Interlocked.Decrement(ref _passedTestCount); + } } /// diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs index 681e69d4ad..376e0d8a27 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs @@ -362,7 +362,7 @@ public void TestResultHandlerShouldAddHierarchicalResultsIfParentTestResultIsPre _testableTrxLogger.TestResultHandler(new object(), resultEventArg3.Object); Assert.AreEqual(1, _testableTrxLogger.TestResultCount, "TestResultHandler is not creating hierarchical results when parent result is present."); - Assert.AreEqual(3, _testableTrxLogger.TotalTestCount, "TestResultHandler is not adding all inner results in parent test result."); + Assert.AreEqual(2, _testableTrxLogger.TotalTestCount, "DataDrivenTest parent result should not be counted; only the inner data row results should be counted."); } [TestMethod] @@ -425,6 +425,39 @@ public void TestResultHandlerShouldAddSingleTestEntryForDataDrivenTests() Assert.AreEqual(1, _testableTrxLogger.TestEntryCount, "TestResultHandler is adding multiple test entries for data driven tests."); } + [TestMethod] + public void TestResultHandlerShouldCountOnlyDataRowResultsNotParentForDataDrivenTests() + { + TestCase testCase1 = CreateTestCase("TestCase1"); + + Guid parentExecutionId = Guid.NewGuid(); + + // Parent data-driven result (Passed overall) + VisualStudio.TestPlatform.ObjectModel.TestResult parentResult = new(testCase1); + parentResult.Outcome = TestOutcome.Passed; + parentResult.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, parentExecutionId); + + // Inner data row 1 (Passed) + VisualStudio.TestPlatform.ObjectModel.TestResult innerResult1 = new(testCase1); + innerResult1.Outcome = TestOutcome.Passed; + innerResult1.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + innerResult1.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + // Inner data row 2 (Failed) + VisualStudio.TestPlatform.ObjectModel.TestResult innerResult2 = new(testCase1); + innerResult2.Outcome = TestOutcome.Failed; + innerResult2.SetPropertyValue(TrxLoggerConstants.ExecutionIdProperty, Guid.NewGuid()); + innerResult2.SetPropertyValue(TrxLoggerConstants.ParentExecIdProperty, parentExecutionId); + + _testableTrxLogger.TestResultHandler(new object(), new Mock(parentResult).Object); + _testableTrxLogger.TestResultHandler(new object(), new Mock(innerResult1).Object); + _testableTrxLogger.TestResultHandler(new object(), new Mock(innerResult2).Object); + + Assert.AreEqual(2, _testableTrxLogger.TotalTestCount, "Only data row results (not the parent DataDrivenTest) should be counted in the total."); + Assert.AreEqual(1, _testableTrxLogger.PassedTestCount, "Only the passed data row result should be counted."); + Assert.AreEqual(1, _testableTrxLogger.FailedTestCount, "Only the failed data row result should be counted."); + } + [TestMethod] public void TestResultHandlerShouldAddHierarchicalResultsForOrderedTest() {