diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestModule.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestModule.java index 6dfc5137337..f0d2adcf08f 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestModule.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestModule.java @@ -64,7 +64,7 @@ public AbstractTestModule( } span = spanBuilder.start(); - tagsPropagator = new SpanTagsPropagator(span); + tagsPropagator = new SpanTagsPropagator(span, config.getCiVisibilityPropagatedTagKeys()); span.setSpanType(InternalSpanTypes.TEST_MODULE_END); span.setTag(Tags.SPAN_KIND, Tags.SPAN_KIND_TEST_MODULE); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java index cb17381f420..a979218d072 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java @@ -99,7 +99,7 @@ public AbstractTestSession( } span = spanBuilder.start(); - tagPropagator = new SpanTagsPropagator(span); + tagPropagator = new SpanTagsPropagator(span, config.getCiVisibilityPropagatedTagKeys()); span.setSpanType(InternalSpanTypes.TEST_SESSION_END); span.setTag(Tags.SPAN_KIND, Tags.SPAN_KIND_TEST_SESSION); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/SpanTagsPropagator.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/SpanTagsPropagator.java index 8002b235356..dbf5e2a9199 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/SpanTagsPropagator.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/SpanTagsPropagator.java @@ -18,15 +18,23 @@ public class SpanTagsPropagator { public static final Consumer NOOP_PROPAGATOR = span -> {}; private final AgentSpan parentSpan; + private final Collection propagatedTagKeys; private final Object tagPropagationLock = new Object(); public SpanTagsPropagator(AgentSpan parentSpan) { + this(parentSpan, Collections.emptyList()); + } + + public SpanTagsPropagator(AgentSpan parentSpan, Collection propagatedTagKeys) { this.parentSpan = parentSpan; + this.propagatedTagKeys = + propagatedTagKeys != null ? propagatedTagKeys : Collections.emptyList(); } public void propagateCiVisibilityTags(AgentSpan childSpan) { mergeTestFrameworks(getFrameworks(childSpan)); propagateStatus(childSpan); + propagateCustomTags(childSpan); } public void propagateStatus(AgentSpan childSpan) { @@ -49,6 +57,34 @@ public void propagateTags(AgentSpan childSpan, TagMergeSpec... specs) { } } + public void propagateCustomTags(AgentSpan childSpan) { + if (propagatedTagKeys.isEmpty()) { + return; + } + synchronized (tagPropagationLock) { + for (String key : propagatedTagKeys) { + Object value = childSpan.getTag(key); + if (value != null) { + parentSpan.setTag(key, value); + } + } + } + } + + public void propagateCustomTags(Map tags) { + if (propagatedTagKeys.isEmpty() || tags == null || tags.isEmpty()) { + return; + } + synchronized (tagPropagationLock) { + for (String key : propagatedTagKeys) { + Object value = tags.get(key); + if (value != null) { + parentSpan.setTag(key, value); + } + } + } + } + private void unsafeMergeTestFrameworks(Collection childFrameworks) { Collection parentFrameworks = getFrameworks(parentSpan); Collection merged = merge(parentFrameworks, childFrameworks); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestSuiteImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestSuiteImpl.java index 3c4a6dc4242..a567ee8fe70 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestSuiteImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestSuiteImpl.java @@ -112,7 +112,7 @@ public TestSuiteImpl( } span = spanBuilder.start(); - tagsPropagator = new SpanTagsPropagator(span); + tagsPropagator = new SpanTagsPropagator(span, config.getCiVisibilityPropagatedTagKeys()); span.setSpanType(InternalSpanTypes.TEST_SUITE_END); span.setTag(Tags.SPAN_KIND, Tags.SPAN_KIND_TEST_SUITE); @@ -275,6 +275,11 @@ public TestImpl testStart( executionResults, configurationErrors, capabilities, - tagsPropagator::propagateStatus); + this::propagateTags); + } + + private void propagateTags(AgentSpan childSpan) { + tagsPropagator.propagateStatus(childSpan); + tagsPropagator.propagateCustomTags(childSpan); } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java index 48915cdb479..cca411ef1a6 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java @@ -290,6 +290,7 @@ private SignalResponse onModuleExecutionResultReceived(ModuleExecutionResult res testsSkipped.add(result.getTestsSkippedTotal()); tagsPropagator.mergeTestFrameworks(result.getTestFrameworks()); + tagsPropagator.propagateCustomTags(result.getPropagatedTags()); return AckResponse.INSTANCE; } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java index 41107bbe923..af942fe4cb0 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java @@ -31,6 +31,8 @@ import datadog.trace.civisibility.test.ExecutionResults; import datadog.trace.civisibility.test.ExecutionStrategy; import java.util.Collection; +import java.util.Map; +import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; @@ -61,6 +63,8 @@ public class ProxyTestModule implements TestFrameworkModule { private final LinesResolver linesResolver; private final CoverageStore.Factory coverageStoreFactory; private final Collection testFrameworks = ConcurrentHashMap.newKeySet(); + private final Map propagatedTags = new ConcurrentHashMap<>(); + private final Set propagatedTagKeys; private final Collection capabilities; public ProxyTestModule( @@ -91,6 +95,7 @@ public ProxyTestModule( this.linesResolver = linesResolver; this.coverageStoreFactory = coverageStoreFactory; this.capabilities = capabilities; + this.propagatedTagKeys = config.getCiVisibilityPropagatedTagKeys(); } @Override @@ -180,7 +185,8 @@ private void sendModuleExecutionResult() { testManagementEnabled, hasFailedTestReplayTests, testsSkippedTotal, - new TreeSet<>(testFrameworks))); + new TreeSet<>(testFrameworks), + propagatedTags)); } catch (Exception e) { log.error("Error while reporting module execution result", e); @@ -215,13 +221,24 @@ public TestSuiteImpl testSuiteStart( executionResults, executionStrategy.getExecutionSettings().getConfigurationErrors(), capabilities, - this::propagateTestFrameworkData); + this::propagateData); } - private void propagateTestFrameworkData(AgentSpan childSpan) { + private void propagateData(AgentSpan childSpan) { testFrameworks.add( new TestFramework( (String) childSpan.getTag(Tags.TEST_FRAMEWORK), (String) childSpan.getTag(Tags.TEST_FRAMEWORK_VERSION))); + + if (propagatedTagKeys.isEmpty()) { + return; + } + + for (String key : propagatedTagKeys) { + Object value = childSpan.getTag(key); + if (value != null) { + propagatedTags.put(key, value); + } + } } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/ModuleExecutionResult.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/ModuleExecutionResult.java index 2f3508c366f..bb4388241d7 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/ModuleExecutionResult.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/ModuleExecutionResult.java @@ -4,6 +4,8 @@ import datadog.trace.civisibility.ipc.serialization.Serializer; import java.nio.ByteBuffer; import java.util.Collection; +import java.util.Collections; +import java.util.Map; import java.util.Objects; public class ModuleExecutionResult extends ModuleSignal { @@ -23,6 +25,7 @@ public class ModuleExecutionResult extends ModuleSignal { private final boolean hasFailedTestReplayTests; private final long testsSkippedTotal; private final Collection testFrameworks; + private final Map propagatedTags; public ModuleExecutionResult( DDTraceId sessionId, @@ -34,7 +37,8 @@ public ModuleExecutionResult( boolean testManagementEnabled, boolean hasFailedTestReplayTests, long testsSkippedTotal, - Collection testFrameworks) { + Collection testFrameworks, + Map propagatedTags) { super(sessionId, moduleId); this.coverageEnabled = coverageEnabled; this.testSkippingEnabled = testSkippingEnabled; @@ -44,6 +48,7 @@ public ModuleExecutionResult( this.hasFailedTestReplayTests = hasFailedTestReplayTests; this.testsSkippedTotal = testsSkippedTotal; this.testFrameworks = testFrameworks; + this.propagatedTags = propagatedTags != null ? propagatedTags : Collections.emptyMap(); } public boolean isCoverageEnabled() { @@ -78,6 +83,10 @@ public Collection getTestFrameworks() { return testFrameworks; } + public Map getPropagatedTags() { + return propagatedTags; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -94,7 +103,8 @@ public boolean equals(Object o) { && testSkippingEnabled == that.testSkippingEnabled && hasFailedTestReplayTests == that.hasFailedTestReplayTests && testsSkippedTotal == that.testsSkippedTotal - && Objects.equals(testFrameworks, that.testFrameworks); + && Objects.equals(testFrameworks, that.testFrameworks) + && Objects.equals(propagatedTags, that.propagatedTags); } @Override @@ -106,7 +116,8 @@ public int hashCode() { testSkippingEnabled, hasFailedTestReplayTests, testsSkippedTotal, - testFrameworks); + testFrameworks, + propagatedTags); } @Override @@ -161,6 +172,7 @@ public ByteBuffer serialize() { s.write(testsSkippedTotal); s.write(testFrameworks, TestFramework::serialize); + s.writeObjectMap(propagatedTags); return s.flush(); } @@ -180,6 +192,7 @@ public static ModuleExecutionResult deserialize(ByteBuffer buffer) { long testsSkippedTotal = Serializer.readLong(buffer); Collection testFrameworks = Serializer.readList(buffer, TestFramework::deserialize); + Map propagatedTags = Serializer.readObjectMap(buffer); return new ModuleExecutionResult( sessionId, @@ -191,6 +204,7 @@ public static ModuleExecutionResult deserialize(ByteBuffer buffer) { testManagementEnabled, hasFailedTestReplayTests, testsSkippedTotal, - testFrameworks); + testFrameworks, + propagatedTags); } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/serialization/Serializer.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/serialization/Serializer.java index 60feee6fd9d..ade8abcc55c 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/serialization/Serializer.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/serialization/Serializer.java @@ -16,6 +16,13 @@ import java.util.function.Supplier; public class Serializer { + private static final byte NULL = 0; + private static final byte STRING = 1; + private static final byte BOOLEAN = 2; + private static final byte INTEGER = 3; + private static final byte LONG = 4; + private static final byte FLOAT = 5; + private static final byte DOUBLE = 6; private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -45,6 +52,14 @@ public void write(long l) { baos.write((int) l); } + public void write(float f) { + write(Float.floatToIntBits(f)); + } + + public void write(double d) { + write(Double.doubleToLongBits(d)); + } + public void write(String s) { if (s == null) { write(-1); @@ -81,6 +96,10 @@ public void write(Map m) { write(m, Serializer::write, Serializer::write); } + public void writeObjectMap(Map m) { + write(m, Serializer::write, Serializer::writeObject); + } + public void write( Map m, BiConsumer keySerializer, @@ -104,6 +123,32 @@ public void write(BitSet bitSet) { } } + private void writeObject(Object value) { + if (value == null) { + write(NULL); + } else if (value instanceof String) { + write(STRING); + write((String) value); + } else if (value instanceof Boolean) { + write(BOOLEAN); + write((boolean) value); + } else if (value instanceof Integer) { + write(INTEGER); + write((int) value); + } else if (value instanceof Long) { + write(LONG); + write((long) value); + } else if (value instanceof Float) { + write(FLOAT); + write((float) value); + } else if (value instanceof Double) { + write(DOUBLE); + write((double) value); + } else { + throw new IllegalArgumentException("Unsupported value type: " + value.getClass()); + } + } + public int length() { return baos.size(); } @@ -133,6 +178,14 @@ public static long readLong(ByteBuffer byteBuffer) { return byteBuffer.getLong(); } + public static float readFloat(ByteBuffer byteBuffer) { + return Float.intBitsToFloat(readInt(byteBuffer)); + } + + public static double readDouble(ByteBuffer byteBuffer) { + return Double.longBitsToDouble(readLong(byteBuffer)); + } + public static String readString(ByteBuffer byteBuffer) { byte[] b = readByteArray(byteBuffer); return b != null ? new String(b, StandardCharsets.UTF_8) : null; @@ -175,6 +228,10 @@ public static Map readStringMap(ByteBuffer byteBuffer) { return readMap(byteBuffer, Serializer::readString, Serializer::readString); } + public static Map readObjectMap(ByteBuffer byteBuffer) { + return readMap(byteBuffer, Serializer::readString, Serializer::readObject); + } + public static Map readMap( ByteBuffer byteBuffer, Function keyDeserializer, @@ -212,6 +269,28 @@ private static Map fillMap( return m; } + private static Object readObject(ByteBuffer byteBuffer) { + byte type = readByte(byteBuffer); + switch (type) { + case NULL: + return null; + case STRING: + return readString(byteBuffer); + case BOOLEAN: + return readBoolean(byteBuffer); + case INTEGER: + return readInt(byteBuffer); + case LONG: + return readLong(byteBuffer); + case FLOAT: + return readFloat(byteBuffer); + case DOUBLE: + return readDouble(byteBuffer); + default: + throw new IllegalArgumentException("Unsupported value type: " + type); + } + } + public static BitSet readBitSet(ByteBuffer byteBuffer) { byte[] bytes = readByteArray(byteBuffer); return bytes != null ? BitSet.valueOf(bytes) : null; diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/SpanTagsPropagatorTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/SpanTagsPropagatorTest.groovy deleted file mode 100644 index f55cc33a753..00000000000 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/SpanTagsPropagatorTest.groovy +++ /dev/null @@ -1,175 +0,0 @@ -package datadog.trace.civisibility.domain - -import static datadog.trace.civisibility.domain.SpanTagsPropagator.TagMergeSpec - -import datadog.trace.api.civisibility.execution.TestStatus -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.civisibility.ipc.TestFramework -import datadog.trace.core.DDSpan -import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import spock.lang.Specification - -class SpanTagsPropagatorTest extends Specification { - def "test getFrameworks"() { - when: - def span = Stub(DDSpan) - span.getTag(Tags.TEST_FRAMEWORK) >> frameworkTag - span.getTag(Tags.TEST_FRAMEWORK_VERSION) >> frameworkVersionTag - - def frameworks = SpanTagsPropagator.getFrameworks(span) - - then: - frameworks == expected - - where: - frameworkTag | frameworkVersionTag | expected - "name" | "version" | [new TestFramework("name", "version")] - "name" | null | [new TestFramework("name", null)] - null | "version" | [] - ["nameA", "nameB"] | ["versionA", "versionB"] | [new TestFramework("nameA", "versionA"), new TestFramework("nameB", "versionB")] - ["nameA", "nameB"] | null | [new TestFramework("nameA", null), new TestFramework("nameB", null)] - ["nameA", "nameB"] | ["versionA", null] | [new TestFramework("nameA", "versionA"), new TestFramework("nameB", null)] - } - - def "test status propagation: #childStatus to #parentStatus"() { - given: - def parentSpan = Mock(DDSpan) - parentSpan.getTag(Tags.TEST_STATUS) >> parentStatus - - def childSpan = Mock(DDSpan) - childSpan.getTag(Tags.TEST_STATUS) >> childStatus - - def propagator = new SpanTagsPropagator(parentSpan) - - when: - propagator.propagateStatus(childSpan) - - then: - if (expectedStatus != null) { - 1 * parentSpan.setTag(Tags.TEST_STATUS, expectedStatus) - } else { - 0 * parentSpan.setTag(Tags.TEST_STATUS, _) - } - - where: - childStatus | parentStatus | expectedStatus - TestStatus.pass | null | TestStatus.pass - TestStatus.pass | TestStatus.skip | TestStatus.pass - TestStatus.pass | TestStatus.pass | null // no change - TestStatus.pass | TestStatus.fail | null // no change - TestStatus.fail | null | TestStatus.fail - TestStatus.fail | TestStatus.pass | TestStatus.fail - TestStatus.fail | TestStatus.skip | TestStatus.fail - TestStatus.fail | TestStatus.fail | TestStatus.fail - TestStatus.skip | null | TestStatus.skip - TestStatus.skip | TestStatus.pass | null // no change - TestStatus.skip | TestStatus.fail | null // no change - TestStatus.skip | TestStatus.skip | null // no change - null | TestStatus.pass | null // no change - } - - def "test framework merging: #childFrameworks and #parentFrameworks"() { - given: - def parentSpan = Mock(DDSpan) - parentSpan.getTag(Tags.TEST_FRAMEWORK) >> parentFrameworks.collect(it -> it.getName()) - parentSpan.getTag(Tags.TEST_FRAMEWORK_VERSION) >> parentFrameworks.collect(it -> it.getVersion()) - - def propagator = new SpanTagsPropagator(parentSpan) - - def expectedNames = expectedFrameworks.collect(it -> it.getName()) - def expectedVersions = expectedFrameworks.collect(it -> it.getVersion()) - - when: - propagator.mergeTestFrameworks(childFrameworks) - - then: - 1 * parentSpan.setAllTags([ - (Tags.TEST_FRAMEWORK) : expectedNames, - (Tags.TEST_FRAMEWORK_VERSION): expectedVersions - ]) - - where: - childFrameworks | parentFrameworks | expectedFrameworks - [] | [new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")] | [new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")] - [new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")] | [] | [new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")] - [new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")] | [new TestFramework("Spock", "2.3")] | [new TestFramework("JUnit", "5.8.0"), new TestFramework("Spock", "2.3"), new TestFramework("TestNG", "7.4.0")] - } - - def "test tag propagation: #childValue and #parentValue with spec #tagSpec"() { - given: - def parentSpan = Mock(DDSpan) - parentSpan.getTag("tag") >> parentValue - - def childSpan = Mock(DDSpan) - childSpan.getTag("tag") >> childValue - - def propagator = new SpanTagsPropagator(parentSpan) - - when: - propagator.propagateTags(childSpan, tagSpec) - - then: - if (expectedChange) { - 1 * parentSpan.setTag("tag", expectedValue) - } else { - 0 * parentSpan.setTag("tag", _) - } - - where: - tagSpec | childValue | parentValue | expectedChange | expectedValue - TagMergeSpec.of("tag") | "a" | "b" | true | "a" - TagMergeSpec.of("tag") | null | "b" | false | "b" - TagMergeSpec.of("tag") | null | null | false | null - TagMergeSpec.of("tag", Boolean::logicalOr) | true | false | true | true - TagMergeSpec.of("tag", Boolean::logicalOr) | false | false | true | false - } - - def "test synchronized propagation"() { - given: - def parentSpan = Mock(DDSpan) - def propagator = new SpanTagsPropagator(parentSpan) - def numThreads = 9 - def latch = new CountDownLatch(numThreads) - def executor = Executors.newFixedThreadPool(numThreads) - def exceptions = Collections.synchronizedList([]) - - when: - numThreads.times { i -> - executor.submit { - try { - switch (i % 3) { - case 0: - def childSpan = Mock(DDSpan) - childSpan.getTag(Tags.TEST_STATUS) >> TestStatus.fail - propagator.propagateStatus(childSpan) - break - case 1: - def frameworks = [new TestFramework("JUnit${i}", "5.${i}")] - propagator.mergeTestFrameworks(frameworks) - break - case 2: - def childSpan = Mock(DDSpan) - childSpan.getTag("custom.tag.${i}") >> "value${i}" - propagator.propagateTags(childSpan, TagMergeSpec.of("custom.tag.${i}")) - break - } - } catch (Exception e) { - exceptions.add(e) - } finally { - latch.countDown() - } - } - } - - latch.await(5, TimeUnit.SECONDS) - executor.shutdown() - - then: - exceptions.isEmpty() - 3 * parentSpan.setTag(Tags.TEST_STATUS, TestStatus.fail) - 3 * parentSpan.setAllTags(_) - 3 * parentSpan.setTag({ it.startsWith("custom.tag.") }, { it.startsWith("value") }) - } -} diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/headless/HeadlessTestSessionTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/headless/HeadlessTestSessionTest.groovy index 2b0ddfeeadb..4e6a1ff5f63 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/headless/HeadlessTestSessionTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/headless/HeadlessTestSessionTest.groovy @@ -24,6 +24,8 @@ class HeadlessTestSessionTest extends SpanWriterTest { def module = session.testModuleStart("module-name", null) when: + module.setTag("custom.propagated_tag", "value") + module.setTag("custom.another_tag", 0.5d) module.end(null) session.end(null) @@ -34,10 +36,16 @@ class HeadlessTestSessionTest extends SpanWriterTest { spanType DDSpanTypes.TEST_SESSION_END tags(false) { "$Tags.TEST_TEST_MANAGEMENT_ENABLED" true + "custom.propagated_tag" "value" + "custom.another_tag" 0.5d } } span(1) { spanType DDSpanTypes.TEST_MODULE_END + tags(false) { + "custom.propagated_tag" "value" + "custom.another_tag" 0.5d + } } } }) @@ -47,13 +55,16 @@ class HeadlessTestSessionTest extends SpanWriterTest { def executionSettings = Stub(ExecutionSettings) executionSettings.getTestManagementSettings() >> new TestManagementSettings(true, 10) - def executionStrategy = new ExecutionStrategy(Stub(Config), executionSettings, Stub(SourcePathResolver), Stub(LinesResolver)) + def config = Stub(Config) + config.getCiVisibilityPropagatedTagKeys() >> ["custom.propagated_tag", "custom.another_tag"] + + def executionStrategy = new ExecutionStrategy(config, executionSettings, Stub(SourcePathResolver), Stub(LinesResolver)) new HeadlessTestSession( "project-name", null, Provider.UNSUPPORTED, - Stub(Config), + config, Stub(CiVisibilityMetricCollector), Stub(TestDecorator), Stub(SourcePathResolver), 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 index 51962ea5704..c59f1d3dc95 100644 --- 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 @@ -23,6 +23,8 @@ class ManualApiTest extends SpanWriterTest { def test = suite.testStart("test-name", null, null) when: + test.setTag("custom.tag", "something") + test.setTag("custom.another_tag", 2) test.end(null) suite.end(null) module.end(null) @@ -47,14 +49,26 @@ class ManualApiTest extends SpanWriterTest { moduleSpan.tags[Tags.TEST_FRAMEWORK] == component suiteSpan.tags[Tags.TEST_FRAMEWORK] == component testSpan.tags[Tags.TEST_FRAMEWORK] == component + sessionSpan.tags["custom.tag"] == "something" + sessionSpan.tags["custom.another_tag"] == 2 + sessionSpan.tags["custom.third_tag"] == null + moduleSpan.tags["custom.tag"] == "something" + moduleSpan.tags["custom.another_tag"] == 2 + moduleSpan.tags["custom.third_tag"] == null + suiteSpan.tags["custom.tag"] == "something" + suiteSpan.tags["custom.another_tag"] == 2 + suiteSpan.tags["custom.third_tag"] == null } private ManualApiTestSession givenAManualApiSession(String component) { + def config = Stub(Config) + config.getCiVisibilityPropagatedTagKeys() >> ["custom.tag", "custom.another_tag", "custom.third_tag"] + new ManualApiTestSession( "project-name", null, Provider.UNSUPPORTED, - Stub(Config), + config, Stub(CiVisibilityMetricCollector), new TestDecoratorImpl(component, "session-name", "test-command", [:]), Stub(SourcePathResolver), diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/ModuleExecutionResultTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/ModuleExecutionResultTest.groovy index 08d58ba8f84..5fd3ac51c4c 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/ModuleExecutionResultTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/ModuleExecutionResultTest.groovy @@ -16,11 +16,12 @@ class ModuleExecutionResultTest extends Specification { where: signal << [ - new ModuleExecutionResult(DDTraceId.from(12345), 67890, false, false, false, false, false, false, 0, Collections.emptyList()), - new ModuleExecutionResult(DDTraceId.from(12345), 67890, true, false, true, true, true, true, 1, Collections.singletonList(new TestFramework("junit", "4.13.2"))), - new ModuleExecutionResult(DDTraceId.from(12345), 67890, false, true, true, false, false, true, 2, Arrays.asList(new TestFramework("junit", "4.13.2"), new TestFramework("junit", "5.9.2"))), - new ModuleExecutionResult(DD128bTraceId.from(12345, 67890), 67890, false, false, false, true, true, false, 3, Arrays.asList(new TestFramework("junit", null), new TestFramework("junit", "5.9.2"))), - new ModuleExecutionResult(DD128bTraceId.from(12345, 67890), 67890, true, true, true, true, true, true, Integer.MAX_VALUE, Arrays.asList(new TestFramework("junit", "4.13.2"), new TestFramework(null, "5.9.2"))) + new ModuleExecutionResult(DDTraceId.from(12345), 67890, false, false, false, false, false, false, 0, Collections.emptyList(), Collections.emptyMap()), + new ModuleExecutionResult(DDTraceId.from(12345), 67890, true, false, true, true, true, true, 1, Collections.singletonList(new TestFramework("junit", "4.13.2")), Collections.emptyMap()), + new ModuleExecutionResult(DDTraceId.from(12345), 67890, false, true, true, false, false, true, 2, Arrays.asList(new TestFramework("junit", "4.13.2"), new TestFramework("junit", "5.9.2")), ["bazel.shard_index": "0", "bazel.total_shards": "2"]), + new ModuleExecutionResult(DDTraceId.from(12345), 67890, false, true, true, false, false, true, 2, Arrays.asList(new TestFramework("junit", "4.13.2"), new TestFramework("junit", "5.9.2")), ["bazel.shard_index": 0, "bazel.total_shards": 2L, "custom.success": true, "custom.ratio": 0.5d]), + new ModuleExecutionResult(DD128bTraceId.from(12345, 67890), 67890, false, false, false, true, true, false, 3, Arrays.asList(new TestFramework("junit", null), new TestFramework("junit", "5.9.2")), Collections.emptyMap()), + new ModuleExecutionResult(DD128bTraceId.from(12345, 67890), 67890, true, true, true, true, true, true, Integer.MAX_VALUE, Arrays.asList(new TestFramework("junit", "4.13.2"), new TestFramework(null, "5.9.2")), ["custom.tier": "gold", "custom.region": "us-east-1"]) ] } } diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/SignalServerTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/SignalServerTest.groovy index 99331516948..7137e22ac12 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/SignalServerTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/ipc/SignalServerTest.groovy @@ -12,7 +12,7 @@ class SignalServerTest extends Specification { def "test message send and receive"() { given: def signalProcessed = new AtomicBoolean(false) - def signal = new ModuleExecutionResult(DDTraceId.from(123), 456, true, true, false, false, false, true, 1, Collections.singletonList(new TestFramework("junit", "4.13.2"))) + def signal = new ModuleExecutionResult(DDTraceId.from(123), 456, true, true, false, false, false, true, 1, Collections.singletonList(new TestFramework("junit", "4.13.2")), ["tag_a": "1", "custom.tag_b": "value"]) def server = new SignalServer() def received = new ArrayList() @@ -41,8 +41,8 @@ class SignalServerTest extends Specification { def "test multiple messages send and receive"() { given: - def signalA = new ModuleExecutionResult(DDTraceId.from(123), 456, false, false, false, false, false, false, 0, Collections.singletonList(new TestFramework("junit", "4.13.2"))) - def signalB = new ModuleExecutionResult(DDTraceId.from(234), 567, true, true, false, false, true, false, 1, Collections.singletonList(new TestFramework("junit", "4.13.2"))) + def signalA = new ModuleExecutionResult(DDTraceId.from(123), 456, false, false, false, false, false, false, 0, Collections.singletonList(new TestFramework("junit", "4.13.2")), Collections.emptyMap()) + def signalB = new ModuleExecutionResult(DDTraceId.from(234), 567, true, true, false, false, true, false, 1, Collections.singletonList(new TestFramework("junit", "4.13.2")), Collections.emptyMap()) def server = new SignalServer() def received = new ArrayList() @@ -70,8 +70,8 @@ class SignalServerTest extends Specification { def "test multiple clients send and receive"() { given: - def signalA = new ModuleExecutionResult(DDTraceId.from(123), 456, true, false, true, false, true, false, 1, Collections.singletonList(new TestFramework("junit", "4.13.2"))) - def signalB = new ModuleExecutionResult(DDTraceId.from(234), 567, false, true, false, true, false, false, 0, Collections.singletonList(new TestFramework("junit", "4.13.2"))) + def signalA = new ModuleExecutionResult(DDTraceId.from(123), 456, true, false, true, false, true, false, 1, Collections.singletonList(new TestFramework("junit", "4.13.2")), Collections.emptyMap()) + def signalB = new ModuleExecutionResult(DDTraceId.from(234), 567, false, true, false, true, false, false, 0, Collections.singletonList(new TestFramework("junit", "4.13.2")), Collections.emptyMap()) def server = new SignalServer() def received = new ArrayList() @@ -118,7 +118,7 @@ class SignalServerTest extends Specification { when: def address = server.getAddress() try (def client = new SignalClient(address, clientTimeoutMillis)) { - client.send(new ModuleExecutionResult(DDTraceId.from(123), 456, false, false, false, false, false, false, 0, Collections.singletonList(new TestFramework("junit", "4.13.2")))) + client.send(new ModuleExecutionResult(DDTraceId.from(123), 456, false, false, false, false, false, false, 0, Collections.singletonList(new TestFramework("junit", "4.13.2")), Collections.emptyMap())) } then: @@ -130,7 +130,7 @@ class SignalServerTest extends Specification { def "test error response receipt"() { given: - def signal = new ModuleExecutionResult(DDTraceId.from(123), 456, true, true, false, false, true, false, 1, Collections.singletonList(new TestFramework("junit", "4.13.2"))) + def signal = new ModuleExecutionResult(DDTraceId.from(123), 456, true, true, false, false, true, false, 1, Collections.singletonList(new TestFramework("junit", "4.13.2")), Collections.emptyMap()) def server = new SignalServer() def errorResponse = new ErrorResponse("An error occurred while processing the signal") diff --git a/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/domain/SpanTagsPropagatorTest.java b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/domain/SpanTagsPropagatorTest.java new file mode 100644 index 00000000000..ebb7fc3d948 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/domain/SpanTagsPropagatorTest.java @@ -0,0 +1,331 @@ +package datadog.trace.civisibility.domain; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import datadog.trace.api.civisibility.execution.TestStatus; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.civisibility.domain.SpanTagsPropagator.TagMergeSpec; +import datadog.trace.civisibility.ipc.TestFramework; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SpanTagsPropagatorTest { + + @ParameterizedTest + @MethodSource("frameworkTags") + void testGetFrameworks( + Object frameworkTag, + Object frameworkVersionTag, + Collection expectedFrameworks) { + AgentSpan span = mock(AgentSpan.class); + when(span.getTag(Tags.TEST_FRAMEWORK)).thenReturn(frameworkTag); + when(span.getTag(Tags.TEST_FRAMEWORK_VERSION)).thenReturn(frameworkVersionTag); + + Collection frameworks = SpanTagsPropagator.getFrameworks(span); + + assertEquals(expectedFrameworks, frameworks); + } + + private static Stream frameworkTags() { + return Stream.of( + Arguments.of("name", "version", singletonList(new TestFramework("name", "version"))), + Arguments.of("name", null, singletonList(new TestFramework("name", null))), + Arguments.of(null, "version", emptyList()), + Arguments.of( + asList("nameA", "nameB"), + asList("versionA", "versionB"), + asList(new TestFramework("nameA", "versionA"), new TestFramework("nameB", "versionB"))), + Arguments.of( + asList("nameA", "nameB"), + null, + asList(new TestFramework("nameA", null), new TestFramework("nameB", null))), + Arguments.of( + asList("nameA", "nameB"), + asList("versionA", null), + asList(new TestFramework("nameA", "versionA"), new TestFramework("nameB", null)))); + } + + @ParameterizedTest + @MethodSource("statusPropagations") + void testStatusPropagation( + TestStatus childStatus, TestStatus parentStatus, TestStatus expectedStatus) { + AgentSpan parentSpan = mock(AgentSpan.class); + when(parentSpan.getTag(Tags.TEST_STATUS)).thenReturn(parentStatus); + + AgentSpan childSpan = mock(AgentSpan.class); + when(childSpan.getTag(Tags.TEST_STATUS)).thenReturn(childStatus); + + SpanTagsPropagator propagator = new SpanTagsPropagator(parentSpan); + + propagator.propagateStatus(childSpan); + + if (expectedStatus != null) { + verify(parentSpan).setTag(Tags.TEST_STATUS, expectedStatus); + } else { + verify(parentSpan, never()).setTag(eq(Tags.TEST_STATUS), isA(Object.class)); + } + } + + private static Stream statusPropagations() { + return Stream.of( + Arguments.of(TestStatus.pass, null, TestStatus.pass), + Arguments.of(TestStatus.pass, TestStatus.skip, TestStatus.pass), + Arguments.of(TestStatus.pass, TestStatus.pass, null), + Arguments.of(TestStatus.pass, TestStatus.fail, null), + Arguments.of(TestStatus.fail, null, TestStatus.fail), + Arguments.of(TestStatus.fail, TestStatus.pass, TestStatus.fail), + Arguments.of(TestStatus.fail, TestStatus.skip, TestStatus.fail), + Arguments.of(TestStatus.fail, TestStatus.fail, TestStatus.fail), + Arguments.of(TestStatus.skip, null, TestStatus.skip), + Arguments.of(TestStatus.skip, TestStatus.pass, null), + Arguments.of(TestStatus.skip, TestStatus.fail, null), + Arguments.of(TestStatus.skip, TestStatus.skip, null), + Arguments.of(null, TestStatus.pass, null)); + } + + @ParameterizedTest + @MethodSource("frameworkMerges") + void testFrameworkMerging( + Collection childFrameworks, + Collection parentFrameworks, + Collection expectedFrameworks) { + AgentSpan parentSpan = mock(AgentSpan.class); + when(parentSpan.getTag(Tags.TEST_FRAMEWORK)).thenReturn(names(parentFrameworks)); + when(parentSpan.getTag(Tags.TEST_FRAMEWORK_VERSION)).thenReturn(versions(parentFrameworks)); + + SpanTagsPropagator propagator = new SpanTagsPropagator(parentSpan); + Map> expectedTags = new HashMap<>(); + expectedTags.put(Tags.TEST_FRAMEWORK, names(expectedFrameworks)); + expectedTags.put(Tags.TEST_FRAMEWORK_VERSION, versions(expectedFrameworks)); + + propagator.mergeTestFrameworks(childFrameworks); + + verify(parentSpan).setAllTags(expectedTags); + } + + private static Stream frameworkMerges() { + return Stream.of( + Arguments.of( + emptyList(), + asList(new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")), + asList(new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0"))), + Arguments.of( + asList(new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")), + emptyList(), + asList(new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0"))), + Arguments.of( + asList(new TestFramework("JUnit", "5.8.0"), new TestFramework("TestNG", "7.4.0")), + singletonList(new TestFramework("Spock", "2.3")), + asList( + new TestFramework("JUnit", "5.8.0"), + new TestFramework("Spock", "2.3"), + new TestFramework("TestNG", "7.4.0")))); + } + + @ParameterizedTest + @MethodSource("tagPropagations") + void testTagPropagation( + TagMergeSpec tagSpec, + T childValue, + T parentValue, + boolean expectedChange, + T expectedValue) { + AgentSpan parentSpan = mock(AgentSpan.class); + when(parentSpan.getTag("tag")).thenReturn(parentValue); + + AgentSpan childSpan = mock(AgentSpan.class); + when(childSpan.getTag("tag")).thenReturn(childValue); + + SpanTagsPropagator propagator = new SpanTagsPropagator(parentSpan); + + propagator.propagateTags(childSpan, tagSpec); + + if (expectedChange) { + verify(parentSpan).setTag("tag", expectedValue); + } else { + verify(parentSpan, never()).setTag(eq("tag"), isA(Object.class)); + } + } + + private static Stream tagPropagations() { + return Stream.of( + Arguments.of(TagMergeSpec.of("tag"), "a", "b", true, "a"), + Arguments.of(TagMergeSpec.of("tag"), null, "b", false, "b"), + Arguments.of(TagMergeSpec.of("tag"), null, null, false, null), + Arguments.of(TagMergeSpec.of("tag", Boolean::logicalOr), true, false, true, true), + Arguments.of(TagMergeSpec.of("tag", Boolean::logicalOr), false, false, true, false)); + } + + @ParameterizedTest + @MethodSource("customTagSpanPropagations") + void testCustomTagPropagationFromSpan( + Collection allowlist, + String key, + Object childValue, + Object parentValue, + Object expectedValue) { + AgentSpan parentSpan = mock(AgentSpan.class); + when(parentSpan.getTag(key)).thenReturn(parentValue); + + AgentSpan childSpan = mock(AgentSpan.class); + when(childSpan.getTag(key)).thenReturn(childValue); + + SpanTagsPropagator propagator = new SpanTagsPropagator(parentSpan, allowlist); + + propagator.propagateCustomTags(childSpan); + + if (expectedValue != null) { + verify(parentSpan).setTag(key, expectedValue); + } else { + verify(parentSpan, never()).setTag(eq(key), isA(Object.class)); + } + } + + private static Stream customTagSpanPropagations() { + return Stream.of( + Arguments.of(singletonList("example.number"), "example.number", 1L, null, 1L), + Arguments.of(singletonList("example.number"), "example.number", 2L, 1L, 2L), + Arguments.of(singletonList("example.number"), "example.number", null, 1L, null), + Arguments.of(singletonList("example.number"), "example.count", 4, null, null), + Arguments.of(emptyList(), "example.number", 1L, null, null), + Arguments.of(null, "example.number", 1L, null, null), + Arguments.of(singletonList("example.flag"), "example.flag", true, null, true), + Arguments.of(singletonList("example.ratio"), "example.ratio", 0.5d, null, 0.5d), + Arguments.of(singletonList("example.label"), "example.label", "red", null, "red")); + } + + @ParameterizedTest + @MethodSource("customTagMapPropagations") + void testCustomTagPropagationFromMap( + Collection allowlist, Map tags, int expectedSets) { + AgentSpan parentSpan = mock(AgentSpan.class); + SpanTagsPropagator propagator = new SpanTagsPropagator(parentSpan, allowlist); + + propagator.propagateCustomTags(tags); + + verify(parentSpan, times(expectedSets)).setTag(any(String.class), any(Object.class)); + } + + private static Stream customTagMapPropagations() { + return Stream.of( + Arguments.of( + asList("example.number", "example.count"), + mapOf("example.number", 1L, "example.count", 4), + 2), + Arguments.of(singletonList("example.flag"), mapOf("example.flag", true), 1), + Arguments.of( + singletonList("example.number"), mapOf("example.number", 1L, "example.count", 4), 1), + Arguments.of(singletonList("example.ratio"), Collections.emptyMap(), 0), + Arguments.of(emptyList(), mapOf("example.ratio", 0.5d), 0), + Arguments.of(null, mapOf("example.ratio", 0.5d), 0)); + } + + @Test + void testSynchronizedPropagation() throws InterruptedException { + AgentSpan parentSpan = mock(AgentSpan.class); + SpanTagsPropagator propagator = new SpanTagsPropagator(parentSpan); + int numThreads = 9; + CountDownLatch latch = new CountDownLatch(numThreads); + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + List exceptions = Collections.synchronizedList(new java.util.ArrayList<>()); + + for (int i = 0; i < numThreads; i++) { + final int index = i; + executor.submit( + () -> { + try { + switch (index % 3) { + case 0: + AgentSpan childSpan = mock(AgentSpan.class); + when(childSpan.getTag(Tags.TEST_STATUS)).thenReturn(TestStatus.fail); + propagator.propagateStatus(childSpan); + break; + case 1: + Collection frameworks = + singletonList(new TestFramework("JUnit" + index, "5." + index)); + propagator.mergeTestFrameworks(frameworks); + break; + case 2: + AgentSpan customTagChildSpan = mock(AgentSpan.class); + String tagKey = "custom.tag." + index; + when(customTagChildSpan.getTag(tagKey)).thenReturn("value" + index); + propagator.propagateTags(customTagChildSpan, TagMergeSpec.of(tagKey)); + break; + default: + throw new IllegalStateException("Unexpected remainder"); + } + } catch (Exception e) { + exceptions.add(e); + } finally { + latch.countDown(); + } + }); + } + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + executor.shutdown(); + + assertTrue(exceptions.isEmpty()); + verify(parentSpan, times(3)).setTag(Tags.TEST_STATUS, TestStatus.fail); + verify(parentSpan, times(3)).setAllTags(any()); + verify(parentSpan, times(3)) + .setTag( + argThat((String key) -> key.startsWith("custom.tag.")), + argThat((Object value) -> value.toString().startsWith("value"))); + } + + private static Collection names(Collection frameworks) { + List names = new java.util.ArrayList<>(); + for (TestFramework framework : frameworks) { + names.add(framework.getName()); + } + return names; + } + + private static Collection versions(Collection frameworks) { + List versions = new java.util.ArrayList<>(); + for (TestFramework framework : frameworks) { + versions.add(framework.getVersion()); + } + return versions; + } + + private static Map mapOf(String key, Object value) { + Map map = new HashMap<>(); + map.put(key, value); + return map; + } + + private static Map mapOf( + String firstKey, Object firstValue, String secondKey, Object secondValue) { + Map map = mapOf(firstKey, firstValue); + map.put(secondKey, secondValue); + return map; + } +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java index 05a1075f57d..198cd5752f8 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java @@ -83,6 +83,7 @@ public final class CiVisibilityConfig { public static final String TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES = "test.management.attempt.to.fix.retries"; public static final String TEST_FAILED_TEST_REPLAY_ENABLED = "test.failed.test.replay.enabled"; + public static final String CIVISIBILITY_PROPAGATED_TAGS = "civisibility.propagated.tags"; /* Git PR info */ public static final String GIT_PULL_REQUEST_BASE_BRANCH = "git.pull.request.base.branch"; diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index a463887f61a..8babb9fa4b3 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -280,6 +280,7 @@ import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JVM_INFO_CACHE_SIZE; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_KNOWN_TESTS_REQUEST_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_MODULE_NAME; +import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_PROPAGATED_TAGS; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_REPO_INDEX_DUPLICATE_KEY_CHECK_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_REPO_INDEX_FOLLOW_SYMLINKS; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_RESOURCE_FOLDER_NAMES; @@ -1169,6 +1170,7 @@ public static String getHostName() { private final String gitPullRequestBaseBranchSha; private final String gitCommitHeadSha; private final boolean ciVisibilityFailedTestReplayEnabled; + private final Set ciVisibilityPropagatedTagKeys; private final String testOptimizationManifestFile; private final boolean testOptimizationPayloadsInFiles; @@ -2708,6 +2710,8 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) gitCommitHeadSha = configProvider.getString(GIT_COMMIT_HEAD_SHA); ciVisibilityFailedTestReplayEnabled = configProvider.getBoolean(TEST_FAILED_TEST_REPLAY_ENABLED, true); + ciVisibilityPropagatedTagKeys = + configProvider.getSet(CIVISIBILITY_PROPAGATED_TAGS, Collections.emptySet()); testOptimizationManifestFile = configProvider.getString(TEST_OPTIMIZATION_MANIFEST_FILE); testOptimizationPayloadsInFiles = @@ -4489,6 +4493,10 @@ public boolean isCiVisibilityFailedTestReplayEnabled() { return ciVisibilityFailedTestReplayEnabled; } + public Set getCiVisibilityPropagatedTagKeys() { + return ciVisibilityPropagatedTagKeys; + } + public String getTestOptimizationManifestFile() { return testOptimizationManifestFile; } diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 8db93e05399..e9b8527786f 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -777,6 +777,14 @@ "aliases": [] } ], + "DD_CIVISIBILITY_PROPAGATED_TAGS": [ + { + "version": "A", + "type": "string", + "default": null, + "aliases": [] + } + ], "DD_CIVISIBILITY_REMOTE_ENV_VARS_PROVIDER_KEY": [ { "version": "A",