diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle index 982b43523a2..87c94dfa151 100644 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle +++ b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/build.gradle @@ -27,6 +27,10 @@ tasks.named("compileTestGroovy", GroovyCompile) { configureCompiler(it, 17) } +tasks.named("compileTestJava", JavaCompile) { + configureCompiler(it, 17) +} + dependencies { implementation project(':dd-java-agent:instrumentation:jms:javax-jms-1.1') diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/gradle.lockfile b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/gradle.lockfile deleted file mode 100644 index 95acd48addf..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/gradle.lockfile +++ /dev/null @@ -1,154 +0,0 @@ -# This is a Gradle generated file for dependency locking. -# Manual edits can break the build and are not advised. -# This file is expected to be part of source control. -cafe.cryptography:curve25519-elisabeth:0.1.0=testRuntimeClasspath -cafe.cryptography:ed25519-elisabeth:0.1.0=testRuntimeClasspath -ch.qos.logback:logback-classic:1.2.13=testCompileClasspath,testRuntimeClasspath -ch.qos.logback:logback-core:1.2.13=testCompileClasspath,testRuntimeClasspath -com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okhttp3:okhttp:3.12.15=testCompileClasspath,testRuntimeClasspath -com.datadoghq.okio:okio:1.17.6=testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:java-dogstatsd-client:4.4.5=testRuntimeClasspath -com.datadoghq:sketches-java:0.8.3=testRuntimeClasspath -com.github.javaparser:javaparser-core:3.25.6=codenarc -com.github.jnr:jffi:1.3.14=testRuntimeClasspath -com.github.jnr:jnr-a64asm:1.0.0=testRuntimeClasspath -com.github.jnr:jnr-constants:0.10.4=testRuntimeClasspath -com.github.jnr:jnr-enxio:0.32.19=testRuntimeClasspath -com.github.jnr:jnr-ffi:2.2.18=testRuntimeClasspath -com.github.jnr:jnr-posix:3.1.21=testRuntimeClasspath -com.github.jnr:jnr-unixsocket:0.38.24=testRuntimeClasspath -com.github.jnr:jnr-x86asm:1.0.2=testRuntimeClasspath -com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath -com.github.spotbugs:spotbugs:4.9.8=spotbugs -com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,testAnnotationProcessor,testCompileClasspath -com.google.auto.service:auto-service:1.1.1=annotationProcessor,testAnnotationProcessor -com.google.auto:auto-common:1.2.1=annotationProcessor,testAnnotationProcessor -com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath -com.google.code.gson:gson:2.13.2=spotbugs -com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.41.0=spotbugs -com.google.guava:failureaccess:1.0.1=annotationProcessor,testAnnotationProcessor -com.google.guava:guava:20.0=testCompileClasspath,testRuntimeClasspath -com.google.guava:guava:32.0.1-jre=annotationProcessor,testAnnotationProcessor -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,testAnnotationProcessor -com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,testAnnotationProcessor -com.google.re2j:re2j:1.7=testRuntimeClasspath -com.squareup.moshi:moshi:1.11.0=testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:logging-interceptor:3.12.12=testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:okhttp:3.12.12=testCompileClasspath,testRuntimeClasspath -com.squareup.okio:okio:1.17.5=testCompileClasspath,testRuntimeClasspath -com.thoughtworks.qdox:qdox:1.12.1=codenarc -commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.11.0=testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.20.0=spotbugs -de.thetaphi:forbiddenapis:3.10=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath -io.netty:netty-all:4.0.30.Final=testCompileClasspath,testRuntimeClasspath -io.sqreen:libsqreen:17.3.0=testRuntimeClasspath -jakarta.annotation:jakarta.annotation-api:2.1.1=testCompileClasspath,testRuntimeClasspath -jakarta.ejb:jakarta.ejb-api:4.0.0=testCompileClasspath,testRuntimeClasspath -jakarta.inject:jakarta.inject-api:2.0.0=testCompileClasspath,testRuntimeClasspath -jakarta.jms:jakarta.jms-api:3.0.0=testCompileClasspath,testRuntimeClasspath -jakarta.transaction:jakarta.transaction-api:2.0.0=testCompileClasspath,testRuntimeClasspath -javax.inject:javax.inject:1=testCompileClasspath,testRuntimeClasspath -javax.servlet:javax.servlet-api:3.1.0=testCompileClasspath,testRuntimeClasspath -jaxen:jaxen:2.0.0=spotbugs -junit:junit:4.13.2=testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.java.dev.jna:jna-platform:5.8.0=testRuntimeClasspath -net.java.dev.jna:jna:5.8.0=testRuntimeClasspath -net.sf.saxon:Saxon-HE:12.9=spotbugs -org.apache.ant:ant-antlr:1.10.14=codenarc -org.apache.ant:ant-junit:1.10.14=codenarc -org.apache.bcel:bcel:6.11.0=spotbugs -org.apache.commons:commons-lang3:3.19.0=spotbugs -org.apache.commons:commons-text:1.14.0=spotbugs -org.apache.logging.log4j:log4j-api:2.25.2=spotbugs -org.apache.logging.log4j:log4j-core:2.25.2=spotbugs -org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath -org.checkerframework:checker-qual:3.33.0=annotationProcessor,testAnnotationProcessor -org.codehaus.groovy:groovy-ant:3.0.23=codenarc -org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc -org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.25=testCompileClasspath,testRuntimeClasspath -org.codehaus.groovy:groovy-templates:3.0.23=codenarc -org.codehaus.groovy:groovy-xml:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.25=testCompileClasspath,testRuntimeClasspath -org.codenarc:CodeNarc:3.7.0=codenarc -org.dom4j:dom4j:2.2.0=spotbugs -org.gmetrics:GMetrics:2.1.0=codenarc -org.hamcrest:hamcrest-core:1.3=testRuntimeClasspath -org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-commons:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-core-client:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-jakarta-client:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-jms-client:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-jms-server:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-journal:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-native:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-server:2.4.9.Final=testCompileClasspath,testRuntimeClasspath -org.jboss.logging:jboss-logging:3.1.0.GA=testCompileClasspath,testRuntimeClasspath -org.jboss.naming:jnpserver:5.0.3.GA=testCompileClasspath,testRuntimeClasspath -org.jboss.spec.javax.jms:jboss-jms-api_2.0_spec:1.0.0.Final=testCompileClasspath,testRuntimeClasspath -org.jboss.spec.javax.resource:jboss-connector-api_1.5_spec:1.0.0.Final=testCompileClasspath,testRuntimeClasspath -org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:1.0.1.Beta1=testCompileClasspath,testRuntimeClasspath -org.jboss:jboss-common-core:2.2.10.GA=testCompileClasspath,testRuntimeClasspath -org.jboss:jboss-transaction-spi:7.0.0.Final=testCompileClasspath,testRuntimeClasspath -org.jctools:jctools-core-jdk11:4.0.6=testRuntimeClasspath -org.jctools:jctools-core:4.0.6=testRuntimeClasspath -org.jgroups:jgroups:3.3.4.Final=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:5.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.14.1=testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.14.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-launcher:1.14.1=testRuntimeClasspath -org.junit.platform:junit-platform-runner:1.14.1=testRuntimeClasspath -org.junit.platform:junit-platform-suite-api:1.14.1=testRuntimeClasspath -org.junit.platform:junit-platform-suite-commons:1.14.1=testRuntimeClasspath -org.junit:junit-bom:5.14.0=spotbugs -org.junit:junit-bom:5.14.1=testCompileClasspath,testRuntimeClasspath -org.mockito:mockito-core:4.4.0=testRuntimeClasspath -org.objenesis:objenesis:3.3=testCompileClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.7.1=testRuntimeClasspath -org.ow2.asm:asm-analysis:9.9=spotbugs -org.ow2.asm:asm-commons:9.9=spotbugs -org.ow2.asm:asm-commons:9.9.1=testRuntimeClasspath -org.ow2.asm:asm-tree:9.9=spotbugs -org.ow2.asm:asm-tree:9.9.1=testRuntimeClasspath -org.ow2.asm:asm-util:9.7.1=testRuntimeClasspath -org.ow2.asm:asm-util:9.9=spotbugs -org.ow2.asm:asm:9.9=spotbugs -org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath -org.slf4j:jul-to-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath -org.slf4j:log4j-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath -org.slf4j:slf4j-api:1.7.32=testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j -org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j -org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath -org.spockframework:spock-bom:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath -org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-aop:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-beans:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-context:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-core:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-expression:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-jcl:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-jms:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-messaging:6.0.11=testCompileClasspath,testRuntimeClasspath -org.springframework:spring-tx:6.0.11=testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-junit:1.2.1=testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-parser:1.2.0=testCompileClasspath,testRuntimeClasspath -org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/JMS2Test.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/JMS2Test.groovy deleted file mode 100644 index fe3229077a9..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/JMS2Test.groovy +++ /dev/null @@ -1,265 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.api.config.TraceInstrumentationConfig -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.api.DDSpanTypes -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import spock.lang.Shared - -import jakarta.jms.Message -import jakarta.jms.MessageListener -import jakarta.jms.Session -import jakarta.jms.TextMessage -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference - -class JMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - Session session - - TextMessage message = session.createTextMessage(messageText) - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.someQueue", "jms.queue.someQueue", true) - clientSession.createQueue("jms.topic.someTopic", "jms.topic.someTopic", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - def producer = session.createProducer(null) - def consumer = session.createConsumer(destination) - - producer.send(destination, message) - - Message receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending to a MessageListener on #jmsResourceName generates a span"() { - setup: - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - - producer.send(message) - lock.countDown() - - expect: - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "failing to receive message with receiveNoWait on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - Message receivedMessage = consumer.receiveNoWait() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "failing to receive message with wait(timeout) on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - Message receivedMessage = consumer.receive(1) - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "sending a message with disabled timestamp generates spans without specific tag"() { - setup: - def producer = session.createProducer(session.createQueue("someQueue")) - def consumer = session.createConsumer(session.createQueue("someQueue")) - - producer.setDisableMessageTimestamp(true) - boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() - producer.send(message) - - consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - assertTraces(2) { - producerTrace(it, "Queue someQueue") - consumerTrace(it, "Queue someQueue", trace(0)[0], isTimeStampDisabled) - } - - cleanup: - producer.close() - consumer.close() - } - - static producerTrace(ListWriterAssert writer, String jmsResourceName) { - writer.trace(1) { - span { - parent() - serviceName "jms" - operationName "jms.produce" - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - defaultTagsNoPeerService() - } - } - } - } - - static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - writer.trace(1) { - span { - childOf parentSpan - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - - tags { - "${Tags.COMPONENT}" "jms" - "${Tags.SPAN_KIND}" "consumer" - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" {it >= 0 } - } - defaultTagsNoPeerService(true) - } - } - } - } -} - -class JMS2ContextSwapForkedTest extends JMS2Test { - @Override - protected void configurePreAgent() { - super.configurePreAgent() - injectSysConfig(TraceInstrumentationConfig.LEGACY_CONTEXT_MANAGER_ENABLED, "false") - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB1.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB1.groovy deleted file mode 100644 index 88d08751e41..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB1.groovy +++ /dev/null @@ -1,16 +0,0 @@ -import jakarta.ejb.MessageDrivenBean -import jakarta.ejb.MessageDrivenContext -import jakarta.jms.Message -import jakarta.jms.MessageListener - -class MDB1 implements MessageDrivenBean, MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } - void ejbRemove() {} - void setMessageDrivenContext(MessageDrivenContext ctx) {} -} - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB2.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB2.groovy deleted file mode 100644 index 80877ea400b..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDB2.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import jakarta.ejb.MessageDriven -import jakarta.jms.Message -import jakarta.jms.MessageListener - -@MessageDriven(mappedName="unusedTopic") -class MDB2 implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBBad.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBBad.groovy deleted file mode 100644 index 1dbb6b1c643..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBBad.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import jakarta.jms.Message -import jakarta.jms.MessageListener - -// Not a valid MDB because it only implements MessageListener. -class MDBBad implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBJmsMsg.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBJmsMsg.groovy deleted file mode 100644 index 978a0d9ecc6..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBJmsMsg.groovy +++ /dev/null @@ -1,230 +0,0 @@ -import jakarta.jms.Message -import jakarta.jms.Destination -import jakarta.jms.JMSException - -class MDBJmsMsg implements Message { - - @Override - String getJMSMessageID() throws JMSException { - return "ABC" - } - - @Override - void setJMSMessageID(String id) throws JMSException { - } - - @Override - long getJMSTimestamp() throws JMSException { - return 0 - } - - @Override - void setJMSTimestamp(long timestamp) throws JMSException { - } - - @Override - byte[] getJMSCorrelationIDAsBytes() throws JMSException { - return new byte[0] - } - - @Override - void setJMSCorrelationIDAsBytes(byte[] correlationID) throws JMSException { - } - - @Override - void setJMSCorrelationID(String correlationID) throws JMSException { - } - - @Override - String getJMSCorrelationID() throws JMSException { - return null - } - - @Override - Destination getJMSReplyTo() throws JMSException { - return null - } - - @Override - void setJMSReplyTo(Destination replyTo) throws JMSException { - } - - @Override - Destination getJMSDestination() throws JMSException { - return null - } - - @Override - void setJMSDestination(Destination destination) throws JMSException { - } - - @Override - int getJMSDeliveryMode() throws JMSException { - return 0 - } - - @Override - void setJMSDeliveryMode(int deliveryMode) throws JMSException { - } - - @Override - boolean getJMSRedelivered() throws JMSException { - return false - } - - @Override - void setJMSRedelivered(boolean redelivered) throws JMSException { - } - - @Override - String getJMSType() throws JMSException { - return null - } - - @Override - void setJMSType(String type) throws JMSException { - } - - @Override - long getJMSExpiration() throws JMSException { - return 0 - } - - @Override - void setJMSExpiration(long expiration) throws JMSException { - } - - @Override - long getJMSDeliveryTime() throws JMSException { - return 0 - } - - @Override - void setJMSDeliveryTime(long deliveryTime) throws JMSException { - } - - @Override - int getJMSPriority() throws JMSException { - return 0 - } - - @Override - void setJMSPriority(int priority) throws JMSException { - } - - @Override - void clearProperties() throws JMSException { - } - - @Override - boolean propertyExists(String name) throws JMSException { - return false - } - - @Override - boolean getBooleanProperty(String name) throws JMSException { - return false - } - - @Override - byte getByteProperty(String name) throws JMSException { - return 0 - } - - @Override - short getShortProperty(String name) throws JMSException { - return 0 - } - - @Override - int getIntProperty(String name) throws JMSException { - return 0 - } - - @Override - long getLongProperty(String name) throws JMSException { - return 0 - } - - @Override - float getFloatProperty(String name) throws JMSException { - return 0 - } - - @Override - double getDoubleProperty(String name) throws JMSException { - return 0 - } - - @Override - String getStringProperty(String name) throws JMSException { - return null - } - - @Override - Object getObjectProperty(String name) throws JMSException { - return null - } - - @Override - Enumeration getPropertyNames() throws JMSException { - return null - } - - @Override - void setBooleanProperty(String name, boolean value) throws JMSException { - } - - @Override - void setByteProperty(String name, byte value) throws JMSException { - } - - @Override - void setShortProperty(String name, short value) throws JMSException { - } - - @Override - void setIntProperty(String name, int value) throws JMSException { - } - - @Override - void setLongProperty(String name, long value) throws JMSException { - } - - @Override - void setFloatProperty(String name, float value) throws JMSException { - } - - @Override - void setDoubleProperty(String name, double value) throws JMSException { - } - - @Override - void setStringProperty(String name, String value) throws JMSException { - } - - @Override - void setObjectProperty(String name, Object value) throws JMSException { - } - - @Override - void acknowledge() throws JMSException { - } - - @Override - void clearBody() throws JMSException { - } - - @Override - def T getBody(Class c) throws JMSException { - return null - } - - @Override - boolean isBodyAssignableTo(Class c) throws JMSException { - return false - } -} - - - diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBTest.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBTest.groovy deleted file mode 100644 index c4b9c6e61e7..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/MDBTest.groovy +++ /dev/null @@ -1,78 +0,0 @@ -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.agent.test.asserts.ListWriterAssert -import spock.lang.Shared - -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace - -class MDBTest extends InstrumentationSpecification { - - @Shared - def msg = new MDBJmsMsg() - - def "Test an incomplete MDB that should not get traced here"() { - setup: - def beanBad = new MDBBad() - - when: - runUnderTrace("parent") { - beanBad.onMessage(msg) - } - - then: - assertTraces(1) { - workerTrace(it) - } - } - - def "Test MDB1"() { - setup: - def bean1 = new MDB1() - - when: - runUnderTrace("parent") { - bean1.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def "Test MDB2"() { - setup: - def bean2 = new MDB2() - - when: - runUnderTrace("parent") { - bean2.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def workerTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - serviceName "worker.org.gradle.process.internal.worker.GradleWorkerMain" - } - } - } - - def jmsTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - spanType "queue" - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from Temporary Queue" - measured true - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringListenerJMS2Test.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringListenerJMS2Test.groovy deleted file mode 100644 index 5532169b96a..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringListenerJMS2Test.groovy +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import datadog.trace.agent.test.InstrumentationSpecification -import listener.Config -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.jms.core.JmsTemplate - -import jakarta.jms.ConnectionFactory - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringListenerJMS2Test extends InstrumentationSpecification { - - def "receiving message in spring listener generates spans"() { - setup: - def context = new AnnotationConfigApplicationContext(Config) - def factory = context.getBean(ConnectionFactory) - def template = new JmsTemplate(factory) - template.convertAndSend("SpringListenerJMS2", "a message") - - expect: - assertTraces(2) { - producerTrace(it, "Queue SpringListenerJMS2") - consumerTrace(it, "Queue SpringListenerJMS2", trace(0)[0]) - } - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringTemplateJMS2Test.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringTemplateJMS2Test.groovy deleted file mode 100644 index a1000cb5865..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/SpringTemplateJMS2Test.groovy +++ /dev/null @@ -1,127 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification - -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.jms.core.JmsTemplate -import spock.lang.Shared - -import jakarta.jms.Session -import jakarta.jms.Message - -import java.util.concurrent.TimeUnit - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringTemplateJMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - JmsTemplate template - @Shared - Session session - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringTemplateJMS2", "jms.queue.SpringTemplateJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - - template = new JmsTemplate(connectionFactory) - template.receiveTimeout = TimeUnit.SECONDS.toMillis(10) - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - template.convertAndSend(destination, messageText) - Message receivedMessage = template.receive(destination) - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } - - def "send and receive message generates spans"() { - setup: - Thread.start { - TEST_WRITER.waitForTraces(1) - Message msg = template.receive(destination) - assert msg.text == messageText - - // There's a chance this might be reported last, messing up the assertion. - template.send(msg.getJMSReplyTo()) { session -> - template.getMessageConverter().toMessage("responded!", session) - } - } - Message receivedMessage = template.sendAndReceive(destination) { session -> - template.getMessageConverter().toMessage(messageText, session) - } - - expect: - receivedMessage.text == "responded!" - assertTraces(4) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root - consumerTrace(it, "Temporary Queue", trace(2)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/Config.groovy deleted file mode 100644 index 16de4f012e0..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/Config.groovy +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package listener - -import com.google.common.io.Files -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.jms.annotation.EnableJms -import org.springframework.jms.config.DefaultJmsListenerContainerFactory -import org.springframework.jms.config.JmsListenerContainerFactory - -import jakarta.annotation.PreDestroy -import jakarta.jms.ConnectionFactory - -@Configuration -@ComponentScan -@EnableJms -class Config { - - private HornetQServer server - - @Bean - ConnectionFactory connectionFactory() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - org.hornetq.core.config.Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringListenerJMS2", "jms.queue.SpringListenerJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - } - - @Bean - JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() - factory.setConnectionFactory(connectionFactory) - return factory - } - - @PreDestroy - void destroy() { - server.stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/TestListener.groovy deleted file mode 100644 index e2eccd25449..00000000000 --- a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/groovy/listener/TestListener.groovy +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package listener - -import org.springframework.jms.annotation.JmsListener -import org.springframework.stereotype.Component - -@Component -class TestListener { - - @JmsListener(destination = "SpringListenerJMS2", containerFactory = "containerFactory") - void receiveMessage(String message) { - println "received: " + message - } -} diff --git a/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/java/datadog/trace/instrumentation/jms/JMS3Test.java b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/java/datadog/trace/instrumentation/jms/JMS3Test.java new file mode 100644 index 00000000000..38548fcf47d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/jakarta-jms-3.0/src/test/java/datadog/trace/instrumentation/jms/JMS3Test.java @@ -0,0 +1,842 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.DDSpan; +import jakarta.jms.Connection; +import jakarta.jms.ConnectionFactory; +import jakarta.jms.DeliveryMode; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.MessageConsumer; +import jakarta.jms.MessageListener; +import jakarta.jms.MessageProducer; +import jakarta.jms.Queue; +import jakarta.jms.Session; +import jakarta.jms.TemporaryQueue; +import jakarta.jms.TemporaryTopic; +import jakarta.jms.TextMessage; +import jakarta.jms.Topic; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.hornetq.api.core.TransportConfiguration; +import org.hornetq.api.jms.HornetQJMSClient; +import org.hornetq.api.jms.JMSFactoryType; +import org.hornetq.core.config.Configuration; +import org.hornetq.core.config.CoreQueueConfiguration; +import org.hornetq.core.config.impl.ConfigurationImpl; +import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory; +import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory; +import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory; +import org.hornetq.core.server.HornetQServer; +import org.hornetq.core.server.HornetQServers; +import org.hornetq.jms.server.impl.JMSServerManagerImpl; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +/** + * Integration tests for Jakarta JMS 3.0 instrumentation covering producer, consumer, and message + * listener spans. Validates span structure, operation names, resource names, span types, span + * kinds, tags, error propagation, and distributed trace context propagation between producer and + * consumer. + * + *

Uses an embedded HornetQ broker with the Jakarta JMS client for realistic end-to-end testing. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class JMS3Test extends AbstractInstrumentationTest { + + static { + // Ensure tracing is enabled before the agent is installed by the parent @BeforeAll. + // This protects against DD_TRACE_ENABLED=false leaking from the environment. + System.setProperty("dd.trace.enabled", "true"); + } + + private HornetQServer server; + private JMSServerManagerImpl jmsServerManager; + private Connection connection; + private Session session; + private int queueCounter = 0; + + @BeforeAll + void setupBroker() throws Exception { + Configuration config = new ConfigurationImpl(); + config.setBindingsDirectory(System.getProperty("java.io.tmpdir") + "/hornetq-bindings-jms3"); + config.setJournalDirectory(System.getProperty("java.io.tmpdir") + "/hornetq-journal-jms3"); + config.setCreateBindingsDir(true); + config.setCreateJournalDir(true); + config.setSecurityEnabled(false); + config.setPersistenceEnabled(false); + config.setQueueConfigurations( + Collections.singletonList( + new CoreQueueConfiguration("someQueue", "someQueue", null, true))); + + HashSet acceptors = new HashSet<>(); + acceptors.add(new TransportConfiguration(NettyAcceptorFactory.class.getName())); + acceptors.add(new TransportConfiguration(InVMAcceptorFactory.class.getName())); + config.setAcceptorConfigurations(acceptors); + + server = HornetQServers.newHornetQServer(config); + + // Wrap in JMSServerManager for dynamic JMS queue/topic creation. + // Do NOT start the core server separately — the JMS manager's start() handles that + // and registers its ActivateCallback so dynamic queue/topic creation works. + jmsServerManager = new JMSServerManagerImpl(server); + jmsServerManager.setContext(null); + jmsServerManager.start(); + + // Pre-create JMS topic for topic tests + jmsServerManager.createTopic(false, "someTopic", "/topic/someTopic"); + + ConnectionFactory connectionFactory = + HornetQJMSClient.createConnectionFactoryWithoutHA( + JMSFactoryType.CF, new TransportConfiguration(InVMConnectorFactory.class.getName())); + connection = connectionFactory.createConnection(); + connection.start(); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + session.run(); + } + + @AfterAll + void tearDownBroker() throws Exception { + if (session != null) { + session.close(); + } + if (connection != null) { + connection.close(); + } + if (jmsServerManager != null) { + jmsServerManager.stop(); + } + if (server != null) { + server.stop(); + } + } + + /** Returns a unique queue for each test to avoid cross-test data leaks. */ + private Queue createUniqueQueue() throws Exception { + String queueName = "test-queue-" + (queueCounter++); + jmsServerManager.createQueue(false, queueName, null, true); + return session.createQueue(queueName); + } + + // ===================== Producer Tests ===================== + + @Test + void sendToQueueCreatesProducerSpanWithCorrectAttributes() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello queue"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + // Operation name + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + // Span type -- JMS producer and consumer spans use type "queue" + assertEquals("queue", producerSpan.getSpanType()); + // Resource name should contain "Produced for Queue " + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") + && resourceName.contains("Queue") + && resourceName.contains(queue.getQueueName()), + "Resource name should be 'Produced for Queue ', got: " + resourceName); + // Tags + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Producer span should have messaging.operation=send"); + // Measured flag + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + // No error + assertFalse(producerSpan.isError(), "Producer span should not be errored"); + // Root span (no parent) + assertEquals(0L, producerSpan.getParentId(), "Producer span should be a root span"); + } + + @Test + void sendToTopicCreatesProducerSpanWithTopicResourceName() throws Exception { + Topic topic = session.createTopic("someTopic"); + MessageProducer producer = session.createProducer(topic); + TextMessage message = session.createTextMessage("hello topic"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for topic"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") && resourceName.contains("Topic"), + "Resource name should contain 'Produced for' and 'Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Topic producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Topic producer span should have messaging.operation=send"); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithExplicitDestinationCreatesProducerSpan() throws Exception { + Queue queue = createUniqueQueue(); + // Create producer without default destination + MessageProducer producer = session.createProducer(null); + TextMessage message = session.createTextMessage("hello explicit dest"); + + producer.send(queue, message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for explicit destination"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithDeliveryModeAndPriorityCreatesProducerSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello with params"); + + // Send with explicit deliveryMode, priority, and timeToLive + producer.send(message, DeliveryMode.NON_PERSISTENT, 7, 60000); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span with delivery params"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendToTemporaryQueueCreatesProducerSpanWithTemporaryResourceName() throws Exception { + TemporaryQueue tempQueue = session.createTemporaryQueue(); + MessageProducer producer = session.createProducer(tempQueue); + TextMessage message = session.createTextMessage("hello temp queue"); + + producer.send(message); + + producer.close(); + tempQueue.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary queue"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Queue"), + "Resource name for temp queue should contain 'Temporary Queue', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + @Test + void sendToTemporaryTopicCreatesProducerSpanWithTemporaryResourceName() throws Exception { + TemporaryTopic tempTopic = session.createTemporaryTopic(); + MessageProducer producer = session.createProducer(tempTopic); + TextMessage message = session.createTextMessage("hello temp topic"); + + producer.send(message); + + producer.close(); + tempTopic.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary topic"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Topic"), + "Resource name for temp topic should contain 'Temporary Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + // ===================== Consumer Tests ===================== + + @Test + void receiveCreatesConsumerSpanLinkedToProducer() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("receive test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received a message"); + assertEquals("receive test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Consumer span attributes + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Consumer receive() span should have messaging.operation=receive"); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + assertFalse(consumerSpan.isError(), "Consumer span should not be errored"); + + // Context propagation: consumer should be child of producer + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer span should be child of the producer span (distributed context propagation)"); + assertEquals( + producerSpan.getTraceId(), + consumerSpan.getTraceId(), + "Consumer and producer spans should share the same trace ID"); + } + + @Test + void receiveWithTimeoutCreatesConsumerSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("timeout receive"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received message with timeout"); + assertEquals("timeout receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receive(timeout)"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void receiveNoWaitCreatesConsumerSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("noWait receive"); + producer.send(sentMessage); + + // Allow time for the message to arrive at the broker + Thread.sleep(500); + + TextMessage receivedMessage = (TextMessage) consumer.receiveNoWait(); + + assertNotNull(receivedMessage, "Should have received message with receiveNoWait"); + assertEquals("noWait receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receiveNoWait"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void multipleMessagesCreateMultipleProducerAndConsumerSpans() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + producer.send(session.createTextMessage("msg1")); + producer.send(session.createTextMessage("msg2")); + producer.send(session.createTextMessage("msg3")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + TextMessage recv3 = (TextMessage) consumer.receive(5000); + + assertNotNull(recv1, "Should receive message 1"); + assertNotNull(recv2, "Should receive message 2"); + assertNotNull(recv3, "Should receive message 3"); + assertEquals("msg1", recv1.getText()); + assertEquals("msg2", recv2.getText()); + assertEquals("msg3", recv3.getText()); + + producer.close(); + + // Trigger last pending consumer span to finish (receiveNoWait causes previous span flush) + consumer.receiveNoWait(); + consumer.close(); + + // 3 producer traces + up to 3 consumer traces + writer.waitForTraces(6); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(3, producerSpans.size(), "Expected 3 producer spans"); + assertEquals(3, consumerSpans.size(), "Expected 3 consumer spans"); + + // Each producer span should have consistent attributes + for (DDSpan pSpan : producerSpans) { + assertEquals("producer", String.valueOf(pSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(pSpan.getTag("component"))); + assertEquals("queue", pSpan.getSpanType()); + } + + // Each consumer span should be linked to a producer span + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertEquals("queue", cSpan.getSpanType()); + // Consumer should be child of a producer (distributed context) + assertTrue( + cSpan.getParentId() != 0, + "Consumer span should have a parent (linked via context propagation)"); + } + } + + @Test + void consumerReceiveFromTopicCreatesConsumerSpan() throws Exception { + Topic topic = session.createTopic("someTopic"); + MessageProducer producer = session.createProducer(topic); + MessageConsumer consumer = session.createConsumer(topic); + + TextMessage sentMessage = session.createTextMessage("topic consume test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should have received topic message"); + assertEquals("topic consume test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for topic"); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Topic"), + "Consumer resource should mention 'Consumed from Topic', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Topic consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Topic consumer span should have messaging.operation=receive"); + } + + // ===================== MessageListener Tests ===================== + + @Test + void messageListenerOnMessageCreatesConsumerSpanLinkedToProducer() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + TestMessageListener listener = new TestMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Listener should receive the message"); + assertNotNull(listener.receivedMessage, "Listener should have captured the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + + // Find the consumer/deliver span created by the MessageListener instrumentation + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull( + listenerSpan, "Expected a consumer or deliver span for MessageListener.onMessage"); + assertEquals("queue", listenerSpan.getSpanType()); + // Resource name should follow the 'Consumed from Queue ' pattern + String listenerResource = listenerSpan.getResourceName().toString(); + assertTrue( + listenerResource.contains("Consumed from") && listenerResource.contains("Queue"), + "Listener span resource should contain 'Consumed from Queue', got: " + listenerResource); + assertEquals("jms", String.valueOf(listenerSpan.getTag("component"))); + String listenerKind = String.valueOf(listenerSpan.getTag("span.kind")); + assertTrue( + "consumer".equals(listenerKind) || "broker".equals(listenerKind), + "Listener span kind should be 'consumer' or 'broker', got: " + listenerKind); + // Messaging semantic convention tags for listener (process operation) + assertEquals( + "jms", + listenerSpan.getTag("messaging.system"), + "Listener span should have messaging.system=jms"); + assertEquals( + "process", + listenerSpan.getTag("messaging.operation"), + "MessageListener.onMessage span should have messaging.operation=process"); + assertFalse(listenerSpan.isError(), "Listener span should not be errored"); + // Linked to producer via context propagation + assertEquals( + producerSpan.getTraceId(), + listenerSpan.getTraceId(), + "Listener span should share trace ID with producer"); + } + + @Test + void messageListenerErrorIsRecordedOnSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + FailingMessageListener listener = new FailingMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("error listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Failing listener should process the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + // Find the listener span -- it should be errored + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull(listenerSpan, "Expected a consumer/deliver span for failing listener"); + assertTrue(listenerSpan.isError(), "Listener span should be marked as errored"); + assertNotNull( + listenerSpan.getTag("error.message"), "Errored span should have error.message tag"); + assertNotNull(listenerSpan.getTag("error.type"), "Errored span should have error.type tag"); + assertNotNull(listenerSpan.getTag("error.stack"), "Errored span should have error.stack tag"); + } + + // ===================== Context Propagation Tests ===================== + + @Test + void producerSpanIsChildOfActiveSpan() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("child span test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertEquals( + parentSpan.getSpanId(), + producerSpan.getParentId(), + "Producer span should be a child of the active parent span"); + assertEquals( + parentSpan.context().getTraceId(), + producerSpan.getTraceId(), + "Producer and parent should share trace ID"); + } + + @Test + void distributedContextPropagatesThroughQueueSendAndReceive() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + // Send under a parent span to verify full trace linkage + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("distributed context test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive message with distributed context"); + assertEquals("distributed context test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify the full trace chain: parent -> producer -> consumer + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (distributed context)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + // ===================== Error Handling Tests ===================== + + @Test + void sendOnClosedProducerRecordsError() throws Exception { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + producer.close(); + + JMSException caughtException = null; + try { + TextMessage message = session.createTextMessage("error test"); + producer.send(message); + } catch (JMSException e) { + caughtException = e; + } + + assertNotNull(caughtException, "Sending on closed producer should throw JMSException"); + + // Allow time for any spans to complete + Thread.sleep(500); + + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + // If the instrumentation creates a span around the failed send, it should be errored + if (producerSpan != null) { + assertTrue(producerSpan.isError(), "Producer span on failed send should be errored"); + assertNotNull( + producerSpan.getTag("error.type"), "Errored producer span should have error.type tag"); + assertNotNull( + producerSpan.getTag("error.message"), + "Errored producer span should have error.message tag"); + } + } + + // ===================== Transacted Session Tests ===================== + + @Test + void receiveInTransactedSessionCreatesConsumerSpanOnCommit() throws Exception { + Queue queue = createUniqueQueue(); + Session transactedSession = connection.createSession(true, Session.SESSION_TRANSACTED); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = transactedSession.createConsumer(queue); + + producer.send(session.createTextMessage("transacted msg1")); + producer.send(session.createTextMessage("transacted msg2")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + transactedSession.commit(); + + assertNotNull(recv1, "Should receive first transacted message"); + assertNotNull(recv2, "Should receive second transacted message"); + assertEquals("transacted msg1", recv1.getText()); + assertEquals("transacted msg2", recv2.getText()); + + producer.close(); + consumer.close(); + transactedSession.close(); + + writer.waitForTraces(4); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(2, producerSpans.size(), "Expected 2 producer spans"); + assertEquals(2, consumerSpans.size(), "Expected 2 consumer spans after commit"); + + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertFalse(cSpan.isError()); + } + } + + // ===================== Helper Methods ===================== + + private List flattenTraces() { + List result = new ArrayList<>(); + for (List trace : writer) { + result.addAll(trace); + } + return result; + } + + private DDSpan findSpanByOperationAndKind( + List spans, String operationName, String spanKind) { + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName) + && spanKind.equals(String.valueOf(span.getTag("span.kind")))) { + return span; + } + } + return null; + } + + private List findAllSpansByOperation(List spans, String operationName) { + List result = new ArrayList<>(); + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName)) { + result.add(span); + } + } + return result; + } + + /** Finds the consumer or deliver span created by MessageListener instrumentation. */ + private DDSpan findConsumerOrDeliverSpan(List spans) { + for (DDSpan span : spans) { + String opName = span.getOperationName().toString(); + if ("jms.consume".equals(opName) || "jms.deliver".equals(opName)) { + return span; + } + } + return null; + } + + /** Simple MessageListener that captures the received message. */ + static class TestMessageListener implements MessageListener { + volatile Message receivedMessage; + private final CountDownLatch latch; + + TestMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + receivedMessage = message; + latch.countDown(); + } + } + + /** MessageListener that throws a RuntimeException to test error recording. */ + static class FailingMessageListener implements MessageListener { + private final CountDownLatch latch; + + FailingMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + latch.countDown(); + throw new RuntimeException("Intentional error in message listener"); + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle index 739bc235846..74b9d050a9a 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle @@ -14,6 +14,7 @@ muzzle { } apply from: "$rootDir/gradle/java.gradle" +apply plugin: 'java-test-fixtures' repositories { maven { @@ -33,6 +34,8 @@ tasks.named("latestDepTest", Test) { dependencies { compileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + testFixturesCompileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + testImplementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation') testImplementation group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5' testImplementation group: 'org.apache.activemq', name: 'activemq-pool', version: '5.14.5' diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/gradle.lockfile b/dd-java-agent/instrumentation/jms/javax-jms-1.1/gradle.lockfile deleted file mode 100644 index cde2ee01a01..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/gradle.lockfile +++ /dev/null @@ -1,163 +0,0 @@ -# This is a Gradle generated file for dependency locking. -# Manual edits can break the build and are not advised. -# This file is expected to be part of source control. -cafe.cryptography:curve25519-elisabeth:0.1.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -cafe.cryptography:ed25519-elisabeth:0.1.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -ch.qos.logback:logback-classic:1.2.13=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -ch.qos.logback:logback-core:1.2.13=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okhttp3:okhttp:3.12.15=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okio:okio:1.17.6=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:java-dogstatsd-client:4.4.5=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.datadoghq:sketches-java:0.8.3=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.javaparser:javaparser-core:3.25.6=codenarc -com.github.jnr:jffi:1.3.14=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-a64asm:1.0.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-constants:0.10.4=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-enxio:0.32.19=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-ffi:2.2.18=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-posix:3.1.21=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-unixsocket:0.38.24=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-x86asm:1.0.2=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath -com.github.spotbugs:spotbugs:4.9.8=spotbugs -com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,latestDepForkedTestAnnotationProcessor,latestDepForkedTestCompileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,testAnnotationProcessor,testCompileClasspath -com.google.auto.service:auto-service:1.1.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.auto:auto-common:1.2.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,latestDepForkedTestAnnotationProcessor,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath -com.google.code.gson:gson:2.13.2=spotbugs -com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.41.0=spotbugs -com.google.guava:failureaccess:1.0.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.guava:guava:20.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.guava:guava:32.0.1-jre=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -com.google.re2j:re2j:1.7=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -com.squareup.moshi:moshi:1.11.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:logging-interceptor:3.12.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okhttp3:okhttp:3.12.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.squareup.okio:okio:1.17.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.thoughtworks.qdox:qdox:1.12.1=codenarc -commons-fileupload:commons-fileupload:1.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.11.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -commons-io:commons-io:2.20.0=spotbugs -commons-logging:commons-logging:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -de.thetaphi:forbiddenapis:3.10=compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -io.leangen.geantyref:geantyref:1.3.16=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-all:4.0.13.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -io.sqreen:libsqreen:17.3.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -javax.annotation:javax.annotation-api:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.ejb:javax.ejb-api:3.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.inject:javax.inject:1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -javax.jms:jms-api:1.1-rev-1=compileClasspath -javax.servlet:javax.servlet-api:3.1.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.transaction:javax.transaction-api:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -jaxen:jaxen:2.0.0=spotbugs -junit:junit:4.13.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.java.dev.jna:jna-platform:5.8.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -net.java.dev.jna:jna:5.8.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -net.sf.saxon:Saxon-HE:12.9=spotbugs -org.apache.activemq.tooling:activemq-junit:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-broker:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-client:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-jms-pool:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-openwire-legacy:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.activemq:activemq-pool:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.ant:ant-antlr:1.10.14=codenarc -org.apache.ant:ant-junit:1.10.14=codenarc -org.apache.bcel:bcel:6.11.0=spotbugs -org.apache.commons:commons-lang3:3.19.0=spotbugs -org.apache.commons:commons-pool2:2.4.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.commons:commons-text:1.14.0=spotbugs -org.apache.geronimo.specs:geronimo-j2ee-management_1.1_spec:1.0.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.geronimo.specs:geronimo-jta_1.0.1B_spec:1.0.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.logging.log4j:log4j-api:2.25.2=spotbugs -org.apache.logging.log4j:log4j-core:2.25.2=spotbugs -org.apiguardian:apiguardian-api:1.1.2=latestDepForkedTestCompileClasspath,latestDepTestCompileClasspath,testCompileClasspath -org.checkerframework:checker-qual:3.33.0=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor -org.codehaus.groovy:groovy-ant:3.0.23=codenarc -org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc -org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.23=codenarc -org.codehaus.groovy:groovy-json:3.0.25=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.codehaus.groovy:groovy-templates:3.0.23=codenarc -org.codehaus.groovy:groovy-xml:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.23=codenarc -org.codehaus.groovy:groovy:3.0.25=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.codenarc:CodeNarc:3.7.0=codenarc -org.dom4j:dom4j:2.2.0=spotbugs -org.fusesource.hawtbuf:hawtbuf:1.11=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.gmetrics:GMetrics:2.1.0=codenarc -org.hamcrest:hamcrest-core:1.3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hamcrest:hamcrest:3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hornetq:hornetq-commons:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-core-client:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-jms-client:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-jms-server:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-journal:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-native:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.hornetq:hornetq-server:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.logging:jboss-logging:3.1.0.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.naming:jnpserver:5.0.3.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.spec.javax.jms:jboss-jms-api_2.0_spec:1.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.spec.javax.resource:jboss-connector-api_1.5_spec:1.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:1.0.1.Beta1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss:jboss-common-core:2.2.10.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jboss:jboss-transaction-spi:7.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.jctools:jctools-core-jdk11:4.0.6=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.jctools:jctools-core:4.0.6=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.jgroups:jgroups:3.3.4.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-launcher:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-runner:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-suite-api:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-suite-commons:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.junit:junit-bom:5.14.0=spotbugs -org.junit:junit-bom:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.mockito:mockito-core:4.4.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.objenesis:objenesis:3.3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.7.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.9=spotbugs -org.ow2.asm:asm-commons:9.9=spotbugs -org.ow2.asm:asm-commons:9.9.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-tree:9.9=spotbugs -org.ow2.asm:asm-tree:9.9.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-util:9.7.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-util:9.9=spotbugs -org.ow2.asm:asm:9.9=spotbugs -org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:jcl-over-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:jul-to-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:log4j-over-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath -org.slf4j:slf4j-api:1.7.32=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j -org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j -org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath -org.spockframework:spock-bom:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.spockframework:spock-core:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-aop:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-beans:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-context:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-core:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-expression:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-jms:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-messaging:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-tx:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-junit:1.2.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-parser:1.2.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/JMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/JMS2Test.groovy deleted file mode 100644 index 196ebccae37..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/JMS2Test.groovy +++ /dev/null @@ -1,257 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.api.DDSpanTypes -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.hornetq.jms.client.HornetQTextMessage -import spock.lang.Shared - -import javax.jms.Message -import javax.jms.MessageListener -import javax.jms.Session -import javax.jms.TextMessage -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference - -class JMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - Session session - - HornetQTextMessage message = session.createTextMessage(messageText) - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.someQueue", "jms.queue.someQueue", true) - clientSession.createQueue("jms.topic.someTopic", "jms.topic.someTopic", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - producer.send(message) - - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending to a MessageListener on #jmsResourceName generates a span"() { - setup: - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - - producer.send(message) - lock.countDown() - - expect: - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText - - cleanup: - producer.close() - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "failing to receive message with receiveNoWait on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receiveNoWait() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "failing to receive message with wait(timeout) on #jmsResourceName works"() { - setup: - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receive(1) - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - } - - def "sending a message with disabled timestamp generates spans without specific tag"() { - setup: - def producer = session.createProducer(session.createQueue("someQueue")) - def consumer = session.createConsumer(session.createQueue("someQueue")) - - producer.setDisableMessageTimestamp(true) - boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() - producer.send(message) - - consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - assertTraces(2) { - producerTrace(it, "Queue someQueue") - consumerTrace(it, "Queue someQueue", trace(0)[0], isTimeStampDisabled) - } - - cleanup: - producer.close() - consumer.close() - } - - static producerTrace(ListWriterAssert writer, String jmsResourceName) { - writer.trace(1) { - span { - parent() - serviceName "jms" - operationName "jms.produce" - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - defaultTagsNoPeerService() - } - } - } - } - - static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - writer.trace(1) { - span { - childOf parentSpan - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - - tags { - "${Tags.COMPONENT}" "jms" - "${Tags.SPAN_KIND}" "consumer" - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" {it >= 0 } - } - defaultTagsNoPeerService(true) - } - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy deleted file mode 100644 index 0856cdab048..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -import datadog.trace.agent.test.InstrumentationSpecification -import listener.Config -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.jms.core.JmsTemplate - -import javax.jms.ConnectionFactory - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringListenerJMS2Test extends InstrumentationSpecification { - - def "receiving message in spring listener generates spans"() { - setup: - def context = new AnnotationConfigApplicationContext(Config) - def factory = context.getBean(ConnectionFactory) - def template = new JmsTemplate(factory) - template.convertAndSend("SpringListenerJMS2", "a message") - - expect: - assertTraces(2) { - producerTrace(it, "Queue SpringListenerJMS2") - consumerTrace(it, "Queue SpringListenerJMS2", trace(0)[0]) - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy deleted file mode 100644 index 25163435b0c..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy +++ /dev/null @@ -1,125 +0,0 @@ -import com.google.common.io.Files -import datadog.trace.agent.test.InstrumentationSpecification -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.Configuration -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.jms.core.JmsTemplate -import spock.lang.Shared - -import javax.jms.Session -import javax.jms.TextMessage -import java.util.concurrent.TimeUnit - -import static JMS2Test.consumerTrace -import static JMS2Test.producerTrace - -class SpringTemplateJMS2Test extends InstrumentationSpecification { - @Shared - HornetQServer server - @Shared - String messageText = "a message" - @Shared - JmsTemplate template - @Shared - Session session - - def setupSpec() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringTemplateJMS2", "jms.queue.SpringTemplateJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - - def connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - session.run() - - template = new JmsTemplate(connectionFactory) - template.receiveTimeout = TimeUnit.SECONDS.toMillis(10) - } - - def cleanupSpec() { - server.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - template.convertAndSend(destination, messageText) - TextMessage receivedMessage = template.receive(destination) - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } - - def "send and receive message generates spans"() { - setup: - Thread.start { - TEST_WRITER.waitForTraces(1) - TextMessage msg = template.receive(destination) - assert msg.text == messageText - - // There's a chance this might be reported last, messing up the assertion. - template.send(msg.getJMSReplyTo()) { session -> - template.getMessageConverter().toMessage("responded!", session) - } - } - TextMessage receivedMessage = template.sendAndReceive(destination) { session -> - template.getMessageConverter().toMessage(messageText, session) - } - - expect: - receivedMessage.text == "responded!" - assertTraces(4) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root - consumerTrace(it, "Temporary Queue", trace(2)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/Config.groovy deleted file mode 100644 index 10dfaeee79a..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/Config.groovy +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package listener - -import com.google.common.io.Files -import org.hornetq.api.core.TransportConfiguration -import org.hornetq.api.core.client.HornetQClient -import org.hornetq.api.jms.HornetQJMSClient -import org.hornetq.api.jms.JMSFactoryType -import org.hornetq.core.config.CoreQueueConfiguration -import org.hornetq.core.config.impl.ConfigurationImpl -import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory -import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory -import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory -import org.hornetq.core.server.HornetQServer -import org.hornetq.core.server.HornetQServers -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.jms.annotation.EnableJms -import org.springframework.jms.config.DefaultJmsListenerContainerFactory -import org.springframework.jms.config.JmsListenerContainerFactory - -import javax.annotation.PreDestroy -import javax.jms.ConnectionFactory - -@Configuration -@ComponentScan -@EnableJms -class Config { - - private HornetQServer server - - @Bean - ConnectionFactory connectionFactory() { - def tempDir = Files.createTempDir() - tempDir.deleteOnExit() - - org.hornetq.core.config.Configuration config = new ConfigurationImpl() - config.bindingsDirectory = tempDir.path - config.journalDirectory = tempDir.path - config.createBindingsDir = false - config.createJournalDir = false - config.securityEnabled = false - config.persistenceEnabled = false - config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) - config.setAcceptorConfigurations([ - new TransportConfiguration(NettyAcceptorFactory.name), - new TransportConfiguration(InVMAcceptorFactory.name) - ].toSet()) - - server = HornetQServers.newHornetQServer(config) - server.start() - - def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) - def sf = serverLocator.createSessionFactory() - def clientSession = sf.createSession(false, false, false) - clientSession.createQueue("jms.queue.SpringListenerJMS2", "jms.queue.SpringListenerJMS2", true) - clientSession.close() - sf.close() - serverLocator.close() - - return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, - new TransportConfiguration(InVMConnectorFactory.name)) - } - - @Bean - JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() - factory.setConnectionFactory(connectionFactory) - return factory - } - - @PreDestroy - void destroy() { - server.stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/TestListener.groovy deleted file mode 100644 index e2eccd25449..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/latestDepTest/groovy/listener/TestListener.groovy +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package listener - -import org.springframework.jms.annotation.JmsListener -import org.springframework.stereotype.Component - -@Component -class TestListener { - - @JmsListener(destination = "SpringListenerJMS2", containerFactory = "containerFactory") - void receiveMessage(String message) { - println "received: " + message - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java index 954dec71ad0..d4fb55f79f6 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java @@ -1,5 +1,7 @@ package datadog.trace.instrumentation.jms; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; @@ -8,13 +10,18 @@ import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_DELIVER; import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import datadog.trace.api.Config; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; import datadog.trace.bootstrap.instrumentation.jms.SessionState; import javax.jms.Message; @@ -61,7 +68,21 @@ public void onMessage(Message message) { span = startSpan("jms", JMS_CONSUME, timeInQueue.context()); } CONSUMER_DECORATE.afterStart(span); - CONSUMER_DECORATE.onConsume(span, message, consumerState.getConsumerResourceName()); + String listenerDestinationName = + consumerState.getConsumerBaseResourceName() != null + ? consumerState.getConsumerBaseResourceName().toString() + : null; + CONSUMER_DECORATE.onConsume( + span, message, consumerState.getConsumerResourceName(), listenerDestinationName, "process"); + + if (Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + DataStreamsTags tags = + create(tech, INBOUND, consumerState.getConsumerBaseResourceName().toString()); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + SessionState sessionState = consumerState.getSessionState(); if (sessionState.isClientAcknowledge()) { // consumed spans will be finished by a call to Message.acknowledge diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java index 87fbfc55fc7..3ca91620a2a 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java @@ -1,5 +1,8 @@ package datadog.trace.instrumentation.jms; +import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.MESSAGING_DESTINATION_NAME; +import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.MESSAGING_OPERATION; +import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.MESSAGING_SYSTEM; import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.RECORD_QUEUE_TIME_MS; import datadog.trace.api.Config; @@ -19,10 +22,13 @@ import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; +import javax.jms.MessageProducer; import javax.jms.Queue; +import javax.jms.QueueSender; import javax.jms.TemporaryQueue; import javax.jms.TemporaryTopic; import javax.jms.Topic; +import javax.jms.TopicPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -116,7 +122,7 @@ public static void logJMSException(JMSException ex) { public static String messageTechnology(Message m) { if (null == m) { - return "null"; + return "jms"; } String messageClass = m.getClass().getName(); @@ -126,7 +132,7 @@ public static String messageTechnology(Message m) { } else if (messageClass.startsWith("com.ibm")) { return "ibmmq"; } else { - return "unknown"; + return "jms"; } } @@ -156,9 +162,28 @@ protected String spanKind() { } public void onConsume(AgentSpan span, Message message, CharSequence resourceName) { + onConsume(span, message, resourceName, null, "receive"); + } + + public void onConsume( + AgentSpan span, Message message, CharSequence resourceName, String destinationName) { + onConsume(span, message, resourceName, destinationName, "receive"); + } + + public void onConsume( + AgentSpan span, + Message message, + CharSequence resourceName, + String destinationName, + String operation) { + span.setTag(MESSAGING_SYSTEM, "jms"); + span.setTag(MESSAGING_OPERATION, operation); if (null != resourceName) { span.setResourceName(resourceName); } + if (null != destinationName && !destinationName.isEmpty()) { + span.setTag(MESSAGING_DESTINATION_NAME, destinationName); + } try { final long produceTime = message.getJMSTimestamp(); @@ -172,9 +197,18 @@ public void onConsume(AgentSpan span, Message message, CharSequence resourceName } public void onProduce(AgentSpan span, CharSequence resourceName) { + onProduce(span, resourceName, null); + } + + public void onProduce(AgentSpan span, CharSequence resourceName, String destinationName) { + span.setTag(MESSAGING_SYSTEM, "jms"); + span.setTag(MESSAGING_OPERATION, "send"); if (null != resourceName) { span.setResourceName(resourceName); } + if (null != destinationName && !destinationName.isEmpty()) { + span.setTag(MESSAGING_DESTINATION_NAME, destinationName); + } } public static boolean canInject(Message message) { @@ -265,6 +299,18 @@ public CharSequence toResourceName(String destinationName, boolean isQueue) { return joiner.apply(destinationName); } + public Destination getDestination(final MessageProducer messageProducer) throws JMSException { + try { + return messageProducer.getDestination(); // >= 1.1 + } catch (AbstractMethodError ignored) { + // <=1.1 getDestination is not available so we need to pay an additional instanceOf + if (messageProducer instanceof QueueSender) { + return ((QueueSender) messageProducer).getQueue(); + } + return ((TopicPublisher) messageProducer).getTopic(); + } + } + public String getDestinationName(Destination destination) { String name = null; try { diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java index 75a59e7406d..2874c77b281 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java @@ -76,7 +76,7 @@ public void methodAdvice(MethodTransformer transformer) { isMethod() .and(named("setMessageListener")) .and(takesArgument(0, hasInterface(named(namespace + ".jms.MessageListener")))), - getClass().getName() + "$DecorateMessageListener"); + JMSMessageConsumerInstrumentation.class.getName() + "$DecorateMessageListener"); } public static class ConsumerAdvice { @@ -160,16 +160,19 @@ public static void afterReceive( } CONSUMER_DECORATE.afterStart(span); - CONSUMER_DECORATE.onConsume(span, message, consumerState.getConsumerResourceName()); + String consumerDestinationName = + consumerState.getConsumerBaseResourceName() != null + ? consumerState.getConsumerBaseResourceName().toString() + : null; + CONSUMER_DECORATE.onConsume( + span, message, consumerState.getConsumerResourceName(), consumerDestinationName); if (Config.get().isDataStreamsEnabled()) { final String tech = messageTechnology(message); - if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ - DataStreamsTags tags = - create(tech, INBOUND, consumerState.getConsumerBaseResourceName().toString()); - DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); - } + DataStreamsTags tags = + create(tech, INBOUND, consumerState.getConsumerBaseResourceName().toString()); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); } CONSUMER_DECORATE.onError(span, throwable); diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java index 972b4382209..76305896d9c 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java @@ -90,10 +90,10 @@ public static AgentScope beforeSend( // fall-back when producer wasn't created via standard Session.createProducer API if (null != producerState) { resourceName = producerState.getResourceName(); - Destination destination = producer.getDestination(); + Destination destination = PRODUCER_DECORATE.getDestination(producer); destinationName = PRODUCER_DECORATE.getDestinationName(destination); } else { - Destination destination = producer.getDestination(); + Destination destination = PRODUCER_DECORATE.getDestination(producer); destinationName = PRODUCER_DECORATE.getDestinationName(destination); boolean isQueue = PRODUCER_DECORATE.isQueue(destination); resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); @@ -105,17 +105,15 @@ public static AgentScope beforeSend( final AgentSpan span = startSpan("jms", JMS_PRODUCE); PRODUCER_DECORATE.afterStart(span); - PRODUCER_DECORATE.onProduce(span, resourceName); + PRODUCER_DECORATE.onProduce(span, resourceName, destinationName); if (null != destinationName && !destinationName.isEmpty() && Config.get().isDataStreamsEnabled()) { final String tech = messageTechnology(message); - if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ - DataStreamsTags tags = create(tech, OUTBOUND, destinationName); - DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); - } + DataStreamsTags tags = create(tech, OUTBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); } if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED && null != producerState) { @@ -175,17 +173,15 @@ public static AgentScope beforeSend( final AgentSpan span = startSpan("jms", JMS_PRODUCE); PRODUCER_DECORATE.afterStart(span); - PRODUCER_DECORATE.onProduce(span, resourceName); + PRODUCER_DECORATE.onProduce(span, resourceName, destinationName); if (null != destinationName && !destinationName.isEmpty() && Config.get().isDataStreamsEnabled()) { final String tech = messageTechnology(message); - if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ - DataStreamsTags tags = create(tech, OUTBOUND, destinationName); - DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); - } + DataStreamsTags tags = create(tech, OUTBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); } if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED) { diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java index d9b878f9c69..3636d63d9e8 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java @@ -6,12 +6,15 @@ import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasSuperType; import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; import static datadog.trace.instrumentation.jms.JMSDecorator.logJMSException; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -21,9 +24,13 @@ import datadog.context.ContextScope; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.annotation.AppliesOn; +import datadog.trace.api.Config; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; @@ -88,16 +95,27 @@ public static AgentScope methodEnter(@Advice.Argument(0) final Message message) AgentSpan span = startSpan("jms", JMS_CONSUME); CONSUMER_DECORATE.afterStart(span); CharSequence consumerResourceName; + String destinationName = null; try { Destination destination = message.getJMSDestination(); boolean isQueue = CONSUMER_DECORATE.isQueue(destination); - String destinationName = CONSUMER_DECORATE.getDestinationName(destination); + destinationName = CONSUMER_DECORATE.getDestinationName(destination); consumerResourceName = CONSUMER_DECORATE.toResourceName(destinationName, isQueue); } catch (JMSException e) { logJMSException(e); consumerResourceName = "unknown JMS destination"; } - CONSUMER_DECORATE.onConsume(span, message, consumerResourceName); + CONSUMER_DECORATE.onConsume(span, message, consumerResourceName, destinationName, "process"); + + if (Config.get().isDataStreamsEnabled() + && null != destinationName + && !destinationName.isEmpty()) { + final String tech = messageTechnology(message); + DataStreamsTags tags = create(tech, INBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + return activateSpan(span); } diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java index 3f7b095b3b7..8c3ffa48231 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java @@ -114,7 +114,7 @@ public static void bindProducerState( int ackMode; try { ackMode = session.getAcknowledgeMode(); - } catch (Exception ignored) { + } catch (Throwable ignored) { ackMode = Session.AUTO_ACKNOWLEDGE; } sessionState = @@ -155,7 +155,7 @@ public static void bindConsumerState( int ackMode; try { ackMode = session.getAcknowledgeMode(); - } catch (Exception ignored) { + } catch (Throwable ignored) { ackMode = Session.AUTO_ACKNOWLEDGE; } sessionState = diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy deleted file mode 100644 index f059016bc62..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy +++ /dev/null @@ -1,1099 +0,0 @@ -import static datadog.trace.api.config.TraceInstrumentationConfig.LEGACY_CONTEXT_MANAGER_ENABLED -import static org.junit.jupiter.api.Assumptions.assumeTrue - -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.agent.test.asserts.TraceAssert -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import datadog.trace.api.Config -import datadog.trace.api.DDSpanTypes -import datadog.trace.api.Trace -import datadog.trace.api.config.TracerConfig -import datadog.trace.api.config.TraceInstrumentationConfig -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import org.apache.activemq.ActiveMQConnectionFactory -import org.apache.activemq.command.ActiveMQTextMessage -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import spock.lang.Shared - -import javax.jms.Connection -import javax.jms.Destination -import javax.jms.Message -import javax.jms.MessageListener -import javax.jms.QueueConnection -import javax.jms.QueueSession -import javax.jms.Session -import javax.jms.TemporaryQueue -import javax.jms.TemporaryTopic -import javax.jms.Queue -import javax.jms.Topic -import javax.jms.TextMessage -import javax.jms.TopicConnection -import javax.jms.TopicSession -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference - -abstract class JMS1Test extends VersionedNamingTestBase { - @Shared - EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() - @Shared - String messageText1 = "a message" - @Shared - String messageText2 = "another message" - @Shared - String messageText3 = "yet another message" - @Shared - Session session - @Shared - QueueSession queueSession - @Shared - TopicSession topicSession - - @Shared - Connection connection - @Shared - QueueConnection queueConnection - @Shared - TopicConnection topicConnection - - ActiveMQTextMessage message1 = session.createTextMessage(messageText1) - ActiveMQTextMessage message2 = session.createTextMessage(messageText2) - ActiveMQTextMessage message3 = session.createTextMessage(messageText3) - - abstract String operationForProducer() - - abstract String operationForConsumer() - - boolean testUnclosedScopeFinished() { - true - } - - def setupSpec() { - broker.start() - final ActiveMQConnectionFactory connectionFactory = broker.createConnectionFactory() - - connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - - queueConnection = connectionFactory.createQueueConnection() - queueConnection.start() - queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE) - - topicConnection = connectionFactory.createTopicConnection() - topicConnection.setClientID('gradle') - topicConnection.start() - topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE) - } - - def cleanupSpec() { - broker.stop() - } - - /** - * Create unique destinations of different types. If queue/topic is not temporary, the name will be unique. This - * avoids leaking data between tests. Type is enum to get a sane toString that guarantees stable test IDs. - */ - enum DestinationType { - QUEUE, TOPIC, TEMPORARY_QUEUE, TEMPORARY_TOPIC - - private static int counter = 0 - - Destination create(final Session session) { - switch (this) { - case QUEUE: - return session.createQueue("queue-${counter++}") - case TOPIC: - return session.createTopic("topic-${counter++}") - case TEMPORARY_QUEUE: - return session.createTemporaryQueue() - case TEMPORARY_TOPIC: - return session.createTemporaryTopic() - default: - throw new IllegalArgumentException("Unknown destination type: $this") - } - } - } - - def "sending messages to #destinationType generates spans"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - TextMessage receivedMessage3 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - consumer.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "closing #destinationType session should close and finish any pending scopes"() { - setup: - assumeTrue(testUnclosedScopeFinished()) - def destination = destinationType.create(session) - def localSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = localSession.createProducer(destination) - def consumer = localSession.createConsumer(destination) - - producer.send(message1) - - TextMessage receivedMessage = consumer.receive() - localSession.close() - - expect: - receivedMessage.text == messageText1 - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "receiving messages from #destinationType in a transacted session"() { - setup: - def destination = destinationType.create(session) - def transactedSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = session.createProducer(destination) - def consumer = transactedSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - transactedSession.commit() - TextMessage receivedMessage3 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - transactedSession.commit() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - transactedSession.commit() - producer.close() - consumer.close() - transactedSession.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "receiving messages from #destinationType with manual acknowledgement"() { - setup: - // Use a long scope iteration keep-alive to prevent early cleanup of the 3rd - // consumer span, ensuring exactly 5 traces before acknowledge (not 6). - injectSysConfig(TracerConfig.SCOPE_ITERATION_KEEP_ALIVE, "10000") - def destination = destinationType.create(session) - def clientSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def producer = session.createProducer(destination) - def consumer = clientSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - receivedMessage2.acknowledge() - TextMessage receivedMessage3 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point because message 3 - // has not been acknowledged and the long keep-alive prevents early cleanup - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - receivedMessage3.acknowledge() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - producer.close() - consumer.close() - clientSession.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "recovering messages from #destinationType with manual acknowledgement"() { - setup: - def destination = destinationType.create(session) - def clientSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def producer = session.createProducer(destination) - def consumer = clientSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - - TextMessage receivedMessage1 = consumer.receive() - TextMessage receivedMessage2 = consumer.receive() - clientSession.recover() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - // two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - receivedMessage1 = consumer.receive() - receivedMessage2 = consumer.receive() - TextMessage receivedMessage3 = consumer.receive() - receivedMessage1.acknowledge() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // the two consume traces plus three more will be finished at this point - assertTraces(8) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(0)[0]) // redelivered message - consumerTraceWithNaming(it, destination, trace(1)[0]) // redelivered message - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - receivedMessage3.acknowledge() - producer.close() - consumer.close() - clientSession.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending to a MessageListener on #destinationType generates a span"() { - setup: - def destination = destinationType.create(session) - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - - producer.send(message1) - lock.countDown() - - expect: - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText1 - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending to a null MessageListener on #destinationType generates only producer spans"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - consumer.setMessageListener(null) - - producer.send(message1) - - expect: - assertTraces(1) { - producerTraceWithNaming(it, destination) - } - - cleanup: - producer.close() - consumer.receiveNoWait() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "failing to receive message with receiveNoWait on #destinationType works"() { - setup: - def destination = destinationType.create(session) - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receiveNoWait() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - } - - def "failing to receive message with wait(timeout) on #destinationType works"() { - setup: - def destination = destinationType.create(session) - def consumer = session.createConsumer(destination) - - // Receive with timeout - TextMessage receivedMessage = consumer.receive(100) - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage == null - assertTraces(0) {} - - cleanup: - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - } - - def "sending a read-only message to #destinationType fails"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - expect: - !message1.isReadOnlyProperties() - - when: - message1.setReadOnlyProperties(true) - and: - producer.send(message1) - - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - then: - receivedMessage.text == messageText1 - - // This will result in a logged failure because we tried to - // write properties in MessagePropertyTextMap when readOnlyProperties = true. - // The consumer span will also not be linked to the parent. - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - // Consumer trace - span { - parent() - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending a message with disabled timestamp generates spans without specific tag"() { - setup: - def destination = DestinationType.QUEUE.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - producer.setDisableMessageTimestamp(true) - producer.send(message1) - - boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() - consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0], isTimeStampDisabled) - } - - cleanup: - producer.close() - consumer.close() - } - - def "traceable work between two #destinationType receive calls has jms.consume parent"() { - setup: - def destination = destinationType.create(session) - def producer = session.createProducer(destination) - def consumer = session.createConsumer(destination) - - producer.send(message1) - - TextMessage receivedMessage = consumer.receive() - doStuff() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - - expect: - receivedMessage.text == messageText1 - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(2) { - consumerSpan(it, toJmsResourceName(destination), trace(0)[0], false, service(), operationForConsumer()) - span { - operationName "do.stuff" - childOf(trace(1)[0]) - } - } - } - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_QUEUE | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "sending a message to #destinationType with given disabled topic or queue disables propagation on producer side"() { - setup: - def destination = destinationType.create(session) - // create consumer while propagation is enabled (state will be cached) - def consumer = session.createConsumer(destination) - // now disable propagation for any messages produced in the given topic/queue - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) - def producer = session.createProducer(destination) - producer.send(message1) - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - expect: - receivedMessage.text == messageText1 - if (expected) { - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - } else { - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - span { - parentSpanId(0 as BigInteger) - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - } - cleanup: - producer.close() - consumer.close() - - where: - destinationType | expected - DestinationType.QUEUE | false - DestinationType.TOPIC | false - DestinationType.TEMPORARY_QUEUE | true - DestinationType.TEMPORARY_TOPIC | true - } - - def "sending a message to #destinationType with given disabled topic or queue disables propagation on consumer side"() { - setup: - def destination = destinationType.create(session) - // create consumer while propagation is disabled for given topic/queue (state will be cached) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) - def consumer = session.createConsumer(destination) - // now enable propagation for the producer and any messages produced - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS) - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES) - def producer = session.createProducer(destination) - producer.send(message1) - TextMessage receivedMessage = consumer.receive() - // required to finish auto-acknowledged spans - consumer.receiveNoWait() - expect: - receivedMessage.text == messageText1 - if (expected) { - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - } else { - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - span { - parentSpanId(0 as BigInteger) - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - } - cleanup: - producer.close() - consumer.close() - - where: - destinationType | expected - DestinationType.QUEUE | false - DestinationType.TOPIC | false - DestinationType.TEMPORARY_QUEUE | true - DestinationType.TEMPORARY_TOPIC | true - } - - def "sending a message to #destinationType with given disabled topic or queue disables propagation in listener"() { - setup: - def destination = destinationType.create(session) - // create consumer while propagation is disabled for given topic/queue (state will be cached) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) - injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) - def consumer = session.createConsumer(destination) - // now enable propagation for the producer and any messages produced - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS) - removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES) - def producer = session.createProducer(destination) - def lock = new CountDownLatch(1) - def messageRef = new AtomicReference() - consumer.setMessageListener new MessageListener() { - @Override - void onMessage(Message message) { - lock.await() // ensure the producer trace is reported first. - messageRef.set(message) - } - } - producer.send(message1) - lock.countDown() - - expect: - if (expected) { - assertTraces(2) { - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - } - } else { - assertTraces(2) { - producerTraceWithNaming(it, destination) - trace(1) { - span { - parentSpanId(0 as BigInteger) - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from ${toJmsResourceName(destination)}" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - defaultTagsNoPeerService() - } - } - } - } - } - // This check needs to go after all traces have been accounted for - messageRef.get().text == messageText1 - - cleanup: - producer.close() - consumer.close() - - where: - destinationType | expected - DestinationType.QUEUE | false - DestinationType.TOPIC | false - DestinationType.TEMPORARY_QUEUE | true - DestinationType.TEMPORARY_TOPIC | true - } - - def "queue session with #destinationType generates spans"() { - setup: - def destination = destinationType.create(queueSession) - def sender = queueSession.createSender(destination) - def receiver = queueSession.createReceiver(destination) - - when: - sender.send(message1) - sender.send(destination, message2) - sender.send(message3) - - TextMessage receivedMessage1 = receiver.receive() - TextMessage receivedMessage2 = receiver.receive() - TextMessage receivedMessage3 = receiver.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - receiver.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - sender.close() - receiver.close() - - where: - destinationType | _ - DestinationType.QUEUE | _ - DestinationType.TEMPORARY_QUEUE | _ - } - - def "topic session with #destinationType generates spans"() { - setup: - def destination = destinationType.create(topicSession) - def publisher = topicSession.createPublisher(destination) - def subscriber = topicSession.createSubscriber(destination) - - when: - publisher.send(message1) - publisher.send(message2) - publisher.send(message3) - - TextMessage receivedMessage1 = subscriber.receive() - TextMessage receivedMessage2 = subscriber.receive() - TextMessage receivedMessage3 = subscriber.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - subscriber.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - publisher.close() - subscriber.close() - - where: - destinationType | _ - DestinationType.TOPIC | _ - DestinationType.TEMPORARY_TOPIC | _ - } - - def "durable topic session generates spans"() { - setup: - def destination = DestinationType.TOPIC.create(topicSession) - def publisher = topicSession.createPublisher(destination) - def subscriber = topicSession.createDurableSubscriber(destination, 'test') - - when: - publisher.send(message1) - publisher.send(message2) - publisher.send(message3) - - TextMessage receivedMessage1 = subscriber.receive() - TextMessage receivedMessage2 = subscriber.receive() - TextMessage receivedMessage3 = subscriber.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - // only two consume traces will be finished at this point - assertTraces(5) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - } - - when: - subscriber.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(6) { - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - producerTraceWithNaming(it, destination) - consumerTraceWithNaming(it, destination, trace(0)[0]) - consumerTraceWithNaming(it, destination, trace(1)[0]) - consumerTraceWithNaming(it, destination, trace(2)[0]) - } - - cleanup: - publisher.close() - subscriber.close() - } - - String toJmsResourceName(Destination destination) { - if (destination instanceof TemporaryQueue) { - return "Temporary Queue" - } else if (destination instanceof TemporaryTopic) { - return "Temporary Topic" - } else if (destination instanceof Queue) { - return "Queue ${((Queue) destination).getQueueName()}" - } else if (destination instanceof Topic) { - return "Topic ${((Topic) destination).getTopicName()}" - } - throw new IllegalArgumentException("Unknown destination type: $destination") - } - - String toName(Destination destination) { - if (destination instanceof TemporaryQueue) { - return "" - } else if (destination instanceof TemporaryTopic) { - return "" - } else if (destination instanceof Queue) { - return ((Queue) destination).getQueueName() - } else if (destination instanceof Topic) { - return ((Topic) destination).getTopicName() - } - throw new IllegalArgumentException("Unknown destination type: $destination") - } - - def producerTraceWithNaming(ListWriterAssert writer, Destination destination) { - producerTrace(writer, toJmsResourceName(destination), service(), operationForProducer()) - } - - static producerTrace(ListWriterAssert writer, String jmsResourceName, String producerService = "jms", String producerOperation = "jms.produce") { - writer.trace(1) { - producerSpan(it, jmsResourceName, producerService, producerOperation) - } - } - - static producerSpan(TraceAssert traceAssert, String jmsResourceName, String producerService = "jms", String producerOperation = "jms.produce") { - return traceAssert.span { - serviceName producerService - operationName producerOperation - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - measured true - parent() - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - defaultTagsNoPeerService() - } - } - } - - def consumerTraceWithNaming(ListWriterAssert writer, Destination destination, DDSpan parentSpan, boolean isTimestampDisabled = false) { - consumerTrace(writer, toJmsResourceName(destination), parentSpan, isTimestampDisabled, service(), operationForConsumer()) - } - - static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false, - String consumerService = "jms", String consumerOperation = "jms.consume") { - writer.trace(1) { - consumerSpan(it, jmsResourceName, parentSpan, isTimestampDisabled, consumerService, consumerOperation) - } - } - - static consumerSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false, - String consumerService = "jms", String consumerOperation = "jms.consume") { - return traceAssert.span { - serviceName consumerService - operationName consumerOperation - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - measured true - childOf parentSpan - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - } - defaultTagsNoPeerService(true) - } - } - } - - @Trace(operationName = "do.stuff") - def doStuff() { - } -} - -class JMS1V0Test extends JMS1Test { - - @Override - int version() { - 0 - } - - @Override - String service() { - "jms" - } - - @Override - String operation() { - null - } - - @Override - String operationForProducer() { - "jms.produce" - } - - @Override - String operationForConsumer() { - "jms.consume" - } -} - -class JMSContextSwapForkedTest extends JMS1V0Test { - @Override - protected void configurePreAgent() { - injectSysConfig(LEGACY_CONTEXT_MANAGER_ENABLED, "false") - } - - @Override - boolean testUnclosedScopeFinished() { - //TODO: This need to be removed when the Context manager will support it - false - } -} - -class JMS1V1ForkedTest extends JMS1Test { - - @Override - int version() { - 1 - } - - @Override - String service() { - Config.get().getServiceName() - } - - @Override - String operation() { - null - } - - @Override - String operationForProducer() { - "jms.send" - } - - @Override - String operationForConsumer() { - "jms.process" - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMSDecoratorTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMSDecoratorTest.groovy deleted file mode 100644 index 1c8f2dc0bdd..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMSDecoratorTest.groovy +++ /dev/null @@ -1,99 +0,0 @@ -import datadog.trace.instrumentation.jms.JMSDecorator -import spock.lang.Specification - -import javax.jms.Queue -import javax.jms.Topic - -class JMSDecoratorTest extends Specification { - - def "test getDestinationName sanitizes Kafka Connect schema suffixes"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def queue = Mock(Queue) { - getQueueName() >> rawQueueName - } - def result = decorator.getDestinationName(queue) - - then: - result == expectedName - - where: - rawQueueName | expectedName - // Customer reported issue: queue name with _messagebody_0 suffix from Kafka Connect IBM MQ connector - // See Zendesk ticket #2429181 - "trainmgt.dispatch.trnsheet.p30.v1.pub_messagebody_0" | "trainmgt.dispatch.trnsheet.p30.v1.pub" - - // Normal queue names should pass through unchanged (like customer's working pure Java apps) - "ee.wo.aei.delmove.cs" | "ee.wo.aei.delmove.cs" - "myqueue" | "myqueue" - "my.queue.name" | "my.queue.name" - - // Other Kafka Connect schema-derived suffixes should also be stripped - "myqueue_messagebody_0" | "myqueue" - "myqueue_text_0" | "myqueue" - "myqueue_bytes_0" | "myqueue" - "myqueue_map_0" | "myqueue" - "myqueue_value_0" | "myqueue" - "myqueue_MESSAGEBODY_0" | "myqueue" // case insensitive - "myqueue_MessageBody_0" | "myqueue" // case insensitive - - // Multiple digit indices - "myqueue_messagebody_10" | "myqueue" - "myqueue_messagebody_123" | "myqueue" - - // Names that look similar but shouldn't be stripped - "myqueue_messagebody" | "myqueue_messagebody" // no index - "messagebody_0_queue" | "messagebody_0_queue" // not at end - "myqueue_othersuffix_0" | "myqueue_othersuffix_0" // unknown suffix - } - - def "test getDestinationName with topic sanitizes Kafka Connect schema suffixes"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def topic = Mock(Topic) { - getTopicName() >> rawTopicName - } - def result = decorator.getDestinationName(topic) - - then: - result == expectedName - - where: - rawTopicName | expectedName - "mytopic" | "mytopic" - "mytopic_messagebody_0" | "mytopic" - "mytopic_text_0" | "mytopic" - } - - def "test getDestinationName returns null for null queue name"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def queue = Mock(Queue) { - getQueueName() >> null - } - def result = decorator.getDestinationName(queue) - - then: - result == null - } - - def "test getDestinationName returns null for TIBCO temp prefix"() { - given: - def decorator = JMSDecorator.CONSUMER_DECORATE - - when: - def queue = Mock(Queue) { - getQueueName() >> '$TMP$myqueue' - } - def result = decorator.getDestinationName(queue) - - then: - result == null - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB1.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB1.groovy deleted file mode 100644 index dc5b8b20657..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB1.groovy +++ /dev/null @@ -1,16 +0,0 @@ -import javax.ejb.MessageDrivenBean -import javax.ejb.MessageDrivenContext -import javax.jms.Message -import javax.jms.MessageListener - -class MDB1 implements MessageDrivenBean, MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } - void ejbRemove() {} - void setMessageDrivenContext(MessageDrivenContext ctx) {} -} - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB2.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB2.groovy deleted file mode 100644 index f748669831a..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDB2.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import javax.ejb.MessageDriven -import javax.jms.Message -import javax.jms.MessageListener - -@MessageDriven(mappedName="unusedTopic") -class MDB2 implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBBad.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBBad.groovy deleted file mode 100644 index f6f1004befd..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBBad.groovy +++ /dev/null @@ -1,14 +0,0 @@ -import javax.jms.Message -import javax.jms.MessageListener - -// Not a valid MDB because it only implements MessageListener. -class MDBBad implements MessageListener { - - void onMessage(Message message) { - if (message == null) { - throw new Exception("null message") - } - } -} - - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBJmsMsg.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBJmsMsg.groovy deleted file mode 100644 index eeb7cef9a12..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBJmsMsg.groovy +++ /dev/null @@ -1,224 +0,0 @@ -import javax.jms.Message -import javax.jms.Destination -import javax.jms.JMSException - -class MDBJmsMsg implements Message { - - long getJMSDeliveryTime() { - return 1L - } - - void setJMSDeliveryTime(long time) {} - - Object getBody(Class aClass) { - return null - } - - boolean isBodyAssignableTo(Class aClass) { - return false - } - - @Override - String getJMSMessageID() throws JMSException { - return "123" - } - - @Override - void setJMSMessageID(String id) throws JMSException {} - - @Override - long getJMSTimestamp() throws JMSException { - return 0 - } - - @Override - void setJMSTimestamp(long timestamp) throws JMSException { - } - - @Override - byte[] getJMSCorrelationIDAsBytes() throws JMSException { - return new byte[0] - } - - @Override - void setJMSCorrelationIDAsBytes(byte[] correlationID) throws JMSException { - } - - @Override - void setJMSCorrelationID(String correlationID) throws JMSException { - } - - @Override - String getJMSCorrelationID() throws JMSException { - return null - } - - @Override - Destination getJMSReplyTo() throws JMSException { - return null - } - - @Override - void setJMSReplyTo(Destination replyTo) throws JMSException { - } - - @Override - Destination getJMSDestination() throws JMSException { - return null - } - - @Override - void setJMSDestination(Destination destination) throws JMSException { - } - - @Override - int getJMSDeliveryMode() throws JMSException { - return 0 - } - - @Override - void setJMSDeliveryMode(int deliveryMode) throws JMSException { - } - - @Override - boolean getJMSRedelivered() throws JMSException { - return false - } - - @Override - void setJMSRedelivered(boolean redelivered) throws JMSException { - } - - @Override - String getJMSType() throws JMSException { - return null - } - - @Override - void setJMSType(String type) throws JMSException { - } - - @Override - long getJMSExpiration() throws JMSException { - return 0 - } - - @Override - void setJMSExpiration(long expiration) throws JMSException { - } - - @Override - int getJMSPriority() throws JMSException { - return 0 - } - - @Override - void setJMSPriority(int priority) throws JMSException { - } - - @Override - void clearProperties() throws JMSException { - } - - @Override - boolean propertyExists(String name) throws JMSException { - return false - } - - @Override - boolean getBooleanProperty(String name) throws JMSException { - return false - } - - @Override - byte getByteProperty(String name) throws JMSException { - return 0 - } - - @Override - short getShortProperty(String name) throws JMSException { - return 0 - } - - @Override - int getIntProperty(String name) throws JMSException { - return 0 - } - - @Override - long getLongProperty(String name) throws JMSException { - return 0 - } - - @Override - float getFloatProperty(String name) throws JMSException { - return 0 - } - - @Override - double getDoubleProperty(String name) throws JMSException { - return 0 - } - - @Override - String getStringProperty(String name) throws JMSException { - return null - } - - @Override - Object getObjectProperty(String name) throws JMSException { - return null - } - - @Override - Enumeration getPropertyNames() throws JMSException { - return null - } - - @Override - void setBooleanProperty(String name, boolean value) throws JMSException { - } - - @Override - void setByteProperty(String name, byte value) throws JMSException { - } - - @Override - void setShortProperty(String name, short value) throws JMSException { - } - - @Override - void setIntProperty(String name, int value) throws JMSException { - } - - @Override - void setLongProperty(String name, long value) throws JMSException { - } - - @Override - void setFloatProperty(String name, float value) throws JMSException { - } - - @Override - void setDoubleProperty(String name, double value) throws JMSException { - } - - @Override - void setStringProperty(String name, String value) throws JMSException { - } - - @Override - void setObjectProperty(String name, Object value) throws JMSException { - } - - @Override - void acknowledge() throws JMSException { - } - - @Override - void clearBody() throws JMSException { - } -} - - - diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBTest.groovy deleted file mode 100644 index 8baadc802a3..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/MDBTest.groovy +++ /dev/null @@ -1,78 +0,0 @@ -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.agent.test.asserts.ListWriterAssert -import spock.lang.Shared - -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace - -class MDBTest extends InstrumentationSpecification { - - @Shared - def msg = new MDBJmsMsg() - - def "Test an incomplete MDB that should not get traced here"() { - setup: - def beanBad = new MDBBad() - - when: - runUnderTrace("parent") { - beanBad.onMessage(msg) - } - - then: - assertTraces(1) { - workerTrace(it) - } - } - - def "Test MDB1"() { - setup: - def bean1 = new MDB1() - - when: - runUnderTrace("parent") { - bean1.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def "Test MDB2"() { - setup: - def bean2 = new MDB2() - - when: - runUnderTrace("parent") { - bean2.onMessage(msg) - } - - then: - assertTraces(2, SORT_TRACES_BY_START) { - workerTrace(it) - jmsTrace(it) - } - } - - def workerTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - serviceName "worker.org.gradle.process.internal.worker.GradleWorkerMain" - } - } - } - - def jmsTrace(ListWriterAssert writer) { - writer.trace(1) { - span(0) { - spanType "queue" - serviceName "jms" - operationName "jms.consume" - resourceName "Consumed from Temporary Queue" - measured true - } - } - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringListenerJMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringListenerJMS1Test.groovy deleted file mode 100644 index 5c8e4f259e8..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringListenerJMS1Test.groovy +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.test.util.Flaky -import listener.Config -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.jms.core.JmsTemplate - -import javax.jms.ConnectionFactory - -import static JMS1Test.consumerTrace -import static JMS1Test.producerTrace - -@Flaky -class SpringListenerJMS1Test extends InstrumentationSpecification { - - def "receiving message in spring listener generates spans"() { - setup: - def context = new AnnotationConfigApplicationContext(Config) - def factory = context.getBean(ConnectionFactory) - def template = new JmsTemplate(factory) - template.convertAndSend("SpringListenerJMS1", "a message") - - expect: - assertTraces(2) { - producerTrace(it, "Queue SpringListenerJMS1") - consumerTrace(it, "Queue SpringListenerJMS1", trace(0)[0]) - } - - cleanup: - context.getBean(EmbeddedActiveMQBroker).stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringTemplateJMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringTemplateJMS1Test.groovy deleted file mode 100644 index 66a0f229d5d..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/SpringTemplateJMS1Test.groovy +++ /dev/null @@ -1,90 +0,0 @@ -import datadog.trace.agent.test.InstrumentationSpecification -import datadog.trace.test.util.Flaky -import org.apache.activemq.ActiveMQConnectionFactory -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import org.springframework.jms.core.JmsTemplate -import spock.lang.Shared - -import javax.jms.Connection -import javax.jms.Session -import javax.jms.TextMessage -import java.util.concurrent.TimeUnit - -import static JMS1Test.consumerTrace -import static JMS1Test.producerTrace - -class SpringTemplateJMS1Test extends InstrumentationSpecification { - @Shared - EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() - @Shared - JmsTemplate template - @Shared - Session session - - def setupSpec() { - broker.start() - final ActiveMQConnectionFactory connectionFactory = broker.createConnectionFactory() - final Connection connection = connectionFactory.createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - - template = new JmsTemplate(connectionFactory) - // Make this longer than timeout on TEST_WRITER.waitForTraces - // Otherwise caller might give up waiting before callee has a chance to respond. - template.receiveTimeout = TimeUnit.SECONDS.toMillis(21) - } - - def cleanupSpec() { - broker.stop() - } - - def "sending a message to #jmsResourceName generates spans"() { - setup: - template.convertAndSend(destination, messageText) - TextMessage receivedMessage = template.receive(destination) - - expect: - receivedMessage.text == messageText - assertTraces(2) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS1") | "Queue SpringTemplateJMS1" - - messageText = "a message" - } - - @Flaky("Sometimes fails when finding errors in traces: Cannot publish to a deleted Destination: temp-queue://...") - def "send and receive message generates spans"() { - setup: - Thread.start { - TextMessage msg = template.receive(destination) - assert msg.text == messageText - - template.send(msg.getJMSReplyTo()) { session -> - template.getMessageConverter().toMessage("responded!", session) - } - } - TextMessage receivedMessage = template.sendAndReceive(destination) { session -> - template.getMessageConverter().toMessage(messageText, session) - } - - expect: - receivedMessage.text == "responded!" - assertTraces(4) { - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root - consumerTrace(it, "Temporary Queue", trace(2)[0]) - } - - where: - destination | jmsResourceName - session.createQueue("SpringTemplateJMS1") | "Queue SpringTemplateJMS1" - - messageText = "a message" - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/TimeInQueueForkedTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/TimeInQueueForkedTest.groovy deleted file mode 100644 index 15e1aa4edb7..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/TimeInQueueForkedTest.groovy +++ /dev/null @@ -1,728 +0,0 @@ -import datadog.trace.agent.test.asserts.ListWriterAssert -import datadog.trace.agent.test.asserts.TraceAssert -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import datadog.trace.api.DDSpanTypes -import datadog.trace.api.config.GeneralConfig -import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.core.DDSpan -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import spock.lang.Shared - -import javax.jms.Connection -import javax.jms.Session -import javax.jms.TextMessage - -abstract class TimeInQueueForkedTestBase extends VersionedNamingTestBase { - @Shared - EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() - @Shared - Connection connection - @Shared - Session session - @Shared - String messageText1 = "a message" - @Shared - String messageText2 = "another message" - @Shared - String messageText3 = "yet another message" - @Shared - String messageText4 = "another message again" - @Shared - String messageText5 = "just one more message" - - TextMessage message1 = session.createTextMessage(messageText1) - TextMessage message2 = session.createTextMessage(messageText2) - TextMessage message3 = session.createTextMessage(messageText3) - TextMessage message4 = session.createTextMessage(messageText4) - TextMessage message5 = session.createTextMessage(messageText5) - - @Override - String operation() { - null - } - - @Override - int version() { - 0 - } - - @Override - String service() { - "myService" - } - - String operationForProducer() { - "jms.produce" - } - - String operationForConsumer() { - "jms.consume" - } - - String serviceForTimeInQueue() { - "jms" - } - - - @Override - protected void configurePreAgent() { - super.configurePreAgent() - - // test explicit only on v0 since we're also testing that in v1 is implicit - if (version() == 0) { - injectSysConfig("jms.legacy.tracing.enabled", 'false') - } - injectSysConfig(GeneralConfig.SERVICE_NAME, 'myService') - } - - abstract boolean splitByDestination() - - def setupSpec() { - broker.start() - connection = broker.createConnectionFactory().createConnection() - connection.start() - session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - } - - def cleanupSpec() { - broker.stop() - } - - def "sending messages to #jmsResourceName generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producer.send(message4) - producer.send(message5) - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only four consume traces will be finished at this point - assertTraces(9, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - consumerTrace(it, jmsResourceName, trace(1)[0]) - consumerTrace(it, jmsResourceName, trace(2)[0]) - consumerTrace(it, jmsResourceName, trace(3)[0]) - } - - when: - consumer.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(10, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - consumerTrace(it, jmsResourceName, trace(0)[0]) - consumerTrace(it, jmsResourceName, trace(1)[0]) - consumerTrace(it, jmsResourceName, trace(2)[0]) - consumerTrace(it, jmsResourceName, trace(3)[0]) - consumerTrace(it, jmsResourceName, trace(4)[0]) - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with manual acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producer.send(message4) - producer.send(message5) - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - receivedMessage2.acknowledge() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only three consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - receivedMessage5.acknowledge() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with listener acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - consumer.setMessageListener { it.acknowledge() } - producer.send(message1) - - then: - assertTraces(2, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(1)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with transacted acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producer.send(message4) - producer.send(message5) - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - consumerSession.commit() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only two consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - consumerSession.commit() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending messages to #jmsResourceName with listener commit generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def consumer = consumerSession.createConsumer(destination) - - when: - consumer.setMessageListener { consumerSession.commit() } - producer.send(message1) - - then: - assertTraces(2, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(1)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending batch messages to #jmsResourceName generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producerSession.commit() - producer.send(message3) - producerSession.commit() - producer.send(message4) - producer.send(message5) - producerSession.commit() - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only four consume traces will be finished at this point - assertTraces(9, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(1) { - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(7)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(8)[0], false) - } - } - - when: - consumer.receiveNoWait() - - then: - // now the last consume trace will also be finished - assertTraces(10, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(1) { - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(7)[0], false) - } - trace(2) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(8)[0], false) - } - trace(1) { - consumerSpan(it, jmsResourceName, trace(8)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending batch messages to #jmsResourceName with manual acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producerSession.commit() - producer.send(message3) - producer.send(message4) - producer.send(message5) - producerSession.commit() - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - def receivedMessage3 = consumer.receive() - receivedMessage2.acknowledge() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only three consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - receivedMessage5.acknowledge() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(3)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def "sending batch messages to #jmsResourceName with transacted acknowledgement generates time-in-queue spans"() { - setup: - def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def producer = producerSession.createProducer(destination) - def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) - def consumer = consumerSession.createConsumer(destination) - - when: - producer.send(message1) - producer.send(message2) - producer.send(message3) - producerSession.commit() - producer.send(message4) - producer.send(message5) - producerSession.commit() - - def receivedMessage1 = consumer.receive() - def receivedMessage2 = consumer.receive() - consumerSession.commit() - def receivedMessage3 = consumer.receive() - def receivedMessage4 = consumer.receive() - def receivedMessage5 = consumer.receive() - - then: - receivedMessage1.text == messageText1 - receivedMessage2.text == messageText2 - receivedMessage3.text == messageText3 - receivedMessage4.text == messageText4 - receivedMessage5.text == messageText5 - // only two consume traces will be finished at this point - assertTraces(6, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - } - - when: - consumerSession.commit() - - then: - // now the other consume traces will be finished - assertTraces(7, SORT_TRACES_BY_ID) { - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - producerTrace(it, jmsResourceName) - trace(3) { - timeInQueueSpan(it, jmsResourceName, trace(0)[0]) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - consumerSpan(it, jmsResourceName, trace(5)[0], false) - } - trace(4) { - timeInQueueSpan(it, jmsResourceName, trace(2)[0]) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - consumerSpan(it, jmsResourceName, trace(6)[0], false) - } - } - - cleanup: - producerSession.close() - consumerSession.close() - - where: - destination | jmsResourceName - session.createQueue("someQueue") | "Queue someQueue" - session.createTopic("someTopic") | "Topic someTopic" - session.createTemporaryQueue() | "Temporary Queue" - session.createTemporaryTopic() | "Temporary Topic" - } - - def producerTrace(ListWriterAssert writer, String jmsResourceName) { - writer.trace(1) { - producerSpan(it, jmsResourceName) - } - } - - def producerSpan(TraceAssert traceAssert, String jmsResourceName) { - return traceAssert.span { - serviceName service() - operationName operationForProducer() - resourceName "Produced for $jmsResourceName" - spanType DDSpanTypes.MESSAGE_PRODUCER - errored false - measured true - parent() - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER - // when not using the legacy tracing the service is set to DD_SERVICE - // while it should just not be set at all. - serviceNameSource "jms" - defaultTagsNoPeerService() - } - } - } - - def consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - writer.trace(2) { - timeInQueueSpan(it, jmsResourceName, parentSpan) - consumerSpan(it, jmsResourceName, span(0), isTimestampDisabled) - } - } - - def consumerSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { - return traceAssert.span { - serviceName service() - operationName operationForConsumer() - resourceName "Consumed from $jmsResourceName" - spanType DDSpanTypes.MESSAGE_CONSUMER - errored false - measured true - childOf parentSpan - - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER - if (!isTimestampDisabled) { - "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } - } - // when not using the legacy tracing the service is set to DD_SERVICE - // while it should just not be set at all. - serviceNameSource "jms" - defaultTags(false) - } - } - } - - def timeInQueueSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan) { - return traceAssert.span { - serviceName splitByDestination() ? "${jmsResourceName.replaceFirst(/(Queue |Topic )/, '')}" : serviceForTimeInQueue() - operationName "jms.deliver" - resourceName "$jmsResourceName" - spanType DDSpanTypes.MESSAGE_BROKER - errored false - childOf parentSpan - tags { - "$Tags.COMPONENT" "jms" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_BROKER - defaultTagsNoPeerService(true) - } - } - } -} - -class TimeInQueueForkedTest extends TimeInQueueForkedTestBase { - @Override - boolean splitByDestination() { - return false - } -} - -class TimeInQueueSplitByDestinationForkedTest extends TimeInQueueForkedTestBase { - @Override - void configurePreAgent() { - super.configurePreAgent() - injectSysConfig("dd.message.broker.split-by-destination", "true") - } - - @Override - boolean splitByDestination() { - return true - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/Config.groovy deleted file mode 100644 index 881ba0ed80a..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/Config.groovy +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package listener - -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.jms.annotation.EnableJms -import org.springframework.jms.config.DefaultJmsListenerContainerFactory -import org.springframework.jms.config.JmsListenerContainerFactory - -import javax.annotation.PreDestroy -import javax.jms.ConnectionFactory - -@Configuration -@ComponentScan -@EnableJms -class Config { - - @Bean - EmbeddedActiveMQBroker broker() { - def broker = new EmbeddedActiveMQBroker() - broker.start() - return broker - } - - @Bean - ConnectionFactory connectionFactory(EmbeddedActiveMQBroker broker) { - return broker.createConnectionFactory() - } - - @Bean - JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() - factory.setConnectionFactory(connectionFactory) - return factory - } - - @PreDestroy - void destroy() { - broker().stop() - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/TestListener.groovy deleted file mode 100644 index b537d7614f2..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/listener/TestListener.groovy +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package listener - -import org.springframework.jms.annotation.JmsListener -import org.springframework.stereotype.Component - -@Component -class TestListener { - - @JmsListener(destination = "SpringListenerJMS1", containerFactory = "containerFactory") - void receiveMessage(String message) { - println "received: " + message - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/datadog/trace/instrumentation/jms/JMS1Test.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/datadog/trace/instrumentation/jms/JMS1Test.java new file mode 100644 index 00000000000..fcc0e89667a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/java/datadog/trace/instrumentation/jms/JMS1Test.java @@ -0,0 +1,1209 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.core.DDSpan; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.DeliveryMode; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TemporaryQueue; +import javax.jms.TemporaryTopic; +import javax.jms.TextMessage; +import javax.jms.Topic; +import org.apache.activemq.junit.EmbeddedActiveMQBroker; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +/** + * Integration tests for JMS 1.1 instrumentation covering producer, consumer, and message listener + * spans. Validates span structure, operation names, resource names, span types, span kinds, tags, + * error propagation, and distributed trace context propagation between producer and consumer. + * + *

Uses an embedded ActiveMQ broker for realistic end-to-end testing. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class JMS1Test extends AbstractInstrumentationTest { + + static { + // Ensure tracing is enabled before the agent is installed by the parent @BeforeAll. + // This protects against DD_TRACE_ENABLED=false leaking from the environment. + System.setProperty("dd.trace.enabled", "true"); + } + + private EmbeddedActiveMQBroker broker; + private Connection connection; + private Session session; + private int queueCounter = 0; + + @BeforeAll + void setupBroker() throws JMSException { + broker = new EmbeddedActiveMQBroker(); + broker.start(); + ConnectionFactory connectionFactory = broker.createConnectionFactory(); + connection = connectionFactory.createConnection(); + connection.start(); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + } + + @AfterAll + void tearDownBroker() throws JMSException { + if (session != null) { + session.close(); + } + if (connection != null) { + connection.close(); + } + if (broker != null) { + broker.stop(); + } + } + + /** Returns a unique queue for each test to avoid cross-test data leaks. */ + private Queue createUniqueQueue() throws JMSException { + return session.createQueue("test-queue-" + (queueCounter++)); + } + + // ===================== Producer Tests ===================== + + @Test + void sendToQueueCreatesProducerSpanWithCorrectAttributes() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello queue"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + // Operation name + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + // Span type — JMS producer and consumer spans use type "queue" + assertEquals("queue", producerSpan.getSpanType()); + // Resource name should contain "Produced for Queue " + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") + && resourceName.contains("Queue") + && resourceName.contains(queue.getQueueName()), + "Resource name should be 'Produced for Queue ', got: " + resourceName); + // Tags + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Producer span should have messaging.operation=send"); + // Measured flag + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + // No error + assertFalse(producerSpan.isError(), "Producer span should not be errored"); + // Root span (no parent) + assertEquals(0L, producerSpan.getParentId(), "Producer span should be a root span"); + } + + @Test + void sendToTopicCreatesProducerSpanWithTopicResourceName() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-producer"); + MessageProducer producer = session.createProducer(topic); + TextMessage message = session.createTextMessage("hello topic"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for topic"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Produced for") && resourceName.contains("Topic"), + "Resource name should contain 'Produced for' and 'Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertEquals( + "jms", + producerSpan.getTag("messaging.system"), + "Topic producer span should have messaging.system=jms"); + assertEquals( + "send", + producerSpan.getTag("messaging.operation"), + "Topic producer span should have messaging.operation=send"); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithExplicitDestinationCreatesProducerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + // Create producer without default destination + MessageProducer producer = session.createProducer(null); + TextMessage message = session.createTextMessage("hello explicit dest"); + + producer.send(queue, message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for explicit destination"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendWithDeliveryModeAndPriorityCreatesProducerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("hello with params"); + + // Send with explicit deliveryMode, priority, and timeToLive + producer.send(message, DeliveryMode.NON_PERSISTENT, 7, 60000); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span with delivery params"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertFalse(producerSpan.isError()); + } + + @Test + void sendToTemporaryQueueCreatesProducerSpanWithTemporaryResourceName() + throws JMSException, InterruptedException, TimeoutException { + TemporaryQueue tempQueue = session.createTemporaryQueue(); + MessageProducer producer = session.createProducer(tempQueue); + TextMessage message = session.createTextMessage("hello temp queue"); + + producer.send(message); + + producer.close(); + tempQueue.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary queue"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Queue"), + "Resource name for temp queue should contain 'Temporary Queue', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + @Test + void sendToTemporaryTopicCreatesProducerSpanWithTemporaryResourceName() + throws JMSException, InterruptedException, TimeoutException { + TemporaryTopic tempTopic = session.createTemporaryTopic(); + MessageProducer producer = session.createProducer(tempTopic); + TextMessage message = session.createTextMessage("hello temp topic"); + + producer.send(message); + + producer.close(); + tempTopic.delete(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for temporary topic"); + String resourceName = producerSpan.getResourceName().toString(); + assertTrue( + resourceName.contains("Temporary Topic"), + "Resource name for temp topic should contain 'Temporary Topic', got: " + resourceName); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + } + + // ===================== Consumer Tests ===================== + + @Test + void receiveCreatesConsumerSpanLinkedToProducer() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("receive test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received a message"); + assertEquals("receive test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Consumer span attributes + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + // Messaging semantic convention tags + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Consumer receive() span should have messaging.operation=receive"); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + assertFalse(consumerSpan.isError(), "Consumer span should not be errored"); + + // Context propagation: consumer should be child of producer + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer span should be child of the producer span (distributed context propagation)"); + assertEquals( + producerSpan.getTraceId(), + consumerSpan.getTraceId(), + "Consumer and producer spans should share the same trace ID"); + } + + @Test + void receiveWithTimeoutCreatesConsumerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("timeout receive"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + + assertNotNull(receivedMessage, "Should have received message with timeout"); + assertEquals("timeout receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receive(timeout)"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void receiveNoWaitCreatesConsumerSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("noWait receive"); + producer.send(sentMessage); + + // Allow time for the message to arrive at the broker + Thread.sleep(500); + + TextMessage receivedMessage = (TextMessage) consumer.receiveNoWait(); + + assertNotNull(receivedMessage, "Should have received message with receiveNoWait"); + assertEquals("noWait receive", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for receiveNoWait"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertFalse(consumerSpan.isError()); + } + + @Test + void multipleMessagesCreateMultipleProducerAndConsumerSpans() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + producer.send(session.createTextMessage("msg1")); + producer.send(session.createTextMessage("msg2")); + producer.send(session.createTextMessage("msg3")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + TextMessage recv3 = (TextMessage) consumer.receive(5000); + + assertNotNull(recv1, "Should receive message 1"); + assertNotNull(recv2, "Should receive message 2"); + assertNotNull(recv3, "Should receive message 3"); + assertEquals("msg1", recv1.getText()); + assertEquals("msg2", recv2.getText()); + assertEquals("msg3", recv3.getText()); + + producer.close(); + + // Trigger last pending consumer span to finish (receiveNoWait causes previous span flush) + consumer.receiveNoWait(); + consumer.close(); + + // 3 producer traces + up to 3 consumer traces + writer.waitForTraces(6); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(3, producerSpans.size(), "Expected 3 producer spans"); + assertEquals(3, consumerSpans.size(), "Expected 3 consumer spans"); + + // Each producer span should have consistent attributes + for (DDSpan pSpan : producerSpans) { + assertEquals("producer", String.valueOf(pSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(pSpan.getTag("component"))); + assertEquals("queue", pSpan.getSpanType()); + } + + // Each consumer span should be linked to a producer span + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertEquals("queue", cSpan.getSpanType()); + // Consumer should be child of a producer (distributed context) + assertTrue( + cSpan.getParentId() != 0, + "Consumer span should have a parent (linked via context propagation)"); + } + } + + @Test + void consumerReceiveFromTopicCreatesConsumerSpan() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-consumer"); + MessageProducer producer = session.createProducer(topic); + MessageConsumer consumer = session.createConsumer(topic); + + TextMessage sentMessage = session.createTextMessage("topic consume test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should have received topic message"); + assertEquals("topic consume test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span for topic"); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Topic"), + "Consumer resource should mention 'Consumed from Topic', got: " + consumerResource); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertEquals( + "jms", + consumerSpan.getTag("messaging.system"), + "Topic consumer span should have messaging.system=jms"); + assertEquals( + "receive", + consumerSpan.getTag("messaging.operation"), + "Topic consumer span should have messaging.operation=receive"); + } + + // ===================== MessageListener Tests ===================== + + @Test + void messageListenerOnMessageCreatesConsumerSpanLinkedToProducer() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + TestMessageListener listener = new TestMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Listener should receive the message"); + assertNotNull(listener.receivedMessage, "Listener should have captured the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + + // Find the consumer/deliver span created by the MessageListener instrumentation + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull( + listenerSpan, "Expected a consumer or deliver span for MessageListener.onMessage"); + assertEquals("queue", listenerSpan.getSpanType()); + // Resource name should follow the 'Consumed from Queue ' pattern + String listenerResource = listenerSpan.getResourceName().toString(); + assertTrue( + listenerResource.contains("Consumed from") && listenerResource.contains("Queue"), + "Listener span resource should contain 'Consumed from Queue', got: " + listenerResource); + assertEquals("jms", String.valueOf(listenerSpan.getTag("component"))); + String listenerKind = String.valueOf(listenerSpan.getTag("span.kind")); + assertTrue( + "consumer".equals(listenerKind) || "broker".equals(listenerKind), + "Listener span kind should be 'consumer' or 'broker', got: " + listenerKind); + // Messaging semantic convention tags for listener (process operation) + assertEquals( + "jms", + listenerSpan.getTag("messaging.system"), + "Listener span should have messaging.system=jms"); + assertEquals( + "process", + listenerSpan.getTag("messaging.operation"), + "MessageListener.onMessage span should have messaging.operation=process"); + assertFalse(listenerSpan.isError(), "Listener span should not be errored"); + // Linked to producer via context propagation + assertEquals( + producerSpan.getTraceId(), + listenerSpan.getTraceId(), + "Listener span should share trace ID with producer"); + } + + @Test + void messageListenerErrorIsRecordedOnSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + FailingMessageListener listener = new FailingMessageListener(latch); + consumer.setMessageListener(listener); + + TextMessage sentMessage = session.createTextMessage("error listener test"); + producer.send(sentMessage); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Failing listener should process the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + + // Find the listener span — it should be errored + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull(listenerSpan, "Expected a consumer/deliver span for failing listener"); + assertTrue(listenerSpan.isError(), "Listener span should be marked as errored"); + assertNotNull( + listenerSpan.getTag("error.message"), "Errored span should have error.message tag"); + assertNotNull(listenerSpan.getTag("error.type"), "Errored span should have error.type tag"); + assertNotNull(listenerSpan.getTag("error.stack"), "Errored span should have error.stack tag"); + } + + // ===================== Context Propagation Tests ===================== + + @Test + void producerSpanIsChildOfActiveSpan() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("child span test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertEquals( + parentSpan.getSpanId(), + producerSpan.getParentId(), + "Producer span should be a child of the active parent span"); + assertEquals( + parentSpan.context().getTraceId(), + producerSpan.getTraceId(), + "Producer and parent should share trace ID"); + } + + @Test + void distributedContextPropagatesThroughQueueSendAndReceive() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + // Send under a parent span to verify full trace linkage + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("distributed context test"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive message with distributed context"); + assertEquals("distributed context test", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify the full trace chain: parent -> producer -> consumer + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (distributed context)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + @Test + void distributedContextPropagatesThroughTopicSendAndReceive() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-ctx-prop"); + MessageProducer producer = session.createProducer(topic); + MessageConsumer consumer = session.createConsumer(topic); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("topic context propagation"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive topic message with distributed context"); + assertEquals("topic context propagation", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify producer span structure + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + String producerResource = producerSpan.getResourceName().toString(); + assertTrue( + producerResource.contains("Produced for") && producerResource.contains("Topic"), + "Producer resource should contain 'Produced for Topic', got: " + producerResource); + + // Verify consumer span structure + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Topic"), + "Consumer resource should contain 'Consumed from Topic', got: " + consumerResource); + + // Verify the full trace chain: parent -> producer -> consumer + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (distributed context via topic)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + @Test + void distributedContextPropagatesThroughExplicitDestinationSend() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + // Create producer without default destination (explicit destination on send) + MessageProducer producer = session.createProducer(null); + MessageConsumer consumer = session.createConsumer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("explicit dest context propagation"); + producer.send(queue, message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should receive message sent with explicit destination"); + assertEquals("explicit dest context propagation", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify producer span structure for explicit destination send + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + + // Verify consumer span structure + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + + // Verify context propagated even with explicit destination send + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (explicit destination send)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + @Test + void distributedContextPropagatesThroughMessageListener() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + CountDownLatch latch = new CountDownLatch(1); + TestMessageListener listener = new TestMessageListener(latch); + consumer.setMessageListener(listener); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("listener context propagation"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Listener should receive the message"); + assertNotNull(listener.receivedMessage, "Listener should have captured the message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + + // Verify producer span structure + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + + // Find consumer/deliver span created by MessageListener + DDSpan listenerSpan = findConsumerOrDeliverSpan(allSpans); + assertNotNull(listenerSpan, "Expected a consumer or deliver span from MessageListener"); + + // Verify listener span structure + assertEquals("queue", listenerSpan.getSpanType()); + assertEquals("jms", String.valueOf(listenerSpan.getTag("component"))); + String listenerKind = String.valueOf(listenerSpan.getTag("span.kind")); + assertTrue( + "consumer".equals(listenerKind) || "broker".equals(listenerKind), + "Listener span kind should be 'consumer' or 'broker', got: " + listenerKind); + assertFalse(listenerSpan.isError(), "Listener span should not be errored"); + + // Producer should be child of parent + assertEquals( + parentSpan.getSpanId(), producerSpan.getParentId(), "Producer should be child of parent"); + + // All spans in same trace — verifies context propagated through message properties + assertEquals( + parentSpan.context().getTraceId(), + producerSpan.getTraceId(), + "Producer should share trace ID with parent"); + assertEquals( + parentSpan.context().getTraceId(), + listenerSpan.getTraceId(), + "Listener span should share trace ID with parent (distributed context via listener)"); + } + + @Test + void distributedContextPropagatesThroughReceiveNoWait() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + TextMessage message = session.createTextMessage("receiveNoWait context propagation"); + producer.send(message); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + // Allow time for message to arrive at broker + Thread.sleep(500); + + TextMessage receivedMessage = (TextMessage) consumer.receiveNoWait(); + assertNotNull(receivedMessage, "Should receive message with receiveNoWait"); + assertEquals("receiveNoWait context propagation", receivedMessage.getText()); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertNotNull(consumerSpan, "Expected a jms.consume span"); + + // Verify producer span structure + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + + // Verify consumer span structure + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + String consumerResource = consumerSpan.getResourceName().toString(); + assertTrue( + consumerResource.contains("Consumed from") && consumerResource.contains("Queue"), + "Consumer resource should contain 'Consumed from Queue', got: " + consumerResource); + + // Verify context propagated through receiveNoWait + assertEquals( + producerSpan.getSpanId(), + consumerSpan.getParentId(), + "Consumer should be child of producer (context propagation via receiveNoWait)"); + assertEquals( + parentSpan.context().getTraceId(), + consumerSpan.getTraceId(), + "All spans should share the same trace ID"); + } + + // ===================== Error Handling Tests ===================== + + @Test + void sendOnClosedProducerRecordsError() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + producer.close(); + + JMSException caughtException = null; + try { + TextMessage message = session.createTextMessage("error test"); + producer.send(message); + } catch (JMSException e) { + caughtException = e; + } + + assertNotNull(caughtException, "Sending on closed producer should throw JMSException"); + + // Allow time for any spans to complete + Thread.sleep(500); + + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + // If the instrumentation creates a span around the failed send, it should be errored + if (producerSpan != null) { + assertTrue(producerSpan.isError(), "Producer span on failed send should be errored"); + assertNotNull( + producerSpan.getTag("error.type"), "Errored producer span should have error.type tag"); + assertNotNull( + producerSpan.getTag("error.message"), + "Errored producer span should have error.message tag"); + } + } + + // ===================== Peer Service / Messaging Destination Tests ===================== + + @Test + void producerSpanHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + TextMessage message = session.createTextMessage("peer service test"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + assertTrue(producerSpan.isMeasured(), "Producer span should be measured"); + + // Verify messaging.destination.name tag is set for peer service computation + Object destinationTag = producerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, + "Producer span should have 'messaging.destination.name' tag for peer service"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains(queue.getQueueName()), + "messaging.destination.name should contain the queue name '" + + queue.getQueueName() + + "', got: " + + destinationName); + } + + @Test + void producerSpanToTopicHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Topic topic = session.createTopic("test-topic-peer-svc"); + MessageProducer producer = session.createProducer(topic); + TextMessage message = session.createTextMessage("topic peer service test"); + + producer.send(message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for topic"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + + // Verify messaging.destination.name tag is set for topic + Object destinationTag = producerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, "Producer span should have 'messaging.destination.name' tag for topic"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains("test-topic-peer-svc"), + "messaging.destination.name should contain the topic name, got: " + destinationName); + } + + @Test + void explicitDestinationSendHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(null); + TextMessage message = session.createTextMessage("explicit dest peer service"); + + producer.send(queue, message); + + producer.close(); + + writer.waitForTraces(1); + List allSpans = flattenTraces(); + DDSpan producerSpan = findSpanByOperationAndKind(allSpans, "jms.produce", "producer"); + + assertNotNull(producerSpan, "Expected a jms.produce span for explicit destination"); + assertEquals("jms.produce", producerSpan.getOperationName().toString()); + assertEquals("queue", producerSpan.getSpanType()); + assertEquals("jms", String.valueOf(producerSpan.getTag("component"))); + assertEquals("producer", String.valueOf(producerSpan.getTag("span.kind"))); + + // Verify messaging.destination.name tag is set for explicit destination send + Object destinationTag = producerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, + "Producer span should have 'messaging.destination.name' for explicit destination send"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains(queue.getQueueName()), + "messaging.destination.name should contain the queue name, got: " + destinationName); + } + + @Test + void consumerSpanHasMessagingDestinationNameTag() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = session.createConsumer(queue); + + TextMessage sentMessage = session.createTextMessage("consumer peer service test"); + producer.send(sentMessage); + + TextMessage receivedMessage = (TextMessage) consumer.receive(5000); + assertNotNull(receivedMessage, "Should have received a message"); + + producer.close(); + consumer.close(); + + writer.waitForTraces(2); + List allSpans = flattenTraces(); + DDSpan consumerSpan = findSpanByOperationAndKind(allSpans, "jms.consume", "consumer"); + + assertNotNull(consumerSpan, "Expected a jms.consume span"); + assertEquals("jms.consume", consumerSpan.getOperationName().toString()); + assertEquals("queue", consumerSpan.getSpanType()); + assertEquals("jms", String.valueOf(consumerSpan.getTag("component"))); + assertEquals("consumer", String.valueOf(consumerSpan.getTag("span.kind"))); + assertTrue(consumerSpan.isMeasured(), "Consumer span should be measured"); + + // Verify messaging.destination.name tag is set on consumer span + Object destinationTag = consumerSpan.getTag("messaging.destination.name"); + assertNotNull( + destinationTag, + "Consumer span should have 'messaging.destination.name' tag for peer service"); + String destinationName = String.valueOf(destinationTag); + assertTrue( + destinationName.contains(queue.getQueueName()), + "messaging.destination.name should contain the queue name, got: " + destinationName); + } + + // ===================== Transacted Session Tests ===================== + + @Test + void receiveInTransactedSessionCreatesConsumerSpanOnCommit() + throws JMSException, InterruptedException, TimeoutException { + Queue queue = createUniqueQueue(); + Session transactedSession = connection.createSession(true, Session.SESSION_TRANSACTED); + MessageProducer producer = session.createProducer(queue); + MessageConsumer consumer = transactedSession.createConsumer(queue); + + producer.send(session.createTextMessage("transacted msg1")); + producer.send(session.createTextMessage("transacted msg2")); + + TextMessage recv1 = (TextMessage) consumer.receive(5000); + TextMessage recv2 = (TextMessage) consumer.receive(5000); + transactedSession.commit(); + + assertNotNull(recv1, "Should receive first transacted message"); + assertNotNull(recv2, "Should receive second transacted message"); + assertEquals("transacted msg1", recv1.getText()); + assertEquals("transacted msg2", recv2.getText()); + + producer.close(); + consumer.close(); + transactedSession.close(); + + writer.waitForTraces(4); + List allSpans = flattenTraces(); + + List producerSpans = findAllSpansByOperation(allSpans, "jms.produce"); + List consumerSpans = findAllSpansByOperation(allSpans, "jms.consume"); + + assertEquals(2, producerSpans.size(), "Expected 2 producer spans"); + assertEquals(2, consumerSpans.size(), "Expected 2 consumer spans after commit"); + + for (DDSpan cSpan : consumerSpans) { + assertEquals("consumer", String.valueOf(cSpan.getTag("span.kind"))); + assertEquals("jms", String.valueOf(cSpan.getTag("component"))); + assertFalse(cSpan.isError()); + } + } + + // ===================== Helper Methods ===================== + + private List flattenTraces() { + List result = new ArrayList<>(); + for (List trace : writer) { + result.addAll(trace); + } + return result; + } + + private DDSpan findSpanByOperationAndKind( + List spans, String operationName, String spanKind) { + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName) + && spanKind.equals(String.valueOf(span.getTag("span.kind")))) { + return span; + } + } + return null; + } + + private List findAllSpansByOperation(List spans, String operationName) { + List result = new ArrayList<>(); + for (DDSpan span : spans) { + if (span.getOperationName().toString().equals(operationName)) { + result.add(span); + } + } + return result; + } + + /** Finds the consumer or deliver span created by MessageListener instrumentation. */ + private DDSpan findConsumerOrDeliverSpan(List spans) { + for (DDSpan span : spans) { + String opName = span.getOperationName().toString(); + if ("jms.consume".equals(opName) || "jms.deliver".equals(opName)) { + return span; + } + } + return null; + } + + /** Simple MessageListener that captures the received message. */ + static class TestMessageListener implements MessageListener { + volatile Message receivedMessage; + private final CountDownLatch latch; + + TestMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + receivedMessage = message; + latch.countDown(); + } + } + + /** MessageListener that throws a RuntimeException to test error recording. */ + static class FailingMessageListener implements MessageListener { + private final CountDownLatch latch; + + FailingMessageListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onMessage(Message message) { + latch.countDown(); + throw new RuntimeException("Intentional error in message listener"); + } + } +}