diff options
author | Brian S. O'Neill <bronee@gmail.com> | 2006-09-03 23:23:51 +0000 |
---|---|---|
committer | Brian S. O'Neill <bronee@gmail.com> | 2006-09-03 23:23:51 +0000 |
commit | f5b0a7c2749afc853a0b7c5b8945ae44120fc4a1 (patch) | |
tree | ff9da44d7324dfb5e0877e028a88cf938f86f262 /src | |
parent | 1e947afa4b660a23a2dcb57463dd810fb73e6030 (diff) |
More tests and fixes.
Diffstat (limited to 'src')
7 files changed, 2829 insertions, 0 deletions
diff --git a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java index 1a015f4..ac3cd24 100644 --- a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java +++ b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java @@ -1527,6 +1527,26 @@ public class StorableIntrospector { return mAdapter;
}
+ public int hashCode() {
+ return (getName().hashCode() * 31 + getType().hashCode()) * 31
+ + getEnclosingType().hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof StorableProperty) {
+ StorableProperty other = (StorableProperty) obj;
+ return getName().equals(other.getName())
+ && getType().equals(other.getType())
+ && getEnclosingType().equals(other.getEnclosingType());
+ }
+
+ return false;
+ }
+
public String toString() {
StringBuilder b = new StringBuilder();
try {
diff --git a/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java index 01f9fbf..84792ad 100644 --- a/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java +++ b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java @@ -147,11 +147,16 @@ public class ToyStorage<S extends Storable> implements Storage<S>, MasterSupport try {
for (S existing : mData) {
if (existing.equalPrimaryKeys(storable)) {
+ // Copy altered values to existing object.
existing.markAllPropertiesDirty();
storable.copyAllProperties(existing);
existing.markAllPropertiesClean();
+
+ // Copy all values to user object, to simulate a reload.
+ storable.markAllPropertiesDirty();
existing.copyAllProperties(storable);
storable.markAllPropertiesClean();
+
return true;
}
}
diff --git a/src/test/java/com/amazon/carbonado/spi/TestStorableSerializer.java b/src/test/java/com/amazon/carbonado/spi/TestStorableSerializer.java new file mode 100644 index 0000000..664bf12 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/TestStorableSerializer.java @@ -0,0 +1,115 @@ +/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.spi;
+
+import java.io.*;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.amazon.carbonado.*;
+import com.amazon.carbonado.lob.*;
+
+import com.amazon.carbonado.repo.toy.ToyRepository;
+import com.amazon.carbonado.stored.*;
+
+/**
+ * Test case for {@link StorableSerializer}.
+ *
+ * @author Brian S O'Neill
+ */
+public class TestStorableSerializer extends TestCase {
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestStorableSerializer.class);
+ }
+
+ private Repository mRepository;
+
+ public TestStorableSerializer(String name) {
+ super(name);
+ }
+
+ protected void setUp() {
+ mRepository = new ToyRepository();
+ }
+
+ protected void tearDown() {
+ mRepository.close();
+ mRepository = null;
+ }
+
+ public void testReadAndWrite() throws Exception {
+ Storage<StorableTestBasic> storage = mRepository.storageFor(StorableTestBasic.class);
+ StorableTestBasic stb = storage.prepare();
+ stb.setId(50);
+ stb.setStringProp("hello");
+ stb.setIntProp(100);
+ stb.setLongProp(999);
+ stb.setDoubleProp(2.718281828d);
+
+ StorableSerializer<StorableTestBasic> serializer =
+ StorableSerializer.forType(StorableTestBasic.class);
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ DataOutputStream dout = new DataOutputStream(bout);
+
+ serializer.write(stb, (DataOutput) dout);
+ dout.flush();
+
+ byte[] bytes = bout.toByteArray();
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
+ DataInputStream din = new DataInputStream(bin);
+
+ StorableTestBasic stb2 = serializer.read(storage, (DataInput) din);
+
+ assertEquals(stb, stb2);
+ }
+
+ /*
+ public void testReadAndWriteLobs() throws Exception {
+ Storage<StorableWithLobs> storage = mRepository.storageFor(StorableWithLobs.class);
+ StorableWithLobs s = storage.prepare();
+ s.setBlobValue(new ByteArrayBlob("Hello Blob".getBytes()));
+ s.setClobValue(new StringClob("Hello Clob"));
+
+ StorableSerializer<StorableWithLobs> serializer =
+ StorableSerializer.forType(StorableWithLobs.class);
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ DataOutputStream dout = new DataOutputStream(bout);
+
+ serializer.write(s, (DataOutput) dout);
+ dout.flush();
+
+ byte[] bytes = bout.toByteArray();
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
+ DataInputStream din = new DataInputStream(bin);
+
+ StorableWithLobs s2 = serializer.read(storage, (DataInput) din);
+
+ assertEquals(s, s2);
+ }
+ */
+}
diff --git a/src/test/java/com/amazon/carbonado/spi/raw/TestDataEncoding.java b/src/test/java/com/amazon/carbonado/spi/raw/TestDataEncoding.java new file mode 100644 index 0000000..a65ab21 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/raw/TestDataEncoding.java @@ -0,0 +1,515 @@ +/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.spi.raw;
+
+import java.util.Random;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test case for {@link DataEncoder} and {@link DataDecoder}.
+ * <p>
+ * It generates random data values, checks that the decoding produces the
+ * original results, and it checks that the order of the encoded bytes matches
+ * the order of the original data values.
+ *
+ * @author Brian S O'Neill
+ */
+public class TestDataEncoding extends TestCase {
+ private static final int SHORT_TEST = 100;
+ private static final int MEDIUM_TEST = 1000;
+ private static final int LONG_TEST = 10000;
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestDataEncoding.class);
+ }
+
+ /**
+ * @return -1, 0, or 1
+ */
+ static int byteArrayCompare(byte[] aa, byte[] ab) {
+ int len = Math.min(aa.length, ab.length);
+ int result = byteArrayCompare(aa, ab, len);
+ if (result == 0 && aa.length != ab.length) {
+ if (aa.length == len) {
+ return -1;
+ }
+ if (ab.length == len) {
+ return 1;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return -1, 0, or 1
+ */
+ static int byteArrayCompare(byte[] aa, byte[] ab, int len) {
+ for (int i=0; i<len; i++) {
+ int a = aa[i] & 0xff;
+ int b = ab[i] & 0xff;
+ if (a < b) {
+ return -1;
+ }
+ if (a > b) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * @return -1, 0, or 1
+ */
+ static int byteArrayOrNullCompare(byte[] aa, byte[] ab) {
+ if (aa == null) {
+ if (ab == null) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if (ab == null) {
+ return -1;
+ } else {
+ return byteArrayCompare(aa, ab);
+ }
+ }
+
+ /**
+ * @return -1, 0, or 1
+ */
+ static int byteArrayOrNullCompare(byte[] aa, byte[] ab, int len) {
+ if (aa == null) {
+ if (ab == null) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if (ab == null) {
+ return -1;
+ } else {
+ return byteArrayCompare(aa, ab, len);
+ }
+ }
+
+ /**
+ * @return -1, 0, or 1
+ */
+ static <C extends Comparable> int compare(C a, C b) {
+ if (a == null) {
+ if (b == null) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if (b == null) {
+ return -1;
+ } else {
+ return Integer.signum(a.compareTo(b));
+ }
+ }
+
+ private final long mSeed;
+
+ private Random mRandom;
+
+ public TestDataEncoding(String name) {
+ super(name);
+ mSeed = 5399777425345431L;
+ }
+
+ protected void setUp() {
+ mRandom = new Random(mSeed);
+ }
+
+ protected void tearDown() {
+ }
+
+ public void test_boolean() throws Exception {
+ byte[] bytes = new byte[1];
+ boolean lastValue = false;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ boolean value = mRandom.nextBoolean();
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeBoolean(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Boolean() throws Exception {
+ byte[] bytes = new byte[1];
+ Boolean lastValue = false;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Boolean value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ value = mRandom.nextBoolean();
+ }
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeBooleanObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_byte() throws Exception {
+ byte[] bytes = new byte[1];
+ byte lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ byte value = (byte) mRandom.nextInt();
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeByte(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Byte() throws Exception {
+ byte[] bytes = new byte[2];
+ Byte lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Byte value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, DataEncoder.encode(value, bytes, 0));
+ } else {
+ value = (byte) mRandom.nextInt();
+ assertEquals(2, DataEncoder.encode(value, bytes, 0));
+ }
+ assertEquals(value, DataDecoder.decodeByteObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_short() throws Exception {
+ byte[] bytes = new byte[2];
+ short lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ short value = (short) mRandom.nextInt();
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeShort(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Short() throws Exception {
+ byte[] bytes = new byte[3];
+ Short lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Short value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, DataEncoder.encode(value, bytes, 0));
+ } else {
+ value = (short) mRandom.nextInt();
+ assertEquals(3, DataEncoder.encode(value, bytes, 0));
+ }
+ assertEquals(value, DataDecoder.decodeShortObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_char() throws Exception {
+ byte[] bytes = new byte[2];
+ char lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ char value = (char) mRandom.nextInt();
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeChar(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Character() throws Exception {
+ byte[] bytes = new byte[3];
+ Character lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Character value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, DataEncoder.encode(value, bytes, 0));
+ } else {
+ value = (char) mRandom.nextInt();
+ assertEquals(3, DataEncoder.encode(value, bytes, 0));
+ }
+ assertEquals(value, DataDecoder.decodeCharacterObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_int() throws Exception {
+ byte[] bytes = new byte[4];
+ int lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ int value = mRandom.nextInt();
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeInt(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Integer() throws Exception {
+ byte[] bytes = new byte[5];
+ Integer lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Integer value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, DataEncoder.encode(value, bytes, 0));
+ } else {
+ value = mRandom.nextInt();
+ assertEquals(5, DataEncoder.encode(value, bytes, 0));
+ }
+ assertEquals(value, DataDecoder.decodeIntegerObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_long() throws Exception {
+ byte[] bytes = new byte[8];
+ long lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ long value = mRandom.nextLong();
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeLong(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Long() throws Exception {
+ byte[] bytes = new byte[9];
+ Long lastValue = 0L;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Long value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, DataEncoder.encode(value, bytes, 0));
+ } else {
+ value = mRandom.nextLong();
+ assertEquals(9, DataEncoder.encode(value, bytes, 0));
+ }
+ assertEquals(value, DataDecoder.decodeLongObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_float() throws Exception {
+ byte[] bytes = new byte[4];
+ float lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ float value = Float.intBitsToFloat(mRandom.nextInt());
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeFloat(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Float() throws Exception {
+ byte[] bytes = new byte[4];
+ Float lastValue = 0f;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ Float value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ value = Float.intBitsToFloat(mRandom.nextInt());
+ }
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeFloatObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_double() throws Exception {
+ byte[] bytes = new byte[8];
+ double lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ double value = Double.longBitsToDouble(mRandom.nextLong());
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeDouble(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_Double() throws Exception {
+ byte[] bytes = new byte[8];
+ Double lastValue = 0d;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ Double value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ value = Double.longBitsToDouble(mRandom.nextLong());
+ }
+ DataEncoder.encode(value, bytes, 0);
+ assertEquals(value, DataDecoder.decodeDoubleObj(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = compare(value, lastValue);
+ assertEquals(sgn, byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_String() throws Exception {
+ String[] ref = new String[1];
+ for (int i=0; i<SHORT_TEST; i++) {
+ String value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ int length;
+ switch (mRandom.nextInt(15)) {
+ default:
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
+ length = mRandom.nextInt(100);
+ break;
+ case 8: case 9: case 10: case 11:
+ length = mRandom.nextInt(200);
+ break;
+ case 12: case 13:
+ length = mRandom.nextInt(20000);
+ break;
+ case 14:
+ length = mRandom.nextInt(3000000);
+ break;
+ }
+ char[] chars = new char[length];
+ for (int j=0; j<length; j++) {
+ char c;
+ switch (mRandom.nextInt(7)) {
+ default:
+ case 0: case 1: case 2: case 3:
+ c = (char) mRandom.nextInt(128);
+ break;
+ case 4: case 5:
+ c = (char) mRandom.nextInt(4000);
+ break;
+ case 6:
+ c = (char) mRandom.nextInt();
+ break;
+ }
+ chars[j] = c;
+ }
+ value = new String(chars);
+ }
+
+ byte[] bytes = new byte[DataEncoder.calculateEncodedStringLength(value)];
+ assertEquals(bytes.length, DataEncoder.encode(value, bytes, 0));
+ assertEquals(bytes.length, DataDecoder.decodeString(bytes, 0, ref));
+ assertEquals(value, ref[0]);
+ }
+ }
+}
diff --git a/src/test/java/com/amazon/carbonado/spi/raw/TestEncodingStrategy.java b/src/test/java/com/amazon/carbonado/spi/raw/TestEncodingStrategy.java new file mode 100644 index 0000000..29572f0 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/raw/TestEncodingStrategy.java @@ -0,0 +1,857 @@ +/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.spi.raw;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.cojen.classfile.*;
+import org.cojen.util.*;
+
+import com.amazon.carbonado.*;
+import com.amazon.carbonado.info.*;
+import com.amazon.carbonado.spi.*;
+
+/**
+ * Test case for {@link GenericEncodingStrategy}.
+ * <p>
+ * It generates random selections of properties, encodes with random values,
+ * and checks that the decoding produces the original results. In addition, the
+ * proper ordering of encoded keys is checked.
+ *
+ * @author Brian S O'Neill
+ */
+public class TestEncodingStrategy extends TestCase {
+ private static final int SHORT_TEST = 100;
+ private static final int MEDIUM_TEST = 1000;
+ private static final int LONG_TEST = 10000;
+
+ private static final int ENCODE_OBJECT_ARRAY = 0;
+ private static final int DECODE_OBJECT_ARRAY = 1;
+ private static final int ENCODE_OBJECT_ARRAY_PARTIAL = 2;
+
+ private static final int BOGUS_GENERATION = 99;
+
+ // Make sure BOGUS_GENERATION is not included.
+ private static final int[] GENERATIONS = {-1, 0, 1, 2, 127, 128, 129, Integer.MAX_VALUE};
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestEncodingStrategy.class);
+ }
+
+ private final long mSeed;
+ private final StorableProperty<TestStorable>[] mProperties;
+
+ private Random mRandom;
+
+ public TestEncodingStrategy(String name) {
+ super(name);
+ mSeed = 986184829029842L;
+ Collection<? extends StorableProperty<TestStorable>> properties =
+ StorableIntrospector.examine(TestStorable.class).getAllProperties().values();
+ mProperties = properties.toArray(new StorableProperty[0]);
+ }
+
+ protected void setUp() {
+ mRandom = new Random(mSeed);
+ }
+
+ protected void tearDown() {
+ }
+
+ public void test_dataEncoding_noProperties() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_noProperties(0, 0, generation);
+ }
+ }
+
+ public void test_dataEncoding_noProperties_prefix() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_noProperties(5, 0, generation);
+ }
+ }
+
+ public void test_dataEncoding_noProperties_suffix() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_noProperties(0, 7, generation);
+ }
+ }
+
+ public void test_dataEncoding_noProperties_prefixAndSuffix() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_noProperties(5, 7, generation);
+ }
+ }
+
+ private void test_dataEncoding_noProperties(int prefix, int suffix, int generation)
+ throws Exception
+ {
+ GenericEncodingStrategy strategy = new GenericEncodingStrategy
+ (TestStorable.class, null, 0, 0, prefix, suffix);
+
+ Method[] methods = generateCodecMethods
+ (strategy, new StorableProperty[0], null, generation);
+
+ byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
+ .invoke(null, new Object[] {new Object[0]});
+
+ int generationPrefix;
+ if (generation < 0) {
+ generationPrefix = 0;
+ } else if (generation < 128) {
+ generationPrefix = 1;
+ } else {
+ generationPrefix = 4;
+ }
+
+ assertEquals(encoded.length, prefix + generationPrefix + suffix);
+
+ if (generation >= 0) {
+ if (generationPrefix == 1) {
+ assertEquals(generation, encoded[prefix]);
+ } else {
+ int actualGeneration = DataDecoder.decodeInt(encoded, prefix);
+ assertEquals(generation, actualGeneration);
+ }
+ }
+
+ // Decode should not throw an exception.
+ methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
+
+ // Generation mismatch should throw an exception.
+ if (generation >= 0) {
+ encoded[prefix] = BOGUS_GENERATION;
+ try {
+ methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
+ fail();
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof CorruptEncodingException) {
+ CorruptEncodingException cee = (CorruptEncodingException) cause;
+ // Make sure error message includes actual generation.
+ assertTrue(cee.getMessage().indexOf(String.valueOf(BOGUS_GENERATION)) >= 0);
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ public void test_dataEncoding_multipleProperties() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_multipleProperties(0, 0, generation);
+ }
+ }
+
+ public void test_dataEncoding_multipleProperties_prefix() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_multipleProperties(5, 0, generation);
+ }
+ }
+
+ public void test_dataEncoding_multipleProperties_suffix() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_multipleProperties(0, 7, generation);
+ }
+ }
+
+ public void test_dataEncoding_multipleProperties_prefixAndSuffix() throws Exception {
+ for (int generation : GENERATIONS) {
+ test_dataEncoding_multipleProperties(5, 7, generation);
+ }
+ }
+
+ /**
+ * @param generation when non-negative, encode a storable layout generation
+ * value in one or four bytes.
+ */
+ private void test_dataEncoding_multipleProperties(int prefix, int suffix, int generation)
+ throws Exception
+ {
+ GenericEncodingStrategy strategy = new GenericEncodingStrategy
+ (TestStorable.class, null, 0, 0, prefix, suffix);
+
+ for (int i=0; i<SHORT_TEST; i++) {
+ StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
+
+ Method[] methods = generateCodecMethods(strategy, properties, null, generation);
+ Object[] values = selectPropertyValues(properties);
+
+ byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
+ .invoke(null, new Object[] {values});
+
+ int generationPrefix;
+ if (generation < 0) {
+ generationPrefix = 0;
+ } else if (generation < 128) {
+ generationPrefix = 1;
+ } else {
+ generationPrefix = 4;
+ }
+
+ assertTrue(encoded.length > (prefix + generationPrefix + suffix));
+
+ if (generation >= 0) {
+ if (generationPrefix == 1) {
+ assertEquals(generation, encoded[prefix]);
+ } else {
+ int actualGeneration = DataDecoder.decodeInt(encoded, prefix);
+ assertEquals(generation, actualGeneration);
+ }
+ }
+
+ if (prefix > 0) {
+ // Fill in with data which should be ignored by decoder.
+ for (int p=0; p<prefix; p++) {
+ encoded[p] = (byte) mRandom.nextInt();
+ }
+ }
+
+ if (suffix > 0) {
+ // Fill in with data which should be ignored by decoder.
+ for (int p=0; p<suffix; p++) {
+ encoded[encoded.length - p - 1] = (byte) mRandom.nextInt();
+ }
+ }
+
+ Object[] decodedValues = new Object[values.length];
+ methods[DECODE_OBJECT_ARRAY].invoke(null, decodedValues, encoded);
+
+ for (int j=0; j<properties.length; j++) {
+ Object a = values[j];
+ Object b = decodedValues[j];
+ if (properties[j].getType() == byte[].class) {
+ assertTrue(0 == TestDataEncoding.byteArrayOrNullCompare
+ ((byte[]) a, (byte[]) b));
+ } else {
+ assertEquals(a, b);
+ }
+ }
+
+ // Generation mismatch should throw an exception.
+ if (generation >= 0) {
+ encoded[prefix] = BOGUS_GENERATION;
+ try {
+ methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
+ fail();
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof CorruptEncodingException) {
+ CorruptEncodingException cee = (CorruptEncodingException) cause;
+ // Make sure error message includes actual generation.
+ assertTrue
+ (cee.getMessage().indexOf(String.valueOf(BOGUS_GENERATION)) >= 0);
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+ }
+
+ public void test_keyEncoding_noProperties() throws Exception {
+ test_keyEncoding_noProperties(0, 0);
+ }
+
+ public void test_keyEncoding_noProperties_prefix() throws Exception {
+ test_keyEncoding_noProperties(5, 0);
+ }
+
+ public void test_keyEncoding_noProperties_suffix() throws Exception {
+ test_keyEncoding_noProperties(0, 7);
+ }
+
+ public void test_keyEncoding_noProperties_prefixAndSuffix() throws Exception {
+ test_keyEncoding_noProperties(5, 7);
+ }
+
+ private void test_keyEncoding_noProperties(int prefix, int suffix) throws Exception {
+ GenericEncodingStrategy strategy = new GenericEncodingStrategy
+ (TestStorable.class, null, prefix, suffix, 0, 0);
+
+ Method[] methods = generateCodecMethods
+ (strategy, new StorableProperty[0], new Direction[0], -1);
+
+ // Encode should return an empty array.
+ byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
+ .invoke(null, new Object[] {new Object[0]});
+ assertEquals(encoded.length, prefix + suffix);
+
+ // Decode should not throw an exception.
+ methods[DECODE_OBJECT_ARRAY].invoke(null, new Object[0], encoded);
+ }
+
+ public void test_keyEncoding_multipleProperties() throws Exception {
+ test_keyEncoding_multipleProperties(0, 0);
+ }
+
+ public void test_keyEncoding_multipleProperties_prefix() throws Exception {
+ test_keyEncoding_multipleProperties(5, 0);
+ }
+
+ public void test_keyEncoding_multipleProperties_suffix() throws Exception {
+ test_keyEncoding_multipleProperties(0, 7);
+ }
+
+ public void test_keyEncoding_multipleProperties_prefixAndSuffix() throws Exception {
+ test_keyEncoding_multipleProperties(5, 7);
+ }
+
+ private void test_keyEncoding_multipleProperties(int prefix, int suffix) throws Exception {
+ GenericEncodingStrategy strategy = new GenericEncodingStrategy
+ (TestStorable.class, null, prefix, suffix, 0, 0);
+
+ for (int i=0; i<MEDIUM_TEST; i++) {
+ StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
+ Direction[] directions = selectDirections(properties.length);
+
+ Method[] methods = generateCodecMethods(strategy, properties, directions, -1);
+ Object[] values = selectPropertyValues(properties);
+
+ byte[] encoded = (byte[]) methods[ENCODE_OBJECT_ARRAY]
+ .invoke(null, new Object[] {values});
+
+ assertTrue(encoded.length > (prefix + suffix));
+
+ // Encode using partial encoding method, but do all
+ // properties. Ensure that the encoding is exactly the same.
+ byte[] encoded2 = (byte[]) methods[ENCODE_OBJECT_ARRAY_PARTIAL]
+ .invoke(null, new Object[] {values, 0, properties.length});
+ assertTrue(Arrays.equals(encoded, encoded2));
+
+ if (prefix > 0) {
+ // Fill in with data which should be ignored by decoder.
+ for (int p=0; p<prefix; p++) {
+ encoded[p] = (byte) mRandom.nextInt();
+ }
+ }
+
+ if (suffix > 0) {
+ // Fill in with data which should be ignored by decoder.
+ for (int p=0; p<suffix; p++) {
+ encoded[encoded.length - p - 1] = (byte) mRandom.nextInt();
+ }
+ }
+
+ Object[] decodedValues = new Object[values.length];
+ methods[DECODE_OBJECT_ARRAY].invoke(null, decodedValues, encoded);
+
+ for (int j=0; j<properties.length; j++) {
+ Object a = values[j];
+ Object b = decodedValues[j];
+ if (properties[j].getType() == byte[].class) {
+ assertTrue(0 == TestDataEncoding.byteArrayOrNullCompare
+ ((byte[]) a, (byte[]) b));
+ } else {
+ assertEquals(a, b);
+ }
+ }
+
+ // Now test partial encoding of keys.
+
+ // Clear out random affixes, since we don't need specific values
+ // anymore and it interferes with next test.
+ if (prefix > 0) {
+ for (int p=0; p<prefix; p++) {
+ encoded[p] = 0;
+ }
+ }
+ if (suffix > 0) {
+ for (int p=0; p<suffix; p++) {
+ encoded[encoded.length - p - 1] = 0;
+ }
+ }
+
+ for (int j=0; j<SHORT_TEST; j++) {
+ int start, end;
+ if (properties.length == 1) {
+ start = 0;
+ end = 1;
+ } else {
+ start = mRandom.nextInt(properties.length - 1);
+ // Partial encoding doesn't support zero properties, so
+ // ensure randomly selected stride is more than zero.
+ int stride;
+ do {
+ stride = mRandom.nextInt(properties.length - start);
+ } while (stride == 0);
+ end = start + stride + 1;
+ }
+
+ Object[] partialValues = new Object[end - start];
+ System.arraycopy(values, start, partialValues, 0, partialValues.length);
+
+ byte[] partial = (byte[]) methods[ENCODE_OBJECT_ARRAY_PARTIAL]
+ .invoke(null, new Object[] {partialValues, start, end});
+
+ // Partial key must be substring of full key.
+ int searchStart = start == 0 ? 0 : prefix;
+ int index = indexOf(encoded, searchStart, encoded.length - searchStart,
+ partial, 0, partial.length, 0);
+
+ if (start == 0) {
+ assertEquals(0, index);
+ } else {
+ assertTrue(index > 0);
+ }
+
+ if (properties.length == 1) {
+ break;
+ }
+ }
+ }
+ }
+
+ public void test_keyEncoding_ordering() throws Exception {
+ GenericEncodingStrategy strategy =
+ new GenericEncodingStrategy(TestStorable.class, null, 0, 0, 0, 0);
+
+ for (int i=0; i<MEDIUM_TEST; i++) {
+ StorableProperty<TestStorable>[] properties = selectProperties(1, 50);
+ Direction[] directions = selectDirections(properties.length);
+ Method[] methods = generateCodecMethods(strategy, properties, directions, -1);
+
+ Object[] values_1 = selectPropertyValues(properties);
+ byte[] encoded_1 = (byte[]) methods[ENCODE_OBJECT_ARRAY]
+ .invoke(null, new Object[] {values_1});
+
+ Object[] values_2 = selectPropertyValues(properties);
+ byte[] encoded_2 = (byte[]) methods[ENCODE_OBJECT_ARRAY]
+ .invoke(null, new Object[] {values_2});
+
+ int byteOrder = TestDataEncoding.byteArrayCompare(encoded_1, encoded_2);
+ int valueOrder = compareValues(properties, directions, values_1, values_2);
+
+ assertEquals(valueOrder, byteOrder);
+ }
+ }
+
+ private int compareValues(StorableProperty<?>[] properties, Direction[] directions,
+ Object[] values_1, Object[] values_2) {
+ int length = directions.length;
+ for (int i=0; i<length; i++) {
+ StorableProperty<?> property = properties[i];
+ Direction direction = directions[i];
+
+ Object value_1 = values_1[i];
+ Object value_2 = values_2[i];
+
+ int result;
+ if (property.getType() == byte[].class) {
+ result = TestDataEncoding.byteArrayOrNullCompare
+ ((byte[]) value_1, (byte[]) value_2);
+ } else {
+ if (value_1 == null) {
+ if (value_2 == null) {
+ result = 0;
+ } else {
+ result = 1;
+ }
+ } else if (value_2 == null) {
+ result = -1;
+ } else {
+ result = Integer.signum(((Comparable) value_1).compareTo(value_2));
+ }
+ }
+
+ if (result != 0) {
+ if (direction == Direction.DESCENDING) {
+ result = -result;
+ }
+ return result;
+ }
+ }
+
+ return 0;
+ }
+
+ // Method taken from String class and modified a bit.
+ private static int indexOf(byte[] source, int sourceOffset, int sourceCount,
+ byte[] target, int targetOffset, int targetCount,
+ int fromIndex) {
+ if (fromIndex >= sourceCount) {
+ return (targetCount == 0 ? sourceCount : -1);
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ if (targetCount == 0) {
+ return fromIndex;
+ }
+
+ byte first = target[targetOffset];
+ int max = sourceOffset + (sourceCount - targetCount);
+
+ for (int i = sourceOffset + fromIndex; i <= max; i++) {
+ // Look for first byte.
+ if (source[i] != first) {
+ while (++i <= max && source[i] != first);
+ }
+
+ // Found first byte, now look at the rest of v2
+ if (i <= max) {
+ int j = i + 1;
+ int end = j + targetCount - 1;
+ for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++);
+
+ if (j == end) {
+ // Found whole string.
+ return i - sourceOffset;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * First method is the encoder, second is the decoder. Both methods are
+ * static. Encoder accepts an object array of property values, and it
+ * returns a byte array. Decoder accepts an object array to receive
+ * property values, an encoded byte array, and it returns void.
+ *
+ * <p>If generating key encoding and the property count is more than zero,
+ * then third element in array is a key encoder that supports partial
+ * encoding. In addition to the object array argument, it also accepts an
+ * int start and an int end argument. The start of the range is inclusive,
+ * and the end is exclusive.
+ *
+ * @param directions when supplied, build key encoding/decoding. Otherwise,
+ * build data encoding/decoding.
+ * @param generation when non-negative, encode a storable layout generation
+ * value in one or four bytes.
+ */
+ private Method[] generateCodecMethods(GenericEncodingStrategy strategy,
+ StorableProperty<TestStorable>[] properties,
+ Direction[] directions,
+ int generation)
+ throws Exception
+ {
+ ClassInjector ci = ClassInjector.create(TestStorable.class.getName(), null);
+ ClassFile cf = new ClassFile(ci.getClassName());
+ cf.markSynthetic();
+ cf.setTarget("1.5");
+
+ cf.addDefaultConstructor();
+
+ TypeDesc byteArrayType = TypeDesc.forClass(byte[].class);
+ TypeDesc objectArrayType = TypeDesc.forClass(Object[].class);
+
+ // Build encode method.
+ MethodInfo mi = cf.addMethod(Modifiers.PUBLIC_STATIC, "encode", byteArrayType,
+ new TypeDesc[] {objectArrayType});
+ CodeBuilder b = new CodeBuilder(mi);
+ LocalVariable encodedVar;
+ if (directions != null) {
+ OrderedProperty<TestStorable>[] ordered =
+ makeOrderedProperties(properties, directions);
+ encodedVar =
+ strategy.buildKeyEncoding(b, ordered, b.getParameter(0), null, false, null, null);
+ } else {
+ encodedVar = strategy.buildDataEncoding
+ (b, properties, b.getParameter(0), null, false, generation);
+ }
+ b.loadLocal(encodedVar);
+ b.returnValue(byteArrayType);
+
+ // Build decode method.
+ mi = cf.addMethod(Modifiers.PUBLIC_STATIC, "decode", null,
+ new TypeDesc[] {objectArrayType, byteArrayType});
+ b = new CodeBuilder(mi);
+ if (directions != null) {
+ OrderedProperty<TestStorable>[] ordered =
+ makeOrderedProperties(properties, directions);
+ strategy.buildKeyDecoding
+ (b, ordered, b.getParameter(0), null, false, b.getParameter(1));
+ } else {
+ strategy.buildDataDecoding
+ (b, properties, b.getParameter(0), null, false,
+ generation, null, b.getParameter(1));
+ }
+ b.returnVoid();
+
+ if (directions != null && properties.length > 0) {
+ // Build encode partial key method.
+ mi = cf.addMethod
+ (Modifiers.PUBLIC_STATIC, "encodePartial", byteArrayType,
+ new TypeDesc[] {objectArrayType, TypeDesc.INT, TypeDesc.INT});
+ b = new CodeBuilder(mi);
+ OrderedProperty<TestStorable>[] ordered =
+ makeOrderedProperties(properties, directions);
+ encodedVar =
+ strategy.buildKeyEncoding(b, ordered, b.getParameter(0), null, false,
+ b.getParameter(1), b.getParameter(2));
+ b.loadLocal(encodedVar);
+ b.returnValue(byteArrayType);
+ }
+
+ Class<?> clazz = ci.defineClass(cf);
+
+ Method encode = clazz.getMethod("encode", Object[].class);
+ Method decode = clazz.getMethod("decode", Object[].class, byte[].class);
+ Method encodePartial = null;
+ if (directions != null && properties.length > 0) {
+ encodePartial = clazz.getMethod("encodePartial", Object[].class, int.class, int.class);
+ }
+
+ Method[] methods = new Method[3];
+ methods[ENCODE_OBJECT_ARRAY] = encode;
+ methods[DECODE_OBJECT_ARRAY] = decode;
+ methods[ENCODE_OBJECT_ARRAY_PARTIAL] = encodePartial;
+
+ return methods;
+ }
+
+ private StorableProperty<TestStorable>[] selectProperties(int minCount, int maxCount) {
+ int length = (minCount == maxCount) ? minCount
+ : (mRandom.nextInt(maxCount - minCount + 1) + minCount);
+
+ StorableProperty<TestStorable>[] selection = new StorableProperty[length];
+
+ for (int i=length; --i>=0; ) {
+ selection[i] = mProperties[mRandom.nextInt(mProperties.length)];
+ }
+
+ return selection;
+ }
+
+ private Direction[] selectDirections(int count) {
+ Direction[] directions = new Direction[count];
+
+ for (int i=count; --i>=0; ) {
+ Direction dir;
+ switch (mRandom.nextInt(3)) {
+ default:
+ dir = Direction.UNSPECIFIED;
+ break;
+ case 1:
+ dir = Direction.ASCENDING;
+ break;
+ case 2:
+ dir = Direction.DESCENDING;
+ break;
+ }
+ directions[i] = dir;
+ }
+
+ return directions;
+ }
+
+ private OrderedProperty<TestStorable>[] makeOrderedProperties
+ (StorableProperty<TestStorable>[] properties, Direction[] directions) {
+
+ int length = properties.length;
+ OrderedProperty<TestStorable>[] ordered = new OrderedProperty[length];
+ for (int i=length; --i>=0; ) {
+ ordered[i] = OrderedProperty.get(properties[i], directions[i]);
+ }
+
+ return ordered;
+ }
+
+ /**
+ * Returns an array of the same size with randomly selected values filled
+ * in that match the property type.
+ */
+ private Object[] selectPropertyValues(StorableProperty<?>[] properties) {
+ int length = properties.length;
+ Object[] values = new Object[length];
+
+ for (int i=length; --i>=0; ) {
+ StorableProperty<?> property = properties[i];
+ TypeDesc type = TypeDesc.forClass(property.getType());
+
+ Object value;
+
+ if (property.isNullable() && mRandom.nextInt(100) == 0) {
+ value = null;
+ } else {
+ TypeDesc prim = type.toPrimitiveType();
+ if (prim != null) {
+ switch (prim.getTypeCode()) {
+ case TypeDesc.BOOLEAN_CODE: default:
+ value = mRandom.nextBoolean();
+ break;
+ case TypeDesc.CHAR_CODE:
+ value = (char) mRandom.nextInt();
+ break;
+ case TypeDesc.FLOAT_CODE:
+ value = Float.intBitsToFloat(mRandom.nextInt());
+ break;
+ case TypeDesc.DOUBLE_CODE:
+ value = Double.longBitsToDouble(mRandom.nextLong());
+ break;
+ case TypeDesc.BYTE_CODE:
+ value = (byte) mRandom.nextInt();
+ break;
+ case TypeDesc.SHORT_CODE:
+ value = (short) mRandom.nextInt();
+ break;
+ case TypeDesc.INT_CODE:
+ value = mRandom.nextInt();
+ break;
+ case TypeDesc.LONG_CODE:
+ value = mRandom.nextLong();
+ break;
+ }
+ } else if (type == TypeDesc.STRING) {
+ int len = mRandom.nextInt(100);
+ StringBuilder sb = new StringBuilder(len);
+ for (int j=0; j<len; j++) {
+ sb.append((char) mRandom.nextInt());
+ }
+ value = sb.toString();
+ } else {
+ int len = mRandom.nextInt(100);
+ byte[] bytes = new byte[len];
+ for (int j=0; j<len; j++) {
+ bytes[j] = (byte) mRandom.nextInt();
+ }
+ value = bytes;
+ }
+ }
+
+ values[i] = value;
+ }
+
+ return values;
+ }
+
+ /**
+ * Just a collection of storable properties. At least one property defined
+ * per supported type.
+ */
+ @PrimaryKey("byte") // I don't really care what the primary key is.
+ public static interface TestStorable extends Storable {
+ byte getByte();
+
+ void setByte(byte b);
+
+ Byte getByteObj();
+
+ void setByteObj(Byte b);
+
+ @Nullable
+ Byte getNullableByteObj();
+
+ void setNullableByteObj(Byte b);
+
+ short getShort();
+
+ void setShort(short s);
+
+ Short getShortObj();
+
+ void setShortObj(Short s);
+
+ @Nullable
+ Short getNullableShortObj();
+
+ void setNullableShortObj(Short s);
+
+ char getChar();
+
+ void setChar(char c);
+
+ Character getCharacterObj();
+
+ void setCharacterObj(Character c);
+
+ @Nullable
+ Character getNullableCharacterObj();
+
+ void setNullableCharacterObj(Character c);
+
+ int getInt();
+
+ void setInt(int i);
+
+ Integer getIntegerObj();
+
+ void setIntegerObj(Integer obj);
+
+ @Nullable
+ Integer getNullableIntegerObj();
+
+ void setNullableIntegerObj(Integer obj);
+
+ long getLong();
+
+ void setLong(long i);
+
+ Long getLongObj();
+
+ void setLongObj(Long i);
+
+ @Nullable
+ Long getNullableLongObj();
+
+ void setNullableLongObj(Long i);
+
+ float getFloat();
+
+ void setFloat(float f);
+
+ Float getFloatObj();
+
+ void setFloatObj(Float f);
+
+ @Nullable
+ Float getNullableFloatObj();
+
+ void setNullableFloatObj(Float f);
+
+ double getDouble();
+
+ void setDouble(double d);
+
+ Double getDoubleObj();
+
+ void setDoubleObj(Double d);
+
+ @Nullable
+ Double getNullableDoubleObj();
+
+ void setNullableDoubleObj(Double d);
+
+ byte[] getByteArray();
+
+ void setByteArray(byte[] b);
+
+ @Nullable
+ byte[] getNullableByteArray();
+
+ void setNullableByteArray(byte[] b);
+
+ String getString();
+
+ void setString(String s);
+
+ @Nullable
+ String getNullableString();
+
+ void setNullableString(String s);
+ }
+}
diff --git a/src/test/java/com/amazon/carbonado/spi/raw/TestKeyEncoding.java b/src/test/java/com/amazon/carbonado/spi/raw/TestKeyEncoding.java new file mode 100644 index 0000000..246e6fe --- /dev/null +++ b/src/test/java/com/amazon/carbonado/spi/raw/TestKeyEncoding.java @@ -0,0 +1,571 @@ +/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.spi.raw;
+
+import java.util.Random;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test case for {@link KeyEncoder} and {@link KeyDecoder}.
+ * <p>
+ * It generates random data values, checks that the decoding produces the
+ * original results, and it checks that the order of the encoded bytes matches
+ * the order of the original data values.
+ *
+ * @author Brian S O'Neill
+ */
+public class TestKeyEncoding extends TestCase {
+ private static final int SHORT_TEST = 100;
+ private static final int MEDIUM_TEST = 1000;
+ private static final int LONG_TEST = 10000;
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestKeyEncoding.class);
+ }
+
+ private final long mSeed;
+
+ private Random mRandom;
+
+ public TestKeyEncoding(String name) {
+ super(name);
+ mSeed = 5399777425345431L;
+ }
+
+ protected void setUp() {
+ mRandom = new Random(mSeed);
+ }
+
+ protected void tearDown() {
+ }
+
+ public void test_booleanDesc() throws Exception {
+ byte[] bytes = new byte[1];
+ boolean lastValue = false;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ boolean value = mRandom.nextBoolean();
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeBooleanDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_BooleanDesc() throws Exception {
+ byte[] bytes = new byte[1];
+ Boolean lastValue = false;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Boolean value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ value = mRandom.nextBoolean();
+ }
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeBooleanObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_byteDesc() throws Exception {
+ byte[] bytes = new byte[1];
+ byte lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ byte value = (byte) mRandom.nextInt();
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeByteDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_ByteDesc() throws Exception {
+ byte[] bytes = new byte[2];
+ Byte lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Byte value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
+ } else {
+ value = (byte) mRandom.nextInt();
+ assertEquals(2, KeyEncoder.encodeDesc(value, bytes, 0));
+ }
+ assertEquals(value, KeyDecoder.decodeByteObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_shortDesc() throws Exception {
+ byte[] bytes = new byte[2];
+ short lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ short value = (short) mRandom.nextInt();
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeShortDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_ShortDesc() throws Exception {
+ byte[] bytes = new byte[3];
+ Short lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Short value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
+ } else {
+ value = (short) mRandom.nextInt();
+ assertEquals(3, KeyEncoder.encodeDesc(value, bytes, 0));
+ }
+ assertEquals(value, KeyDecoder.decodeShortObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_charDesc() throws Exception {
+ byte[] bytes = new byte[2];
+ char lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ char value = (char) mRandom.nextInt();
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeCharDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_CharacterDesc() throws Exception {
+ byte[] bytes = new byte[3];
+ Character lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Character value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
+ } else {
+ value = (char) mRandom.nextInt();
+ assertEquals(3, KeyEncoder.encodeDesc(value, bytes, 0));
+ }
+ assertEquals(value, KeyDecoder.decodeCharacterObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_intDesc() throws Exception {
+ byte[] bytes = new byte[4];
+ int lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ int value = mRandom.nextInt();
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeIntDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_IntegerDesc() throws Exception {
+ byte[] bytes = new byte[5];
+ Integer lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Integer value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
+ } else {
+ value = mRandom.nextInt();
+ assertEquals(5, KeyEncoder.encodeDesc(value, bytes, 0));
+ }
+ assertEquals(value, KeyDecoder.decodeIntegerObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_longDesc() throws Exception {
+ byte[] bytes = new byte[8];
+ long lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ long value = mRandom.nextLong();
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeLongDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_LongDesc() throws Exception {
+ byte[] bytes = new byte[9];
+ Long lastValue = 0L;
+ byte[] lastBytes = null;
+ for (int i=0; i<SHORT_TEST; i++) {
+ Long value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ assertEquals(1, KeyEncoder.encodeDesc(value, bytes, 0));
+ } else {
+ value = mRandom.nextLong();
+ assertEquals(9, KeyEncoder.encodeDesc(value, bytes, 0));
+ }
+ assertEquals(value, KeyDecoder.decodeLongObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_floatDesc() throws Exception {
+ byte[] bytes = new byte[4];
+ float lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ float value = Float.intBitsToFloat(mRandom.nextInt());
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeFloatDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_FloatDesc() throws Exception {
+ byte[] bytes = new byte[4];
+ Float lastValue = 0f;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ Float value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ value = Float.intBitsToFloat(mRandom.nextInt());
+ }
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeFloatObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_doubleDesc() throws Exception {
+ byte[] bytes = new byte[8];
+ double lastValue = 0;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ double value = Double.longBitsToDouble(mRandom.nextLong());
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeDoubleDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_DoubleDesc() throws Exception {
+ byte[] bytes = new byte[8];
+ Double lastValue = 0d;
+ byte[] lastBytes = null;
+ for (int i=0; i<LONG_TEST; i++) {
+ Double value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ value = Double.longBitsToDouble(mRandom.nextLong());
+ }
+ KeyEncoder.encodeDesc(value, bytes, 0);
+ assertEquals(value, KeyDecoder.decodeDoubleObjDesc(bytes, 0));
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_String() throws Exception {
+ String lastValue = null;
+ byte[] lastBytes = null;
+ String[] ref = new String[1];
+ for (int i=0; i<SHORT_TEST; i++) {
+ String value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ int length;
+ switch (mRandom.nextInt(15)) {
+ default:
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
+ length = mRandom.nextInt(100);
+ break;
+ case 8: case 9: case 10: case 11:
+ length = mRandom.nextInt(200);
+ break;
+ case 12: case 13:
+ length = mRandom.nextInt(20000);
+ break;
+ case 14:
+ length = mRandom.nextInt(3000000);
+ break;
+ }
+ char[] chars = new char[length];
+ for (int j=0; j<length; j++) {
+ char c;
+ switch (mRandom.nextInt(7)) {
+ default:
+ case 0: case 1: case 2: case 3:
+ c = (char) mRandom.nextInt(128);
+ break;
+ case 4: case 5:
+ c = (char) mRandom.nextInt(4000);
+ break;
+ case 6:
+ c = (char) mRandom.nextInt();
+ break;
+ }
+ chars[j] = c;
+ }
+ value = new String(chars);
+ }
+
+ byte[] bytes = new byte[KeyEncoder.calculateEncodedStringLength(value)];
+ assertEquals(bytes.length, KeyEncoder.encode(value, bytes, 0));
+ assertEquals(bytes.length, KeyDecoder.decodeString(bytes, 0, ref));
+ assertEquals(value, ref[0]);
+
+ if (lastBytes != null) {
+ int sgn = TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_StringDesc() throws Exception {
+ String lastValue = null;
+ byte[] lastBytes = null;
+ String[] ref = new String[1];
+ for (int i=0; i<SHORT_TEST; i++) {
+ String value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ int length;
+ switch (mRandom.nextInt(15)) {
+ default:
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
+ length = mRandom.nextInt(100);
+ break;
+ case 8: case 9: case 10: case 11:
+ length = mRandom.nextInt(200);
+ break;
+ case 12: case 13:
+ length = mRandom.nextInt(20000);
+ break;
+ case 14:
+ length = mRandom.nextInt(3000000);
+ break;
+ }
+ char[] chars = new char[length];
+ for (int j=0; j<length; j++) {
+ char c;
+ switch (mRandom.nextInt(7)) {
+ default:
+ case 0: case 1: case 2: case 3:
+ c = (char) mRandom.nextInt(128);
+ break;
+ case 4: case 5:
+ c = (char) mRandom.nextInt(4000);
+ break;
+ case 6:
+ c = (char) mRandom.nextInt();
+ break;
+ }
+ chars[j] = c;
+ }
+ value = new String(chars);
+ }
+
+ byte[] bytes = new byte[KeyEncoder.calculateEncodedStringLength(value)];
+ assertEquals(bytes.length, KeyEncoder.encodeDesc(value, bytes, 0));
+ assertEquals(bytes.length, KeyDecoder.decodeStringDesc(bytes, 0, ref));
+ assertEquals(value, ref[0]);
+
+
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.compare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_byteArray() throws Exception {
+ byte[] lastValue = null;
+ byte[] lastBytes = null;
+ byte[][] ref = new byte[1][];
+ for (int i=0; i<LONG_TEST; i++) {
+ byte[] value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ int length = mRandom.nextInt(4000);
+ value = new byte[length];
+ for (int j=0; j<length; j++) {
+ value[j] = (byte) mRandom.nextInt();
+ }
+ }
+
+ byte[] bytes = new byte[KeyEncoder.calculateEncodedLength(value)];
+ assertEquals(bytes.length, KeyEncoder.encode(value, bytes, 0));
+ assertEquals(bytes.length, KeyDecoder.decode(bytes, 0, ref));
+ if (ref[0] == null) {
+ assertEquals(value, null);
+ } else if (value == null) {
+ assertEquals(value, ref[0]);
+ } else {
+ assertEquals(0, TestDataEncoding.byteArrayCompare(value, ref[0], value.length));
+ }
+
+ if (lastBytes != null) {
+ int sgn = TestDataEncoding.byteArrayOrNullCompare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+
+ public void test_byteArrayDesc() throws Exception {
+ byte[] lastValue = null;
+ byte[] lastBytes = null;
+ byte[][] ref = new byte[1][];
+ for (int i=0; i<LONG_TEST; i++) {
+ byte[] value;
+ if (mRandom.nextInt(10) == 1) {
+ value = null;
+ } else {
+ int length = mRandom.nextInt(4000);
+ value = new byte[length];
+ for (int j=0; j<length; j++) {
+ value[j] = (byte) mRandom.nextInt();
+ }
+ }
+
+ byte[] bytes = new byte[KeyEncoder.calculateEncodedLength(value)];
+ assertEquals(bytes.length, KeyEncoder.encodeDesc(value, bytes, 0));
+ assertEquals(bytes.length, KeyDecoder.decodeDesc(bytes, 0, ref));
+ if (ref[0] == null) {
+ assertEquals(value, null);
+ } else if (value == null) {
+ assertEquals(value, ref[0]);
+ } else {
+ assertEquals(0, TestDataEncoding.byteArrayCompare(value, ref[0], value.length));
+ }
+
+ if (lastBytes != null) {
+ int sgn = -TestDataEncoding.byteArrayOrNullCompare(value, lastValue);
+ assertEquals(sgn, TestDataEncoding.byteArrayCompare(bytes, lastBytes));
+ }
+ lastValue = value;
+ lastBytes = bytes.clone();
+ }
+ }
+}
diff --git a/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java b/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java new file mode 100644 index 0000000..faace6d --- /dev/null +++ b/src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java @@ -0,0 +1,746 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * 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.amazon.carbonado.synthetic; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; +import java.util.Set; +import java.util.Map; +import java.util.HashMap; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.joda.time.DateTime; + +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.PrimaryKey; +import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.Version; +import com.amazon.carbonado.Nullable; +import com.amazon.carbonado.Join; + +import com.amazon.carbonado.adapter.YesNoAdapter; +import com.amazon.carbonado.adapter.TextAdapter; +import com.amazon.carbonado.info.Direction; +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import com.amazon.carbonado.repo.toy.ToyRepository; + +import com.amazon.carbonado.stored.StorableTestBasic; + +/** + * @author Don Schneider + */ +public class TestSyntheticStorableBuilders extends TestCase { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestSyntheticStorableBuilders.class); + } + + private static final ValueVendor VENDOR = new ValueVendor(); + + public static final String VERSION = '@' + + Version.class.toString().substring(10) + "()"; + + public static final String NULLABLE = '@' + + Nullable.class.toString().substring(10) + "()"; + + public static final String JOIN = '@' + Join.class.toString().substring(10) + '('; + + public static final String INTERNAL = "internal=[]"; + + public static final String EXTERNAL = "external=[]"; + + public final static String YESNO = '@' + + YesNoAdapter.class.toString().substring(10) + "(lenient=true)"; + + public static final String TEXT = '@' + + TextAdapter.class.toString().substring(10) + "(charset=UTF-8, altCharsets=[])"; + + private static Set s_storableInterfaceMethods = new HashSet(); + + public static final TestDef[] TESTS = new TestDef[] { + new TestDef("string/long", + StorableTestBasic.class, + StorableTestBasic.class.getName() + "~N+stringProp-longProp", + new IndexDef[] { + new IndexDef("stringProp", Direction.ASCENDING), + new IndexDef("longProp", Direction.DESCENDING), }, + new MethodDef[] { + new MethodDef("getStringProp", new String[] { TEXT }), + new MethodDef("getLongProp", null), + new MethodDef("getId", null), + new MethodDef("getIntProp", null), + new MethodDef("getDoubleProp", null), + // FIXME: add version prop to STB +// new MethodDef("getVersionNumber", +// new String[] { VERSION }), + new MethodDef("getMaster_0", new String[] { + NULLABLE, JOIN }), + new MethodDef("getIsConsistent_0", null), +// new MethodDef("setVersionNumber", null), + new MethodDef("setAllProperties_0", null), + new MethodDef("setMaster_0", null), + new MethodDef("setId", null), + + new MethodDef("setStringProp", null), + new MethodDef("setLongProp", null), + new MethodDef("setID", null), + new MethodDef("setIntProp", null), + new MethodDef("setDoubleProp", null), + + new MethodDef("getClass", null), + new MethodDef("wait", null), + new MethodDef("notify", null), + new MethodDef("notifyAll", null), }, + null), + new TestDef("string/long+synthetic", + StorableTestBasic.class, + StorableTestBasic.class.getName() + "~N+stringProp-longProp", + new IndexDef[] { + new IndexDef("stringProp", Direction.ASCENDING), + new IndexDef("longProp", Direction.DESCENDING), }, + new MethodDef[] { + new MethodDef("getStringProp", new String[] { TEXT }), + new MethodDef("getLongProp", null), + new MethodDef("getId", null), + new MethodDef("getIntProp", null), + new MethodDef("getDoubleProp", null), + // FIXME: add version prop to STB +// new MethodDef("getVersionNumber", +// new String[] { VERSION }), + new MethodDef("getMaster_0", new String[] { + NULLABLE, JOIN }), + new MethodDef("getIsConsistent_0", null), +// new MethodDef("setVersionNumber", null), + new MethodDef("setAllProperties_0", null), + new MethodDef("setMaster_0", null), + new MethodDef("setId", null), + + new MethodDef("setStringProp", null), + new MethodDef("setLongProp", null), + new MethodDef("setID", null), + new MethodDef("setIntProp", null), + new MethodDef("setDoubleProp", null), + + new MethodDef("getClass", null), + new MethodDef("wait", null), + new MethodDef("notify", null), + new MethodDef("notifyAll", null), + + new MethodDef("getTestSynth", new String[] { NULLABLE }), + new MethodDef("setTestSynth", null), + }, + new SyntheticProperty[] { new SyntheticProperty("testSynth", + String.class, + true, + false) }), + new TestDef("string/long+versionedSynthetic", + StorableTestBasic.class, + StorableTestBasic.class.getName() + "~N+stringProp-longProp", + new IndexDef[] { + new IndexDef("stringProp", Direction.ASCENDING), + new IndexDef("longProp", Direction.DESCENDING), }, + new MethodDef[] { + + new MethodDef("getStringProp", new String[] { TEXT }), + new MethodDef("getLongProp", null), + new MethodDef("getId", null), + new MethodDef("getIntProp", null), + new MethodDef("getDoubleProp", null), + new MethodDef("getMaster_0", new String[] { + NULLABLE, JOIN }), + new MethodDef("getIsConsistent_0", null), + new MethodDef("setAllProperties_0", null), + new MethodDef("setMaster_0", null), + new MethodDef("setId", null), + + new MethodDef("setStringProp", null), + new MethodDef("setLongProp", null), + new MethodDef("setID", null), + new MethodDef("setIntProp", null), + new MethodDef("setDoubleProp", null), + + new MethodDef("getClass", null), + new MethodDef("wait", null), + new MethodDef("notify", null), + new MethodDef("notifyAll", null), + + new MethodDef("getTestSynth", new String[] { VERSION }), + new MethodDef("setTestSynth", null), + }, + new SyntheticProperty[] { new SyntheticProperty("testSynth", + int.class, + false, + true) }) }; + + static { + for (Method m : Storable.class.getMethods()) { + s_storableInterfaceMethods.add(m.getName()); + } + } + + protected Repository mRepository; + + public TestSyntheticStorableBuilders(String name) { + super(name); + } + + /* (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception + { + super.setUp(); + mRepository = new ToyRepository(); + } + + @Override + protected void tearDown() throws Exception { + if (mRepository != null) { + mRepository.close(); + mRepository = null; + } + } + + public void testSanity() throws Exception { + exerciseStorable(StorableTestBasic.class); + } + + + /** + * Test syntheticBuilder + */ + public <S extends Storable> void test_syntheticBuilder() throws Exception { + SyntheticProperty[] props = new SyntheticProperty[] { + new SyntheticProperty("synthId", + int.class, + false, + false), + new SyntheticProperty("synthStr", + String.class, + true, + false), + new SyntheticProperty("synthInt", + int.class, + false, + false), + new SyntheticProperty("synthVers", + int.class, + false, + true), + new SyntheticProperty("synthBool", + boolean.class, + false, + false), }; + + TestDef test = new TestDef("synthetic", + null, + "TSSB~synthId~synthStr~synthInt~synthVers~synthBool", + null, + new MethodDef[] { + new MethodDef("getSynthId", + null), + new MethodDef("getSynthStr", + new String[] { NULLABLE }), + new MethodDef("getSynthInt", null), + new MethodDef("getSynthVers", + new String[] { VERSION }), + new MethodDef("isSynthBool", null), + + new MethodDef("setSynthId", null), + new MethodDef("setSynthStr", null), + new MethodDef("setSynthInt", null), + new MethodDef("setSynthVers", null), + new MethodDef("setSynthBool", null), + + new MethodDef("getClass", null), + new MethodDef("wait", null), + new MethodDef("notify", null), + new MethodDef("notifyAll", null), }, + props); + + SyntheticStorableBuilder b = new SyntheticStorableBuilder( + "TSSB", this.getClass().getClassLoader()); + int i = 0; + for (SyntheticProperty source : props) { + b.addProperty(source); + if (i == 0) { + // First is primary key. + b.addPrimaryKey().addProperty(source.getName()); + } + i++; + } + Class synth = b.build(); + validateIndexEntry(test, synth); + + exerciseStorable(synth); + } + + + + public void testSyntheticReference() throws Exception { + for (TestDef test : TESTS) { + StorableInfo info = StorableIntrospector.examine(test.mClass); + + SyntheticStorableReferenceBuilder<StorableTestBasic> builder + = new SyntheticStorableReferenceBuilder<StorableTestBasic>(test.mClass, false); + + for (IndexDef refProp : test.mProps) { + StorableProperty storableProperty = ((StorableProperty) info.getAllProperties() + .get(refProp.mProp)); + builder.addKeyProperty(refProp.mProp, refProp.getDir()); + } + builder.build(); + Class s = builder.getStorableClass(); + validateIndexEntry(test, s); + exerciseStorable(s); + + StorableTestBasic master = + mRepository.storageFor(StorableTestBasic.class).prepare(); + populate(master); + master.insert(); + + Storable index = mRepository.storageFor(s).prepare(); + builder.setAllProperties(index, master); + index.insert(); + + Storable indexChecker = mRepository.storageFor(s).prepare(); + builder.setAllProperties(indexChecker, master); + assertTrue(indexChecker.tryLoad()); + + StorableTestBasic masterChecker = builder.loadMaster(indexChecker); + assertEquals(master, masterChecker); + + assertTrue(builder.isConsistent(index, master)); + masterChecker = + mRepository.storageFor(StorableTestBasic.class).prepare(); + master.copyAllProperties(masterChecker); + assertTrue(builder.isConsistent(index, masterChecker)); + masterChecker.setId(-42); + assertFalse(builder.isConsistent(index, masterChecker)); + + } + } + + /** + * @param <S> + * @param storableType + * @throws SupportException + * @throws RepositoryException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws PersistException + * @throws FetchException + */ + protected <T extends Storable> void exerciseStorable(Class<T> storableType) throws SupportException, RepositoryException, IllegalAccessException, InvocationTargetException, PersistException, FetchException { + T persister = mRepository.storageFor(storableType).prepare(); + Map<String, Object> valueMap = populate(persister); + + persister.insert(); + T reader = mRepository.storageFor(storableType).prepare(); + persister.copyPrimaryKeyProperties(reader); + assertTrue(reader.tryLoad()); + + for (Method method : storableType.getMethods()) { + if (method.getName().startsWith("get") ) { + Class returnType = method.getReturnType(); + Class[] paramTypes = method.getParameterTypes(); + if (VENDOR.hasValue(returnType) && paramTypes.length == 0) { + Object expectedValue = valueMap.get(method.getName().substring(3)); + Object actualValue = method.invoke(persister, (Object[]) null); + assertEquals(expectedValue, actualValue); + } + } + } + } + + /** + * Populates a storable with random values. Any complex properties will be skipped. + * @param storable to populate + * @return map from property name to value set into it + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + protected <T extends Storable> Map<String, Object> populate(T storable) + throws IllegalAccessException, InvocationTargetException { + ValueVendor vendor = new ValueVendor(); + Map<String, Object> valueMap = new HashMap<String, Object>(); + vendor.reset(); + for (Method method : storable.getClass().getMethods()) { + if (method.getName().startsWith("set") ) { + Class[] paramTypes = method.getParameterTypes(); + Class type = paramTypes[0]; + if (vendor.hasValue(type)) { + Object value = vendor.getValue(type); + method.invoke(storable, new Object[] {value}); + valueMap.put(method.getName().substring(3), value); + } + } + } + return valueMap; + } + + + + + protected void validateIndexEntry(TestDef test, Class syntheticClass) { + assertEquals(test.mClassName, syntheticClass.getName()); + Map<String, String[]> expectedMethods = new HashMap<String, String[]>(); + if (null != test.mMethods) { + for (MethodDef m : test.mMethods) { + expectedMethods.put(m.mName, m.mAnnotations); + } + } + + Class iface = syntheticClass.getInterfaces()[0]; + assertEquals(test.mTestMoniker, Storable.class, iface); + + boolean missed = false; + int storableIfaceMethodCount = 0; + int methodCount = 0; + for (Method m : syntheticClass.getMethods()) { + if (s_storableInterfaceMethods.contains(m.getName())) { + storableIfaceMethodCount++; + } else { + Set expectedAnnotations = null; + if (expectedMethods.containsKey(m.getName())) { + expectedAnnotations = new HashSet(); + String[] expectedAnnotationArray = expectedMethods.get(m.getName()); + if (null != expectedAnnotationArray) { + for (String a : expectedAnnotationArray) { + expectedAnnotations.add(a); + } + } + } else { + System.out.println("missed method " + methodCount + + "\nMethodDef(\"" + m.getName() + "\", null),"); + missed = true; + } + + if (expectedAnnotations != null) { + assertEquals(test.mTestMoniker + ": " + m.getName(), + expectedAnnotations.size(), + m.getDeclaredAnnotations().length); + } else { + assertEquals(test.mTestMoniker + ": " + m.getName(), + 0, + m.getDeclaredAnnotations().length); + } + + for (Annotation a : m.getDeclaredAnnotations()) { + if (missed) { + System.out.println(" " + a); + } + if (expectedAnnotations != null) { + if (!expectedAnnotations.contains(a.toString())) { + boolean found = false; + for (Object candidate : expectedAnnotations.toArray()) { + if (a.toString().startsWith((String) candidate)) { + found = true; + break; + } + } + assertTrue(test.mTestMoniker + ':' + m.getName() + + " -- unexpected annotation " + a, found); + } // else we're ok + } + ; + } + } + methodCount++; + } + assertEquals(test.mTestMoniker, + s_storableInterfaceMethods.size(), + storableIfaceMethodCount); + assertFalse(test.mTestMoniker, missed); + } + + static class MethodDef { + String mName; + + String[] mAnnotations; + + public MethodDef(String name, String[] annotations) { + mName = name; + mAnnotations = annotations; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + String annotations = null; + if (null != mAnnotations) { + for (int i = 0; i < mAnnotations.length; i++) { + if (null == annotations) { + annotations = ""; + } else { + annotations += ", "; + } + annotations += mAnnotations[i]; + } + } else { + annotations = " -- "; + } + return mName + " [" + annotations + ']'; + } + + } + + /** + * More intuitive mechanism for defining index properties; propClass is + * optionally used for synthetic references (since we don't formally define + * properties in this test environment) + */ + public static class IndexDef { + private String mProp; + + private Direction mDir; + + public IndexDef(String prop, Direction dir) { + mProp = prop; + mDir = dir; + } + + public String getProp() { + return mProp; + } + + public Direction getDir() { + return mDir; + } + } + + public static class TestDef { + public String mTestMoniker; + + public Class mClass; + + public String mClassName; + + public IndexDef[] mProps; + + public MethodDef[] mMethods; + + public SyntheticProperty[] mSynths; + + public TestDef(String moniker, + Class aClass, + String aClassName, + IndexDef[] props, + MethodDef[] methods, + SyntheticProperty[] synths) { + mTestMoniker = moniker; + mClass = aClass; + mClassName = aClassName; + mProps = props; + mMethods = methods; + mSynths = synths; + } + + public String toString() { + return mTestMoniker; + } + } + + public interface ValueGetter { + public void reset(); + public Object getValue(); + } + + static class BoolGetter implements ValueGetter { + final boolean start = false; + boolean mNext = start; + public void reset() { + mNext = start; + } + public Boolean getValue() { + mNext = !mNext; + return mNext; + } + } + + static class IntGetter implements ValueGetter { + // hang onto the seed so we get a reproducible stream + int mSeed; + Random mNext; + + IntGetter() { + reset(); + } + + public void reset() { + Random seeder = new Random(); + while (mSeed == 0) { + mSeed = seeder.nextInt(); + } + mNext = new Random(mSeed); + } + + public Integer getValue() { + return mNext.nextInt(); + } + + public Integer getPositiveValue() { + return Math.abs(mNext.nextInt()); + } + } + + static class LongGetter implements ValueGetter { + final long start = 56789; + long mNext = start; + public void reset() { + mNext = start; + } + public Long getValue() { + return mNext++; + } + } + + static class FloatGetter implements ValueGetter { + final float start = (float) Math.PI; + float mNext = start; + public void reset() { + mNext = start; + } + public Float getValue() { + float next = mNext; + mNext = next + 0.1f; + return next; + } + } + + static class DoubleGetter implements ValueGetter { + final double start = Math.PI; + double mNext = start; + public void reset() { + mNext = start; + } + public Double getValue() { + double next = mNext; + mNext = next + 0.1; + return next; + } + } + + static class CharGetter implements ValueGetter { + + static final String source = "zookeepers fly piglatin homogeneous travesty"; + + IntGetter mNext = new IntGetter(); + + public void reset() { + mNext.reset(); + } + + + public Character getValue() { + return source.charAt(mNext.getPositiveValue() % source.length()); + } + } + + static class StringGetter implements ValueGetter { + IntGetter mSizer = new IntGetter(); + CharGetter mNext = new CharGetter(); + + public void reset() { + mSizer.reset(); + mNext.reset(); + } + public String getValue() { + int size = mSizer.getPositiveValue() % 255; + StringBuilder sb = new StringBuilder(size); + for (int i = 0; i<size; i++) { + sb.append(mNext.getValue()); + } + return sb.toString(); + } + } + + static class DateTimeGetter implements ValueGetter { + static final DateTime start = new DateTime("2005-01-02"); + DateTime mNext = start; + public void reset() { + mNext = start; + } + public DateTime getValue() { + DateTime next = mNext; + mNext = mNext.dayOfYear().addToCopy(1); + return next; + } + } + + static class ValueVendor { + Map<Class, ValueGetter> getters = new HashMap(10); + ValueVendor() { + getters.put(String.class, new StringGetter()); + getters.put(DateTime.class, new DateTimeGetter()); + + getters.put(Boolean.class, new BoolGetter()); + getters.put(Integer.class, new IntGetter()); + getters.put(Long.class, new LongGetter()); + getters.put(Float.class, new FloatGetter()); + getters.put(Double.class, new DoubleGetter()); + getters.put(Character.class, new CharGetter()); + + getters.put(boolean.class, new BoolGetter()); + getters.put(int.class, new IntGetter()); + getters.put(long.class, new LongGetter()); + getters.put(float.class, new FloatGetter()); + getters.put(double.class, new DoubleGetter()); + getters.put(char.class, new CharGetter()); + } + + void reset() { + for (Iterator iter = getters.values().iterator(); iter.hasNext();) { + ValueGetter getter = (ValueGetter) iter.next(); + getter.reset(); + } + } + + boolean hasValue(Class type) { + return getters.containsKey(type); + } + + + Object getValue(Class type) { + return getters.get(type).getValue(); + } + } +} |