/* * Copyright (c) 2013-2017 Cinchapi Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cinchapi.concourse.server.storage; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Assert; import org.junit.Test; import com.cinchapi.concourse.server.storage.AtomicOperation; import com.cinchapi.concourse.server.storage.AtomicStateException; import com.cinchapi.concourse.server.storage.AtomicSupport; import com.cinchapi.concourse.server.storage.temp.Write; import com.cinchapi.concourse.test.Variables; import com.cinchapi.concourse.thrift.Operator; import com.cinchapi.concourse.thrift.TObject; import com.cinchapi.concourse.time.Time; import com.cinchapi.concourse.util.Convert; import com.cinchapi.concourse.util.TestData; import com.google.common.collect.Sets; /** * Unit tests for {@link AtomicOperation}. * * @author Jeff Nelson */ public abstract class AtomicOperationTest extends BufferedStoreTest { protected AtomicSupport destination; @Test public void testNoDeadlockIfAddToKeyAsValueBeforeFindingEqKeyAndValue() { long record = TestData.getLong(); String key = "foo"; TObject value = Convert.javaToThrift("bar"); add(key, value, record); store.find(key, Operator.EQUALS, value); Assert.assertTrue(((AtomicOperation) store).commit()); } @Test public void testNoDeadlockIfAddToKeyAsValueBeforeFindingGttKeyAndValue() { long record = TestData.getLong(); String key = "foo"; TObject value = Convert.javaToThrift("bar"); add(key, value, record); store.find(key, Operator.GREATER_THAN, value); Assert.assertTrue(((AtomicOperation) store).commit()); } @Test public void testNoDeadlockIfAddToKeyAsValueBeforeFindingBwtKeyAndValue() { long record = TestData.getLong(); String key = "foo"; TObject value = Convert.javaToThrift("bar"); add(key, value, record); store.find(key, Operator.BETWEEN, value, Convert.javaToThrift("bars")); Assert.assertTrue(((AtomicOperation) store).commit()); } @Test public void testAbort() { String key = TestData.getSimpleString(); TObject value = TestData.getTObject(); long record = TestData.getLong(); add(key, value, record); Assert.assertTrue(store.verify(key, value, record)); ((AtomicOperation) store).abort(); Assert.assertFalse(destination.verify(key, value, record)); } @Test public void testCommit() { String key = TestData.getSimpleString(); TObject value = TestData.getTObject(); long record = TestData.getLong(); add(key, value, record); ((AtomicOperation) store).commit(); Assert.assertTrue(destination.verify(key, value, record)); } @Test public void testCommitFailsIfVersionChanges() { String key = Variables.register("key", TestData.getSimpleString()); TObject value = Variables.register("value", TestData.getTObject()); long record = Variables.register("record", TestData.getLong()); add(key, value, record); AtomicOperation other = destination.startAtomicOperation(); other.add(key, value, record); Assert.assertTrue(other.commit()); Assert.assertFalse(((AtomicOperation) store).commit()); } @Test public void testCommitSucceedsIfChangeIsMadeToRecordInDiffKey() { // CON-20 long record = 1; String keyA = "keyA"; TObject valueA = Convert.javaToThrift("valueA"); String keyB = "keyB"; TObject valueB = Convert.javaToThrift("valueB"); add(keyA, valueA, 1); AtomicOperation other = destination.startAtomicOperation(); other.add(keyB, valueB, record); Assert.assertTrue(other.commit()); Assert.assertTrue(((AtomicOperation) store).commit()); } @Test public void testFailureIfWriteToKeyInRecordThatIsRead() throws InterruptedException { final String key = TestData.getSimpleString(); final long record = TestData.getLong(); AtomicOperation operation = (AtomicOperation) store; operation.select(key, record); Thread thread = new Thread(new Runnable() { @Override public void run() { destination.accept(Write.add(key, TestData.getTObject(), record)); } }); thread.start(); thread.join(); Assert.assertFalse(operation.commit()); } @Test public void testFailureIfWriteToRecordThatIsRead() throws InterruptedException { final long record = TestData.getLong(); AtomicOperation operation = (AtomicOperation) store; operation.describe(record); Thread thread = new Thread(new Runnable() { @Override public void run() { destination.accept(Write.add(TestData.getSimpleString(), TestData.getTObject(), record)); } }); thread.start(); thread.join(); Assert.assertFalse(operation.commit()); } @Test public void testImmediateVisibility() { String key = TestData.getSimpleString(); long record = TestData.getLong(); Set<TObject> values = Sets.newHashSet(); for (int i = 0; i < TestData.getScaleCount(); i++) { TObject value = TestData.getTObject(); while (values.contains(value)) { value = TestData.getTObject(); } values.add(value); add(key, value, record); } Assert.assertEquals(Sets.newHashSet(), destination.select(key, record)); ((AtomicOperation) store).commit(); Assert.assertEquals(values, destination.select(key, record)); } @Test public void testIsolation() { AtomicOperation a = destination.startAtomicOperation(); AtomicOperation b = destination.startAtomicOperation(); String key = TestData.getSimpleString(); TObject value = TestData.getTObject(); long record = TestData.getLong(); Assert.assertTrue(((AtomicOperation) a).add(key, value, record)); Assert.assertTrue(((AtomicOperation) b).add(key, value, record)); Assert.assertFalse(destination.verify(key, value, record)); } @Test public void testNoChangesPersistOnFailure() { int count = TestData.getScaleCount(); String key0 = ""; for (int i = 0; i < count; i++) { String key = TestData.getSimpleString(); if(i == 0) { key0 = key; } TObject value = TestData.getTObject(); ((AtomicOperation) store).add(key, value, i); } destination.accept(Write.add(key0, Convert.javaToThrift("foo"), 0)); ((AtomicOperation) store).commit(); for (int i = 1; i < count; i++) { Assert.assertTrue(destination.audit(i).isEmpty()); } } @Test public void testLockUpgrade() { String key = TestData.getSimpleString(); TObject value = TestData.getTObject(); long record = TestData.getLong(); store.verify(key, value, record); add(key, value, record); Assert.assertTrue(((AtomicOperation) store).commit()); } @Test public void testOnlyOneSuccessDuringRaceConditionWithConflict() throws InterruptedException { final AtomicOperation a = doTestOnlyOneSuccessDuringRaceCondition(); final AtomicOperation b = doTestOnlyOneSuccessDuringRaceCondition(); a.add("foo", Convert.javaToThrift("bar"), 1000); b.add("foo", Convert.javaToThrift("bar"), 1000); final AtomicBoolean aSuccess = new AtomicBoolean(false); final AtomicBoolean bSuccess = new AtomicBoolean(false); Thread aThread = new Thread(new Runnable() { @Override public void run() { try { aSuccess.set(a.commit()); } catch (AtomicStateException e) {} // swallow } }); Thread bThread = new Thread(new Runnable() { @Override public void run() { try { bSuccess.set(b.commit()); } catch (AtomicStateException e) {} // swallow } }); aThread.start(); bThread.start(); aThread.join(); bThread.join(); Assert.assertTrue((aSuccess.get() && !bSuccess.get()) || (!aSuccess.get() && bSuccess.get())); } @Test public void testSucceessIfNoWriteToKeyInRecordThatIsRead() throws InterruptedException { final String key = TestData.getSimpleString(); final long record = TestData.getLong(); AtomicOperation operation = (AtomicOperation) store; operation.select(key, record); Thread thread = new Thread(new Runnable() { @Override public void run() { destination.accept(Write.add(key + TestData.getSimpleString(), TestData.getTObject(), record)); } }); thread.start(); thread.join(); Assert.assertTrue(operation.commit()); } @Test public void testSuccessIfNoWriteToRecordThatIsRead() throws InterruptedException { final long record = TestData.getLong(); AtomicOperation operation = (AtomicOperation) store; operation.describe(record); Thread thread = new Thread(new Runnable() { @Override public void run() { destination.accept(Write.add(TestData.getSimpleString(), TestData.getTObject(), record + 1)); } }); thread.start(); thread.join(); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindEqOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.EQUALS, value); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindGtOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.GREATER_THAN, value); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindGteOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.GREATER_THAN_OR_EQUALS, value); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindLteOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.LESS_THAN_OR_EQUALS, value); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindLtOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.LESS_THAN, value); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindBwOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.BETWEEN, value, Convert.javaToThrift(3)); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindRegexOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.REGEX, value); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test public void testNoDeadLockIfFindNotRegexOnKeyBeforeAddingToKey() { String key = "ipeds_id"; TObject value = Convert.javaToThrift(1); long record = Time.now(); AtomicOperation operation = (AtomicOperation) store; operation.find(key, Operator.NOT_REGEX, value); operation.add(key, value, record); Assert.assertTrue(operation.commit()); } @Test(expected = AtomicStateException.class) public void testCannotOperateOnClosedAtomicOperation() { AtomicOperation operation = (AtomicOperation) store; operation.commit(); operation.audit(1); } @Override protected void add(String key, TObject value, long record) { ((AtomicOperation) store).add(key, value, record); } protected abstract AtomicSupport getDestination(); @Override protected AtomicOperation getStore() { destination = getDestination(); return destination.startAtomicOperation(); } @Override protected void remove(String key, TObject value, long record) { ((AtomicOperation) store).remove(key, value, record); } private AtomicOperation doTestOnlyOneSuccessDuringRaceCondition() { AtomicOperation operation = destination.startAtomicOperation(); for (int i = 0; i < 1; i++) { operation.add(TestData.getSimpleString(), TestData.getTObject(), i); } return operation; } }