/* * 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; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; import org.junit.Assert; import org.junit.Test; import com.cinchapi.concourse.Concourse; import com.cinchapi.concourse.DuplicateEntryException; import com.cinchapi.concourse.Timestamp; import com.cinchapi.concourse.lang.Criteria; import com.cinchapi.concourse.test.ConcourseIntegrationTest; import com.cinchapi.concourse.test.Variables; import com.cinchapi.concourse.thrift.Operator; import com.cinchapi.concourse.time.Time; import com.cinchapi.concourse.util.Random; import com.cinchapi.concourse.util.TestData; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; /** * Tests for atomic operations that are defined in {@link ConcourseServer}. * * @author Jeff Nelson */ public class AtomicOperationWofkflowTest extends ConcourseIntegrationTest { /** * Return a random collection of data to be used in a test of the insert * function. * * @return the data */ private static Multimap<String, Object> getInsertData() { Multimap<String, Object> data = LinkedHashMultimap.create(); for (int i = 0; i < TestData.getScaleCount(); i++) { String key = TestData.getSimpleString(); if(Time.now() % 3 == 0) { for (int j = 0; j < TestData.getScaleCount() / 4; j++) { // add multiple values data.put(key, TestData.getObject()); } } else { data.put(key, TestData.getObject()); } } return data; } /** * Return a random collection of data that also includes {@code key} and * {@code value} to be used in a test of the insert function. * * @param key * @param value * @return the data */ private static Multimap<String, Object> getInsertData(String key, Object value) { Multimap<String, Object> data = getInsertData(); data.put(key, value); data.putAll(getInsertData()); return data; } /** * Convert an object to a {@link JsonElement}. * * @param object * @return the JSON element */ private static JsonElement toJsonElement(Object object) { if(object instanceof Double) { return new JsonPrimitive(object + "D"); } else if(object instanceof Number) { return new JsonPrimitive((Number) object); } else if(object instanceof Boolean) { return new JsonPrimitive((Boolean) object); } else { return new JsonPrimitive(object.toString()); } } /** * Convert a multimap containing key/value data to a JSON formatted string. * * @param data * @return the JSON string */ private static String toJsonString(Multimap<String, Object> data) { JsonObject object = new JsonObject(); for (String key : data.keySet()) { if(data.get(key).size() > 1) { JsonArray array = new JsonArray(); for (Object value : data.get(key)) { array.add(toJsonElement(value)); } object.add(key, array); } else if(data.get(key).size() == 1) { object.add(key, toJsonElement(Iterables.getOnlyElement(data.get(key)))); } } return object.toString(); } @Test public void testCannotVerifyAndSwapDuplicateValue() { client.add("foo", 1, 1); client.add("foo", 2, 1); Assert.assertFalse(client.verifyAndSwap("foo", 2, 1, 1)); } @Test public void testClearSanityCheck() { String key = Variables.register("key", TestData.getSimpleString()); long record = Variables.register("record", TestData.getLong()); Set<Object> initValues = Variables.register("initValues", Sets.newHashSet()); for (int i = 0; i < Variables.register("count", TestData.getScaleCount()); i++) { Object value = null; while (value == null || initValues.contains(value)) { value = TestData.getObject(); } initValues.add(value); client.add(key, value, record); } client.clear(key, record); Assert.assertTrue(client.select(key, record).isEmpty()); } @Test public void testInserMultiValuesForKeyFailsIfOneOfTheMappingsExists() { long record = Time.now(); Multimap<String, Object> data = Variables.register("data", LinkedHashMultimap.<String, Object> create()); String key = Random.getSimpleString(); for (int i = 0; i < TestData.getScaleCount(); i++) { data.put(key, Random.getObject()); } Object v = Random.getObject(); client.add(key, v, record); data.put(key, v); for (int i = 0; i < TestData.getScaleCount(); i++) { data.put(key, Random.getObject()); } String json = Variables.register("json", toJsonString(data)); Assert.assertFalse(client.insert(json, record)); } @Test public void testInsertFailsIfSomeDataAlreadyExists() { long record = Time.now(); String key0 = TestData.getSimpleString(); Object value0 = TestData.getObject(); Multimap<String, Object> data = Variables.register("data", getInsertData(key0, value0)); String json = Variables.register("json", toJsonString(data)); client.add(key0, value0, record); Assert.assertFalse(client.insert(json, record)); for (String key : data.keySet()) { for (Object value : data.get(key)) { if(!key.equals(key0) && !value.equals(value0)) { Assert.assertFalse(client.verify(key, value, record)); } } } } @Test public void testInsertIntoNewRecordAlwaysSucceeds() { Multimap<String, Object> data = Variables.register("data", getInsertData()); String json = Variables.register("json", toJsonString(data)); long record = client.insert(json).iterator().next(); for (String key : data.keySet()) { for (Object value : data.get(key)) { Variables.register("key", key); Variables.register("value", value); Assert.assertTrue(client.verify(key, value, record)); } } } @Test public void testInsertIntoNewRecordAlwaysSucceedsReproA() { Multimap<String, Object> data = Variables.register("data", HashMultimap.<String, Object> create()); data.put( "zotcstcgyjmgecajmebeqnmdpjddhlhbvyegkkjbedvrgqosrvqiuxsrhowedzuyxesmxqkncvxghflh", "3"); String json = Variables.register("json", toJsonString(data)); long record = client.insert(json).iterator().next(); for (String key : data.keySet()) { for (Object value : data.get(key)) { Variables.register("key", key); Variables.register("value", value); Assert.assertTrue(client.verify(key, value, record)); } } } @Test public void testInsertMultiValuesForKey() { long record = Time.now(); Multimap<String, Object> data = Variables.register("data", LinkedHashMultimap.<String, Object> create()); String key = Random.getSimpleString(); for (int i = 0; i < TestData.getScaleCount(); i++) { data.put(key, Random.getObject()); } String json = Variables.register("json", toJsonString(data)); Assert.assertTrue(client.insert(json, record)); for (Object value : data.get(key)) { Assert.assertTrue(client.verify(key, value, record)); } } @Test public void testInsertSucceedsIfAllDataIsNew() { long record = Time.now(); Multimap<String, Object> data = Variables.register("data", getInsertData()); String json = Variables.register("json", toJsonString(data)); Assert.assertTrue(client.insert(json, record)); for (String key : data.keySet()) { for (Object value : data.get(key)) { Variables.register("key", key); Variables.register("value", value); Assert.assertTrue(client.verify(key, value, record)); } } } @Test public void testInsertSucceedsIfAllDataIsNewReproA() { long record = Time.now(); Multimap<String, Object> data = Variables.register("data", HashMultimap.<String, Object> create()); data.put("foo", "007"); String json = Variables.register("json", toJsonString(data)); Assert.assertTrue(client.insert(json, record)); for (String key : data.keySet()) { for (Object value : data.get(key)) { Assert.assertTrue(client.verify(key, value, record)); } } } @Test(expected = RuntimeException.class) public void testInsertFailsForNonJsonString() { Assert.assertTrue(client.insert(TestData.getSimpleString()).isEmpty()); } // TODO testRevertCompletesEvenIfInterrupted @Test public void testRevertSanityCheck() { String key = Variables.register("key", TestData.getSimpleString()); long record = Variables.register("record", TestData.getLong()); Set<Object> initValues = Variables.register("initValues", Sets.newHashSet()); for (int i = 0; i < Variables.register("count", TestData.getScaleCount()); i++) { Object value = null; while (value == null || initValues.contains(value)) { value = TestData.getObject(); } initValues.add(value); client.add(key, value, record); } Timestamp timestamp = Timestamp.now(); Set<Object> values = Variables.register("values", Sets.newHashSet(initValues)); for (int i = 0; i < Variables.register("count", TestData.getScaleCount()); i++) { Object value = null; while (value == null || values.contains(value)) { value = TestData.getObject(); } values.add(value); client.add(key, value, record); } client.revert(key, record, timestamp); Assert.assertEquals(initValues, client.select(key, record)); } // TODO testClearCompletesEvenIfInterrupted @Test public void testSetCompletesEvenIfInterrupted() throws InterruptedException { final Concourse client2 = Concourse.connect(SERVER_HOST, SERVER_PORT, "admin", "admin"); final int count = 100; for (int i = 0; i < count; i++) { client.add("foo", i, 1); } final CountDownLatch latch = new CountDownLatch(1); Thread t1 = new Thread() { @Override public void run() { latch.countDown(); client.set("foo", -1, 1); } }; // Attempt to interrupt the #set operation happening in thread t1 Thread t2 = new Thread() { @Override public void run() { Assert.assertTrue(client2.add("foo", 1000, 1)); } }; t1.start(); latch.await(); t2.start(); // wait for threads to finish so that the server isn't stopped // prematurely t1.join(); t2.join(); Assert.assertTrue(client.select("foo", 1).contains(-1)); // this shows // that the // atomic // operation // has retry // logic } @Test public void testSetSanityCheck() { for (int i = 0; i < TestData.getScaleCount(); i++) { client.add("foo", i, 1); } client.set("foo", -1, 1); Assert.assertEquals(Sets.newHashSet(-1), client.select("foo", 1)); } @Test public void testVerifyAndSwapInAbortedTransaction() { String key = Variables.register("key", TestData.getSimpleString()); Object expected = Variables.register("expected", TestData.getObject()); long record = Variables.register("record", TestData.getLong()); client.add(key, expected, record); client.stage(); Object replacement = null; while (replacement == null || expected.equals(replacement)) { replacement = Variables.register("replacement", TestData.getObject()); } client.verifyAndSwap(key, expected, record, replacement); client.abort(); Assert.assertTrue(client.select(key, record).contains(expected)); Assert.assertFalse(client.select(key, record).contains(replacement)); } @Test public void testVerifyAndSwapInCommittedTransaction() { String key = Variables.register("key", TestData.getSimpleString()); Object expected = Variables.register("expected", TestData.getObject()); long record = Variables.register("record", TestData.getLong()); client.add(key, expected, record); client.stage(); Object replacement = null; while (replacement == null || expected.equals(replacement)) { replacement = Variables.register("replacement", TestData.getObject()); } client.verifyAndSwap(key, expected, record, replacement); client.commit(); Assert.assertFalse(client.select(key, record).contains(expected)); Assert.assertTrue(client.select(key, record).contains(replacement)); } @Test public void testVerifyAndSwapMultiValues() { String key = Variables.register("key", TestData.getSimpleString()); long record = Variables.register("record", TestData.getLong()); HashSet<Object> values = Variables .register("values", Sets.newHashSet()); for (int i = 0; i < TestData.getScaleCount(); i++) { Object value = null; while (value == null || values.contains(value)) { value = TestData.getObject(); } values.add(value); client.add(key, value, record); } Object replacement = null; while (replacement == null || values.contains(replacement)) { replacement = Variables.register("replacement", TestData.getObject()); } Object expected = Variables.register("expected", values.toArray()[TestData.getScaleCount() % values.size()]); Assert.assertTrue(client.verifyAndSwap(key, expected, record, replacement)); Assert.assertFalse(client.select(key, record).contains(expected)); Assert.assertTrue(client.select(key, record).contains(replacement)); } @Test public void testVerifyAndSwapNegativeCase() { String key = Variables.register("key", TestData.getSimpleString()); Object expected = Variables.register("expected", TestData.getObject()); Object actual = null; while (actual == null || expected.equals(actual)) { actual = Variables.register("actual", TestData.getObject()); } long record = Variables.register("record", TestData.getLong()); Object replacement = null; while (replacement == null || expected.equals(replacement) || actual.equals(replacement)) { replacement = Variables.register("replacement", TestData.getObject()); } client.add(key, actual, record); Assert.assertFalse(client.verifyAndSwap(key, expected, record, replacement)); Assert.assertFalse(client.select(key, record).contains(replacement)); Assert.assertTrue(client.select(key, record).contains(actual)); } @Test public void testVerifyAndSwapSanityCheck() { String key = Variables.register("key", TestData.getSimpleString()); Object expected = Variables.register("expected", TestData.getObject()); long record = Variables.register("record", TestData.getLong()); client.add(key, expected, record); Object replacement = null; while (replacement == null || expected.equals(replacement)) { replacement = Variables.register("replacement", TestData.getObject()); } Assert.assertTrue(client.verifyAndSwap(key, expected, record, replacement)); Assert.assertTrue(client.select(key, record).contains(replacement)); Assert.assertFalse(client.select(key, record).contains(expected)); } @Test public void testFindOrAddNotExists() { String key = TestData.getSimpleString(); Object value = TestData.getObject(); long record = client.findOrAdd(key, value); Assert.assertEquals(value, client.get(key, record)); } @Test public void testFindOrAddExists() { String key = TestData.getSimpleString(); Object value = TestData.getObject(); long existing = TestData.getLong(); client.add(key, value, existing); Assert.assertEquals(existing, client.findOrAdd(key, value)); } @Test public void testFindOrInsertCriteriaExists() { String key = TestData.getSimpleString(); int value = 10; String json = toJsonString(getInsertData(key, value)); long existing = TestData.getLong(); client.insert(json, existing); long record = client.findOrInsert( Criteria.where().key(key).operator(Operator.GREATER_THAN) .value(5), json); Assert.assertEquals(existing, record); } @Test public void testFindOrInsertCclExists() { String key = "foo"; int value = 10; String json = toJsonString(getInsertData(key, value)); long record = TestData.getLong(); client.insert(json, record); Assert.assertEquals(record, client.findOrInsert("foo > 5", json)); } @Test public void testFindOrInsertCriteriaNotExists() { String key = TestData.getSimpleString(); int value = 10; String json = toJsonString(getInsertData(key, value)); long record = TestData.getLong(); client.insert(json, record); Assert.assertNotEquals(record, client.findOrInsert(Criteria.where() .key(key).operator(Operator.GREATER_THAN).value(11), json)); } @Test public void testFindOrInsertCclNotExists() { String key = "foo"; int value = 10; String json = toJsonString(getInsertData(key, value)); long record = TestData.getLong(); client.insert(json, record); Assert.assertNotEquals(record, client.findOrInsert("foo != 10", json)); } @Test(expected = DuplicateEntryException.class) public void testFindOrAddDuplicateEntry() { String key = TestData.getSimpleString(); int value = TestData.getInt(); Set<Long> records = Sets.newHashSet(); while (records.size() < 2) { records.add(TestData.getLong()); } client.add(key, value, records); client.findOrAdd(key, value); } @Test(expected = DuplicateEntryException.class) public void testFindOrInsertCriteriaDuplicateEntry() { String key = "foo"; int value = 10; String json = toJsonString(getInsertData(key, value)); Set<Long> records = Sets.newHashSet(); while (records.size() < 2) { records.add(TestData.getLong()); } client.insert(json, records); client.findOrInsert(Criteria.where().key(key).operator(Operator.EQUALS) .value(10), json); } @Test(expected = DuplicateEntryException.class) public void testFindOrInsertCclDuplicateEntry() { String key = "foo"; int value = 10; String json = toJsonString(getInsertData(key, value)); Set<Long> records = Sets.newHashSet(); while (records.size() < 2) { records.add(TestData.getLong()); } client.insert(json, records); client.findOrInsert("foo = 10", json); } // TODO more insert tests! }