diff --git a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Instrumentation.java b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Instrumentation.java index 8fdf749a5aa..f0b16304693 100644 --- a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Instrumentation.java +++ b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Instrumentation.java @@ -99,34 +99,48 @@ public static void onRunnerCreation(@Advice.This final Runner runner) { public static class JUnit4Advice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void addTracingListener( + public static JUnit4TracingListener addTracingListener( @Advice.This final Runner runner, @Advice.Argument(0) final RunNotifier runNotifier) { if (!JUnit4Utils.runnerToFramework(runner).equals(TestFrameworkInstrumentation.JUNIT4)) { // checking class names in hierarchyMatcher alone is not enough: // for example, Karate calls #run method of its super class, // that was transformed - return; + return null; } // No public accessor to get already installed listeners. // The installed RunListeners list are obtained using reflection. final List runListeners = JUnit4Utils.runListenersFromRunNotifier(runNotifier); if (runListeners == null) { - return; + return null; } for (final RunListener listener : runListeners) { RunListener tracingListener = JUnit4Utils.toTracingListener(listener); if (tracingListener != null) { // prevents installing TracingListener multiple times - return; + return null; } } - final TracingListener tracingListener = + final JUnit4TracingListener tracingListener = new JUnit4TracingListener( InstrumentationContext.get(Description.class, TestExecutionTracker.class)); runNotifier.addListener(tracingListener); + return tracingListener; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void finishTestSuites( + @Advice.Enter final JUnit4TracingListener tracingListener, + @Advice.Argument(0) final RunNotifier runNotifier) { + if (tracingListener != null) { + try { + tracingListener.finishTestSuites(); + } finally { + runNotifier.removeListener(tracingListener); + } + } } // JUnit 4.10 and above diff --git a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4TracingListener.java b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4TracingListener.java index afa43e45493..72443566543 100644 --- a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4TracingListener.java +++ b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4TracingListener.java @@ -8,6 +8,8 @@ import datadog.trace.bootstrap.ContextStore; import java.lang.reflect.Method; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.junit.Ignore; import org.junit.runner.Description; import org.junit.runner.notification.Failure; @@ -18,6 +20,7 @@ public class JUnit4TracingListener extends TracingListener { private static final String FRAMEWORK_VERSION = JUnit4Utils.getVersion(); private final ContextStore executionTrackers; + private final Map startedSuites = new ConcurrentHashMap<>(); public JUnit4TracingListener(ContextStore executionTrackers) { this.executionTrackers = executionTrackers; @@ -32,6 +35,21 @@ public void testSuiteStarted(final Description description) { } TestSuiteDescriptor suiteDescriptor = JUnit4Utils.toSuiteDescriptor(description); + startTestSuite(suiteDescriptor, description, false); + } + + private void startTestSuite( + TestSuiteDescriptor suiteDescriptor, Description description, boolean parallelized) { + startedSuites.computeIfAbsent( + suiteDescriptor, + key -> { + onTestSuiteStart(key, description, parallelized); + return parallelized; + }); + } + + private void onTestSuiteStart( + TestSuiteDescriptor suiteDescriptor, Description description, boolean parallelized) { Class testClass = description.getTestClass(); String testSuiteName = JUnit4Utils.getSuiteName(testClass, description); List categories = JUnit4Utils.getCategories(testClass, null); @@ -44,7 +62,7 @@ public void testSuiteStarted(final Description description) { FRAMEWORK_VERSION, testClass, categories, - false, + parallelized, TestFrameworkInstrumentation.JUNIT4, null); } @@ -58,11 +76,28 @@ public void testSuiteFinished(final Description description) { } TestSuiteDescriptor suiteDescriptor = JUnit4Utils.toSuiteDescriptor(description); + finishTestSuite(suiteDescriptor); + } + + private void finishTestSuite(TestSuiteDescriptor suiteDescriptor) { + if (startedSuites.remove(suiteDescriptor) == null) { + return; + } TestEventsHandlerHolder.HANDLERS .get(TestFrameworkInstrumentation.JUNIT4) .onTestSuiteFinish(suiteDescriptor, null); } + public void finishTestSuites() { + // Fallback suites have no thread-bound scope: their callbacks may run on an executor. + // Suites with native lifecycle events are finished by their own callbacks. + for (Map.Entry suite : startedSuites.entrySet()) { + if (suite.getValue()) { + finishTestSuite(suite.getKey()); + } + } + } + @Override public void testStarted(final Description description) { if (JUnit4Utils.isJUnitPlatformRunnerTest(description)) { @@ -70,6 +105,7 @@ public void testStarted(final Description description) { } TestSuiteDescriptor suiteDescriptor = JUnit4Utils.toSuiteDescriptor(description); + startTestSuite(suiteDescriptor, description, true); TestDescriptor testDescriptor = JUnit4Utils.toTestDescriptor(description); TestSourceData testSourceData = JUnit4Utils.toTestSourceData(description); @@ -179,21 +215,7 @@ public void testIgnored(final Description description) { TestSuiteDescriptor suiteDescriptor = JUnit4Utils.toSuiteDescriptor(description); Class testClass = description.getTestClass(); - String testSuiteName = JUnit4Utils.getSuiteName(testClass, description); - List categories = JUnit4Utils.getCategories(testClass, null); - - TestEventsHandlerHolder.HANDLERS - .get(TestFrameworkInstrumentation.JUNIT4) - .onTestSuiteStart( - suiteDescriptor, - testSuiteName, - FRAMEWORK_NAME, - FRAMEWORK_VERSION, - testClass, - categories, - false, - TestFrameworkInstrumentation.JUNIT4, - null); + startTestSuite(suiteDescriptor, description, false); TestEventsHandlerHolder.HANDLERS .get(TestFrameworkInstrumentation.JUNIT4) .onTestSuiteSkip(suiteDescriptor, reason); @@ -203,9 +225,7 @@ public void testIgnored(final Description description) { testIgnored(description, testMethod, reason); } - TestEventsHandlerHolder.HANDLERS - .get(TestFrameworkInstrumentation.JUNIT4) - .onTestSuiteFinish(suiteDescriptor, null); + finishTestSuite(suiteDescriptor); } } @@ -215,6 +235,7 @@ private void testIgnored(Description description, Method testMethod, String reas } TestSuiteDescriptor suiteDescriptor = JUnit4Utils.toSuiteDescriptor(description); + startTestSuite(suiteDescriptor, description, true); TestDescriptor testDescriptor = JUnit4Utils.toTestDescriptor(description); Class testClass = description.getTestClass(); diff --git a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/groovy/JUnit4Test.groovy b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/groovy/JUnit4Test.groovy index f38332ccdac..9c402aad390 100644 --- a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/groovy/JUnit4Test.groovy +++ b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/groovy/JUnit4Test.groovy @@ -2,19 +2,121 @@ import datadog.trace.api.DisableTestTrace import datadog.trace.api.civisibility.config.TestFQN import datadog.trace.api.civisibility.config.TestIdentifier import datadog.trace.api.civisibility.telemetry.tag.TestFrameworkInstrumentation +import datadog.trace.bootstrap.instrumentation.api.AgentTracer import datadog.trace.civisibility.CiVisibilityInstrumentationTest import datadog.trace.civisibility.diff.LineDiff import datadog.trace.instrumentation.junit4.JUnit4Utils import datadog.trace.instrumentation.junit4.TestEventsHandlerHolder import org.example.* import org.junit.jupiter.api.Assumptions +import org.junit.experimental.ParallelComputer +import org.junit.runner.Computer import org.junit.runner.JUnitCore +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors + @DisableTestTrace(reason = "avoid self-tracing") class JUnit4Test extends CiVisibilityInstrumentationTest { def runner = new JUnitCore() + def "test runner without suite lifecycle events"() { + when: + TestEventsHandlerHolder.start(TestFrameworkInstrumentation.JUNIT4, JUnit4Utils.capabilities(false)) + def result + try { + result = runner.run(TestSucceedCustomRunner) + } finally { + TestEventsHandlerHolder.stop(TestFrameworkInstrumentation.JUNIT4) + } + + assert result.runCount == 2 + assert result.ignoreCount == 1 + assert result.failures*.description*.methodName == ["existing failure"] + assert result.failures*.exception*.class == [AssertionError] + assertSpansData("test-custom-runner") + + then: + noExceptionThrown() + } + + def "test multiple runners without suite lifecycle events #mode"() { + when: + def initialSpan = AgentTracer.activeSpan() + TestEventsHandlerHolder.start(TestFrameworkInstrumentation.JUNIT4, JUnit4Utils.capabilities(false)) + def result + try { + result = runner.run(computer, tests as Class[]) + } finally { + TestEventsHandlerHolder.stop(TestFrameworkInstrumentation.JUNIT4) + } + + assert result.runCount == 4 + assert result.ignoreCount == 2 + assert result.failures*.description*.methodName == ["existing failure", "existing failure"] + assert result.failures*.exception*.class == [AssertionError, AssertionError] + + def events = getEventsAsJson(TEST_WRITER.toList()) + def suites = events.findAll { it.type == "test_suite_end" } + assert suites*.content*.meta*.get("test.suite").sort() == [TestSucceedCustomRunner.name, TestSucceedCustomRunner.AnotherSuite.name].sort() + suites.each { suite -> + def suiteTests = events.findAll { it.type == "test" && it.content.test_suite_id == suite.content.test_suite_id } + assert suiteTests.size() == 3 + assert suiteTests.collectEntries { [(it.content.meta["test.name"]): it.content.meta["test.status"]] } == ["adds": "pass", "existing failure": "fail", "ignored": "skip"] + } + assert AgentTracer.activeSpan() == initialSpan + + then: + noExceptionThrown() + + where: + mode | computer | tests + "parallel" | new ParallelComputer(true, false) | [TestSucceedCustomRunner, TestSucceedCustomRunner.AnotherSuite] + "sequential" | new Computer() | [TestSucceedCustomRunner, TestSucceedCustomRunner.AnotherSuite] + "sequential reversed" | new Computer() | [TestSucceedCustomRunner.AnotherSuite, TestSucceedCustomRunner] + } + + def "test executor callbacks without suite lifecycle events"() { + setup: + def executor = Executors.newSingleThreadExecutor() + TestSucceedExecutorRunner.executor = executor + + when: + def initialSpan = AgentTracer.activeSpan() + def initialWorkerSpan = CompletableFuture.supplyAsync({ AgentTracer.activeSpan() }, executor).join() + TestEventsHandlerHolder.start(TestFrameworkInstrumentation.JUNIT4, JUnit4Utils.capabilities(false)) + def result + def workerSpan + try { + result = runner.run(TestSucceedExecutorRunner) + workerSpan = CompletableFuture.supplyAsync({ AgentTracer.activeSpan() }, executor).join() + } finally { + TestEventsHandlerHolder.stop(TestFrameworkInstrumentation.JUNIT4) + } + + assert workerSpan == initialWorkerSpan + assert AgentTracer.activeSpan() == initialSpan + assert result.runCount == 2 + assert result.ignoreCount == 1 + assert result.failures*.description*.methodName == ["existing failure"] + assert result.failures*.exception*.class == [AssertionError] + + def events = getEventsAsJson(TEST_WRITER.toList()) + def suites = events.findAll { it.type == "test_suite_end" } + assert suites*.content*.meta*.get("test.suite") == [TestSucceedExecutorRunner.name] + def suiteTests = events.findAll { it.type == "test" && it.content.test_suite_id == suites[0].content.test_suite_id } + assert suiteTests.size() == 3 + assert suiteTests.collectEntries { [(it.content.meta["test.name"]): it.content.meta["test.status"]] } == ["adds": "pass", "existing failure": "fail", "ignored": "skip"] + + then: + noExceptionThrown() + + cleanup: + executor.shutdownNow() + TestSucceedExecutorRunner.executor = null + } + def "test #testcaseName"() { runTests(tests, success) diff --git a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/java/org/example/TestSucceedCustomRunner.java b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/java/org/example/TestSucceedCustomRunner.java new file mode 100644 index 00000000000..7a797b4a5fd --- /dev/null +++ b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/java/org/example/TestSucceedCustomRunner.java @@ -0,0 +1,51 @@ +package org.example; + +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; + +@RunWith(TestSucceedCustomRunner.CustomRunner.class) +public class TestSucceedCustomRunner { + + public static class AnotherSuite extends TestSucceedCustomRunner {} + + public static class CustomRunner extends Runner { + private final Class testClass; + + public CustomRunner(Class testClass) { + this.testClass = testClass; + } + + @Override + public Description getDescription() { + Description suite = Description.createSuiteDescription(testClass); + suite.addChild(Description.createTestDescription(testClass, "adds")); + suite.addChild(Description.createTestDescription(testClass, "existing failure")); + suite.addChild(Description.createTestDescription(testClass, "ignored")); + return suite; + } + + @Override + public void run(RunNotifier notifier) { + emitTestEvents(notifier); + } + + protected void emitTestEvents(RunNotifier notifier) { + // Like ScalaTest's JUnitRunner, emit test callbacks without suite lifecycle callbacks. + notifier.fireTestIgnored(testDescription("ignored")); + Description passing = testDescription("adds"); + notifier.fireTestStarted(passing); + notifier.fireTestFinished(passing); + Description failing = testDescription("existing failure"); + notifier.fireTestStarted(failing); + notifier.fireTestFailure(new Failure(failing, new AssertionError("existing failure"))); + notifier.fireTestFinished(failing); + } + + private Description testDescription(String name) { + return Description.createSuiteDescription(name + "(" + testClass.getName() + ")"); + } + } +} diff --git a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/java/org/example/TestSucceedExecutorRunner.java b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/java/org/example/TestSucceedExecutorRunner.java new file mode 100644 index 00000000000..85879e08e7a --- /dev/null +++ b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/java/org/example/TestSucceedExecutorRunner.java @@ -0,0 +1,23 @@ +package org.example; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import org.junit.runner.RunWith; +import org.junit.runner.notification.RunNotifier; + +@RunWith(TestSucceedExecutorRunner.ExecutorRunner.class) +public class TestSucceedExecutorRunner { + + public static ExecutorService executor; + + public static class ExecutorRunner extends TestSucceedCustomRunner.CustomRunner { + public ExecutorRunner(Class testClass) { + super(testClass); + } + + @Override + public void run(RunNotifier notifier) { + CompletableFuture.runAsync(() -> emitTestEvents(notifier), executor).join(); + } + } +} diff --git a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/resources/test-custom-runner/coverages.ftl b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/resources/test-custom-runner/coverages.ftl new file mode 100644 index 00000000000..8878e547a79 --- /dev/null +++ b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/resources/test-custom-runner/coverages.ftl @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/resources/test-custom-runner/events.ftl b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/resources/test-custom-runner/events.ftl new file mode 100644 index 00000000000..07a5ec05941 --- /dev/null +++ b/dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/test/resources/test-custom-runner/events.ftl @@ -0,0 +1,241 @@ +[ { + "content" : { + "duration" : ${content_duration}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid}, + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "junit4", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test_session_end", + "test.command" : "junit-4.10", + "test.framework" : "junit4", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.status" : "fail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id} + }, + "name" : "junit4.test_session", + "resource" : "junit-4.10", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start}, + "test_session_id" : ${content_test_session_id} + }, + "type" : "test_session_end", + "version" : 1 +}, { + "content" : { + "duration" : ${content_duration_2}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid_2}, + "component" : "junit4", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "library_version" : ${content_meta_library_version}, + "span.kind" : "test_module_end", + "test.framework" : "junit4", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "junit-4.10", + "test.status" : "fail", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_2} + }, + "name" : "junit4.test_module", + "resource" : "junit-4.10", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start_2}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id} + }, + "type" : "test_module_end", + "version" : 1 +}, { + "content" : { + "duration" : ${content_duration_3}, + "error" : 0, + "meta" : { + "_dd.p.tid" : ${content_meta__dd_p_tid_3}, + "component" : "junit4", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "library_version" : ${content_meta_library_version}, + "span.kind" : "test_suite_end", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.framework" : "junit4", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "junit-4.10", + "test.source.file" : "dummy_source_path", + "test.status" : "fail", + "test.suite" : "org.example.TestSucceedCustomRunner", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_3}, + "test.source.end" : 19, + "test.source.start" : 11 + }, + "name" : "junit4.test_suite", + "resource" : "org.example.TestSucceedCustomRunner", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "start" : ${content_start_3}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id} + }, + "type" : "test_suite_end", + "version" : 1 +}, { + "content" : { + "duration" : ${content_duration_4}, + "error" : 0, + "meta" : { + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "junit4", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.final_status" : "pass", + "test.framework" : "junit4", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "junit-4.10", + "test.name" : "adds", + "test.source.file" : "dummy_source_path", + "test.status" : "pass", + "test.suite" : "org.example.TestSucceedCustomRunner", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_4}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id} + }, + "name" : "junit4.test", + "parent_id" : ${content_parent_id}, + "resource" : "org.example.TestSucceedCustomRunner.adds", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "span_id" : ${content_span_id}, + "start" : ${content_start_4}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id}, + "trace_id" : ${content_trace_id} + }, + "type" : "test", + "version" : 2 +}, { + "content" : { + "duration" : ${content_duration_5}, + "error" : 1, + "meta" : { + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "junit4", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "error.message" : ${content_meta_error_message}, + "error.stack" : ${content_meta_error_stack}, + "error.type" : "java.lang.AssertionError", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.final_status" : "fail", + "test.framework" : "junit4", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "junit-4.10", + "test.name" : "existing failure", + "test.source.file" : "dummy_source_path", + "test.status" : "fail", + "test.suite" : "org.example.TestSucceedCustomRunner", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_5}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id} + }, + "name" : "junit4.test", + "parent_id" : ${content_parent_id}, + "resource" : "org.example.TestSucceedCustomRunner.existing failure", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "span_id" : ${content_span_id_2}, + "start" : ${content_start_5}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id}, + "trace_id" : ${content_trace_id_2} + }, + "type" : "test", + "version" : 2 +}, { + "content" : { + "duration" : ${content_duration_6}, + "error" : 0, + "meta" : { + "_dd.profiling.ctx" : "test", + "_dd.tracer_host" : ${content_meta__dd_tracer_host}, + "component" : "junit4", + "dummy_ci_tag" : "dummy_ci_tag_value", + "env" : "none", + "language" : "jvm", + "library_version" : ${content_meta_library_version}, + "runtime-id" : ${content_meta_runtime_id}, + "span.kind" : "test", + "test.codeowners" : "[\"owner1\",\"owner2\"]", + "test.final_status" : "skip", + "test.framework" : "junit4", + "test.framework_version" : ${content_meta_test_framework_version}, + "test.module" : "junit-4.10", + "test.name" : "ignored", + "test.source.file" : "dummy_source_path", + "test.status" : "skip", + "test.suite" : "org.example.TestSucceedCustomRunner", + "test.type" : "test", + "test_session.name" : "session-name" + }, + "metrics" : { + "_dd.host.vcpu_count" : ${content_metrics__dd_host_vcpu_count_6}, + "_dd.profiling.enabled" : 0, + "_dd.trace_span_attribute_schema" : 0, + "process_id" : ${content_metrics_process_id} + }, + "name" : "junit4.test", + "parent_id" : ${content_parent_id}, + "resource" : "org.example.TestSucceedCustomRunner.ignored", + "service" : "worker.org.gradle.process.internal.worker.gradleworkermain", + "span_id" : ${content_span_id_3}, + "start" : ${content_start_6}, + "test_module_id" : ${content_test_module_id}, + "test_session_id" : ${content_test_session_id}, + "test_suite_id" : ${content_test_suite_id}, + "trace_id" : ${content_trace_id_3} + }, + "type" : "test", + "version" : 2 +} ] \ No newline at end of file