// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.transaction; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; /** * @author bratseth */ public class NestedTransactionTestCase { @Test public void testNestedTransaction() { NestedTransaction t = new NestedTransaction(); t.add(new TransactionTypeB("B1"), TransactionTypeC.class); t.add(new TransactionTypeC("C1")); t.add(new TransactionTypeA("A1"), TransactionTypeB.class); t.add(new TransactionTypeA("A2")); t.add(new TransactionTypeC("C2")); // Add two tasks to run after commit MutableInteger tasksRun = new MutableInteger(); t.onCommitted(() -> { tasksRun.value++; }); t.onCommitted(() -> { tasksRun.value++; }); assertEquals(3, t.transactions().size()); assertEquals(TransactionTypeA.class, t.transactions().get(0).getClass()); assertEquals(TransactionTypeB.class, t.transactions().get(1).getClass()); assertEquals(TransactionTypeC.class, t.transactions().get(2).getClass()); assertEquals("A1", ((MockOperation)t.transactions().get(0).operations().get(0)).name); assertEquals("A2", ((MockOperation)t.transactions().get(0).operations().get(1)).name); assertEquals("B1", ((MockOperation)t.transactions().get(1).operations().get(0)).name); assertEquals("C1", ((MockOperation)t.transactions().get(2).operations().get(0)).name); assertEquals("C2", ((MockOperation)t.transactions().get(2).operations().get(1)).name); t.commit(); assertTrue(((MockTransaction)t.transactions().get(0)).committed); assertTrue(((MockTransaction)t.transactions().get(1)).committed); assertTrue(((MockTransaction)t.transactions().get(2)).committed); assertEquals("After commit tasks are run", 2, tasksRun.value); } @Test public void testNestedTransactionFailingOnCommit() { NestedTransaction t = new NestedTransaction(); t.add(new TransactionTypeC("C1")); t.add(new TransactionTypeA("A1"), TransactionTypeB.class, FailAtCommitTransactionType.class); t.add(new TransactionTypeA("A2")); t.add(new FailAtCommitTransactionType("Fail"), TransactionTypeC.class); t.add(new TransactionTypeC("C2")); t.add(new TransactionTypeB("B1"), TransactionTypeC.class, FailAtCommitTransactionType.class); // Add task to run after commit MutableInteger tasksRun = new MutableInteger(); t.onCommitted(() -> { tasksRun.value++; }); assertEquals(4, t.transactions().size()); assertEquals(TransactionTypeA.class, t.transactions().get(0).getClass()); assertEquals(TransactionTypeB.class, t.transactions().get(1).getClass()); assertEquals(FailAtCommitTransactionType.class, t.transactions().get(2).getClass()); assertEquals(TransactionTypeC.class, t.transactions().get(3).getClass()); try { t.commit(); } catch (IllegalStateException expected) { } assertTrue(((MockTransaction)t.transactions().get(0)).rolledback); assertTrue(((MockTransaction)t.transactions().get(1)).rolledback); assertFalse(((MockTransaction) t.transactions().get(2)).committed); assertEquals("After commit tasks are not run", 0, tasksRun.value); } @Test public void testConflictingOrdering() { NestedTransaction t = new NestedTransaction(); t.add(new TransactionTypeA("A1"), TransactionTypeB.class); t.add(new TransactionTypeB("B1"), TransactionTypeC.class); t.add(new TransactionTypeC("C1"), TransactionTypeA.class); try { t.commit(); fail("Expected exception"); } catch (IllegalStateException expected) { } } @Test public void testMoreThanOneCommitThrows() { NestedTransaction t = new NestedTransaction(); t.add(new TransactionTypeA("A1"), TransactionTypeB.class); t.commit(); try { t.commit(); fail("Expected exception"); } catch (IllegalStateException expected) { } } private static class TransactionTypeA extends MockTransaction { public TransactionTypeA(String name) { super(name); } } private static class TransactionTypeB extends MockTransaction { public TransactionTypeB(String name) { super(name); } } private static class TransactionTypeC extends MockTransaction { public TransactionTypeC(String name) { super(name); } } private static class FailAtCommitTransactionType extends MockTransaction { public FailAtCommitTransactionType(String name) { super(name); } @Override public void commit() { throw new RuntimeException(); } } private static class MockTransaction extends AbstractTransaction { public boolean prepared = false, committed = false, rolledback = false; public MockTransaction(String name) { add(new MockOperation(name)); } @Override public void prepare() { prepared = true; } @Override public void commit() { if ( ! prepared) throw new IllegalStateException("Commit before prepare"); committed = true; } @Override public void rollbackOrLog() { if ( ! committed) throw new IllegalStateException("Rollback before commit"); rolledback = true; } } private static class MockOperation implements Transaction.Operation { public String name; public MockOperation(String name) { this.name = name; } } private static class MutableInteger { public int value = 0; } }