diff --git a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/groovy/SpringJpaTest.groovy b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/groovy/SpringJpaTest.groovy deleted file mode 100644 index 054659c38a00..000000000000 --- a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/groovy/SpringJpaTest.groovy +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import spring.jpa.JpaCustomer -import spring.jpa.JpaCustomerRepository -import spring.jpa.JpaPersistenceConfig - -class SpringJpaTest extends AbstractSpringJpaTest { - - JpaCustomer newCustomer(String firstName, String lastName) { - return new JpaCustomer(firstName, lastName) - } - - Long id(JpaCustomer customer) { - return customer.id - } - - void setFirstName(JpaCustomer customer, String firstName) { - customer.firstName = firstName - } - - Class repositoryClass() { - return JpaCustomerRepository - } - - JpaCustomerRepository repository() { - def context = new AnnotationConfigApplicationContext(JpaPersistenceConfig) - def repo = context.getBean(JpaCustomerRepository) - - // when Spring JPA sets up, it issues metadata queries -- clear those traces - clearExportedData() - - return repo - } - - List findByLastName(JpaCustomerRepository repository, String lastName) { - return repository.findByLastName(lastName) - } - - List findSpecialCustomers(JpaCustomerRepository repository) { - return repository.findSpecialCustomers() - } -} diff --git a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java new file mode 100644 index 000000000000..93189f602b9c --- /dev/null +++ b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import java.util.List; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import spring.jpa.JpaCustomer; +import spring.jpa.JpaCustomerRepository; +import spring.jpa.JpaPersistenceConfig; + +public class SprintJpaTest extends AbstractSpringJpaTest { + + @Override + JpaCustomer newCustomer(String firstName, String lastName) { + return new JpaCustomer(firstName, lastName); + } + + @Override + Long id(JpaCustomer customer) { + return customer.getId(); + } + + @Override + void setFirstName(JpaCustomer customer, String firstName) { + customer.setFirstName(firstName); + } + + @Override + Class repositoryClass() { + return JpaCustomerRepository.class; + } + + @Override + JpaCustomerRepository repository() { + AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(JpaPersistenceConfig.class); + JpaCustomerRepository repo = context.getBean(JpaCustomerRepository.class); + + // when Spring JPA sets up, it issues metadata queries -- clear those traces + clearData(); + + return repo; + } + + @Override + List findByLastName(JpaCustomerRepository repository, String lastName) { + return repository.findByLastName(lastName); + } + + @Override + List findSpecialCustomers(JpaCustomerRepository repository) { + return repository.findSpecialCustomers(); + } +} diff --git a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/groovy/SpringJpaTest.groovy b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/groovy/SpringJpaTest.groovy deleted file mode 100644 index b24dc2c36da5..000000000000 --- a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/groovy/SpringJpaTest.groovy +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import spring.jpa.JpaCustomer -import spring.jpa.JpaCustomerRepository -import spring.jpa.JpaPersistenceConfig - -class SpringJpaTest extends AbstractSpringJpaTest { - - ConfigurableApplicationContext context - JpaCustomerRepository repository - - def setup() { - context = new AnnotationConfigApplicationContext(JpaPersistenceConfig) - repository = context.getBean(JpaCustomerRepository) - - // when Spring JPA sets up, it issues metadata queries -- clear those traces - clearExportedData() - } - - def cleanup() { - context.close() - } - - JpaCustomer newCustomer(String firstName, String lastName) { - return new JpaCustomer(firstName, lastName) - } - - Long id(JpaCustomer customer) { - return customer.id - } - - void setFirstName(JpaCustomer customer, String firstName) { - customer.firstName = firstName - } - - Class repositoryClass() { - return JpaCustomerRepository - } - - JpaCustomerRepository repository() { - return repository - } - - List findByLastName(JpaCustomerRepository repository, String lastName) { - return repository.findByLastName(lastName) - } - - List findSpecialCustomers(JpaCustomerRepository repository) { - return repository.findSpecialCustomers() - } -} diff --git a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java new file mode 100644 index 000000000000..ffe7702bcd56 --- /dev/null +++ b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import java.util.List; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import spring.jpa.JpaCustomer; +import spring.jpa.JpaCustomerRepository; +import spring.jpa.JpaPersistenceConfig; + +public class SpringJpaTest extends AbstractSpringJpaTest { + + @Override + JpaCustomer newCustomer(String firstName, String lastName) { + return new JpaCustomer(firstName, lastName); + } + + @Override + Long id(JpaCustomer customer) { + return customer.getId(); + } + + @Override + void setFirstName(JpaCustomer customer, String firstName) { + customer.setFirstName(firstName); + } + + @Override + Class repositoryClass() { + return JpaCustomerRepository.class; + } + + @Override + JpaCustomerRepository repository() { + AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(JpaPersistenceConfig.class); + JpaCustomerRepository repo = context.getBean(JpaCustomerRepository.class); + + // when Spring JPA sets up, it issues metadata queries -- clear those traces + clearData(); + + return repo; + } + + @Override + List findByLastName(JpaCustomerRepository repository, String lastName) { + return repository.findByLastName(lastName); + } + + @Override + List findSpecialCustomers(JpaCustomerRepository repository) { + return repository.findSpecialCustomers(); + } +} diff --git a/instrumentation/spring/spring-data/spring-data-common/testing/src/main/groovy/AbstractSpringJpaTest.groovy b/instrumentation/spring/spring-data/spring-data-common/testing/src/main/groovy/AbstractSpringJpaTest.groovy deleted file mode 100644 index 032acd456e16..000000000000 --- a/instrumentation/spring/spring-data/spring-data-common/testing/src/main/groovy/AbstractSpringJpaTest.groovy +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.hibernate.Version -import org.springframework.data.jpa.repository.JpaRepository - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -abstract class AbstractSpringJpaTest> extends AgentInstrumentationSpecification { - - abstract ENTITY newCustomer(String firstName, String lastName) - - abstract Long id(ENTITY customer) - - abstract void setFirstName(ENTITY customer, String firstName) - - abstract Class repositoryClass() - - abstract REPOSITORY repository() - - abstract List findByLastName(REPOSITORY repository, String lastName) - - abstract List findSpecialCustomers(REPOSITORY repository) - - def "test object method"() { - setup: - def repo = repository() - - when: - runWithSpan("toString test") { - repo.toString() - } - - then: - // Asserting that a span is NOT created for toString - assertTraces(1) { - trace(0, 1) { - span(0) { - name "toString test" - attributes { - } - } - } - } - } - - def "test CRUD"() { - def isHibernate4 = Version.getVersionString().startsWith("4.") - - def repo = repository() - def repoClassName = repositoryClass().name - - setup: - def customer = newCustomer("Bob", "Anonymous") - - expect: - id(customer) == null - !repo.findAll().iterator().hasNext() // select - - assertTraces(1) { - trace(0, 2) { - span(0) { - name "JpaCustomerRepository.findAll" - kind INTERNAL - attributes { - "$SemanticAttributes.CODE_NAMESPACE" repoClassName - "$SemanticAttributes.CODE_FUNCTION" "findAll" - } - } - span(1) { // select - name "SELECT test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^select / - "$SemanticAttributes.DB_OPERATION" "SELECT" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - } - } - clearExportedData() - - when: - repo.save(customer) // insert - def savedId = id(customer) - - then: - id(customer) != null - assertTraces(1) { - trace(0, 2 + (isHibernate4 ? 0 : 1)) { - span(0) { - name "JpaCustomerRepository.save" - kind INTERNAL - attributes { - "$SemanticAttributes.CODE_NAMESPACE" repoClassName - "$SemanticAttributes.CODE_FUNCTION" "save" - } - } - def offset = 0 - // hibernate5+ has extra span - if (!isHibernate4) { - offset = 1 - span(1) { - name "CALL test" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^call next value for / - "$SemanticAttributes.DB_OPERATION" "CALL" - } - } - } - span(1 + offset) { // insert - name "INSERT test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^insert / - "$SemanticAttributes.DB_OPERATION" "INSERT" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - } - } - clearExportedData() - - when: - setFirstName(customer, "Bill") - repo.save(customer) - - then: - id(customer) == savedId - assertTraces(1) { - trace(0, 3) { - span(0) { - name "JpaCustomerRepository.save" - kind INTERNAL - attributes { - "$SemanticAttributes.CODE_NAMESPACE" repoClassName - "$SemanticAttributes.CODE_FUNCTION" "save" - } - } - span(1) { // select - name "SELECT test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^select / - "$SemanticAttributes.DB_OPERATION" "SELECT" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - span(2) { // update - name "UPDATE test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^update / - "$SemanticAttributes.DB_OPERATION" "UPDATE" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - } - } - clearExportedData() - - when: - customer = findByLastName(repo, "Anonymous")[0] // select - - then: - assertTraces(1) { - trace(0, 2) { - span(0) { - name "JpaCustomerRepository.findByLastName" - kind INTERNAL - attributes { - "$SemanticAttributes.CODE_NAMESPACE" repoClassName - "$SemanticAttributes.CODE_FUNCTION" "findByLastName" - } - } - span(1) { // select - name "SELECT test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^select / - "$SemanticAttributes.DB_OPERATION" "SELECT" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - } - } - clearExportedData() - - when: - repo.delete(customer) // delete - - then: - assertTraces(1) { - trace(0, 3) { - span(0) { - name "JpaCustomerRepository.delete" - kind INTERNAL - attributes { - "$SemanticAttributes.CODE_NAMESPACE" repoClassName - "$SemanticAttributes.CODE_FUNCTION" "delete" - } - } - span(1) { // select - name "SELECT test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^select / - "$SemanticAttributes.DB_OPERATION" "SELECT" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - span(2) { // delete - name "DELETE test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^delete / - "$SemanticAttributes.DB_OPERATION" "DELETE" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - } - } - } - - def "test custom repository method"() { - setup: - def repo = repository() - def repoClassName = repositoryClass().name - - when: - def customers = findSpecialCustomers(repo) - - then: - customers.isEmpty() - - assertTraces(1) { - trace(0, 2) { - span(0) { - name "JpaCustomerRepository.findSpecialCustomers" - kind INTERNAL - attributes { - "$SemanticAttributes.CODE_NAMESPACE" repoClassName - "$SemanticAttributes.CODE_FUNCTION" "findSpecialCustomers" - } - } - span(1) { // select - name "SELECT test.JpaCustomer" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.DB_SYSTEM" "hsqldb" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_USER" "sa" - "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" - "$SemanticAttributes.DB_STATEMENT" ~/^select / - "$SemanticAttributes.DB_OPERATION" "SELECT" - "$SemanticAttributes.DB_SQL_TABLE" "JpaCustomer" - } - } - } - } - } -} diff --git a/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java b/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java new file mode 100644 index 000000000000..0455ee4865bc --- /dev/null +++ b/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java @@ -0,0 +1,322 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; +import org.hibernate.Version; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.data.jpa.repository.JpaRepository; + +public abstract class AbstractSpringJpaTest< + ENTITY, REPOSITORY extends JpaRepository> { + + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + abstract ENTITY newCustomer(String firstName, String lastName); + + abstract Long id(ENTITY customer); + + abstract void setFirstName(ENTITY customer, String firstName); + + abstract Class repositoryClass(); + + abstract REPOSITORY repository(); + + abstract List findByLastName(REPOSITORY repository, String lastName); + + abstract List findSpecialCustomers(REPOSITORY repository); + + void clearData() { + testing.clearData(); + } + + @Test + void testObjectMethod() { + REPOSITORY repo = repository(); + + testing.runWithSpan("toString test", repo::toString); + + // Asserting that a span is NOT created for toString + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(1) + .hasSpansSatisfyingExactly( + span -> span.hasName("toString test").hasTotalAttributeCount(0))); + } + + static void assertHibernate4Trace(TraceAssert trace, String repoClassName) { + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.save") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "save")), + span -> + span.hasName("INSERT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, val -> val.startsWith("insert ")), + equalTo(SemanticAttributes.DB_OPERATION, "INSERT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer"))); + } + + static void assertHibernateTrace(TraceAssert trace, String repoClassName) { + trace + .hasSize(3) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.save") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "save")), + span -> + span.hasName("CALL test") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("call next value for ")), + equalTo(SemanticAttributes.DB_OPERATION, "CALL")), + span -> + span.hasName("INSERT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, val -> val.startsWith("insert ")), + equalTo(SemanticAttributes.DB_OPERATION, "INSERT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer"))); + } + + @Test + void testCrud() { + boolean isHibernate4 = Version.getVersionString().startsWith("4."); + REPOSITORY repo = repository(); + String repoClassName = repositoryClass().getName(); + + ENTITY customer = newCustomer("Bob", "Anonymous"); + + assertNull(id(customer)); + assertFalse(repo.findAll().iterator().hasNext()); + + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.findAll") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "findAll")), + span -> + span.hasName("SELECT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("select ")), + equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); + clearData(); + + repo.save(customer); + assertNotNull(id(customer)); + Long savedId = id(customer); + if (isHibernate4) { + testing.waitAndAssertTraces(trace -> assertHibernate4Trace(trace, repoClassName)); + } else { + testing.waitAndAssertTraces(trace -> assertHibernateTrace(trace, repoClassName)); + } + clearData(); + + setFirstName(customer, "Bill"); + repo.save(customer); + assertEquals(id(customer), savedId); + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(3) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.save") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "save")), + span -> + span.hasName("SELECT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("select ")), + equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")), + span -> + span.hasName("UPDATE test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("update ")), + equalTo(SemanticAttributes.DB_OPERATION, "UPDATE"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); + clearData(); + + customer = findByLastName(repo, "Anonymous").get(0); + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.findByLastName") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "findByLastName")), + span -> + span.hasName("SELECT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("select ")), + equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); + clearData(); + + repo.delete(customer); + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(3) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.delete") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "delete")), + span -> + span.hasName("SELECT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("select ")), + equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")), + span -> + span.hasName("DELETE test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("delete ")), + equalTo(SemanticAttributes.DB_OPERATION, "DELETE"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); + } + + @Test + void testCustomRepositoryMethod() { + REPOSITORY repo = repository(); + String repoClassName = repositoryClass().getName(); + List customers = findSpecialCustomers(repo); + + assertTrue(customers.isEmpty()); + + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.findSpecialCustomers") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "findSpecialCustomers")), + span -> + span.hasName("SELECT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("select ")), + equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); + } +}