// Copyright (c) 2006 Dustin Sallings <dustin@spy.net> package net.spy.db; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import net.spy.concurrent.SynchronizationObject; import net.spy.test.db.DeleteTest; import net.spy.util.SpyConfig; import org.jmock.Mock; import org.jmock.MockObjectTestCase; import org.jmock.core.constraint.IsEqual; import org.jmock.core.matcher.InvokeOnceMatcher; import org.jmock.core.stub.ReturnStub; /** * Test the transaction pipeline. */ public class TransactionPipelineTest extends MockObjectTestCase { private SpyConfig successConfig=null; @Override protected void setUp() { successConfig=new SpyConfig(); successConfig.put("dbConnectionSource", SuccessConnectionSource.class.getName()); } /** * Test a simple transaction. */ public void testSimpleTransaction() throws Exception { TransactionPipeline tp=new TransactionPipeline(); TestSavable ts=new TestSavable("testSimpleTransaction"); assertTrue(ts.isNew()); tp.addTransaction(ts, successConfig); ts.so.waitUntilNotNull(1, TimeUnit.SECONDS); assertFalse(ts.isNew()); tp.shutdown(); } /** * Test using a future to monitor the transaction. */ public void testSimpleTransactionFuture() throws Exception { TransactionPipeline tp=new TransactionPipeline(); TestSavable ts=new TestSavable("testSimpleTransactionFuture"); assertTrue(ts.isNew()); ScheduledFuture<?> f=tp.addTransaction(ts, successConfig); try { f.get(25, TimeUnit.MILLISECONDS); fail("Shouldn't got a return value in the first 25ms"); } catch (TimeoutException e) { // expected } f.get(750, TimeUnit.MILLISECONDS); assertFalse(ts.isNew()); tp.shutdown(); } /** * Test two transactions. */ public void testTwoTransactions() throws Exception { TransactionPipeline tp=new TransactionPipeline(); TestSavable ts1=new TestSavable("testTwoTransactions(1)"); assertTrue(ts1.isNew()); tp.addTransaction(ts1, successConfig); Thread.sleep(250); assertTrue(ts1.isNew()); TestSavable ts2=new TestSavable("testTwoTransactions(2)"); ScheduledFuture<?>f=tp.addTransaction(ts2, successConfig); ts1.so.waitUntilNotNull(1, TimeUnit.SECONDS); assertFalse(ts1.isNew()); assertTrue(ts2.isNew()); f.get(350, TimeUnit.MILLISECONDS); assertFalse(ts2.isNew()); tp.shutdown(); } private static class TestSavable extends AbstractSavable { private String which=null; public SynchronizationObject<Boolean> so= new SynchronizationObject<Boolean>(null); public TestSavable(String w) { super(); setModified(false); setNew(true); which=w; } public void save(Connection conn, SaveContext ctx) throws SaveException, SQLException { try { DeleteTest dt=new DeleteTest(conn); dt.setSomeColumn(1); dt.executeUpdate(); } catch(Throwable t) { getLogger().error("Problem saving " + which, t); if(t instanceof RuntimeException) { throw(RuntimeException)t; } else if(t instanceof Error) { throw(Error)t; } else if(t instanceof SQLException) { throw(SQLException)t; } else if(t instanceof SaveException) { throw(SaveException)t; } } } @Override public void transactionCommited() { super.transactionCommited(); so.set(Boolean.TRUE); } } /** * A connection source for mock connections. */ public static class SuccessConnectionSource extends MockConnectionSource { @Override protected void setupMock(Mock connMock, SpyConfig conf) { // autocommit will be enabled, and then disabled connMock.expects(new InvokeOnceMatcher()).method("setAutoCommit") .with(new IsEqual(Boolean.FALSE)).id("disableAutocommit"); connMock.expects(new InvokeOnceMatcher()).method("commit") .after("disableAutocommit").id("commitSuccess"); connMock.expects(new InvokeOnceMatcher()).method("setAutoCommit") .with(new IsEqual(Boolean.TRUE)).after("commitSuccess"); Mock mdMock=new Mock(DatabaseMetaData.class); mdMock.expects(new InvokeOnceMatcher()) .method("getDatabaseProductName") .will(new ReturnStub("Unknown database")); connMock.expects(new InvokeOnceMatcher()) .method("getMetaData") .will(new ReturnStub(mdMock.proxy())); Mock pstMock=new Mock(PreparedStatement.class); registerMock(pstMock); pstMock.expects(new InvokeOnceMatcher()).method("setInt") .with(new IsEqual(1), new IsEqual(1)); pstMock.expects(new InvokeOnceMatcher()) .method("setQueryTimeout") .with(new IsEqual(0)); pstMock.expects(new InvokeOnceMatcher()) .method("setMaxRows") .with(new IsEqual(0)); pstMock.expects(new InvokeOnceMatcher()).method("executeUpdate") .will(new ReturnStub(1)); try { connMock.expects(new InvokeOnceMatcher()) .method("prepareStatement") .with(new IsEqual(new DeleteTest(new SpyConfig()) .getRegisteredQueries() .get(QuerySelector.DEFAULT_QUERY))) .will(new ReturnStub(pstMock.proxy())); } catch (SQLException e) { throw new RuntimeException("Couldn't set up delete", e); } connMock.expects(new InvokeOnceMatcher()).method("close"); } } }