Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 23 additions & 2 deletions src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ internal TrxLogger(IFileHelper fileHelper, TrxFileHelper trxFileHelper)
private ConcurrentDictionary<Guid, ITestResult>? _innerResults;
private ConcurrentDictionary<Guid, TestEntry>? _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<Guid, bool>? _parentResultsCorrected;

private readonly TrxFileHelper _trxFileHelper;

/// <summary>
Expand All @@ -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; }

/// <inheritdoc/>
[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));
Expand All @@ -120,6 +124,7 @@ public void Initialize(TestLoggerEvents events, string testResultsDirPath)
PassedTestCount = 0;
FailedTestCount = 0;
_runLevelStdOut = new StringBuilder();
_parentResultsCorrected = new ConcurrentDictionary<Guid, bool>();
TestRunStartTime = DateTime.UtcNow;

IsInitialized = true;
Expand Down Expand Up @@ -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);
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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<TestResultEventArgs>(parentResult).Object);
_testableTrxLogger.TestResultHandler(new object(), new Mock<TestResultEventArgs>(innerResult1).Object);
_testableTrxLogger.TestResultHandler(new object(), new Mock<TestResultEventArgs>(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()
{
Expand Down
Loading