package org.ovirt.engine.core.utils.transaction;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.Mock;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.powermock.api.mockito.PowerMockito.doThrow;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import javax.ejb.TransactionRolledbackLocalException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.ovirt.engine.core.compat.TransactionScopeOption;
import org.ovirt.engine.core.utils.ejb.ContainerManagedResourceType;
import org.ovirt.engine.core.utils.ejb.EjbUtils;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@PrepareForTest({EjbUtils.class})
@RunWith(PowerMockRunner.class)
public class TransactionSupportTest {
@Mock TransactionManager mgr = mock(TransactionManager.class);
@Mock Transaction transaction;
TransactionMethod<Boolean> tm = new TransactionMethod<Boolean>() {
boolean succeeded = false;
@Override
public Boolean runInTransaction() {
succeeded = true;
return succeeded;
}
};
public TransactionSupportTest() {
initMocks(this);
mockStatic(EjbUtils.class);
when(EjbUtils.findResource(ContainerManagedResourceType.TRANSACTION_MANAGER)).thenReturn(mgr);
}
@Test(expected = RuntimeException.class)
public void resumeWithError() throws InvalidTransactionException, SystemException {
doThrow(new SystemException()).when(mgr).resume(any(Transaction.class));
TransactionSupport.resume(mock(Transaction.class));
}
@Test(expected = RuntimeException.class)
public void suspendWithError() throws SystemException {
doThrow(new SystemException()).when(mgr).suspend();
TransactionSupport.suspend();
}
@Test(expected = RuntimeException.class)
public void currentWithError() throws SystemException {
getTransactionThrowsSystemException();
TransactionSupport.current();
}
@Test
public void registerRollbackHandler() throws SystemException, RollbackException {
transactionManagerReturnsTransaction();
RollbackHandler handler = mock(RollbackHandler.class);
TransactionSupport.registerRollbackHandler(handler);
verify(transaction).registerSynchronization(any(Synchronization.class));
}
@Test(expected = RuntimeException.class)
public void statusCheckFailed() throws SystemException {
when(mgr.getStatus()).thenThrow(new SystemException());
TransactionSupport.executeInScope(TransactionScopeOption.Required, tm);
}
@Test(expected = TransactionRolledbackLocalException.class)
public void needToRollbackRollback() throws SystemException {
when(mgr.getStatus()).thenReturn(Status.STATUS_MARKED_ROLLBACK);
TransactionSupport.executeInScope(TransactionScopeOption.Required, tm);
}
@Test(expected = TransactionRolledbackLocalException.class)
public void needToRollbackRolledBack() throws SystemException {
when(mgr.getStatus()).thenReturn(Status.STATUS_ROLLEDBACK);
TransactionSupport.executeInScope(TransactionScopeOption.Required, tm);
}
@Test(expected = TransactionRolledbackLocalException.class)
public void needToRollbackInProgress() throws SystemException {
when(mgr.getStatus()).thenReturn(Status.STATUS_ROLLING_BACK);
TransactionSupport.executeInScope(TransactionScopeOption.Required, tm);
}
@Test
public void executeInScopeRequiresNew() throws SystemException {
transactionManagerReturnsTransaction();
assertTrue(TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew, tm));
}
@Test
public void requiresNewSuspendsExisting() throws Exception {
Transaction existingTrans = mock(Transaction.class);
Transaction newTrans = mock(Transaction.class);
when(mgr.getTransaction()).thenReturn(existingTrans).thenReturn(newTrans);
when(mgr.suspend()).thenReturn(existingTrans);
TransactionSupport.executeInNewTransaction(tm);
InOrder inOrder = inOrder(mgr);
inOrder.verify(mgr).suspend();
inOrder.verify(mgr).begin();
inOrder.verify(mgr).commit();
inOrder.verify(mgr).resume(existingTrans);
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesCodeThrowing() throws SystemException {
transactionManagerReturnsTransaction();
try {
TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
throw new RuntimeException();
}
});
} catch (RuntimeException e) {
verify(mgr).rollback();
throw e;
}
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesSystemException() throws SystemException {
getTransactionThrowsSystemException();
TransactionSupport.executeInNewTransaction(tm);
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesSecurityException() throws SystemException {
transactionManagerReturnsTransaction();
when(transaction.getStatus()).thenReturn(Status.STATUS_MARKED_ROLLBACK);
doThrow(new SecurityException()).when(mgr).rollback();
TransactionSupport.executeInNewTransaction(tm);
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesIllegalStateException() throws Exception {
exceptionDuringCommitTest(new IllegalStateException());
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesRollbackException() throws Exception {
exceptionDuringCommitTest(new RollbackException("foo"));
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesHeuristicMixedException() throws Exception {
exceptionDuringCommitTest(new HeuristicMixedException());
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesHeuristicRollbackException() throws Exception {
exceptionDuringCommitTest(new HeuristicRollbackException());
}
@Test(expected = RuntimeException.class)
public void requiresNewHandlesNotSupportedException() throws Exception {
transactionManagerReturnsTransaction();
doThrow(new NotSupportedException()).when(mgr).begin();
TransactionSupport.executeInNewTransaction(tm);
}
@Test(expected = RuntimeException.class)
public void requiresHandlesRuntimeException() throws Exception {
transactionManagerReturnsTransaction();
TransactionSupport.executeInScope(TransactionScopeOption.Required, new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
throw new RuntimeException("foo");
}
});
}
@Test(expected = RuntimeException.class)
public void requiredHasCompletedTransaction() throws Exception {
transactionManagerReturnsTransaction();
when(mgr.getStatus()).thenReturn(Status.STATUS_COMMITTED);
TransactionSupport.executeInScope(TransactionScopeOption.Required, tm);
}
@Test(expected = RuntimeException.class)
public void requiredTransactionRollbackNeeded() throws Exception {
transactionManagerReturnsTransaction();
when(mgr.getStatus()).thenReturn(Status.STATUS_MARKED_ROLLBACK);
TransactionSupport.executeInScope(TransactionScopeOption.Required, tm);
}
@Test
public void executeInScopeSuppress() {
assertTrue(TransactionSupport.executeInScope(TransactionScopeOption.Suppress, tm));
}
@Test
public void executeInScopeRequired() {
assertTrue(TransactionSupport.executeInScope(TransactionScopeOption.Required, tm));
}
@Test
public void setRollback() throws SystemException {
transactionManagerReturnsTransaction();
TransactionSupport.setRollbackOnly();
verify(transaction).setRollbackOnly();
}
@Test(expected = RuntimeException.class)
public void setRollbackFailure() throws SystemException {
getTransactionThrowsSystemException();
TransactionSupport.setRollbackOnly();
}
@Test
public void executeSuppressed() throws SystemException, InvalidTransactionException {
transactionManagerReturnsTransaction();
suspendReturnsTransaction();
assertTrue(TransactionSupport.executeInScope(TransactionScopeOption.Suppress, tm));
InOrder inOrder = inOrder(mgr);
inOrder.verify(mgr).suspend();
inOrder.verify(mgr).resume(transaction);
}
@Test(expected = RuntimeException.class)
public void executeSuppressedThrowsRuntime() throws SystemException {
when(mgr.getTransaction()).thenThrow(new RuntimeException());
TransactionSupport.executeInScope(TransactionScopeOption.Suppress, tm);
}
@Test(expected = RuntimeException.class)
public void executeSuppressedHandlesSystemException() throws SystemException {
getTransactionThrowsSystemException();
TransactionSupport.executeInScope(TransactionScopeOption.Suppress, tm);
}
@Test(expected = RuntimeException.class)
public void executeSuppressedHandlesInvalidTransactionException() throws SystemException, InvalidTransactionException {
transactionManagerReturnsTransaction();
suspendReturnsTransaction();
doThrow(new InvalidTransactionException()).when(mgr).resume(transaction);
TransactionSupport.executeInScope(TransactionScopeOption.Suppress, tm);
}
private void exceptionDuringCommitTest(Exception e) throws Exception {
transactionManagerReturnsTransaction();
doThrow(e).when(mgr).commit();
TransactionSupport.executeInNewTransaction(tm);
}
private void transactionManagerReturnsTransaction() throws SystemException {
when(mgr.getTransaction()).thenReturn(transaction);
}
private void suspendReturnsTransaction() throws SystemException {
when(mgr.suspend()).thenReturn(transaction);
}
private void getTransactionThrowsSystemException() throws SystemException {
when(mgr.getTransaction()).thenThrow(new SystemException());
}
}