From edd65e010c3bb7644c7eb26d82f8a1eea8e77285 Mon Sep 17 00:00:00 2001 From: Graham Crockford Date: Tue, 19 Dec 2023 14:01:05 +0000 Subject: [PATCH] Expand use of base acceptance tests to Jackson and Jooq modules, improving coverage --- .../transactionoutbox/acceptance/TestH2.java | 16 +- .../acceptance/TestMySql8.java | 2 +- .../guice/acceptance}/TestGuiceBinding.java | 2 +- .../acceptance}/TestGuiceInstantiator.java | 2 +- transactionoutbox-jackson/pom.xml | 6 + .../acceptance/TestJacksonSerializer.java | 58 +- transactionoutbox-jooq/pom.xml | 38 ++ ...sactionManagerWithThreadLocalProvider.java | 495 ------------------ .../acceptance/TestUtils.java | 94 ---- .../AbstractJooqAcceptanceTest.java | 25 + ...AbstractJooqAcceptanceThreadLocalTest.java | 238 +++++++++ .../jooq/acceptance/JooqTestUtils.java | 51 ++ .../acceptance/TestJooqThreadLocalH2.java | 6 + .../acceptance/TestJooqThreadLocalMySql5.java | 37 ++ .../acceptance/TestJooqThreadLocalMySql8.java | 37 ++ .../TestJooqThreadLocalPostgres16.java | 37 ++ ...ultProviderAndExplicitlyPassedContext.java | 31 +- ...hDefaultProviderAndThreadLocalContext.java | 21 +- .../acceptance/ApplicationConfig.java | 94 ++-- .../acceptance/BusinessService.java | 50 +- .../acceptance/BusinessServiceTest.java | 2 +- .../{ => quarkus}/acceptance/DaoImpl.java | 2 +- .../acceptance/RemoteCallService.java | 2 +- .../{ => spring}/acceptance/Customer.java | 2 +- .../acceptance/CustomerRepository.java | 2 +- .../{ => spring}/acceptance/Event.java | 2 +- .../acceptance/EventPublisher.java | 2 +- .../acceptance/EventRepository.java | 2 +- .../EventuallyConsistentController.java | 3 +- .../EventuallyConsistentControllerTest.java | 2 +- .../acceptance/ExternalsConfiguration.java | 2 +- ...ransactionOutboxSpringDemoApplication.java | 2 +- .../{ => spring}/acceptance/Utils.java | 2 +- .../testing/AbstractAcceptanceTest.java | 70 +-- .../transactionoutbox/testing/TestUtils.java | 20 + 35 files changed, 664 insertions(+), 793 deletions(-) rename transactionoutbox-guice/src/test/java/{com.gruelbox.transactionoutbox.acceptance => com/gruelbox/transactionoutbox/guice/acceptance}/TestGuiceBinding.java (98%) rename transactionoutbox-guice/src/test/java/{com.gruelbox.transactionoutbox.acceptance => com/gruelbox/transactionoutbox/guice/acceptance}/TestGuiceInstantiator.java (93%) rename transactionoutbox-jackson/src/test/java/com/gruelbox/transactionoutbox/{ => jackson}/acceptance/TestJacksonSerializer.java (56%) delete mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithThreadLocalProvider.java delete mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestUtils.java create mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceTest.java create mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceThreadLocalTest.java create mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/JooqTestUtils.java create mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalH2.java create mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql5.java create mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql8.java create mode 100644 transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalPostgres16.java rename transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/{ => jooq}/acceptance/TestJooqTransactionManagerWithDefaultProviderAndExplicitlyPassedContext.java (92%) rename transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/{ => jooq}/acceptance/TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext.java (96%) rename transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/{ => quarkus}/acceptance/ApplicationConfig.java (93%) rename transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/{ => quarkus}/acceptance/BusinessService.java (89%) rename transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/{ => quarkus}/acceptance/BusinessServiceTest.java (96%) rename transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/{ => quarkus}/acceptance/DaoImpl.java (96%) rename transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/{ => quarkus}/acceptance/RemoteCallService.java (91%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/Customer.java (85%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/CustomerRepository.java (76%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/Event.java (87%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/EventPublisher.java (86%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/EventRepository.java (76%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/EventuallyConsistentController.java (96%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/EventuallyConsistentControllerTest.java (95%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/ExternalsConfiguration.java (83%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/TransactionOutboxSpringDemoApplication.java (94%) rename transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/{ => spring}/acceptance/Utils.java (93%) diff --git a/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestH2.java b/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestH2.java index 6db389dd..dccc783b 100644 --- a/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestH2.java +++ b/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestH2.java @@ -17,23 +17,11 @@ class TestH2 extends AbstractAcceptanceTest { static final ThreadLocal inWrappedInvocation = ThreadLocal.withInitial(() -> false); - @Override - protected ConnectionDetails connectionDetails() { - return ConnectionDetails.builder() - .dialect(Dialect.H2) - .driverClassName("org.h2.Driver") - .url( - "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=60000;LOB_TIMEOUT=2000;MV_STORE=TRUE") - .user("test") - .password("test") - .build(); - } - @Test final void wrapInvocations() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); TransactionOutbox outbox = TransactionOutbox.builder() .transactionManager(transactionManager) @@ -78,7 +66,7 @@ public void wrapInvocation(Invocator invocator) final void wrapInvocationsWithMDC() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); TransactionOutbox outbox = TransactionOutbox.builder() .transactionManager(transactionManager) diff --git a/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestMySql8.java b/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestMySql8.java index a9eb9dff..f32e316a 100644 --- a/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestMySql8.java +++ b/transactionoutbox-acceptance/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestMySql8.java @@ -15,7 +15,7 @@ class TestMySql8 extends AbstractAcceptanceTest { @Container @SuppressWarnings({"rawtypes", "resource"}) private static final JdbcDatabaseContainer container = - new MySQLContainer<>("mysql:8.0.27").withStartupTimeout(Duration.ofMinutes(5)); + new MySQLContainer<>("mysql:8").withStartupTimeout(Duration.ofMinutes(5)); @Override protected ConnectionDetails connectionDetails() { diff --git a/transactionoutbox-guice/src/test/java/com.gruelbox.transactionoutbox.acceptance/TestGuiceBinding.java b/transactionoutbox-guice/src/test/java/com/gruelbox/transactionoutbox/guice/acceptance/TestGuiceBinding.java similarity index 98% rename from transactionoutbox-guice/src/test/java/com.gruelbox.transactionoutbox.acceptance/TestGuiceBinding.java rename to transactionoutbox-guice/src/test/java/com/gruelbox/transactionoutbox/guice/acceptance/TestGuiceBinding.java index 46d17ef9..98cf5b85 100644 --- a/transactionoutbox-guice/src/test/java/com.gruelbox.transactionoutbox.acceptance/TestGuiceBinding.java +++ b/transactionoutbox-guice/src/test/java/com/gruelbox/transactionoutbox/guice/acceptance/TestGuiceBinding.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.guice.acceptance; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/transactionoutbox-guice/src/test/java/com.gruelbox.transactionoutbox.acceptance/TestGuiceInstantiator.java b/transactionoutbox-guice/src/test/java/com/gruelbox/transactionoutbox/guice/acceptance/TestGuiceInstantiator.java similarity index 93% rename from transactionoutbox-guice/src/test/java/com.gruelbox.transactionoutbox.acceptance/TestGuiceInstantiator.java rename to transactionoutbox-guice/src/test/java/com/gruelbox/transactionoutbox/guice/acceptance/TestGuiceInstantiator.java index 2bf5ab41..e272f901 100644 --- a/transactionoutbox-guice/src/test/java/com.gruelbox.transactionoutbox.acceptance/TestGuiceInstantiator.java +++ b/transactionoutbox-guice/src/test/java/com/gruelbox/transactionoutbox/guice/acceptance/TestGuiceInstantiator.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.guice.acceptance; import com.google.inject.Guice; import com.google.inject.Inject; diff --git a/transactionoutbox-jackson/pom.xml b/transactionoutbox-jackson/pom.xml index 47c54bbf..0b304397 100644 --- a/transactionoutbox-jackson/pom.xml +++ b/transactionoutbox-jackson/pom.xml @@ -22,6 +22,12 @@ transactionoutbox-core ${project.version} + + com.gruelbox + transactionoutbox-testing + ${project.version} + test + com.fasterxml.jackson.core jackson-databind diff --git a/transactionoutbox-jackson/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJacksonSerializer.java b/transactionoutbox-jackson/src/test/java/com/gruelbox/transactionoutbox/jackson/acceptance/TestJacksonSerializer.java similarity index 56% rename from transactionoutbox-jackson/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJacksonSerializer.java rename to transactionoutbox-jackson/src/test/java/com/gruelbox/transactionoutbox/jackson/acceptance/TestJacksonSerializer.java index e90a3c3c..2d8b88d9 100644 --- a/transactionoutbox-jackson/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJacksonSerializer.java +++ b/transactionoutbox-jackson/src/test/java/com/gruelbox/transactionoutbox/jackson/acceptance/TestJacksonSerializer.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.jackson.acceptance; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -10,51 +10,38 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.gruelbox.transactionoutbox.*; import com.gruelbox.transactionoutbox.jackson.JacksonInvocationSerializer; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import java.sql.SQLException; +import com.gruelbox.transactionoutbox.testing.AbstractAcceptanceTest; import java.time.LocalDate; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @Slf4j -class TestJacksonSerializer { +class TestJacksonSerializer extends AbstractAcceptanceTest { - private HikariDataSource dataSource; - private ThreadLocalContextTransactionManager transactionManager; - private DefaultPersistor persistor; - private TransactionOutbox outbox; private final CountDownLatch latch = new CountDownLatch(1); + private TransactionManager transactionManager; + private TransactionOutbox outbox; @BeforeEach void beforeEach() { - dataSource = pooledDataSource(); - transactionManager = - TransactionManager.fromConnectionDetails( - "org.h2.Driver", - "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=60000;LOB_TIMEOUT=2000;MV_STORE=TRUE", - "test", - "test"); var mapper = new ObjectMapper(); mapper.registerModule(new GuavaModule()); mapper.registerModule(new Jdk8Module()); mapper.registerModule(new JavaTimeModule()); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); - - persistor = - DefaultPersistor.builder() - .dialect(Dialect.H2) - .serializer(JacksonInvocationSerializer.builder().mapper(mapper).build()) - .build(); + transactionManager = txManager(); outbox = TransactionOutbox.builder() .transactionManager(transactionManager) - .persistor(persistor) + .persistor( + DefaultPersistor.builder() + .dialect(Dialect.H2) + .serializer(JacksonInvocationSerializer.builder().mapper(mapper).build()) + .build()) .instantiator(Instantiator.using(clazz -> TestJacksonSerializer.this)) .listener( new TransactionOutboxListener() { @@ -64,29 +51,6 @@ public void success(TransactionOutboxEntry entry) { } }) .build(); - transactionManager.inTransaction( - tx -> { - try { - persistor.clear(tx); - } catch (SQLException e) { - throw new RuntimeException(e); - } - }); - } - - @AfterEach - void afterEach() { - dataSource.close(); - } - - private HikariDataSource pooledDataSource() { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl( - "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=2000;LOB_TIMEOUT=2000;MV_STORE=TRUE"); - config.setUsername("test"); - config.setPassword("test"); - config.addDataSourceProperty("cachePrepStmts", "true"); - return new HikariDataSource(config); } void process(List difficultDataStructure) { diff --git a/transactionoutbox-jooq/pom.xml b/transactionoutbox-jooq/pom.xml index 861b10af..3dd5a84f 100644 --- a/transactionoutbox-jooq/pom.xml +++ b/transactionoutbox-jooq/pom.xml @@ -20,6 +20,12 @@ transactionoutbox-core ${project.version} + + com.gruelbox + transactionoutbox-testing + ${project.version} + test + org.jooq jooq @@ -59,5 +65,37 @@ org.hamcrest hamcrest-core + + org.testcontainers + testcontainers + + + org.testcontainers + junit-jupiter + + + org.testcontainers + postgresql + + + org.testcontainers + oracle-xe + + + org.testcontainers + mysql + + + org.postgresql + postgresql + + + com.oracle.database.jdbc + ojdbc11 + + + mysql + mysql-connector-java + diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithThreadLocalProvider.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithThreadLocalProvider.java deleted file mode 100644 index cd886851..00000000 --- a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithThreadLocalProvider.java +++ /dev/null @@ -1,495 +0,0 @@ -package com.gruelbox.transactionoutbox.acceptance; - -import static com.gruelbox.transactionoutbox.acceptance.TestUtils.uncheck; -import static java.util.concurrent.CompletableFuture.runAsync; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import com.gruelbox.transactionoutbox.DefaultPersistor; -import com.gruelbox.transactionoutbox.Dialect; -import com.gruelbox.transactionoutbox.Instantiator; -import com.gruelbox.transactionoutbox.JooqTransactionListener; -import com.gruelbox.transactionoutbox.JooqTransactionManager; -import com.gruelbox.transactionoutbox.Persistor; -import com.gruelbox.transactionoutbox.Submitter; -import com.gruelbox.transactionoutbox.ThreadLocalContextTransactionManager; -import com.gruelbox.transactionoutbox.ThrowingRunnable; -import com.gruelbox.transactionoutbox.TransactionManager; -import com.gruelbox.transactionoutbox.TransactionOutbox; -import com.gruelbox.transactionoutbox.TransactionOutboxEntry; -import com.gruelbox.transactionoutbox.TransactionOutboxListener; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import java.sql.SQLException; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.IntStream; -import lombok.extern.slf4j.Slf4j; -import org.hamcrest.MatcherAssert; -import org.jooq.DSLContext; -import org.jooq.SQLDialect; -import org.jooq.impl.DSL; -import org.jooq.impl.DataSourceConnectionProvider; -import org.jooq.impl.DefaultConfiguration; -import org.jooq.impl.ThreadLocalTransactionProvider; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -@Slf4j -class TestJooqTransactionManagerWithThreadLocalProvider { - - private final ExecutorService unreliablePool = - new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(16)); - - private HikariDataSource dataSource; - private ThreadLocalContextTransactionManager transactionManager; - private DSLContext dsl; - - @BeforeEach - void beforeEach() { - dataSource = pooledDataSource(); - transactionManager = createTransactionManager(); - TestUtils.createTestTable(dsl); - } - - @AfterEach - void afterEach() { - dataSource.close(); - } - - private HikariDataSource pooledDataSource() { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl( - "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=2000;LOB_TIMEOUT=2000;MV_STORE=TRUE"); - config.setUsername("test"); - config.setPassword("test"); - config.addDataSourceProperty("cachePrepStmts", "true"); - return new HikariDataSource(config); - } - - private ThreadLocalContextTransactionManager createTransactionManager() { - DataSourceConnectionProvider connectionProvider = new DataSourceConnectionProvider(dataSource); - DefaultConfiguration configuration = new DefaultConfiguration(); - configuration.setConnectionProvider(connectionProvider); - configuration.setSQLDialect(SQLDialect.H2); - configuration.setTransactionProvider( - new ThreadLocalTransactionProvider(connectionProvider, true)); - JooqTransactionListener listener = JooqTransactionManager.createListener(); - configuration.set(listener); - dsl = DSL.using(configuration); - return JooqTransactionManager.create(dsl, listener); - } - - @Test - void testSimpleDirectInvocationWithThreadContext() throws InterruptedException { - CountDownLatch latch = new CountDownLatch(1); - TransactionOutbox outbox = - TransactionOutbox.builder() - .transactionManager(transactionManager) - .persistor(Persistor.forDialect(Dialect.H2)) - .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) - .listener( - new TransactionOutboxListener() { - @Override - public void success(TransactionOutboxEntry entry) { - latch.countDown(); - } - }) - .build(); - - clearOutbox(transactionManager); - - transactionManager.inTransaction( - () -> { - outbox.schedule(Worker.class).process(1); - try { - // Should not be fired until after commit - assertFalse(latch.await(2, TimeUnit.SECONDS)); - } catch (InterruptedException e) { - fail("Interrupted"); - } - }); - - // Should be fired after commit - assertTrue(latch.await(2, TimeUnit.SECONDS)); - TestUtils.assertRecordExists(dsl, 1); - } - - @Test - void testNestedDirectInvocation() throws Exception { - CountDownLatch latch1 = new CountDownLatch(1); - CountDownLatch latch2 = new CountDownLatch(1); - TransactionOutbox outbox = - TransactionOutbox.builder() - .transactionManager(transactionManager) - .persistor(Persistor.forDialect(Dialect.H2)) - .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) - .attemptFrequency(Duration.of(1, ChronoUnit.SECONDS)) - .listener( - new TransactionOutboxListener() { - @Override - public void success(TransactionOutboxEntry entry) { - if (entry.getInvocation().getArgs()[0].equals(1)) { - latch1.countDown(); - } else { - latch2.countDown(); - } - } - }) - .build(); - - clearOutbox(transactionManager); - - withRunningFlusher( - outbox, - () -> { - transactionManager.inTransactionThrows( - tx1 -> { - outbox.schedule(Worker.class).process(1); - - transactionManager.inTransactionThrows( - tx2 -> outbox.schedule(Worker.class).process(2)); - - // Neither should be fired - the second job is in a nested transaction - CompletableFuture.allOf( - runAsync( - () -> uncheck(() -> assertFalse(latch1.await(2, TimeUnit.SECONDS)))), - runAsync( - () -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) - .get(); - }); - - // Should be fired after commit - CompletableFuture.allOf( - runAsync(() -> uncheck(() -> assertTrue(latch1.await(2, TimeUnit.SECONDS)))), - runAsync(() -> uncheck(() -> assertTrue(latch2.await(2, TimeUnit.SECONDS))))) - .get(); - }); - - TestUtils.assertRecordExists(dsl, 1); - TestUtils.assertRecordExists(dsl, 2); - } - - @Test - void testSimpleViaListenerWithThreadContext() throws InterruptedException { - CountDownLatch latch = new CountDownLatch(1); - TransactionOutbox outbox = - TransactionOutbox.builder() - .transactionManager(transactionManager) - .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) - .persistor(Persistor.forDialect(Dialect.H2)) - .listener( - new TransactionOutboxListener() { - @Override - public void success(TransactionOutboxEntry entry) { - latch.countDown(); - } - }) - .build(); - - clearOutbox(transactionManager); - - dsl.transaction( - () -> { - outbox.schedule(Worker.class).process(1); - try { - // Should not be fired until after commit - assertFalse(latch.await(2, TimeUnit.SECONDS)); - } catch (InterruptedException e) { - fail("Interrupted"); - } - }); - - // Should be fired after commit - assertTrue(latch.await(2, TimeUnit.SECONDS)); - TestUtils.assertRecordExists(dsl, 1); - } - - @Test - void testNestedViaListener() throws Exception { - CountDownLatch latch1 = new CountDownLatch(1); - CountDownLatch latch2 = new CountDownLatch(1); - TransactionOutbox outbox = - TransactionOutbox.builder() - .transactionManager(transactionManager) - .persistor(Persistor.forDialect(Dialect.H2)) - .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) - .attemptFrequency(Duration.of(1, ChronoUnit.SECONDS)) - .listener( - new TransactionOutboxListener() { - @Override - public void success(TransactionOutboxEntry entry) { - if (entry.getInvocation().getArgs()[0].equals(1)) { - latch1.countDown(); - } else { - latch2.countDown(); - } - } - }) - .build(); - - clearOutbox(transactionManager); - - withRunningFlusher( - outbox, - () -> { - dsl.transaction( - ctx -> { - outbox.schedule(Worker.class).process(1); - ctx.dsl().transaction(() -> outbox.schedule(Worker.class).process(2)); - - // Neither should be fired - the second job is in a nested transaction - CompletableFuture.allOf( - runAsync( - () -> uncheck(() -> assertFalse(latch1.await(2, TimeUnit.SECONDS)))), - runAsync( - () -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) - .get(); - }); - - // Both should be fired after commit - CompletableFuture.allOf( - runAsync(() -> uncheck(() -> assertTrue(latch1.await(2, TimeUnit.SECONDS)))), - runAsync(() -> uncheck(() -> assertTrue(latch2.await(2, TimeUnit.SECONDS))))) - .get(); - }); - TestUtils.assertRecordExists(dsl, 1); - TestUtils.assertRecordExists(dsl, 2); - } - - /** - * Ensures that given the rollback of an inner transaction, any outbox work scheduled in the inner - * transaction is rolled back while the outer transaction's works. - */ - @Test - void testNestedWithInnerFailure() throws Exception { - CountDownLatch latch1 = new CountDownLatch(1); - CountDownLatch latch2 = new CountDownLatch(1); - TransactionOutbox outbox = - TransactionOutbox.builder() - .transactionManager(transactionManager) - .persistor(Persistor.forDialect(Dialect.H2)) - .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) - .attemptFrequency(Duration.of(1, ChronoUnit.SECONDS)) - .listener( - new TransactionOutboxListener() { - @Override - public void success(TransactionOutboxEntry entry) { - if (entry.getInvocation().getArgs()[0].equals(1)) { - latch1.countDown(); - } else { - latch2.countDown(); - } - } - }) - .build(); - - clearOutbox(transactionManager); - - withRunningFlusher( - outbox, - () -> { - dsl.transaction( - ctx -> { - outbox.schedule(Worker.class).process(1); - - assertThrows( - UnsupportedOperationException.class, - () -> - ctx.dsl() - .transaction( - () -> { - outbox.schedule(Worker.class).process(2); - throw new UnsupportedOperationException(); - })); - - CompletableFuture.allOf( - runAsync( - () -> uncheck(() -> assertFalse(latch1.await(2, TimeUnit.SECONDS)))), - runAsync( - () -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) - .get(); - }); - - CompletableFuture.allOf( - runAsync(() -> uncheck(() -> assertTrue(latch1.await(2, TimeUnit.SECONDS)))), - runAsync(() -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) - .get(); - }); - } - - @Test - void retryBehaviour() throws Exception { - CountDownLatch latch = new CountDownLatch(1); - TransactionOutbox outbox = - TransactionOutbox.builder() - .transactionManager(transactionManager) - .persistor(Persistor.forDialect(Dialect.H2)) - .instantiator(new FailingInstantiator()) - .submitter(Submitter.withExecutor(unreliablePool)) - .attemptFrequency(Duration.ofSeconds(1)) - .listener( - new TransactionOutboxListener() { - @Override - public void success(TransactionOutboxEntry entry) { - latch.countDown(); - } - }) - .build(); - - clearOutbox(transactionManager); - - withRunningFlusher( - outbox, - () -> { - transactionManager.inTransaction(() -> outbox.schedule(InterfaceWorker.class).process(3)); - assertTrue(latch.await(15, TimeUnit.SECONDS)); - }); - } - - @Test - void highVolumeUnreliable() throws Exception { - int count = 10; - - CountDownLatch latch = new CountDownLatch(count * 10); - ConcurrentHashMap results = new ConcurrentHashMap<>(); - ConcurrentHashMap duplicates = new ConcurrentHashMap<>(); - - TransactionOutbox outbox = - TransactionOutbox.builder() - .transactionManager(transactionManager) - .persistor(Persistor.forDialect(Dialect.H2)) - .instantiator(new FailingInstantiator()) - .submitter(Submitter.withExecutor(unreliablePool)) - .attemptFrequency(Duration.ofSeconds(1)) - .flushBatchSize(1000) - .listener( - new TransactionOutboxListener() { - @Override - public void success(TransactionOutboxEntry entry) { - Integer i = (Integer) entry.getInvocation().getArgs()[0]; - if (results.putIfAbsent(i, i) != null) { - duplicates.put(i, i); - } - latch.countDown(); - } - }) - .build(); - - withRunningFlusher( - outbox, - () -> { - IntStream.range(0, count) - .parallel() - .forEach( - i -> - transactionManager.inTransaction( - () -> { - for (int j = 0; j < 10; j++) { - outbox.schedule(InterfaceWorker.class).process(i * 10 + j); - } - })); - assertTrue(latch.await(30, TimeUnit.SECONDS)); - }); - - MatcherAssert.assertThat( - "Should never get duplicates running to full completion", duplicates.keySet(), empty()); - MatcherAssert.assertThat( - "Only got: " + results.keySet(), - results.keySet(), - containsInAnyOrder(IntStream.range(0, count * 10).boxed().toArray())); - } - - private void clearOutbox(TransactionManager transactionManager) { - DefaultPersistor persistor = Persistor.forDialect(Dialect.H2); - persistor.migrate(transactionManager); - transactionManager.inTransaction( - tx -> { - try { - persistor.clear(tx); - } catch (SQLException e) { - throw new RuntimeException(e); - } - }); - } - - private void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable) - throws Exception { - ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - try { - scheduler.scheduleAtFixedRate( - () -> { - if (Thread.interrupted()) { - return; - } - outbox.flush(); - }, - 500, - 500, - TimeUnit.MILLISECONDS); - runnable.run(); - } finally { - scheduler.shutdown(); - assertTrue(scheduler.awaitTermination(20, TimeUnit.SECONDS)); - } - } - - interface InterfaceWorker { - - void process(int i); - } - - @SuppressWarnings("EmptyMethod") - static class Worker { - - private final ThreadLocalContextTransactionManager transactionManager; - - Worker(ThreadLocalContextTransactionManager transactionManager) { - this.transactionManager = transactionManager; - } - - @SuppressWarnings("SameParameterValue") - void process(int i) { - TestUtils.writeRecord(transactionManager, i); - } - } - - private static class FailingInstantiator implements Instantiator { - - private final AtomicInteger attempts; - - FailingInstantiator() { - this.attempts = new AtomicInteger(0); - } - - @Override - public String getName(Class clazz) { - return clazz.getName(); - } - - @Override - public Object getInstance(String name) { - return (InterfaceWorker) - (i) -> { - if (attempts.incrementAndGet() < 3) { - throw new RuntimeException("Temporary failure"); - } - }; - } - } -} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestUtils.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestUtils.java deleted file mode 100644 index bbffa060..00000000 --- a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.gruelbox.transactionoutbox.acceptance; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.gruelbox.transactionoutbox.ThreadLocalContextTransactionManager; -import com.gruelbox.transactionoutbox.ThrowingRunnable; -import com.gruelbox.transactionoutbox.Transaction; -import com.gruelbox.transactionoutbox.TransactionManager; -import com.gruelbox.transactionoutbox.UncheckedException; -import java.sql.Statement; -import lombok.extern.slf4j.Slf4j; -import org.jooq.Configuration; -import org.jooq.DSLContext; -import org.jooq.Record; -import org.jooq.Table; -import org.jooq.impl.DSL; -import org.jooq.impl.SQLDataType; - -@Slf4j -class TestUtils { - - private static final Table TEST_TABLE = DSL.table("TEST_TABLE"); - - @SuppressWarnings("SameParameterValue") - static void runSql(TransactionManager transactionManager, String sql) { - transactionManager.inTransaction( - tx -> { - try { - try (Statement statement = tx.connection().createStatement()) { - statement.execute(sql); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - static void uncheck(ThrowingRunnable runnable) { - try { - runnable.run(); - } catch (Exception e) { - uncheckAndThrow(e); - } - } - - static T uncheckAndThrow(Throwable e) { - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - if (e instanceof Error) { - throw (Error) e; - } - throw new UncheckedException(e); - } - - static void createTestTable(DSLContext dsl) { - log.info("Creating table"); - dsl.dropTableIfExists(TEST_TABLE).execute(); - dsl.createTable(TEST_TABLE).column("VAL", SQLDataType.INTEGER).execute(); - } - - static void writeRecord(Configuration configuration, int value) { - log.info("Inserting record {}", value); - configuration.dsl().insertInto(TEST_TABLE).values(value).execute(); - } - - static void writeRecord(Transaction transaction, int value) { - Configuration configuration = transaction.context(); - writeRecord(configuration, value); - } - - static void writeRecord(ThreadLocalContextTransactionManager transactionManager, int value) { - transactionManager.requireTransaction(tx -> writeRecord(tx, value)); - } - - static void assertRecordExists(DSLContext dsl, int value) { - assertTrue( - dsl.select() - .from(TEST_TABLE) - .where(DSL.field("VAL").eq(value)) - .fetchOptional() - .isPresent()); - } - - static void assertRecordNotExists(DSLContext dsl, int value) { - assertFalse( - dsl.select() - .from(TEST_TABLE) - .where(DSL.field("VAL").eq(value)) - .fetchOptional() - .isPresent()); - } -} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceTest.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceTest.java new file mode 100644 index 00000000..729e3c39 --- /dev/null +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceTest.java @@ -0,0 +1,25 @@ +package com.gruelbox.transactionoutbox.jooq.acceptance; + +import com.gruelbox.transactionoutbox.TransactionManager; +import com.gruelbox.transactionoutbox.testing.AbstractAcceptanceTest; +import org.jooq.DSLContext; +import org.junit.jupiter.api.Test; + +abstract class AbstractJooqAcceptanceTest extends AbstractAcceptanceTest { + + protected DSLContext dsl; + + @Override + protected TransactionManager txManager() { + throw new IllegalStateException("Needs to be defined"); + } + + @Test + abstract void testNestedDirectInvocation() throws Exception; + + @Test + abstract void testNestedViaListener() throws Exception; + + @Test + abstract void testNestedWithInnerFailure() throws Exception; +} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceThreadLocalTest.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceThreadLocalTest.java new file mode 100644 index 00000000..6a0b968a --- /dev/null +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/AbstractJooqAcceptanceThreadLocalTest.java @@ -0,0 +1,238 @@ +package com.gruelbox.transactionoutbox.jooq.acceptance; + +import static com.gruelbox.transactionoutbox.testing.TestUtils.uncheck; +import static java.util.concurrent.CompletableFuture.runAsync; +import static org.junit.jupiter.api.Assertions.*; + +import com.gruelbox.transactionoutbox.*; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.jooq.SQLDialect; +import org.jooq.impl.DSL; +import org.jooq.impl.DataSourceConnectionProvider; +import org.jooq.impl.DefaultConfiguration; +import org.jooq.impl.ThreadLocalTransactionProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@Slf4j +abstract class AbstractJooqAcceptanceThreadLocalTest extends AbstractJooqAcceptanceTest { + private ThreadLocalContextTransactionManager txm; + + @Override + protected final ThreadLocalContextTransactionManager txManager() { + return txm; + } + + protected SQLDialect jooqDialect() { + return SQLDialect.H2; + } + + @BeforeEach + final void beforeEach() { + DataSourceConnectionProvider connectionProvider = new DataSourceConnectionProvider(dataSource); + DefaultConfiguration configuration = new DefaultConfiguration(); + configuration.setConnectionProvider(connectionProvider); + configuration.setSQLDialect(jooqDialect()); + configuration.setTransactionProvider( + new ThreadLocalTransactionProvider(connectionProvider, true)); + JooqTransactionListener listener = JooqTransactionManager.createListener(); + configuration.set(listener); + dsl = DSL.using(configuration); + txm = JooqTransactionManager.create(dsl, listener); + JooqTestUtils.createTestTable(dsl); + } + + @Test + @Override + void testNestedDirectInvocation() throws Exception { + CountDownLatch latch1 = new CountDownLatch(1); + CountDownLatch latch2 = new CountDownLatch(1); + ThreadLocalContextTransactionManager transactionManager = txManager(); + TransactionOutbox outbox = + TransactionOutbox.builder() + .transactionManager(transactionManager) + .persistor(Persistor.forDialect(connectionDetails().dialect())) + .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) + .attemptFrequency(Duration.of(1, ChronoUnit.SECONDS)) + .listener( + new TransactionOutboxListener() { + @Override + public void success(TransactionOutboxEntry entry) { + if (entry.getInvocation().getArgs()[0].equals(1)) { + latch1.countDown(); + } else { + latch2.countDown(); + } + } + }) + .build(); + + clearOutbox(); + + withRunningFlusher( + outbox, + () -> { + transactionManager.inTransactionThrows( + tx1 -> { + outbox.schedule(Worker.class).process(1); + + transactionManager.inTransactionThrows( + tx2 -> outbox.schedule(Worker.class).process(2)); + + // Neither should be fired - the second job is in a nested transaction + CompletableFuture.allOf( + runAsync( + () -> uncheck(() -> assertFalse(latch1.await(2, TimeUnit.SECONDS)))), + runAsync( + () -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) + .get(); + }); + + // Should be fired after commit + CompletableFuture.allOf( + runAsync(() -> uncheck(() -> assertTrue(latch1.await(2, TimeUnit.SECONDS)))), + runAsync(() -> uncheck(() -> assertTrue(latch2.await(2, TimeUnit.SECONDS))))) + .get(); + }); + + JooqTestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 2); + } + + @Test + @Override + void testNestedViaListener() throws Exception { + CountDownLatch latch1 = new CountDownLatch(1); + CountDownLatch latch2 = new CountDownLatch(1); + ThreadLocalContextTransactionManager transactionManager = txManager(); + TransactionOutbox outbox = + TransactionOutbox.builder() + .transactionManager(transactionManager) + .persistor(Persistor.forDialect(connectionDetails().dialect())) + .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) + .attemptFrequency(Duration.of(1, ChronoUnit.SECONDS)) + .listener( + new TransactionOutboxListener() { + @Override + public void success(TransactionOutboxEntry entry) { + if (entry.getInvocation().getArgs()[0].equals(1)) { + latch1.countDown(); + } else { + latch2.countDown(); + } + } + }) + .build(); + + clearOutbox(); + + withRunningFlusher( + outbox, + () -> { + dsl.transaction( + ctx -> { + outbox.schedule(Worker.class).process(1); + ctx.dsl().transaction(() -> outbox.schedule(Worker.class).process(2)); + + // Neither should be fired - the second job is in a nested transaction + CompletableFuture.allOf( + runAsync( + () -> uncheck(() -> assertFalse(latch1.await(2, TimeUnit.SECONDS)))), + runAsync( + () -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) + .get(); + }); + + // Both should be fired after commit + CompletableFuture.allOf( + runAsync(() -> uncheck(() -> assertTrue(latch1.await(2, TimeUnit.SECONDS)))), + runAsync(() -> uncheck(() -> assertTrue(latch2.await(2, TimeUnit.SECONDS))))) + .get(); + }); + JooqTestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 2); + } + + /** + * Ensures that given the rollback of an inner transaction, any outbox work scheduled in the inner + * transaction is rolled back while the outer transaction's works. + */ + @Test + @Override + void testNestedWithInnerFailure() throws Exception { + CountDownLatch latch1 = new CountDownLatch(1); + CountDownLatch latch2 = new CountDownLatch(1); + ThreadLocalContextTransactionManager transactionManager = txManager(); + TransactionOutbox outbox = + TransactionOutbox.builder() + .transactionManager(transactionManager) + .persistor(Persistor.forDialect(connectionDetails().dialect())) + .instantiator(Instantiator.using(clazz -> new Worker(transactionManager))) + .attemptFrequency(Duration.of(1, ChronoUnit.SECONDS)) + .listener( + new TransactionOutboxListener() { + @Override + public void success(TransactionOutboxEntry entry) { + if (entry.getInvocation().getArgs()[0].equals(1)) { + latch1.countDown(); + } else { + latch2.countDown(); + } + } + }) + .build(); + + clearOutbox(); + + withRunningFlusher( + outbox, + () -> { + dsl.transaction( + ctx -> { + outbox.schedule(Worker.class).process(1); + + assertThrows( + UnsupportedOperationException.class, + () -> + ctx.dsl() + .transaction( + () -> { + outbox.schedule(Worker.class).process(2); + throw new UnsupportedOperationException(); + })); + + CompletableFuture.allOf( + runAsync( + () -> uncheck(() -> assertFalse(latch1.await(2, TimeUnit.SECONDS)))), + runAsync( + () -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) + .get(); + }); + + CompletableFuture.allOf( + runAsync(() -> uncheck(() -> assertTrue(latch1.await(2, TimeUnit.SECONDS)))), + runAsync(() -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) + .get(); + }); + } + + @SuppressWarnings("EmptyMethod") + static class Worker { + + private final ThreadLocalContextTransactionManager transactionManager; + + Worker(ThreadLocalContextTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + @SuppressWarnings("SameParameterValue") + void process(int i) { + JooqTestUtils.writeRecord(transactionManager, i); + } + } +} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/JooqTestUtils.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/JooqTestUtils.java new file mode 100644 index 00000000..1549e088 --- /dev/null +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/JooqTestUtils.java @@ -0,0 +1,51 @@ +package com.gruelbox.transactionoutbox.jooq.acceptance; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.gruelbox.transactionoutbox.ThreadLocalContextTransactionManager; +import com.gruelbox.transactionoutbox.Transaction; +import lombok.extern.slf4j.Slf4j; +import org.jooq.Configuration; +import org.jooq.DSLContext; +import org.jooq.Record; +import org.jooq.Table; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; + +@Slf4j +class JooqTestUtils { + + private static final Table TEST_TABLE = DSL.table("TEST_TABLE_JOOQ"); + private static final String VAL = "val"; + + static void createTestTable(DSLContext dsl) { + log.info("Creating table"); + dsl.dropTableIfExists(TEST_TABLE).execute(); + dsl.createTable(TEST_TABLE).column(VAL, SQLDataType.INTEGER).execute(); + } + + static void writeRecord(Configuration configuration, int value) { + log.info("Inserting record {}", value); + configuration.dsl().insertInto(TEST_TABLE).values(value).execute(); + } + + static void writeRecord(Transaction transaction, int value) { + Configuration configuration = transaction.context(); + writeRecord(configuration, value); + } + + static void writeRecord(ThreadLocalContextTransactionManager transactionManager, int value) { + transactionManager.requireTransaction(tx -> writeRecord(tx, value)); + } + + static void assertRecordExists(DSLContext dsl, int value) { + assertTrue( + dsl.select().from(TEST_TABLE).where(DSL.field(VAL).eq(value)).fetchOptional().isPresent()); + } + + static void assertRecordNotExists(DSLContext dsl, @SuppressWarnings("SameParameterValue") int value) { + assertFalse( + dsl.select().from(TEST_TABLE).where(DSL.field(VAL).eq(value)).fetchOptional().isPresent()); + } +} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalH2.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalH2.java new file mode 100644 index 00000000..1575cb5e --- /dev/null +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalH2.java @@ -0,0 +1,6 @@ +package com.gruelbox.transactionoutbox.jooq.acceptance; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class TestJooqThreadLocalH2 extends AbstractJooqAcceptanceThreadLocalTest {} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql5.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql5.java new file mode 100644 index 00000000..63ab27be --- /dev/null +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql5.java @@ -0,0 +1,37 @@ +package com.gruelbox.transactionoutbox.jooq.acceptance; + +import com.gruelbox.transactionoutbox.Dialect; +import java.time.Duration; +import lombok.extern.slf4j.Slf4j; +import org.jooq.SQLDialect; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Slf4j +@Testcontainers +class TestJooqThreadLocalMySql5 extends AbstractJooqAcceptanceThreadLocalTest { + + @Container + @SuppressWarnings({"rawtypes", "resource"}) + private static final JdbcDatabaseContainer container = + (JdbcDatabaseContainer) + new MySQLContainer("mysql:5").withStartupTimeout(Duration.ofMinutes(5)); + + @Override + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(Dialect.MY_SQL_5) + .driverClassName("com.mysql.cj.jdbc.Driver") + .url(container.getJdbcUrl()) + .user(container.getUsername()) + .password(container.getPassword()) + .build(); + } + + @Override + protected SQLDialect jooqDialect() { + return SQLDialect.MYSQL; + } +} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql8.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql8.java new file mode 100644 index 00000000..f4f1a546 --- /dev/null +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalMySql8.java @@ -0,0 +1,37 @@ +package com.gruelbox.transactionoutbox.jooq.acceptance; + +import com.gruelbox.transactionoutbox.*; +import java.time.Duration; +import lombok.extern.slf4j.Slf4j; +import org.jooq.SQLDialect; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Slf4j +@Testcontainers +class TestJooqThreadLocalMySql8 extends AbstractJooqAcceptanceThreadLocalTest { + + @Container + @SuppressWarnings({"rawtypes", "resource"}) + private static final JdbcDatabaseContainer container = + (JdbcDatabaseContainer) + new MySQLContainer("mysql:8").withStartupTimeout(Duration.ofMinutes(5)); + + @Override + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(Dialect.MY_SQL_8) + .driverClassName("com.mysql.cj.jdbc.Driver") + .url(container.getJdbcUrl()) + .user(container.getUsername()) + .password(container.getPassword()) + .build(); + } + + @Override + protected SQLDialect jooqDialect() { + return SQLDialect.MYSQL; + } +} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalPostgres16.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalPostgres16.java new file mode 100644 index 00000000..8a1565e4 --- /dev/null +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqThreadLocalPostgres16.java @@ -0,0 +1,37 @@ +package com.gruelbox.transactionoutbox.jooq.acceptance; + +import com.gruelbox.transactionoutbox.Dialect; +import java.time.Duration; +import lombok.extern.slf4j.Slf4j; +import org.jooq.SQLDialect; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Slf4j +@Testcontainers +class TestJooqThreadLocalPostgres16 extends AbstractJooqAcceptanceThreadLocalTest { + + @Container + @SuppressWarnings({"rawtypes", "resource"}) + private static final JdbcDatabaseContainer container = + (JdbcDatabaseContainer) + new PostgreSQLContainer("postgres:16").withStartupTimeout(Duration.ofHours(1)); + + @Override + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(Dialect.POSTGRESQL_9) + .driverClassName("org.postgresql.Driver") + .url(container.getJdbcUrl()) + .user(container.getUsername()) + .password(container.getPassword()) + .build(); + } + + @Override + protected SQLDialect jooqDialect() { + return SQLDialect.POSTGRES; + } +} diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithDefaultProviderAndExplicitlyPassedContext.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqTransactionManagerWithDefaultProviderAndExplicitlyPassedContext.java similarity index 92% rename from transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithDefaultProviderAndExplicitlyPassedContext.java rename to transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqTransactionManagerWithDefaultProviderAndExplicitlyPassedContext.java index c85a4be5..ae17c774 100644 --- a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithDefaultProviderAndExplicitlyPassedContext.java +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqTransactionManagerWithDefaultProviderAndExplicitlyPassedContext.java @@ -1,7 +1,8 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.jooq.acceptance; -import static com.gruelbox.transactionoutbox.acceptance.TestUtils.createTestTable; -import static com.gruelbox.transactionoutbox.acceptance.TestUtils.uncheck; +import static com.gruelbox.transactionoutbox.jooq.acceptance.JooqTestUtils.createTestTable; +import static com.gruelbox.transactionoutbox.testing.TestUtils.runSql; +import static com.gruelbox.transactionoutbox.testing.TestUtils.uncheck; import static java.util.concurrent.CompletableFuture.runAsync; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -56,7 +57,7 @@ void afterEach() { private HikariDataSource pooledDataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl( - "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=2000;LOB_TIMEOUT=2000;MV_STORE=TRUE"); + "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=2000;LOB_TIMEOUT=2000;MV_STORE=TRUE;DATABASE_TO_UPPER=FALSE"); config.setUsername("test"); config.setPassword("test"); config.addDataSourceProperty("cachePrepStmts", "true"); @@ -105,7 +106,7 @@ public void success(TransactionOutboxEntry entry) { // Should be fired after commit assertTrue(latch.await(2, TimeUnit.SECONDS)); - TestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 1); } @Test @@ -140,7 +141,7 @@ public void success(TransactionOutboxEntry entry) { // Should be fired after commit assertTrue(latch.await(2, TimeUnit.SECONDS)); - TestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 1); } @Test @@ -190,8 +191,8 @@ public void success(TransactionOutboxEntry entry) { runAsync(() -> uncheck(() -> assertTrue(latch2.await(2, TimeUnit.SECONDS))))) .get(); }); - TestUtils.assertRecordExists(dsl, 1); - TestUtils.assertRecordExists(dsl, 2); + JooqTestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 2); } @Test @@ -238,14 +239,14 @@ public void success(TransactionOutboxEntry entry) { () -> uncheck(() -> assertTrue(latch2.await(2, TimeUnit.SECONDS))))) .get(); - TestUtils.assertRecordExists(dsl, 2); + JooqTestUtils.assertRecordExists(dsl, 2); }); // Should be fired after commit assertTrue(latch1.await(2, TimeUnit.SECONDS)); }); - TestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 1); } /** @@ -306,12 +307,12 @@ public void success(TransactionOutboxEntry entry) { runAsync(() -> uncheck(() -> assertFalse(latch2.await(2, TimeUnit.SECONDS))))) .get(); }); - TestUtils.assertRecordExists(dsl, 1); - TestUtils.assertRecordNotExists(dsl, 2); + JooqTestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordNotExists(dsl, 2); } private void clearOutbox(TransactionManager transactionManager) { - TestUtils.runSql(transactionManager, "DELETE FROM TXNO_OUTBOX"); + runSql(transactionManager, "DELETE FROM TXNO_OUTBOX"); } private void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable) @@ -340,11 +341,11 @@ static class Worker { @SuppressWarnings("SameParameterValue") void process(int i, Transaction transaction) { - TestUtils.writeRecord(transaction, i); + JooqTestUtils.writeRecord(transaction, i); } void process(int i, Configuration configuration) { - TestUtils.writeRecord(configuration, i); + JooqTestUtils.writeRecord(configuration, i); } } } diff --git a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext.java b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext.java similarity index 96% rename from transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext.java rename to transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext.java index 3fccd97b..6a0f196f 100644 --- a/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/acceptance/TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext.java +++ b/transactionoutbox-jooq/src/test/java/com/gruelbox/transactionoutbox/jooq/acceptance/TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext.java @@ -1,6 +1,7 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.jooq.acceptance; -import static com.gruelbox.transactionoutbox.acceptance.TestUtils.uncheck; +import static com.gruelbox.transactionoutbox.testing.TestUtils.runSql; +import static com.gruelbox.transactionoutbox.testing.TestUtils.uncheck; import static java.util.concurrent.CompletableFuture.runAsync; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; @@ -61,7 +62,7 @@ class TestJooqTransactionManagerWithDefaultProviderAndThreadLocalContext { void beforeEach() { dataSource = pooledDataSource(); transactionManager = createTransactionManager(); - TestUtils.createTestTable(dsl); + JooqTestUtils.createTestTable(dsl); } @AfterEach @@ -72,7 +73,7 @@ void afterEach() { private HikariDataSource pooledDataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl( - "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=2000;LOB_TIMEOUT=2000;MV_STORE=TRUE"); + "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=2000;LOB_TIMEOUT=2000;MV_STORE=TRUE;DATABASE_TO_UPPER=FALSE"); config.setUsername("test"); config.setPassword("test"); config.addDataSourceProperty("cachePrepStmts", "true"); @@ -121,7 +122,7 @@ public void success(TransactionOutboxEntry entry) { // Should be fired after commit assertTrue(latch.await(2, TimeUnit.SECONDS)); - TestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 1); } @Test @@ -156,7 +157,7 @@ public void success(TransactionOutboxEntry entry) { // Should be fired after commit assertTrue(latch.await(2, TimeUnit.SECONDS)); - TestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 1); } @Test @@ -207,8 +208,8 @@ public void success(TransactionOutboxEntry entry) { runAsync(() -> uncheck(() -> assertTrue(latch2.await(2, TimeUnit.SECONDS))))) .get(); }); - TestUtils.assertRecordExists(dsl, 1); - TestUtils.assertRecordExists(dsl, 2); + JooqTestUtils.assertRecordExists(dsl, 1); + JooqTestUtils.assertRecordExists(dsl, 2); } /** @@ -355,7 +356,7 @@ public void success(TransactionOutboxEntry entry) { } private void clearOutbox(TransactionManager transactionManager) { - TestUtils.runSql(transactionManager, "DELETE FROM TXNO_OUTBOX"); + runSql(transactionManager, "DELETE FROM TXNO_OUTBOX"); } private void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable) @@ -395,7 +396,7 @@ static class Worker { @SuppressWarnings("SameParameterValue") void process(int i) { - TestUtils.writeRecord(transactionManager, i); + JooqTestUtils.writeRecord(transactionManager, i); } } diff --git a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/ApplicationConfig.java b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/ApplicationConfig.java similarity index 93% rename from transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/ApplicationConfig.java rename to transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/ApplicationConfig.java index ae74fc4f..568e6041 100644 --- a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/ApplicationConfig.java +++ b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/ApplicationConfig.java @@ -1,47 +1,47 @@ -package com.gruelbox.transactionoutbox.acceptance; - -import com.gruelbox.transactionoutbox.CdiInstantiator; -import com.gruelbox.transactionoutbox.Dialect; -import com.gruelbox.transactionoutbox.Persistor; -import com.gruelbox.transactionoutbox.QuarkusTransactionManager; -import com.gruelbox.transactionoutbox.TransactionOutbox; -import com.gruelbox.transactionoutbox.TransactionOutboxEntry; -import com.gruelbox.transactionoutbox.TransactionOutboxListener; -import java.util.HashSet; -import java.util.Set; -import javax.enterprise.inject.Produces; -import javax.ws.rs.core.Application; - -public class ApplicationConfig extends Application { - - @Override - public Set> getClasses() { - final Set> classes = new HashSet>(); - - classes.add(BusinessService.class); - - return classes; - } - - @Produces - public TransactionOutbox transactionOutbox( - QuarkusTransactionManager transactionManager, RemoteCallService testProxy) { - return TransactionOutbox.builder() - .instantiator(CdiInstantiator.create()) - .blockAfterAttempts(1) - .listener( - new TransactionOutboxListener() { - @Override - public void blocked(TransactionOutboxEntry entry, Throwable cause) { - block(testProxy); - } - }) - .transactionManager(transactionManager) - .persistor(Persistor.forDialect(Dialect.H2)) - .build(); - } - - private void block(RemoteCallService testProxy) { - testProxy.block(); - } -} +package com.gruelbox.transactionoutbox.quarkus.acceptance; + +import com.gruelbox.transactionoutbox.CdiInstantiator; +import com.gruelbox.transactionoutbox.Dialect; +import com.gruelbox.transactionoutbox.Persistor; +import com.gruelbox.transactionoutbox.QuarkusTransactionManager; +import com.gruelbox.transactionoutbox.TransactionOutbox; +import com.gruelbox.transactionoutbox.TransactionOutboxEntry; +import com.gruelbox.transactionoutbox.TransactionOutboxListener; +import java.util.HashSet; +import java.util.Set; +import javax.enterprise.inject.Produces; +import javax.ws.rs.core.Application; + +public class ApplicationConfig extends Application { + + @Override + public Set> getClasses() { + final Set> classes = new HashSet>(); + + classes.add(BusinessService.class); + + return classes; + } + + @Produces + public TransactionOutbox transactionOutbox( + QuarkusTransactionManager transactionManager, RemoteCallService testProxy) { + return TransactionOutbox.builder() + .instantiator(CdiInstantiator.create()) + .blockAfterAttempts(1) + .listener( + new TransactionOutboxListener() { + @Override + public void blocked(TransactionOutboxEntry entry, Throwable cause) { + block(testProxy); + } + }) + .transactionManager(transactionManager) + .persistor(Persistor.forDialect(Dialect.H2)) + .build(); + } + + private void block(RemoteCallService testProxy) { + testProxy.block(); + } +} diff --git a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/BusinessService.java b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/BusinessService.java similarity index 89% rename from transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/BusinessService.java rename to transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/BusinessService.java index 21a2fd93..83e7809f 100644 --- a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/BusinessService.java +++ b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/BusinessService.java @@ -1,25 +1,25 @@ -package com.gruelbox.transactionoutbox.acceptance; - -import com.gruelbox.transactionoutbox.TransactionOutbox; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.transaction.Transactional; - -@ApplicationScoped -public class BusinessService { - private DaoImpl dao; - - @Inject private TransactionOutbox outbox; - - @Inject - public BusinessService(DaoImpl dao) { - this.dao = dao; - } - - @Transactional - public void writeSomeThingAndRemoteCall(String value, boolean throwException) { - dao.writeSomethingIntoDatabase(value); - RemoteCallService proxy = outbox.schedule(RemoteCallService.class); - proxy.callRemote(throwException); - } -} +package com.gruelbox.transactionoutbox.quarkus.acceptance; + +import com.gruelbox.transactionoutbox.TransactionOutbox; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.transaction.Transactional; + +@ApplicationScoped +public class BusinessService { + private DaoImpl dao; + + @Inject private TransactionOutbox outbox; + + @Inject + public BusinessService(DaoImpl dao) { + this.dao = dao; + } + + @Transactional + public void writeSomeThingAndRemoteCall(String value, boolean throwException) { + dao.writeSomethingIntoDatabase(value); + RemoteCallService proxy = outbox.schedule(RemoteCallService.class); + proxy.callRemote(throwException); + } +} diff --git a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/BusinessServiceTest.java b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/BusinessServiceTest.java similarity index 96% rename from transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/BusinessServiceTest.java rename to transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/BusinessServiceTest.java index 07c8e9d9..18d7f5ed 100644 --- a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/BusinessServiceTest.java +++ b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/BusinessServiceTest.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.quarkus.acceptance; import io.quarkus.test.junit.QuarkusTest; import javax.inject.Inject; diff --git a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/DaoImpl.java b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/DaoImpl.java similarity index 96% rename from transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/DaoImpl.java rename to transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/DaoImpl.java index 5456e1de..c3314e75 100644 --- a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/DaoImpl.java +++ b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/DaoImpl.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.quarkus.acceptance; import java.sql.Connection; import java.sql.PreparedStatement; diff --git a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/RemoteCallService.java b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/RemoteCallService.java similarity index 91% rename from transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/RemoteCallService.java rename to transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/RemoteCallService.java index fce54af9..b8812e58 100644 --- a/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/acceptance/RemoteCallService.java +++ b/transactionoutbox-quarkus/src/test/java/com/gruelbox/transactionoutbox/quarkus/acceptance/RemoteCallService.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.quarkus.acceptance; import javax.enterprise.context.ApplicationScoped; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Customer.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Customer.java similarity index 85% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Customer.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Customer.java index cc6fd958..9858179e 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Customer.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Customer.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/CustomerRepository.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/CustomerRepository.java similarity index 76% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/CustomerRepository.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/CustomerRepository.java index 8fa7e06e..304ad8ad 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/CustomerRepository.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/CustomerRepository.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Event.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Event.java similarity index 87% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Event.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Event.java index 8d525b84..6016d2da 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Event.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Event.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventPublisher.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventPublisher.java similarity index 86% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventPublisher.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventPublisher.java index 8f12a1b6..9f581e2a 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventPublisher.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventPublisher.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import java.time.LocalDateTime; import org.springframework.beans.factory.annotation.Autowired; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventRepository.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventRepository.java similarity index 76% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventRepository.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventRepository.java index 94ff98de..b8a390f8 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventRepository.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventRepository.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventuallyConsistentController.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventuallyConsistentController.java similarity index 96% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventuallyConsistentController.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventuallyConsistentController.java index cc058a19..76ae36ab 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventuallyConsistentController.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventuallyConsistentController.java @@ -1,7 +1,8 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import com.gruelbox.transactionoutbox.TransactionOutbox; import java.time.LocalDateTime; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventuallyConsistentControllerTest.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventuallyConsistentControllerTest.java similarity index 95% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventuallyConsistentControllerTest.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventuallyConsistentControllerTest.java index d2ecc21d..c6ba8659 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/EventuallyConsistentControllerTest.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/EventuallyConsistentControllerTest.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/ExternalsConfiguration.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/ExternalsConfiguration.java similarity index 83% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/ExternalsConfiguration.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/ExternalsConfiguration.java index 0910c042..b59854f9 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/ExternalsConfiguration.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/ExternalsConfiguration.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import com.gruelbox.transactionoutbox.SpringTransactionOutboxConfiguration; import org.springframework.context.annotation.Configuration; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/TransactionOutboxSpringDemoApplication.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/TransactionOutboxSpringDemoApplication.java similarity index 94% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/TransactionOutboxSpringDemoApplication.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/TransactionOutboxSpringDemoApplication.java index 0d6f5e8b..0b753afa 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/TransactionOutboxSpringDemoApplication.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/TransactionOutboxSpringDemoApplication.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import com.gruelbox.transactionoutbox.Dialect; import com.gruelbox.transactionoutbox.Persistor; diff --git a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Utils.java b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Utils.java similarity index 93% rename from transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Utils.java rename to transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Utils.java index a3f21a81..57253736 100644 --- a/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/acceptance/Utils.java +++ b/transactionoutbox-spring/src/test/java/com/gruelbox/transactionoutbox/spring/acceptance/Utils.java @@ -1,4 +1,4 @@ -package com.gruelbox.transactionoutbox.acceptance; +package com.gruelbox.transactionoutbox.spring.acceptance; import com.gruelbox.transactionoutbox.ThrowingRunnable; import java.util.Arrays; diff --git a/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java index 38cdbb57..d923be77 100644 --- a/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java +++ b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java @@ -28,9 +28,7 @@ import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import org.hamcrest.MatcherAssert; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,9 +39,23 @@ public abstract class AbstractAcceptanceTest { private final ExecutorService unreliablePool = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(16)); - protected abstract ConnectionDetails connectionDetails(); - private static final Random random = new Random(); + protected HikariDataSource dataSource; + + @BeforeEach + final void baseBeforeEach() { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(connectionDetails().url()); + config.setUsername(connectionDetails().user()); + config.setPassword(connectionDetails().password()); + config.addDataSourceProperty("cachePrepStmts", "true"); + dataSource = new HikariDataSource(config); + } + + @AfterEach + final void baseAfterEach() { + dataSource.close(); + } /** * Uses a simple direct transaction manager and connection manager and attempts to fire an @@ -53,7 +65,7 @@ public abstract class AbstractAcceptanceTest { final void simpleConnectionProviderCustomInstantiatorInterfaceClass() throws InterruptedException { - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); CountDownLatch latch = new CountDownLatch(1); CountDownLatch chainedLatch = new CountDownLatch(1); @@ -110,7 +122,7 @@ public void success(TransactionOutboxEntry entry) { @Test final void noAutomaticInitialization() { - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); TransactionOutbox outbox = TransactionOutbox.builder() .transactionManager(transactionManager) @@ -124,7 +136,7 @@ final void noAutomaticInitialization() { .initializeImmediately(false) .build(); - Persistor.forDialect(connectionDetails().dialect()).migrate(simpleTxnManager()); + Persistor.forDialect(connectionDetails().dialect()).migrate(txManager()); clearOutbox(); Assertions.assertThrows( @@ -137,7 +149,7 @@ final void noAutomaticInitialization() { @Test void duplicateRequests() { - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); List ids = new ArrayList<>(); AtomicReference clockProvider = new AtomicReference<>(Clock.systemDefaultZone()); @@ -242,7 +254,7 @@ public void success(TransactionOutboxEntry entry) { @Test final void dataSourceConnectionProviderReflectionInstantiatorConcreteClass() throws InterruptedException { - try (HikariDataSource ds = pooledDataSource()) { + try (HikariDataSource ds = dataSource) { CountDownLatch latch = new CountDownLatch(1); @@ -363,7 +375,7 @@ public T requireTransactionReturns( */ @Test final void retryBehaviour() throws Exception { - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); CountDownLatch latch = new CountDownLatch(1); AtomicInteger attempts = new AtomicInteger(); TransactionOutbox outbox = @@ -393,7 +405,7 @@ final void onSchedulingFailure_BubbleExceptionsUp() throws Exception { Dialect.MY_SQL_8.equals(connectionDetails().dialect()) || Dialect.MY_SQL_5.equals(connectionDetails().dialect())); - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); CountDownLatch latch = new CountDownLatch(1); TransactionOutbox outbox = TransactionOutbox.builder() @@ -432,7 +444,7 @@ final void onSchedulingFailure_BubbleExceptionsUp() throws Exception { @Test final void lastAttemptTime_updatesEveryTime() throws Exception { - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); CountDownLatch successLatch = new CountDownLatch(1); CountDownLatch blockLatch = new CountDownLatch(1); AtomicInteger attempts = new AtomicInteger(); @@ -487,7 +499,7 @@ final void lastAttemptTime_updatesEveryTime() throws Exception { */ @Test final void blockAndThenUnblockForRetry() throws Exception { - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); CountDownLatch successLatch = new CountDownLatch(1); CountDownLatch blockLatch = new CountDownLatch(1); LatchListener latchListener = new LatchListener(successLatch, blockLatch); @@ -523,7 +535,7 @@ final void blockAndThenUnblockForRetry() throws Exception { final void highVolumeUnreliable() throws Exception { int count = 10; - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); CountDownLatch latch = new CountDownLatch(count * 10); ConcurrentHashMap results = new ConcurrentHashMap<>(); ConcurrentHashMap duplicates = new ConcurrentHashMap<>(); @@ -573,26 +585,24 @@ public void success(TransactionOutboxEntry entry) { containsInAnyOrder(IntStream.range(0, count * 10).boxed().toArray())); } - protected TransactionManager simpleTxnManager() { - return TransactionManager.fromConnectionDetails( - connectionDetails().driverClassName(), - connectionDetails().url(), - connectionDetails().user(), - connectionDetails().password()); + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(Dialect.H2) + .driverClassName("org.h2.Driver") + .url( + "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=60000;LOB_TIMEOUT=2000;MV_STORE=TRUE;DATABASE_TO_UPPER=FALSE") + .user("test") + .password("test") + .build(); } - private HikariDataSource pooledDataSource() { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl(connectionDetails().url()); - config.setUsername(connectionDetails().user()); - config.setPassword(connectionDetails().password()); - config.addDataSourceProperty("cachePrepStmts", "true"); - return new HikariDataSource(config); + protected TransactionManager txManager() { + return TransactionManager.fromDataSource(dataSource); } protected void clearOutbox() { DefaultPersistor persistor = Persistor.forDialect(connectionDetails().dialect()); - TransactionManager transactionManager = simpleTxnManager(); + TransactionManager transactionManager = txManager(); transactionManager.inTransaction( tx -> { try { @@ -603,7 +613,7 @@ protected void clearOutbox() { }); } - private void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable) + protected void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable) throws Exception { Thread backgroundThread = new Thread( diff --git a/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/TestUtils.java b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/TestUtils.java index e4c119fc..7c9fd8b0 100644 --- a/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/TestUtils.java +++ b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/TestUtils.java @@ -1,6 +1,8 @@ package com.gruelbox.transactionoutbox.testing; +import com.gruelbox.transactionoutbox.ThrowingRunnable; import com.gruelbox.transactionoutbox.TransactionManager; +import com.gruelbox.transactionoutbox.UncheckedException; import java.sql.Statement; public class TestUtils { @@ -18,4 +20,22 @@ public static void runSql(TransactionManager transactionManager, String sql) { } }); } + + public static void uncheck(ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (Exception e) { + uncheckAndThrow(e); + } + } + + public static T uncheckAndThrow(Throwable e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + if (e instanceof Error) { + throw (Error) e; + } + throw new UncheckedException(e); + } }