From f5b0a7c2749afc853a0b7c5b8945ae44120fc4a1 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 3 Sep 2006 23:23:51 +0000 Subject: More tests and fixes. --- .../com/amazon/carbonado/repo/toy/ToyStorage.java | 5 + .../carbonado/spi/TestStorableSerializer.java | 115 +++ .../amazon/carbonado/spi/raw/TestDataEncoding.java | 515 +++++++++++++ .../carbonado/spi/raw/TestEncodingStrategy.java | 857 +++++++++++++++++++++ .../amazon/carbonado/spi/raw/TestKeyEncoding.java | 571 ++++++++++++++ .../synthetic/TestSyntheticStorableBuilders.java | 746 ++++++++++++++++++ 6 files changed, 2809 insertions(+) create mode 100644 src/test/java/com/amazon/carbonado/spi/TestStorableSerializer.java create mode 100644 src/test/java/com/amazon/carbonado/spi/raw/TestDataEncoding.java create mode 100644 src/test/java/com/amazon/carbonado/spi/raw/TestEncodingStrategy.java create mode 100644 src/test/java/com/amazon/carbonado/spi/raw/TestKeyEncoding.java create mode 100644 src/test/java/com/amazon/carbonado/synthetic/TestSyntheticStorableBuilders.java (limited to 'src/test/java/com') 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 implements Storage, 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 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 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 storage = mRepository.storageFor(StorableWithLobs.class); + StorableWithLobs s = storage.prepare(); + s.setBlobValue(new ByteArrayBlob("Hello Blob".getBytes())); + s.setClobValue(new StringClob("Hello Clob")); + + StorableSerializer 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}. + *

+ * 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 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 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 + * 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[] mProperties; + + private Random mRandom; + + public TestEncodingStrategy(String name) { + super(name); + mSeed = 986184829029842L; + Collection> 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[] 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 0) { + // Fill in with data which should be ignored by decoder. + for (int p=0; p= 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[] 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 0) { + // Fill in with data which should be ignored by decoder. + for (int p=0; p 0) { + for (int p=0; p 0) { + for (int p=0; p 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[] 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 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. + * + *

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[] 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[] 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[] 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[] 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[] selectProperties(int minCount, int maxCount) { + int length = (minCount == maxCount) ? minCount + : (mRandom.nextInt(maxCount - minCount + 1) + minCount); + + StorableProperty[] 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[] makeOrderedProperties + (StorableProperty[] properties, Direction[] directions) { + + int length = properties.length; + OrderedProperty[] 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 + * 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 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 builder + = new SyntheticStorableReferenceBuilder(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 + * @param storableType + * @throws SupportException + * @throws RepositoryException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws PersistException + * @throws FetchException + */ + protected void exerciseStorable(Class storableType) throws SupportException, RepositoryException, IllegalAccessException, InvocationTargetException, PersistException, FetchException { + T persister = mRepository.storageFor(storableType).prepare(); + Map 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 Map populate(T storable) + throws IllegalAccessException, InvocationTargetException { + ValueVendor vendor = new ValueVendor(); + Map valueMap = new HashMap(); + 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 expectedMethods = new HashMap(); + 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 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(); + } + } +} -- cgit v1.2.3