From 478272cb03df2073f1e2a57c8a62804dfbdea192 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Thu, 8 Jan 2026 10:04:26 +0100 Subject: [PATCH] feat: add framework tag on manual API suites and tests --- .../domain/manualapi/ManualApiTestModule.java | 49 ++++++++------ .../domain/manualapi/ManualApiTestSuite.java | 51 ++++++++++++++ .../domain/manualapi/ManualApiTest.groovy | 66 +++++++++++++++++++ 3 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestSuite.java create mode 100644 dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/manualapi/ManualApiTest.groovy diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestModule.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestModule.java index 22dbe30994a..53bcaebc4fa 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestModule.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestModule.java @@ -7,6 +7,7 @@ import datadog.trace.api.civisibility.telemetry.tag.TestFrameworkInstrumentation; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.civisibility.codeowners.Codeowners; import datadog.trace.civisibility.decorator.TestDecorator; import datadog.trace.civisibility.domain.AbstractTestModule; @@ -56,30 +57,36 @@ public ManualApiTestModule( } @Override - public TestSuiteImpl testSuiteStart( + public ManualApiTestSuite testSuiteStart( String testSuiteName, @Nullable Class testClass, @Nullable Long startTime, boolean parallelized) { - return new TestSuiteImpl( - span.context(), - moduleName, - testSuiteName, - null, - testClass, - startTime, - parallelized, - InstrumentationType.MANUAL_API, - TestFrameworkInstrumentation.OTHER, - config, - metricCollector, - testDecorator, - sourcePathResolver, - codeowners, - linesResolver, - coverageStoreFactory, - executionResults, - Collections.emptyList(), - tagsPropagator::propagateCiVisibilityTags); + TestSuiteImpl suite = + new TestSuiteImpl( + span.context(), + moduleName, + testSuiteName, + null, + testClass, + startTime, + parallelized, + InstrumentationType.MANUAL_API, + TestFrameworkInstrumentation.OTHER, // for metric purposes, framework is OTHER + config, + metricCollector, + testDecorator, + sourcePathResolver, + codeowners, + linesResolver, + coverageStoreFactory, + executionResults, + Collections.emptyList(), + tagsPropagator::propagateCiVisibilityTags); + + String frameworkName = testDecorator.component().toString(); + suite.setTag(Tags.TEST_FRAMEWORK, frameworkName); + + return new ManualApiTestSuite(suite, frameworkName); } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestSuite.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestSuite.java new file mode 100644 index 00000000000..312ce6c8cc9 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/manualapi/ManualApiTestSuite.java @@ -0,0 +1,51 @@ +package datadog.trace.civisibility.domain.manualapi; + +import datadog.trace.api.civisibility.DDTest; +import datadog.trace.api.civisibility.DDTestSuite; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.civisibility.domain.TestImpl; +import datadog.trace.civisibility.domain.TestSuiteImpl; +import java.lang.reflect.Method; +import javax.annotation.Nullable; + +/** + * Test suite that was created using manual API ({@link + * datadog.trace.api.civisibility.CIVisibility}). + */ +public class ManualApiTestSuite implements DDTestSuite { + + private final TestSuiteImpl delegate; + private final String frameworkName; + + public ManualApiTestSuite(TestSuiteImpl delegate, String frameworkName) { + this.delegate = delegate; + this.frameworkName = frameworkName; + } + + @Override + public void setTag(String key, Object value) { + delegate.setTag(key, value); + } + + @Override + public void setErrorInfo(Throwable error) { + delegate.setErrorInfo(error); + } + + @Override + public void setSkipReason(String skipReason) { + delegate.setSkipReason(skipReason); + } + + @Override + public void end(@Nullable Long endTime) { + delegate.end(endTime); + } + + @Override + public DDTest testStart(String testName, @Nullable Method testMethod, @Nullable Long startTime) { + TestImpl test = delegate.testStart(testName, testMethod, startTime); + test.setTag(Tags.TEST_FRAMEWORK, frameworkName); + return test; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/manualapi/ManualApiTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/manualapi/ManualApiTest.groovy new file mode 100644 index 00000000000..51962ea5704 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/manualapi/ManualApiTest.groovy @@ -0,0 +1,66 @@ +package datadog.trace.civisibility.domain.manualapi + +import datadog.trace.api.Config +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.civisibility.coverage.CoverageStore +import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector +import datadog.trace.api.civisibility.telemetry.tag.Provider +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.civisibility.codeowners.Codeowners +import datadog.trace.civisibility.decorator.TestDecoratorImpl +import datadog.trace.civisibility.domain.SpanWriterTest +import datadog.trace.civisibility.source.LinesResolver +import datadog.trace.civisibility.source.SourcePathResolver + +class ManualApiTest extends SpanWriterTest { + + def "test framework tag is set on suite, test, module and session with component info"() { + setup: + def component = "my-custom-framework" + def session = givenAManualApiSession(component) + def module = session.testModuleStart("module-name", null) + def suite = module.testSuiteStart("suite-name", null, null, false) + def test = suite.testStart("test-name", null, null) + + when: + test.end(null) + suite.end(null) + module.end(null) + session.end(null) + + then: + def traces = TEST_WRITER.toList() + traces.size() == 2 + + def allSpans = traces.flatten() + def sessionSpan = allSpans.find { it.spanType == DDSpanTypes.TEST_SESSION_END } + def moduleSpan = allSpans.find { it.spanType == DDSpanTypes.TEST_MODULE_END } + def suiteSpan = allSpans.find { it.spanType == DDSpanTypes.TEST_SUITE_END } + def testSpan = allSpans.find { it.spanType == DDSpanTypes.TEST } + + sessionSpan != null + moduleSpan != null + suiteSpan != null + testSpan != null + + sessionSpan.tags[Tags.TEST_FRAMEWORK] == component + moduleSpan.tags[Tags.TEST_FRAMEWORK] == component + suiteSpan.tags[Tags.TEST_FRAMEWORK] == component + testSpan.tags[Tags.TEST_FRAMEWORK] == component + } + + private ManualApiTestSession givenAManualApiSession(String component) { + new ManualApiTestSession( + "project-name", + null, + Provider.UNSUPPORTED, + Stub(Config), + Stub(CiVisibilityMetricCollector), + new TestDecoratorImpl(component, "session-name", "test-command", [:]), + Stub(SourcePathResolver), + Stub(Codeowners), + Stub(LinesResolver), + Stub(CoverageStore.Factory) + ) + } +}