Skip to content
Merged
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
3 changes: 3 additions & 0 deletions tracer/src/Datadog.Trace/Ci/Tagging/TestSpanTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ public TestSpanTags(TestSuiteSpanTags suiteTags, string testName)
[Tag(TestTags.TestAttemptToFixPassed)]
public string? AttemptToFixPassed { get; set; }

[Tag(TestTags.TestFinalStatus)]
public string? FinalStatus { get; set; }

[Tag(CapabilitiesTags.LibraryCapabilitiesTestImpactAnalysis)]
public string? CapabilitiesTestImpactAnalysis { get; set; }

Expand Down
20 changes: 20 additions & 0 deletions tracer/src/Datadog.Trace/Ci/Tags/TestTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,21 @@ internal static class TestTags
/// </summary>
public const string TestRetryReason = "test.retry_reason";

/// <summary>
/// Retry reason value for Early Flake Detection
/// </summary>
public const string TestRetryReasonEfd = "efd";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, we're not using any namespacing on these tag names? I'm sure these are defined somewhere else, but still 😬

Also, you have efd and atr but not atf? 😅


/// <summary>
/// Retry reason value for Auto Test Retries
/// </summary>
public const string TestRetryReasonAtr = "atr";

/// <summary>
/// Retry reason value for Attempt to Fix (Test Management)
/// </summary>
public const string TestRetryReasonAttemptToFix = "attempt_to_fix";

/// <summary>
/// Test is quarantined flag
/// </summary>
Expand Down Expand Up @@ -185,4 +200,9 @@ internal static class TestTags
/// Test management enabled flag
/// </summary>
public const string TestManagementEnabled = "test.test_management.enabled";

/// <summary>
/// Test final status - the adjusted test outcome for CI pipelines
/// </summary>
public const string TestFinalStatus = "test.final_status";
}
4 changes: 2 additions & 2 deletions tracer/src/Datadog.Trace/Ci/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,8 @@ public void Close(TestStatus status, TimeSpan? duration, string? skipReason)
{
var retryReasonTag = tags.TestRetryReason switch
{
"efd" => MetricTags.CIVisibilityTestingEventTypeRetryReason.EarlyFlakeDetection,
"atr" => MetricTags.CIVisibilityTestingEventTypeRetryReason.AutomaticTestRetry,
TestTags.TestRetryReasonEfd => MetricTags.CIVisibilityTestingEventTypeRetryReason.EarlyFlakeDetection,
TestTags.TestRetryReasonAtr => MetricTags.CIVisibilityTestingEventTypeRetryReason.AutomaticTestRetry,
_ => MetricTags.CIVisibilityTestingEventTypeRetryReason.None
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading;
using Datadog.Trace.Ci;
using Datadog.Trace.Ci.Net;
using Datadog.Trace.Ci.Tagging;
using Datadog.Trace.Ci.Tags;
using Datadog.Trace.Logging;
using Datadog.Trace.Util;
Expand Down Expand Up @@ -188,7 +189,7 @@ internal static bool SetEarlyFlakeDetectionTestTagsAndAbortReason(Test test, boo
if (isRetry)
{
testTags.TestIsRetry = "true";
testTags.TestRetryReason = "efd";
testTags.TestRetryReason = TestTags.TestRetryReasonEfd;
}
else
{
Expand All @@ -211,7 +212,7 @@ internal static bool SetFlakyRetryTags(Test test, bool isRetry)
{
var testTags = test.GetTags();
testTags.TestIsRetry = "true";
testTags.TestRetryReason = "atr";
testTags.TestRetryReason = TestTags.TestRetryReasonAtr;
}

return flakyRetryFeature;
Expand Down Expand Up @@ -244,7 +245,7 @@ internal static TestOptimizationClient.TestManagementResponseTestPropertiesAttri
if (isRetry)
{
testTags.TestIsRetry = "true";
testTags.TestRetryReason = "attempt_to_fix";
testTags.TestRetryReason = TestTags.TestRetryReasonAttemptToFix;
}
}

Expand Down Expand Up @@ -273,4 +274,49 @@ internal static void CheckFaultyThreshold(Test test, long nTestCases, long tTest
}
}
}

/// <summary>
/// Calculates the final status for a test based on execution results and test management tags.
/// Priority order (first match wins):
/// 1. Quarantined/disabled -> skip (always mask to skip)
/// 2. For ATF tests: any execution failed -> fail (flaky test = fix didn't work)
/// 3. Any execution passed -> pass
/// 4. Skip/inconclusive AND no pass -> skip
/// 5. All executions failed -> fail
/// </summary>
/// <param name="anyExecutionPassed">True if any execution (initial or retry) passed.</param>
/// <param name="anyExecutionFailed">True if any execution (initial or retry) failed.</param>
/// <param name="isSkippedOrInconclusive">True if the current/last execution was skip or inconclusive.</param>
/// <param name="testTags">The test tags to check for quarantine/disabled/ATF status.</param>
/// <returns>The final status string: "pass", "fail", or "skip".</returns>
internal static string CalculateFinalStatus(bool anyExecutionPassed, bool anyExecutionFailed, bool isSkippedOrInconclusive, TestSpanTags? testTags)
{
// Priority 1: Quarantined/disabled tests always mask to skip
if (testTags?.IsQuarantined == "true" || testTags?.IsDisabled == "true")
{
return TestTags.StatusSkip;
}

// Priority 2: For ATF tests, any failure means fix didn't work (test is still flaky)
// This must be checked BEFORE anyPassed for ATF tests
if (testTags?.IsAttemptToFix == "true" && anyExecutionFailed)
{
return TestTags.StatusFail;
}

// Priority 3: Any execution passed -> pass (pass takes precedence over skip)
if (anyExecutionPassed)
{
return TestTags.StatusPass;
}

// Priority 4: Skip/inconclusive AND no pass -> skip
if (isSkippedOrInconclusive)
{
return TestTags.StatusSkip;
}

// Priority 5: All executions failed -> fail
return TestTags.StatusFail;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Reflection;
using System.Threading.Tasks;
using Datadog.Trace.Ci;
using Datadog.Trace.Ci.Tags;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Testing.MsTestV2;
Expand Down Expand Up @@ -37,8 +38,13 @@ protected void ProcessTestMethod(object testMethod)
if (testMethod.TryDuckCast<ITestMethod>(out var testMethodInfo))
{
// Create the skip span
MsTestIntegration.OnMethodBegin(testMethodInfo, testMethod.GetType(), isRetry: false)?
.Close(TestStatus.Skip, TimeSpan.Zero, _skipReason);
var test = MsTestIntegration.OnMethodBegin(testMethodInfo, testMethod.GetType(), isRetry: false);
if (test is not null)
{
// Set final_status = skip for pre-execution skipped tests (ITR/attribute-based skips)
test.GetTags().FinalStatus = TestTags.StatusSkip;
test.Close(TestStatus.Skip, TimeSpan.Zero, _skipReason);
}
}
}

Expand Down
Loading
Loading