package org.multiverse.stms.gamma.transactions.fat;
import org.junit.Before;
import org.junit.Test;
import org.multiverse.SomeUncheckedException;
import org.multiverse.api.LockMode;
import org.multiverse.api.TxnStatus;
import org.multiverse.api.exceptions.*;
import org.multiverse.api.functions.Functions;
import org.multiverse.api.functions.LongFunction;
import org.multiverse.stms.gamma.GammaConstants;
import org.multiverse.stms.gamma.GammaStm;
import org.multiverse.stms.gamma.GammaTestUtils;
import org.multiverse.stms.gamma.transactionalobjects.GammaTxnLong;
import org.multiverse.stms.gamma.transactionalobjects.Tranlocal;
import org.multiverse.stms.gamma.transactions.GammaTxn;
import org.multiverse.stms.gamma.transactions.GammaTxnConfig;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.*;
import static org.multiverse.TestUtils.LOCKMODE_NONE;
import static org.multiverse.TestUtils.*;
import static org.multiverse.stms.gamma.GammaTestUtils.*;
public abstract class FatGammaTxn_commuteTest<T extends GammaTxn> {
protected GammaStm stm;
@Before
public void setUp() {
stm = new GammaStm();
}
protected abstract T newTransaction();
protected abstract T newTransaction(GammaTxnConfig config);
protected abstract int getMaxCapacity();
@Test
public void whenTransactionAbortOnly_thenWriteStillPossible() {
GammaTxnLong ref = new GammaTxnLong(stm, 0);
GammaTxn tx = stm.newDefaultTxn();
tx.setAbortOnly();
ref.commute(tx, Functions.incLongFunction());
Tranlocal tranlocal = tx.locate(ref);
assertNotNull(tranlocal);
assertEquals(TRANLOCAL_COMMUTING, tranlocal.getMode());
assertTrue(tx.isAbortOnly());
assertIsActive(tx);
}
@Test
public void whenMultipleCommutesOnSingleRef() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn tx = stm.newDefaultTxn();
ref.commute(tx, Functions.incLongFunction());
ref.commute(tx, Functions.incLongFunction());
ref.commute(tx, Functions.incLongFunction());
tx.commit();
assertRefHasNoLocks(ref);
assertVersionAndValue(ref, initialVersion + 1, initialValue + 3);
}
@Test
public void whenAlreadyOpenedForRead() {
whenAlreadyOpenedForRead(LockMode.None);
whenAlreadyOpenedForRead(LockMode.Read);
whenAlreadyOpenedForRead(LockMode.Write);
whenAlreadyOpenedForRead(LockMode.Exclusive);
}
public void whenAlreadyOpenedForRead(LockMode lockMode) {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.version;
GammaTxn tx = newTransaction();
Tranlocal tranlocal = ref.openForRead(tx, lockMode.asInt());
LongFunction incFunction = Functions.incLongFunction();
ref.commute(tx, incFunction);
assertEquals(initialValue + 1, tranlocal.long_value);
assertTrue(tx.hasWrites);
assertIsActive(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, lockMode);
assertTrue(tranlocal.isWrite());
assertNull(tranlocal.headCallable);
}
@Test
public void whenAlreadyOpenedForReadAndFunctionCausesProblem() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initalVersion = ref.getVersion();
GammaTxn tx = newTransaction();
Tranlocal tranlocal = ref.openForRead(tx, LOCKMODE_NONE);
LongFunction function = mock(LongFunction.class);
when(function.call(anyLong())).thenThrow(new SomeUncheckedException());
try {
ref.commute(tx, function);
fail();
} catch (SomeUncheckedException expected) {
}
assertIsAborted(tx);
assertNull(tranlocal.owner);
assertRefHasNoLocks(ref);
assertVersionAndValue(ref, initalVersion, initialValue);
}
@Test
public void whenAlreadyOpenedForWrite() {
whenAlreadyOpenedForWrite(LockMode.None);
whenAlreadyOpenedForWrite(LockMode.Read);
whenAlreadyOpenedForWrite(LockMode.Write);
whenAlreadyOpenedForWrite(LockMode.Exclusive);
}
public void whenAlreadyOpenedForWrite(LockMode lockMode) {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.version;
GammaTxn tx = newTransaction();
Tranlocal tranlocal = ref.openForWrite(tx, lockMode.asInt());
LongFunction incFunction = Functions.incLongFunction();
ref.commute(tx, incFunction);
assertEquals(initialValue + 1, tranlocal.long_value);
assertTrue(tx.hasWrites);
assertIsActive(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, lockMode);
assertTrue(tranlocal.isWrite());
assertNull(tranlocal.headCallable);
}
@Test
public void whenAlreadyOpenedForWriteAndFunctionCausesProblem() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initalVersion = ref.getVersion();
GammaTxn tx = newTransaction();
Tranlocal tranlocal = ref.openForWrite(tx, LOCKMODE_NONE);
LongFunction function = mock(LongFunction.class);
when(function.call(anyLong())).thenThrow(new SomeUncheckedException());
try {
ref.commute(tx, function);
fail();
} catch (SomeUncheckedException expected) {
}
assertIsAborted(tx);
assertNull(tranlocal.owner);
assertRefHasNoLocks(ref);
assertVersionAndValue(ref, initalVersion, initialValue);
}
@Test
public void whenNotOpenedBefore() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn tx = stm.newDefaultTxn();
LongFunction function = mock(LongFunction.class);
ref.commute(tx, function);
Tranlocal tranlocal = tx.getRefTranlocal(ref);
assertNotNull(tranlocal);
assertTrue(tranlocal.isCommuting());
assertSame(ref, tranlocal.owner);
assertEquals(LOCKMODE_NONE, tranlocal.getLockMode());
assertIsActive(tx);
GammaTestUtils.assertHasCommutingFunctions(tranlocal, function);
assertRefHasNoLocks(ref);
assertVersionAndValue(ref, initialVersion, initialValue);
}
@Test
public void whenAlreadyOpenedForCommute() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn tx = stm.newDefaultTxn();
LongFunction function1 = mock(LongFunction.class);
LongFunction function2 = mock(LongFunction.class);
ref.commute(tx, function1);
ref.commute(tx, function2);
Tranlocal tranlocal = tx.getRefTranlocal(ref);
assertNotNull(tranlocal);
assertTrue(tranlocal.isCommuting());
assertSame(ref, tranlocal.owner);
assertEquals(LOCKMODE_NONE, tranlocal.getLockMode());
assertIsActive(tx);
GammaTestUtils.assertHasCommutingFunctions(tranlocal, function2, function1);
assertRefHasNoLocks(ref);
assertVersionAndValue(ref, initialVersion, initialValue);
}
@Test
public void lockedByOther() {
lockedByOther(LockMode.None);
lockedByOther(LockMode.Read);
lockedByOther(LockMode.Write);
lockedByOther(LockMode.Exclusive);
}
public void lockedByOther(LockMode otherLockMode) {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn otherTx = stm.newDefaultTxn();
ref.getLock().acquire(otherTx, otherLockMode);
GammaTxn tx = stm.newDefaultTxn();
LongFunction function1 = mock(LongFunction.class);
LongFunction function2 = mock(LongFunction.class);
ref.commute(tx, function1);
ref.commute(tx, function2);
Tranlocal tranlocal = tx.getRefTranlocal(ref);
assertNotNull(tranlocal);
assertTrue(tranlocal.isCommuting());
assertSame(ref, tranlocal.owner);
assertEquals(LOCKMODE_NONE, tranlocal.getLockMode());
assertIsActive(tx);
GammaTestUtils.assertHasCommutingFunctions(tranlocal, function2, function1);
assertRefHasLockMode(ref, otherTx, otherLockMode.asInt());
assertVersionAndValue(ref, initialVersion, initialValue);
}
@Test
public void whenAlreadyOpenedForConstruction() {
GammaTxn tx = newTransaction();
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(tx, initialValue);
Tranlocal tranlocal = tx.locate(ref);
LongFunction incFunction = Functions.incLongFunction();
ref.commute(tx, incFunction);
assertEquals(initialValue + 1, tranlocal.long_value);
assertTrue(tx.hasWrites);
assertIsActive(tx);
assertVersionAndValue(ref, GammaConstants.VERSION_UNCOMMITTED, 0);
assertLockMode(ref, LockMode.Exclusive);
assertTrue(tranlocal.isConstructing());
assertNull(tranlocal.headCallable);
}
@Test
public void whenAlreadyOpenedForConstructionAndFunctionCausesProblem() {
GammaTxn tx = newTransaction();
LongFunction function = mock(LongFunction.class);
when(function.call(anyLong())).thenThrow(new SomeUncheckedException());
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(tx, initialValue);
Tranlocal tranlocal = ref.openForWrite(tx, LOCKMODE_NONE);
try {
ref.commute(tx, function);
fail();
} catch (SomeUncheckedException expected) {
}
assertIsAborted(tx);
assertNull(tranlocal.owner);
assertLockMode(ref, LockMode.Exclusive);
assertVersionAndValue(ref, GammaConstants.VERSION_UNCOMMITTED, 0);
}
@Test
public void whenOverflowing() {
int maxCapacity = getMaxCapacity();
assumeTrue(maxCapacity < Integer.MAX_VALUE);
GammaTxn tx = newTransaction();
for (int k = 0; k < maxCapacity; k++) {
GammaTxnLong ref = new GammaTxnLong(stm, 0);
ref.openForRead(tx, LOCKMODE_NONE);
}
GammaTxnLong ref = new GammaTxnLong(stm, 0);
try {
ref.commute(tx, Functions.incLongFunction());
fail();
} catch (SpeculativeConfigurationError expected) {
}
assertEquals(TxnStatus.Aborted, tx.getStatus());
}
@Test
public void whenNullTransaction() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
LongFunction function = mock(LongFunction.class);
try {
ref.commute((FatFixedLengthGammaTxn) null, function);
fail();
} catch (NullPointerException expected) {
}
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, LOCKMODE_NONE);
verifyZeroInteractions(function);
}
@Test
public void whenNullFunction() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxnConfig config = new GammaTxnConfig(stm)
.setReadonly(true);
GammaTxn tx = newTransaction(config);
try {
ref.commute(tx, null);
fail();
} catch (NullPointerException expected) {
}
assertIsAborted(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, LOCKMODE_NONE);
}
@Test
public void whenReadonlyTransaction_thenReadonlyException() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxnConfig config = new GammaTxnConfig(stm)
.setReadonly(true);
GammaTxn tx = newTransaction(config);
LongFunction function = mock(LongFunction.class);
try {
ref.commute(tx, function);
fail();
} catch (ReadonlyException expected) {
}
assertIsAborted(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, LOCKMODE_NONE);
verifyZeroInteractions(function);
}
@Test
public void whenStmMismatch() {
GammaStm otherStm = new GammaStm();
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(otherStm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn tx = stm.newDefaultTxn();
LongFunction function = mock(LongFunction.class);
try {
ref.commute(tx, function);
fail();
} catch (StmMismatchException expected) {
}
assertIsAborted(tx);
assertRefHasNoLocks(ref);
assertVersionAndValue(ref, initialVersion, initialValue);
verifyZeroInteractions(function);
}
// =========================== commuting =========================
@Test
public void commuting_whenCommuting_thenFailure() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
T tx = newTransaction();
tx.evaluatingCommute = true;
LongFunction function = mock(LongFunction.class);
try {
ref.commute(tx, function);
fail();
} catch (IllegalCommuteException expected) {
}
assertIsAborted(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertRefHasNoLocks(ref);
verifyZeroInteractions(function);
}
// ========================== state ==============================
@Test
public void whenTransactionPrepared_thenPreparedTxnException() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn tx = newTransaction();
LongFunction function = mock(LongFunction.class);
tx.prepare();
try {
ref.commute(tx, function);
fail();
} catch (PreparedTxnException expected) {
}
assertIsAborted(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, LOCKMODE_NONE);
verifyZeroInteractions(function);
}
@Test
public void whenTransactionAborted_thenDeadTxnException() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn tx = newTransaction();
LongFunction function = mock(LongFunction.class);
tx.abort();
try {
ref.commute(tx, function);
fail();
} catch (DeadTxnException expected) {
}
assertIsAborted(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, LOCKMODE_NONE);
verifyZeroInteractions(function);
}
@Test
public void whenTransactionCommitted_thenDeadTxnException() {
long initialValue = 10;
GammaTxnLong ref = new GammaTxnLong(stm, initialValue);
long initialVersion = ref.getVersion();
GammaTxn tx = newTransaction();
LongFunction function = mock(LongFunction.class);
tx.commit();
try {
ref.commute(tx, function);
fail();
} catch (DeadTxnException expected) {
}
assertIsCommitted(tx);
assertVersionAndValue(ref, initialVersion, initialValue);
assertLockMode(ref, LOCKMODE_NONE);
verifyZeroInteractions(function);
}
}