Convert JMS Groovy tests to Java (#9476)
This commit is contained in:
parent
27a14e12a8
commit
331aa04e35
|
@ -1,317 +0,0 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
|
||||
import io.opentelemetry.sdk.trace.data.SpanData
|
||||
import io.opentelemetry.semconv.SemanticAttributes
|
||||
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.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.nio.file.Files
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CONSUMER
|
||||
import static io.opentelemetry.api.trace.SpanKind.PRODUCER
|
||||
|
||||
class Jms2Test extends AgentInstrumentationSpecification {
|
||||
@Shared
|
||||
HornetQServer server
|
||||
@Shared
|
||||
String messageText = "a message"
|
||||
@Shared
|
||||
Session session
|
||||
|
||||
HornetQTextMessage message = session.createTextMessage(messageText)
|
||||
|
||||
def setupSpec() {
|
||||
def tempDir = Files.createTempDirectory("jmsTempDir").toFile()
|
||||
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(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 #destinationName generates spans"() {
|
||||
setup:
|
||||
def producer = session.createProducer(destination)
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
runWithSpan("producer parent") {
|
||||
producer.send(message)
|
||||
}
|
||||
|
||||
TextMessage receivedMessage = runWithSpan("consumer parent") {
|
||||
return consumer.receive() as TextMessage
|
||||
}
|
||||
String messageId = receivedMessage.getJMSMessageID()
|
||||
|
||||
expect:
|
||||
receivedMessage.text == messageText
|
||||
assertTraces(2) {
|
||||
SpanData producerSpanData
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
name "producer parent"
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, destinationName, span(0))
|
||||
|
||||
producerSpanData = span(1)
|
||||
}
|
||||
trace(1, 2) {
|
||||
span(0) {
|
||||
name "consumer parent"
|
||||
hasNoParent()
|
||||
}
|
||||
consumerSpan(it, 1, destinationName, messageId, "receive", span(0), producerSpanData)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
session.createTemporaryQueue() | "(temporary)"
|
||||
session.createTemporaryTopic() | "(temporary)"
|
||||
}
|
||||
|
||||
def "sending to a MessageListener on #destinationName generates a span"() {
|
||||
setup:
|
||||
def lock = new CountDownLatch(1)
|
||||
def messageRef = new AtomicReference<TextMessage>()
|
||||
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 as TextMessage)
|
||||
}
|
||||
}
|
||||
|
||||
runWithSpan("parent") {
|
||||
producer.send(message)
|
||||
}
|
||||
lock.countDown()
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, destinationName, span(0))
|
||||
consumerSpan(it, 2, destinationName, messageRef.get().getJMSMessageID(), "process", span(1))
|
||||
}
|
||||
}
|
||||
// This check needs to go after all traces have been accounted for
|
||||
messageRef.get().text == messageText
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
session.createTemporaryQueue() | "(temporary)"
|
||||
session.createTemporaryTopic() | "(temporary)"
|
||||
}
|
||||
|
||||
def "failing to receive message with receiveNoWait on #destinationName works"() {
|
||||
setup:
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
// Receive with timeout
|
||||
Message receivedMessage = consumer.receiveNoWait()
|
||||
|
||||
expect:
|
||||
receivedMessage == null
|
||||
// span is not created if no message is received
|
||||
assertTraces(0) {}
|
||||
|
||||
cleanup:
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
}
|
||||
|
||||
def "failing to receive message with wait(timeout) on #destinationName works"() {
|
||||
setup:
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
// Receive with timeout
|
||||
Message receivedMessage = consumer.receive(100)
|
||||
|
||||
expect:
|
||||
receivedMessage == null
|
||||
// span is not created if no message is received
|
||||
assertTraces(0) {}
|
||||
|
||||
cleanup:
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
}
|
||||
|
||||
def "sending a message to #destinationName with explicit destination propagates context"() {
|
||||
given:
|
||||
def producer = session.createProducer(null)
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
def lock = new CountDownLatch(1)
|
||||
def messageRef = new AtomicReference<TextMessage>()
|
||||
consumer.setMessageListener new MessageListener() {
|
||||
@Override
|
||||
void onMessage(Message message) {
|
||||
lock.await() // ensure the producer trace is reported first.
|
||||
messageRef.set(message as TextMessage)
|
||||
}
|
||||
}
|
||||
|
||||
when:
|
||||
runWithSpan("parent") {
|
||||
producer.send(destination, message)
|
||||
}
|
||||
lock.countDown()
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, destinationName, span(0))
|
||||
consumerSpan(it, 2, destinationName, messageRef.get().getJMSMessageID(), "process", span(1))
|
||||
}
|
||||
}
|
||||
// This check needs to go after all traces have been accounted for
|
||||
messageRef.get().text == messageText
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
session.createTemporaryQueue() | "(temporary)"
|
||||
session.createTemporaryTopic() | "(temporary)"
|
||||
}
|
||||
|
||||
static producerSpan(TraceAssert trace, int index, String destinationName, SpanData parentSpan = null) {
|
||||
trace.span(index) {
|
||||
name destinationName + " publish"
|
||||
kind PRODUCER
|
||||
if (parentSpan == null) {
|
||||
hasNoParent()
|
||||
} else {
|
||||
childOf(parentSpan)
|
||||
}
|
||||
attributes {
|
||||
"$SemanticAttributes.MESSAGING_SYSTEM" "jms"
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_NAME" destinationName
|
||||
if (destinationName == "(temporary)") {
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY" true
|
||||
}
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// passing messageId = null will verify message.id is not captured,
|
||||
// passing messageId = "" will verify message.id is captured (but won't verify anything about the value),
|
||||
// any other value for messageId will verify that message.id is captured and has that same value
|
||||
static consumerSpan(TraceAssert trace, int index, String destinationName, String messageId, String operation, SpanData parentSpan, SpanData linkedSpan = null) {
|
||||
trace.span(index) {
|
||||
name destinationName + " " + operation
|
||||
kind CONSUMER
|
||||
if (parentSpan == null) {
|
||||
hasNoParent()
|
||||
} else {
|
||||
childOf(parentSpan)
|
||||
}
|
||||
if (linkedSpan == null) {
|
||||
hasNoLinks()
|
||||
} else {
|
||||
hasLink(linkedSpan)
|
||||
}
|
||||
attributes {
|
||||
"$SemanticAttributes.MESSAGING_SYSTEM" "jms"
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_NAME" destinationName
|
||||
"$SemanticAttributes.MESSAGING_OPERATION" operation
|
||||
if (messageId != null) {
|
||||
//In some tests we don't know exact messageId, so we pass "" and verify just the existence of the attribute
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" { it == messageId || messageId == "" }
|
||||
}
|
||||
if (destinationName == "(temporary)") {
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY" true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CONSUMER;
|
||||
import static io.opentelemetry.api.trace.SpanKind.PRODUCER;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
|
||||
import io.opentelemetry.sdk.trace.data.LinkData;
|
||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||
import io.opentelemetry.semconv.SemanticAttributes;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TextMessage;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.hornetq.api.core.TransportConfiguration;
|
||||
import org.hornetq.api.core.client.ClientSession;
|
||||
import org.hornetq.api.core.client.ClientSessionFactory;
|
||||
import org.hornetq.api.core.client.HornetQClient;
|
||||
import org.hornetq.api.core.client.ServerLocator;
|
||||
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.server.HornetQServer;
|
||||
import org.hornetq.core.server.HornetQServers;
|
||||
import org.hornetq.jms.client.HornetQConnectionFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
public class Jms2InstrumentationTest {
|
||||
|
||||
@RegisterExtension
|
||||
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||
|
||||
@RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create();
|
||||
|
||||
static HornetQServer server;
|
||||
static HornetQConnectionFactory connectionFactory;
|
||||
static Session session;
|
||||
static Connection connection;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
File tempDir = Files.createTempDirectory("jmsTempDir").toFile();
|
||||
tempDir.deleteOnExit();
|
||||
|
||||
Configuration config = new ConfigurationImpl();
|
||||
config.setBindingsDirectory(tempDir.getPath());
|
||||
config.setJournalDirectory(tempDir.getPath());
|
||||
config.setCreateBindingsDir(false);
|
||||
config.setCreateJournalDir(false);
|
||||
config.setSecurityEnabled(false);
|
||||
config.setPersistenceEnabled(false);
|
||||
config.setQueueConfigurations(
|
||||
Collections.singletonList(
|
||||
new CoreQueueConfiguration("someQueue", "someQueue", null, true)));
|
||||
config.setAcceptorConfigurations(
|
||||
new HashSet<>(
|
||||
Collections.singletonList(
|
||||
new TransportConfiguration(InVMAcceptorFactory.class.getName()))));
|
||||
|
||||
server = HornetQServers.newHornetQServer(config);
|
||||
server.start();
|
||||
|
||||
ServerLocator serverLocator =
|
||||
HornetQClient.createServerLocatorWithoutHA(
|
||||
new TransportConfiguration(InVMConnectorFactory.class.getName()));
|
||||
ClientSessionFactory sf = serverLocator.createSessionFactory();
|
||||
ClientSession 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();
|
||||
|
||||
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
|
||||
static void tearDown() throws Exception {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
if (connectionFactory != null) {
|
||||
connectionFactory.close();
|
||||
}
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ArgumentsSource(DestinationsProvider.class)
|
||||
@ParameterizedTest
|
||||
void testMessageConsumer(
|
||||
DestinationFactory destinationFactory, String destinationName, boolean isTemporary)
|
||||
throws JMSException {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
TextMessage sentMessage = session.createTextMessage("a message");
|
||||
|
||||
MessageProducer producer = session.createProducer(destination);
|
||||
cleanup.deferCleanup(producer);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer);
|
||||
|
||||
// when
|
||||
testing.runWithSpan("producer parent", () -> producer.send(sentMessage));
|
||||
|
||||
TextMessage receivedMessage =
|
||||
testing.runWithSpan("consumer parent", () -> (TextMessage) consumer.receive());
|
||||
|
||||
// then
|
||||
assertThat(receivedMessage.getText()).isEqualTo(sentMessage.getText());
|
||||
|
||||
String messageId = receivedMessage.getJMSMessageID();
|
||||
|
||||
AtomicReference<SpanData> producerSpan = new AtomicReference<>();
|
||||
testing.waitAndAssertTraces(
|
||||
trace -> {
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("producer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " publish")
|
||||
.hasKind(PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary)));
|
||||
|
||||
producerSpan.set(trace.getSpan(1));
|
||||
},
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("consumer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " receive")
|
||||
.hasKind(CONSUMER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasLinks(LinkData.create(producerSpan.get().getSpanContext()))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary))));
|
||||
}
|
||||
|
||||
@ArgumentsSource(DestinationsProvider.class)
|
||||
@ParameterizedTest
|
||||
void testMessageListener(
|
||||
DestinationFactory destinationFactory, String destinationName, boolean isTemporary)
|
||||
throws Exception {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
TextMessage sentMessage = session.createTextMessage("a message");
|
||||
|
||||
MessageProducer producer = session.createProducer(null);
|
||||
cleanup.deferCleanup(producer);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer);
|
||||
|
||||
CompletableFuture<TextMessage> receivedMessageFuture = new CompletableFuture<>();
|
||||
consumer.setMessageListener(
|
||||
message ->
|
||||
testing.runWithSpan(
|
||||
"consumer", () -> receivedMessageFuture.complete((TextMessage) message)));
|
||||
|
||||
// when
|
||||
testing.runWithSpan("producer parent", () -> producer.send(destination, sentMessage));
|
||||
|
||||
// then
|
||||
TextMessage receivedMessage = receivedMessageFuture.get(10, TimeUnit.SECONDS);
|
||||
assertThat(receivedMessage.getText()).isEqualTo(sentMessage.getText());
|
||||
|
||||
String messageId = receivedMessage.getJMSMessageID();
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("producer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " publish")
|
||||
.hasKind(PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary)),
|
||||
span ->
|
||||
span.hasName(destinationName + " process")
|
||||
.hasKind(CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary)),
|
||||
span -> span.hasName("consumer").hasParent(trace.getSpan(2))));
|
||||
}
|
||||
|
||||
@ArgumentsSource(EmptyReceiveArgumentsProvider.class)
|
||||
@ParameterizedTest
|
||||
void shouldNotEmitTelemetryOnEmptyReceive(
|
||||
DestinationFactory destinationFactory, MessageReceiver receiver) throws JMSException {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer);
|
||||
|
||||
// when
|
||||
Message message = receiver.receive(consumer);
|
||||
|
||||
// then
|
||||
assertThat(message).isNull();
|
||||
|
||||
testing.waitForTraces(0);
|
||||
}
|
||||
|
||||
private static AttributeAssertion messagingTempDestination(boolean isTemporary) {
|
||||
return isTemporary
|
||||
? equalTo(SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY, true)
|
||||
: satisfies(SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY, AbstractAssert::isNull);
|
||||
}
|
||||
|
||||
static final class EmptyReceiveArgumentsProvider implements ArgumentsProvider {
|
||||
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||
DestinationFactory topic = session -> session.createTopic("someTopic");
|
||||
DestinationFactory queue = session -> session.createQueue("someQueue");
|
||||
MessageReceiver receive = consumer -> consumer.receive(100);
|
||||
MessageReceiver receiveNoWait = MessageConsumer::receiveNoWait;
|
||||
|
||||
return Stream.of(
|
||||
arguments(topic, receive),
|
||||
arguments(queue, receive),
|
||||
arguments(topic, receiveNoWait),
|
||||
arguments(queue, receiveNoWait));
|
||||
}
|
||||
}
|
||||
|
||||
static final class DestinationsProvider implements ArgumentsProvider {
|
||||
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||
DestinationFactory topic = session -> session.createTopic("someTopic");
|
||||
DestinationFactory queue = session -> session.createQueue("someQueue");
|
||||
DestinationFactory tempTopic = Session::createTemporaryTopic;
|
||||
DestinationFactory tempQueue = Session::createTemporaryQueue;
|
||||
|
||||
return Stream.of(
|
||||
arguments(topic, "someTopic", false),
|
||||
arguments(queue, "someQueue", false),
|
||||
arguments(tempTopic, "(temporary)", true),
|
||||
arguments(tempQueue, "(temporary)", true));
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface DestinationFactory {
|
||||
|
||||
Destination create(Session session) throws JMSException;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface MessageReceiver {
|
||||
|
||||
Message receive(MessageConsumer consumer) throws JMSException;
|
||||
}
|
||||
}
|
|
@ -1,382 +0,0 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
|
||||
import io.opentelemetry.sdk.trace.data.SpanData
|
||||
import io.opentelemetry.semconv.SemanticAttributes
|
||||
import org.apache.activemq.ActiveMQConnectionFactory
|
||||
import org.apache.activemq.command.ActiveMQTextMessage
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.testcontainers.containers.GenericContainer
|
||||
import org.testcontainers.containers.output.Slf4jLogConsumer
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Unroll
|
||||
|
||||
import javax.jms.Connection
|
||||
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
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CONSUMER
|
||||
import static io.opentelemetry.api.trace.SpanKind.PRODUCER
|
||||
|
||||
@Unroll
|
||||
class Jms1Test extends AgentInstrumentationSpecification {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Jms1Test)
|
||||
|
||||
private static final GenericContainer broker = new GenericContainer("rmohr/activemq:latest")
|
||||
.withExposedPorts(61616, 8161)
|
||||
.withLogConsumer(new Slf4jLogConsumer(logger))
|
||||
|
||||
@Shared
|
||||
String messageText = "a message"
|
||||
@Shared
|
||||
Session session
|
||||
|
||||
ActiveMQTextMessage message = session.createTextMessage(messageText)
|
||||
|
||||
def setupSpec() {
|
||||
broker.start()
|
||||
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:" + broker.getMappedPort(61616))
|
||||
|
||||
Connection connection = connectionFactory.createConnection()
|
||||
connection.start()
|
||||
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
broker.stop()
|
||||
}
|
||||
|
||||
def "sending a message to #destinationName generates spans"() {
|
||||
setup:
|
||||
def producer = session.createProducer(destination)
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
runWithSpan("producer parent") {
|
||||
producer.send(message)
|
||||
}
|
||||
|
||||
TextMessage receivedMessage = runWithSpan("consumer parent") {
|
||||
return consumer.receive() as TextMessage
|
||||
}
|
||||
String messageId = receivedMessage.getJMSMessageID()
|
||||
|
||||
expect:
|
||||
receivedMessage.text == messageText
|
||||
assertTraces(2) {
|
||||
SpanData producerSpanData
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
name "producer parent"
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, destinationName, span(0))
|
||||
|
||||
producerSpanData = span(1)
|
||||
}
|
||||
trace(1, 2) {
|
||||
span(0) {
|
||||
name "consumer parent"
|
||||
hasNoParent()
|
||||
}
|
||||
consumerSpan(it, 1, destinationName, messageId, "receive", span(0), producerSpanData)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
session.createTemporaryQueue() | "(temporary)"
|
||||
session.createTemporaryTopic() | "(temporary)"
|
||||
}
|
||||
|
||||
def "sending to a MessageListener on #destinationName generates a span"() {
|
||||
setup:
|
||||
def lock = new CountDownLatch(1)
|
||||
def messageRef = new AtomicReference<TextMessage>()
|
||||
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 as TextMessage)
|
||||
}
|
||||
}
|
||||
|
||||
producer.send(message)
|
||||
lock.countDown()
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
producerSpan(it, 0, destinationName)
|
||||
consumerSpan(it, 1, destinationName, messageRef.get().getJMSMessageID(), "process", span(0))
|
||||
}
|
||||
}
|
||||
// This check needs to go after all traces have been accounted for
|
||||
messageRef.get().text == messageText
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
session.createTemporaryQueue() | "(temporary)"
|
||||
session.createTemporaryTopic() | "(temporary)"
|
||||
}
|
||||
|
||||
def "failing to receive message with receiveNoWait on #destinationName works"() {
|
||||
setup:
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
// Receive with timeout
|
||||
Message receivedMessage = consumer.receiveNoWait()
|
||||
|
||||
expect:
|
||||
receivedMessage == null
|
||||
// span is not created if no message is received
|
||||
assertTraces(0) {}
|
||||
|
||||
cleanup:
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
}
|
||||
|
||||
def "failing to receive message with wait(timeout) on #destinationName works"() {
|
||||
setup:
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
// Receive with timeout
|
||||
Message receivedMessage = consumer.receive(100)
|
||||
|
||||
expect:
|
||||
receivedMessage == null
|
||||
// span is not created if no message is received
|
||||
assertTraces(0) {}
|
||||
|
||||
cleanup:
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
}
|
||||
|
||||
def "sending a read-only message to #destinationName fails"() {
|
||||
setup:
|
||||
def producer = session.createProducer(destination)
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
expect:
|
||||
!message.isReadOnlyProperties()
|
||||
|
||||
when:
|
||||
message.setReadOnlyProperties(true)
|
||||
and:
|
||||
producer.send(message)
|
||||
|
||||
TextMessage receivedMessage = consumer.receive() as TextMessage
|
||||
|
||||
then:
|
||||
receivedMessage.text == messageText
|
||||
|
||||
// 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) {
|
||||
trace(0, 1) {
|
||||
producerSpan(it, 0, destinationName)
|
||||
}
|
||||
trace(1, 1) {
|
||||
consumerSpan(it, 0, destinationName, "", "receive", null)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
session.createTemporaryQueue() | "(temporary)"
|
||||
session.createTemporaryTopic() | "(temporary)"
|
||||
}
|
||||
|
||||
def "sending a message to #destinationName with explicit destination propagates context"() {
|
||||
given:
|
||||
def producer = session.createProducer(null)
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
def lock = new CountDownLatch(1)
|
||||
def messageRef = new AtomicReference<TextMessage>()
|
||||
consumer.setMessageListener new MessageListener() {
|
||||
@Override
|
||||
void onMessage(Message message) {
|
||||
lock.await() // ensure the producer trace is reported first.
|
||||
messageRef.set(message as TextMessage)
|
||||
}
|
||||
}
|
||||
|
||||
when:
|
||||
runWithSpan("parent") {
|
||||
producer.send(destination, message)
|
||||
}
|
||||
lock.countDown()
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, destinationName, span(0))
|
||||
consumerSpan(it, 2, destinationName, messageRef.get().getJMSMessageID(), "process", span(1))
|
||||
}
|
||||
}
|
||||
// This check needs to go after all traces have been accounted for
|
||||
messageRef.get().text == messageText
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
|
||||
where:
|
||||
destination | destinationName
|
||||
session.createQueue("someQueue") | "someQueue"
|
||||
session.createTopic("someTopic") | "someTopic"
|
||||
session.createTemporaryQueue() | "(temporary)"
|
||||
session.createTemporaryTopic() | "(temporary)"
|
||||
}
|
||||
|
||||
def "capture message header as span attribute"() {
|
||||
setup:
|
||||
def destinationName = "someQueue"
|
||||
def destination = session.createQueue(destinationName)
|
||||
def producer = session.createProducer(destination)
|
||||
def consumer = session.createConsumer(destination)
|
||||
|
||||
def message = session.createTextMessage(messageText)
|
||||
message.setStringProperty("test-message-header", "test")
|
||||
message.setIntProperty("test-message-int-header", 1234)
|
||||
runWithSpan("producer parent") {
|
||||
producer.send(message)
|
||||
}
|
||||
|
||||
TextMessage receivedMessage = runWithSpan("consumer parent") {
|
||||
return consumer.receive() as TextMessage
|
||||
}
|
||||
String messageId = receivedMessage.getJMSMessageID()
|
||||
|
||||
expect:
|
||||
receivedMessage.text == messageText
|
||||
assertTraces(2) {
|
||||
SpanData producerSpanData
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
name "producer parent"
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, destinationName, span(0), true)
|
||||
|
||||
producerSpanData = span(1)
|
||||
}
|
||||
trace(1, 2) {
|
||||
span(0) {
|
||||
name "consumer parent"
|
||||
hasNoParent()
|
||||
}
|
||||
consumerSpan(it, 1, destinationName, messageId, "receive", span(0), producerSpanData, true)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
producer.close()
|
||||
consumer.close()
|
||||
}
|
||||
|
||||
static producerSpan(TraceAssert trace, int index, String destinationName, SpanData parentSpan = null, boolean testHeaders = false) {
|
||||
trace.span(index) {
|
||||
name destinationName + " publish"
|
||||
kind PRODUCER
|
||||
if (parentSpan == null) {
|
||||
hasNoParent()
|
||||
} else {
|
||||
childOf(parentSpan)
|
||||
}
|
||||
attributes {
|
||||
"$SemanticAttributes.MESSAGING_SYSTEM" "jms"
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_NAME" destinationName
|
||||
if (destinationName == "(temporary)") {
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY" true
|
||||
}
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" String
|
||||
if (testHeaders) {
|
||||
"messaging.header.test_message_header" { it == ["test"] }
|
||||
"messaging.header.test_message_int_header" { it == ["1234"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// passing messageId = null will verify message.id is not captured,
|
||||
// passing messageId = "" will verify message.id is captured (but won't verify anything about the value),
|
||||
// any other value for messageId will verify that message.id is captured and has that same value
|
||||
static consumerSpan(TraceAssert trace, int index, String destinationName, String messageId, String operation, SpanData parentSpan, SpanData linkedSpan = null, boolean testHeaders = false) {
|
||||
trace.span(index) {
|
||||
name destinationName + " " + operation
|
||||
kind CONSUMER
|
||||
if (parentSpan == null) {
|
||||
hasNoParent()
|
||||
} else {
|
||||
childOf(parentSpan)
|
||||
}
|
||||
if (linkedSpan == null) {
|
||||
hasNoLinks()
|
||||
} else {
|
||||
hasLink(linkedSpan)
|
||||
}
|
||||
attributes {
|
||||
"$SemanticAttributes.MESSAGING_SYSTEM" "jms"
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_NAME" destinationName
|
||||
"$SemanticAttributes.MESSAGING_OPERATION" operation
|
||||
if (messageId != null) {
|
||||
//In some tests we don't know exact messageId, so we pass "" and verify just the existence of the attribute
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" { it == messageId || messageId == "" }
|
||||
}
|
||||
if (destinationName == "(temporary)") {
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY" true
|
||||
}
|
||||
if (testHeaders) {
|
||||
"messaging.header.test_message_header" { it == ["test"] }
|
||||
"messaging.header.test_message_int_header" { it == ["1234"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
|
||||
|
||||
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
|
||||
import static io.opentelemetry.api.trace.SpanKind.CONSUMER;
|
||||
import static io.opentelemetry.api.trace.SpanKind.PRODUCER;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
|
||||
import io.opentelemetry.sdk.trace.data.LinkData;
|
||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||
import io.opentelemetry.semconv.SemanticAttributes;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TextMessage;
|
||||
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||
import org.apache.activemq.command.ActiveMQTextMessage;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.output.Slf4jLogConsumer;
|
||||
|
||||
public class Jms1InstrumentationTest {
|
||||
|
||||
static final Logger logger = LoggerFactory.getLogger(Jms1InstrumentationTest.class);
|
||||
|
||||
@RegisterExtension
|
||||
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||
|
||||
@RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create();
|
||||
|
||||
static GenericContainer<?> broker;
|
||||
static ActiveMQConnectionFactory connectionFactory;
|
||||
static Connection connection;
|
||||
static Session session;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws JMSException {
|
||||
broker =
|
||||
new GenericContainer<>("rmohr/activemq:latest")
|
||||
.withExposedPorts(61616, 8161)
|
||||
.withLogConsumer(new Slf4jLogConsumer(logger));
|
||||
broker.start();
|
||||
|
||||
connectionFactory =
|
||||
new ActiveMQConnectionFactory("tcp://localhost:" + broker.getMappedPort(61616));
|
||||
Connection connection = connectionFactory.createConnection();
|
||||
connection.start();
|
||||
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() throws JMSException {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
if (broker != null) {
|
||||
broker.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ArgumentsSource(DestinationsProvider.class)
|
||||
@ParameterizedTest
|
||||
void testMessageConsumer(
|
||||
DestinationFactory destinationFactory, String destinationName, boolean isTemporary)
|
||||
throws JMSException {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
TextMessage sentMessage = session.createTextMessage("a message");
|
||||
|
||||
MessageProducer producer = session.createProducer(destination);
|
||||
cleanup.deferCleanup(producer::close);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer::close);
|
||||
|
||||
// when
|
||||
testing.runWithSpan("producer parent", () -> producer.send(sentMessage));
|
||||
|
||||
TextMessage receivedMessage =
|
||||
testing.runWithSpan("consumer parent", () -> (TextMessage) consumer.receive());
|
||||
|
||||
// then
|
||||
assertThat(receivedMessage.getText()).isEqualTo(sentMessage.getText());
|
||||
|
||||
String messageId = receivedMessage.getJMSMessageID();
|
||||
|
||||
AtomicReference<SpanData> producerSpan = new AtomicReference<>();
|
||||
testing.waitAndAssertTraces(
|
||||
trace -> {
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("producer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " publish")
|
||||
.hasKind(PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary)));
|
||||
|
||||
producerSpan.set(trace.getSpan(1));
|
||||
},
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("consumer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " receive")
|
||||
.hasKind(CONSUMER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasLinks(LinkData.create(producerSpan.get().getSpanContext()))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary))));
|
||||
}
|
||||
|
||||
@ArgumentsSource(DestinationsProvider.class)
|
||||
@ParameterizedTest
|
||||
void testMessageListener(
|
||||
DestinationFactory destinationFactory, String destinationName, boolean isTemporary)
|
||||
throws Exception {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
TextMessage sentMessage = session.createTextMessage("a message");
|
||||
|
||||
MessageProducer producer = session.createProducer(null);
|
||||
cleanup.deferCleanup(producer::close);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer::close);
|
||||
|
||||
CompletableFuture<TextMessage> receivedMessageFuture = new CompletableFuture<>();
|
||||
consumer.setMessageListener(
|
||||
message ->
|
||||
testing.runWithSpan(
|
||||
"consumer", () -> receivedMessageFuture.complete((TextMessage) message)));
|
||||
|
||||
// when
|
||||
testing.runWithSpan("producer parent", () -> producer.send(destination, sentMessage));
|
||||
|
||||
// then
|
||||
TextMessage receivedMessage = receivedMessageFuture.get(10, TimeUnit.SECONDS);
|
||||
assertThat(receivedMessage.getText()).isEqualTo(sentMessage.getText());
|
||||
|
||||
String messageId = receivedMessage.getJMSMessageID();
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("producer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " publish")
|
||||
.hasKind(PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary)),
|
||||
span ->
|
||||
span.hasName(destinationName + " process")
|
||||
.hasKind(CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary)),
|
||||
span -> span.hasName("consumer").hasParent(trace.getSpan(2))));
|
||||
}
|
||||
|
||||
@ArgumentsSource(EmptyReceiveArgumentsProvider.class)
|
||||
@ParameterizedTest
|
||||
void shouldNotEmitTelemetryOnEmptyReceive(
|
||||
DestinationFactory destinationFactory, MessageReceiver receiver) throws JMSException {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer::close);
|
||||
|
||||
// when
|
||||
Message message = receiver.receive(consumer);
|
||||
|
||||
// then
|
||||
assertThat(message).isNull();
|
||||
|
||||
testing.waitForTraces(0);
|
||||
}
|
||||
|
||||
@ArgumentsSource(DestinationsProvider.class)
|
||||
@ParameterizedTest
|
||||
void shouldCaptureMessageHeaders(
|
||||
DestinationFactory destinationFactory, String destinationName, boolean isTemporary)
|
||||
throws Exception {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
TextMessage sentMessage = session.createTextMessage("a message");
|
||||
sentMessage.setStringProperty("test_message_header", "test");
|
||||
sentMessage.setIntProperty("test_message_int_header", 1234);
|
||||
|
||||
MessageProducer producer = session.createProducer(destination);
|
||||
cleanup.deferCleanup(producer::close);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer::close);
|
||||
|
||||
CompletableFuture<TextMessage> receivedMessageFuture = new CompletableFuture<>();
|
||||
consumer.setMessageListener(
|
||||
message ->
|
||||
testing.runWithSpan(
|
||||
"consumer", () -> receivedMessageFuture.complete((TextMessage) message)));
|
||||
|
||||
// when
|
||||
testing.runWithSpan("producer parent", () -> producer.send(sentMessage));
|
||||
|
||||
// then
|
||||
TextMessage receivedMessage = receivedMessageFuture.get(10, TimeUnit.SECONDS);
|
||||
assertThat(receivedMessage.getText()).isEqualTo(sentMessage.getText());
|
||||
|
||||
String messageId = receivedMessage.getJMSMessageID();
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("producer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " publish")
|
||||
.hasKind(PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary),
|
||||
equalTo(
|
||||
stringArrayKey("messaging.header.test_message_header"),
|
||||
singletonList("test")),
|
||||
equalTo(
|
||||
stringArrayKey("messaging.header.test_message_int_header"),
|
||||
singletonList("1234"))),
|
||||
span ->
|
||||
span.hasName(destinationName + " process")
|
||||
.hasKind(CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary),
|
||||
equalTo(
|
||||
stringArrayKey("messaging.header.test_message_header"),
|
||||
singletonList("test")),
|
||||
equalTo(
|
||||
stringArrayKey("messaging.header.test_message_int_header"),
|
||||
singletonList("1234"))),
|
||||
span -> span.hasName("consumer").hasParent(trace.getSpan(2))));
|
||||
}
|
||||
|
||||
@ArgumentsSource(DestinationsProvider.class)
|
||||
@ParameterizedTest
|
||||
void shouldFailWhenSendingReadOnlyMessage(
|
||||
DestinationFactory destinationFactory, String destinationName, boolean isTemporary)
|
||||
throws Exception {
|
||||
|
||||
// given
|
||||
Destination destination = destinationFactory.create(session);
|
||||
ActiveMQTextMessage sentMessage = (ActiveMQTextMessage) session.createTextMessage("a message");
|
||||
|
||||
MessageProducer producer = session.createProducer(destination);
|
||||
cleanup.deferCleanup(producer::close);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
cleanup.deferCleanup(consumer::close);
|
||||
|
||||
sentMessage.setReadOnlyProperties(true);
|
||||
|
||||
// when
|
||||
testing.runWithSpan("producer parent", () -> producer.send(sentMessage));
|
||||
|
||||
TextMessage receivedMessage = (TextMessage) consumer.receive();
|
||||
|
||||
// then
|
||||
assertThat(receivedMessage.getText()).isEqualTo(sentMessage.getText());
|
||||
|
||||
String messageId = receivedMessage.getJMSMessageID();
|
||||
|
||||
// This will result in a logged failure because we tried to
|
||||
// write properties in MessagePropertyTextMap when readOnlyProperties = true.
|
||||
// As a result, the consumer span will not be linked to the producer span as we are unable to
|
||||
// propagate the trace context as a message property.
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("producer parent").hasNoParent(),
|
||||
span ->
|
||||
span.hasName(destinationName + " publish")
|
||||
.hasKind(PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary))),
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span ->
|
||||
span.hasName(destinationName + " receive")
|
||||
.hasKind(CONSUMER)
|
||||
.hasNoParent()
|
||||
.hasTotalRecordedLinks(0)
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "jms"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
messagingTempDestination(isTemporary))));
|
||||
}
|
||||
|
||||
private static AttributeAssertion messagingTempDestination(boolean isTemporary) {
|
||||
return isTemporary
|
||||
? equalTo(SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY, true)
|
||||
: satisfies(SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY, AbstractAssert::isNull);
|
||||
}
|
||||
|
||||
static final class EmptyReceiveArgumentsProvider implements ArgumentsProvider {
|
||||
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||
DestinationFactory topic = session -> session.createTopic("someTopic");
|
||||
DestinationFactory queue = session -> session.createQueue("someQueue");
|
||||
MessageReceiver receive = consumer -> consumer.receive(100);
|
||||
MessageReceiver receiveNoWait = MessageConsumer::receiveNoWait;
|
||||
|
||||
return Stream.of(
|
||||
arguments(topic, receive),
|
||||
arguments(queue, receive),
|
||||
arguments(topic, receiveNoWait),
|
||||
arguments(queue, receiveNoWait));
|
||||
}
|
||||
}
|
||||
|
||||
static final class DestinationsProvider implements ArgumentsProvider {
|
||||
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||
DestinationFactory topic = session -> session.createTopic("someTopic");
|
||||
DestinationFactory queue = session -> session.createQueue("someQueue");
|
||||
DestinationFactory tempTopic = Session::createTemporaryTopic;
|
||||
DestinationFactory tempQueue = Session::createTemporaryQueue;
|
||||
|
||||
return Stream.of(
|
||||
arguments(topic, "someTopic", false),
|
||||
arguments(queue, "someQueue", false),
|
||||
arguments(tempTopic, "(temporary)", true),
|
||||
arguments(tempQueue, "(temporary)", true));
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface DestinationFactory {
|
||||
|
||||
Destination create(Session session) throws JMSException;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface MessageReceiver {
|
||||
|
||||
Message receive(MessageConsumer consumer) throws JMSException;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue