package com.github.witoldsz.ultm.test; import com.github.witoldsz.ultm.TxManager; import com.github.witoldsz.ultm.ULTM; import com.github.witoldsz.ultm.UnitOfWorkException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import static java.util.Arrays.asList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static java.util.concurrent.TimeUnit.SECONDS; import javax.sql.DataSource; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import org.junit.After; import org.junit.Test; import static org.junit.Assert.*; import org.junit.Before; /** * * @author witoldsz */ public class ULTMTest { private final H2DemoDatabase h2DemoDatabase = new H2DemoDatabase(); private TxManager txManager; private DataSource managedDataSource; @Before public void setup() throws SQLException { h2DemoDatabase.setup(); ULTM ultm = new ULTM(h2DemoDatabase.getDataSource()); txManager = ultm.getTxManager(); managedDataSource = ultm.getManagedDataSource(); } @After public void tearDown() throws SQLException { h2DemoDatabase.tearDown(); } private int insertPerson() throws SQLException { try (Connection conn = managedDataSource.getConnection()) { return conn.createStatement().executeUpdate("insert into PERSONS (ID, NAME) values (1, 'Mr Foo');"); } } private Integer personsCount() throws SQLException { try (Connection conn = managedDataSource.getConnection()) { try (ResultSet r = conn.createStatement().executeQuery("select count(*) from PERSONS;")) { r.first(); return r.getInt(1); } } } @Test public void should_begin_and_commit() throws SQLException { txManager.begin(); insertPerson(); assertThat(personsCount(), is(1)); txManager.commit(); txManager.begin(); assertThat(personsCount(), is(1)); txManager.commit(); } @Test public void should_begin_and_rollback() throws SQLException { txManager.begin(); insertPerson(); assertThat(personsCount(), is(1)); txManager.rollback(); txManager.begin(); assertThat(personsCount(), is(0)); txManager.commit(); } @Test public void should_wrap_tx_within_UnitOfWork() { txManager.tx(this::insertPerson); assertThat(txManager.txResult(this::personsCount), is(1)); } @Test public void should_rollback_tx_within_UnitOfWork() { try { txManager.tx(() -> { insertPerson(); throw new RuntimeException("Something happened!"); }); fail("This test should not get here."); } catch (RuntimeException e) { // ignore } assertThat(txManager.txResult(this::personsCount), is(0)); } @Test public void should_wrap_tx_within_UnitOfWorkCall_with_result() { Integer result = txManager.txResult(this::insertPerson); assertThat(txManager.txResult(this::personsCount), is(1)); assertThat(result, is(1)); } @Test public void should_rollback_tx_within_UnitOfWorkCall_with_result() { try { Object ignore = txManager.txResult(() -> { insertPerson(); throw new RuntimeException("Something happened!"); }); fail("This test should not get here."); } catch (RuntimeException e) { // noop } assertThat(txManager.txResult(this::personsCount), is(0)); } @Test public void should_propagate_every_exceptions_unwrapped_from_UnitOfWork() { try { txManager.txUnwrapped(() -> { throw new Exception("Something bad happened"); }); fail("This test should not get here."); } catch (Exception ex) { assertThat(ex.getClass(), equalTo(Exception.class)); assertThat(ex.getMessage(), is("Something bad happened")); } } @Test public void should_propagate_every_exceptions_as_is_from_UnitOfWorkCall() { try { txManager.txUnwrappedResult(() -> { throw new Exception("Something bad happened"); }); fail("This test should not get here."); } catch (Exception ex) { assertThat(ex.getClass(), equalTo(Exception.class)); assertThat(ex.getMessage(), is("Something bad happened")); } } @Test public void should_wrap_checked_exceptions_from_UnitOfWork() { try { txManager.tx(() -> { throw new Exception("Something bad happened"); }); fail("This test should not get here."); } catch (Exception ex) { assertThat(ex.getClass(), equalTo(UnitOfWorkException.class)); assertThat(ex.getCause().getMessage(), is("Something bad happened")); } } @Test public void should_wrap_checked_exceptions_from_UnitOfWorkCall() { try { txManager.txResult(() -> { throw new Exception("Something bad happened"); }); fail("This test should not get here."); } catch (Exception ex) { assertThat(ex.getClass(), equalTo(UnitOfWorkException.class)); assertThat(ex.getCause().getMessage(), is("Something bad happened")); } } @Test public void should_execute_afterRollbackListener_in_new_transaction() { // setup List<Integer> personCount = new ArrayList<>(); txManager.setAfterRollbackListener(() -> personCount.add(txManager.txResult(this::personsCount))); // let's play try { txManager.tx(() -> { insertPerson(); throw new RuntimeException("Something bad happened"); }); } catch (RuntimeException ex) {/* ignore */} assertThat(personCount, equalTo(asList(0))); } @Test public void many_threads_torture_scenario() throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(50); for (int i = 0; i < 1000; ++i) { int id = i; executor.submit(() -> txManager.tx(() -> { insertPerson(); if (id % 2 == 1) { throw new RuntimeException("Odd have no luck..."); } })); } executor.shutdown(); executor.awaitTermination(15, SECONDS); //usually few hundred ms should do assertThat(txManager.txResult(this::personsCount), is(500)); } @Test public void should_allow_noop() { txManager.begin(); // noop txManager.commit(); txManager.begin(); // noop txManager.rollback(); txManager.tx(() -> {/* noop */}); } @Test(expected = IllegalStateException.class) public void should_not_allow_begin_within_transaction() { try { txManager.begin(); txManager.begin(); } catch (IllegalStateException ex) { assertThat(ex.getMessage(), is("Transaction is in progress already.")); throw ex; } } @Test(expected = IllegalStateException.class) public void should_not_allow_commit_without_transaction() { try { txManager.commit(); } catch (IllegalStateException ex) { assertThat("commit", ex.getMessage(), is("Transaction is not active.")); throw ex; } } @Test(expected = IllegalStateException.class) public void should_not_allow_rollback_without_transaction() { try { txManager.rollback(); } catch (IllegalStateException ex) { assertThat("rollback", ex.getMessage(), is("Transaction is not active.")); throw ex; } } @Test(expected = IllegalStateException.class) public void should_throw_when_acting_without_transaction() throws SQLException { try { insertPerson(); } catch (IllegalStateException ex) { assertThat(ex.getMessage(), is("Transaction is not active.")); throw ex; } } }