diff options
Diffstat (limited to 'src/test/java/com')
| -rw-r--r-- | src/test/java/com/amazon/carbonado/TestUtilities.java | 140 | ||||
| -rw-r--r-- | src/test/java/com/amazon/carbonado/layout/TestLayout.java | 827 | 
2 files changed, 967 insertions, 0 deletions
diff --git a/src/test/java/com/amazon/carbonado/TestUtilities.java b/src/test/java/com/amazon/carbonado/TestUtilities.java new file mode 100644 index 0000000..65b4a13 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/TestUtilities.java @@ -0,0 +1,140 @@ +/*
 + * 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;
 +
 +import java.io.File;
 +import java.io.IOException;
 +import java.util.Random;
 +
 +import com.amazon.carbonado.repo.sleepycat.BDBRepositoryBuilder;
 +
 +/**
 + * TestUtilities
 + *
 + * @author Don Schneider
 + * @author Brian S O'Neill
 + */
 +public class TestUtilities {
 +    public static final String FILE_PATH_KEY = "filepath";
 +
 +    // Keep memory usage low to prevent spurious out-of-memory errors while running tests.
 +    private static final int DEFAULT_CAPACITY = 100000;
 +
 +    private static final Random sRandom = new Random();
 +
 +    public static String makeTestDirectoryString(String nameElement) {
 +        return makeTestDirectory(nameElement).getAbsolutePath();
 +    }
 +
 +    public static File makeTestDirectory(String nameElement) {
 +        String dirPath = System.getProperty(FILE_PATH_KEY);
 +        File dir;
 +        if (dirPath == null) {
 +            dir = new File(new File(System.getProperty("java.io.tmpdir")), "carbonado-test");
 +        } else {
 +            dir = new File(dirPath);
 +        }
 +        String path = nameElement + '-' + System.currentTimeMillis() + '-' + sRandom.nextInt(1000);
 +        File file = new File(dir, path);
 +        try {
 +            // For better error reporting
 +            file = file.getCanonicalFile();
 +        } catch (IOException e) {
 +            // Ignore.
 +        }
 +        // BDBRepositoryBuilder will make directory if it doesn't exist.
 +        return file;
 +    }
 +
 +    public static Repository buildTempRepository() {
 +        return buildTempRepository("test");
 +    }
 +
 +    public static Repository buildTempRepository(String name) {
 +        return buildTempRepository(name, DEFAULT_CAPACITY);
 +    }
 +
 +    public static Repository buildTempRepository(String name, int capacity) {
 +        BDBRepositoryBuilder builder = new BDBRepositoryBuilder();
 +        builder.setProduct("JE");
 +        builder.setName(name);
 +        builder.setTransactionNoSync(true);
 +        builder.setCacheSize(capacity);
 +        builder.setLogInMemory(true);
 +
 +        if (sTempRepoDir == null) {
 +            sTempRepoDir = makeTestDirectoryString(name);
 +        }
 +
 +        builder.setEnvironmentHome(sTempRepoDir);
 +
 +        try {
 +            return builder.build();
 +        } catch (RepositoryException e) {
 +            throw new UnsupportedOperationException("Could not create repository", e);
 +        }
 +    }
 +
 +    private static String sTempRepoDir;
 +
 +    private static String sAlphabet = "abcdefghijklmnopqrstuvwxyz";
 +    private static Random sNumbers = new Random();
 +
 +    public static void sAddRandomWord(StringBuffer buffer, int aCount) {
 +        sAddRandomWord(buffer, aCount, sNumbers);
 +    }
 +
 +    public static void sAddRandomWord(StringBuffer buffer, int aCount, Random rnd) {
 +        int position = rnd.nextInt(sAlphabet.length());
 +        char c = sAlphabet.charAt(position);
 +        for (int i = 0; i < aCount; i++) {
 +            buffer.append(c);
 +        }
 +    }
 +
 +    public static String sRandomText(int aMin, int aMax) {
 +        return sRandomText(aMin, aMax, sNumbers);
 +    }
 +
 +    public static String sRandomText(int aMin, int aMax, Random rnd) {
 +        return sRandomText(rnd.nextInt(aMax-aMin) + aMin, rnd);
 +    }
 +
 +    public static String sRandomText(int aCount) {
 +        return sRandomText(aCount, sNumbers);
 +    }
 +
 +    public static String sRandomText(int aCount, Random rnd) {
 +        if (aCount <= 0) {
 +            return "";
 +        }
 +
 +        final StringBuffer buffer = new StringBuffer(aCount);
 +        int filled = 0;
 +        while (filled<aCount) {
 +            int wordsize = rnd.nextInt(Math.min(10, aCount - filled)) + 1;
 +            sAddRandomWord(buffer, wordsize, rnd);
 +            filled += wordsize;
 +            if (filled<aCount) {
 +                buffer.append(' ');
 +                filled++;
 +            }
 +        }
 +        return buffer.toString();
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/layout/TestLayout.java b/src/test/java/com/amazon/carbonado/layout/TestLayout.java new file mode 100644 index 0000000..9fb97b1 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/layout/TestLayout.java @@ -0,0 +1,827 @@ +/*
 + * 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.layout;
 +
 +import java.util.List;
 +import java.util.Map;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import org.joda.time.DateTime;
 +
 +import org.cojen.classfile.ClassFile;
 +import org.cojen.classfile.CodeBuilder;
 +import org.cojen.classfile.Modifiers;
 +import org.cojen.classfile.TypeDesc;
 +import org.cojen.classfile.attribute.Annotation;
 +
 +import org.cojen.util.BeanPropertyAccessor;
 +import org.cojen.util.ClassInjector;
 +
 +import com.amazon.carbonado.adapter.YesNoAdapter;
 +import com.amazon.carbonado.PrimaryKey;
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.Storable;
 +import com.amazon.carbonado.Storage;
 +
 +import com.amazon.carbonado.info.StorableInfo;
 +import com.amazon.carbonado.info.StorableIntrospector;
 +import com.amazon.carbonado.info.StorableProperty;
 +import com.amazon.carbonado.info.StorablePropertyAdapter;
 +
 +import com.amazon.carbonado.stored.FileInfo;
 +import com.amazon.carbonado.stored.StorableDateIndex;
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +import com.amazon.carbonado.stored.StorableTestMinimal;
 +
 +import com.amazon.carbonado.TestUtilities;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestLayout extends TestCase {
 +    private static final String TEST_STORABLE_NAME = "test.TheTestStorableType";
 +    private static final String TEST_STORABLE_NAME_2 = "test.TheOtherTestStorableType";
 +
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestLayout.class);
 +    }
 +
 +    private Repository mRepository;
 +    private LayoutFactory mFactory;
 +
 +    public TestLayout(String name) {
 +        super(name);
 +    }
 +
 +    protected void setUp() throws Exception {
 +        mRepository = TestUtilities.buildTempRepository();
 +        mFactory = new LayoutFactory(mRepository);
 +    }
 +
 +    protected void tearDown() throws Exception {
 +        mRepository.close();
 +        mRepository = null;
 +    }
 +
 +    public void testBasic() throws Exception {
 +        // Run test twice: First time, records get inserted. Second time, they are loaded.
 +        for (int i=0; i<2; i++) {
 +            Layout layout = mFactory.layoutFor(StorableTestBasic.class);
 +            
 +            assertEquals(StorableTestBasic.class.getName(), layout.getStorableTypeName());
 +            assertEquals(-8576805570777985009L, layout.getLayoutID());
 +            assertEquals(0, layout.getGeneration());
 +            assertTrue(layout.getCreationDateTime().getMillis() > 0);
 +            assertTrue(layout.getCreationDateTime().getMillis() <= System.currentTimeMillis());
 +            
 +            List<LayoutProperty> properties = layout.getAllProperties();
 +            
 +            assertEquals(6, properties.size());
 +            
 +            LayoutProperty property;
 +            
 +            property = properties.get(0);
 +            assertEquals("id", property.getPropertyName());
 +            assertEquals("I", property.getPropertyTypeDescriptor());
 +            assertFalse(property.isNullable());
 +            assertTrue(property.isPrimaryKeyMember());
 +            assertEquals(null, property.getAdapterTypeName());
 +            assertEquals(null, property.getAdapterParams());
 +            
 +            property = properties.get(1);
 +            assertEquals("date", property.getPropertyName());
 +            assertEquals("Lorg/joda/time/DateTime;", property.getPropertyTypeDescriptor());
 +            assertTrue(property.isNullable());
 +            assertFalse(property.isPrimaryKeyMember());
 +            assertEquals(null, property.getAdapterTypeName());
 +            assertEquals(null, property.getAdapterParams());
 +            
 +            property = properties.get(2);
 +            assertEquals("doubleProp", property.getPropertyName());
 +            assertEquals("D", property.getPropertyTypeDescriptor());
 +            assertFalse(property.isNullable());
 +            assertFalse(property.isPrimaryKeyMember());
 +            assertEquals(null, property.getAdapterTypeName());
 +            assertEquals(null, property.getAdapterParams());
 +            
 +            property = properties.get(3);
 +            assertEquals("intProp", property.getPropertyName());
 +            assertEquals("I", property.getPropertyTypeDescriptor());
 +            assertFalse(property.isNullable());
 +            assertFalse(property.isPrimaryKeyMember());
 +            assertEquals(null, property.getAdapterTypeName());
 +            assertEquals(null, property.getAdapterParams());
 +            
 +            property = properties.get(4);
 +            assertEquals("longProp", property.getPropertyName());
 +            assertEquals("J", property.getPropertyTypeDescriptor());
 +            assertFalse(property.isNullable());
 +            assertFalse(property.isPrimaryKeyMember());
 +            assertEquals(null, property.getAdapterTypeName());
 +            assertEquals(null, property.getAdapterParams());
 +            
 +            property = properties.get(5);
 +            assertEquals("stringProp", property.getPropertyName());
 +            assertEquals("Ljava/lang/String;", property.getPropertyTypeDescriptor());
 +            assertFalse(property.isNullable());
 +            assertFalse(property.isPrimaryKeyMember());
 +            assertEquals(null, property.getAdapterTypeName());
 +            assertEquals(null, property.getAdapterParams());
 +        }
 +    }
 +
 +    public void testAdapter() throws Exception {
 +        // Run test twice: First time, records get inserted. Second time, they are loaded.
 +        for (int i=0; i<2; i++) {
 +            Layout layout = mFactory.layoutFor(FileInfo.class);
 +            
 +            assertEquals(FileInfo.class.getName(), layout.getStorableTypeName());
 +            
 +            List<LayoutProperty> properties = layout.getAllProperties();
 +
 +            // Should exclude joins.
 +            assertEquals(8, properties.size());
 +
 +            LayoutProperty property;
 +            
 +            property = properties.get(1);
 +            assertEquals("directory", property.getPropertyName());
 +            assertEquals("Z", property.getPropertyTypeDescriptor());
 +            assertFalse(property.isNullable());
 +            assertFalse(property.isPrimaryKeyMember());
 +            assertEquals(YesNoAdapter.class.getName(), property.getAdapterTypeName());
 +            assertEquals("@Lcom/amazon/carbonado/adapter/YesNoAdapter;lenient=Z1;",
 +                         property.getAdapterParams());
 +        }
 +    }
 +
 +    public void testAddProperty() throws Exception {
 +        for (int i=0; i<2; i++) {
 +            {
 +                // Zero extra properties.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 0, TypeDesc.INT);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(0, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(1, properties.size());
 +            }
 +
 +            {
 +                // One extra property.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.INT);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(1, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("I", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +        }
 +    }
 +
 +    public void testRemoveProperty() throws Exception {
 +        for (int i=0; i<2; i++) {
 +            {
 +                // One extra property.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.INT);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(0, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("I", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +
 +            {
 +                // Zero extra properties.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 0, TypeDesc.INT);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(1, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(1, properties.size());
 +            }
 +        }
 +    }
 +
 +    public void testChangePropertyType() throws Exception {
 +        for (int i=0; i<2; i++) {
 +            {
 +                // One extra property, type int.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.INT);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(0, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("I", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +
 +            {
 +                // One extra property, type String.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.STRING);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(1, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("Ljava/lang/String;", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +        }
 +    }
 +
 +    public void testWithMulipleTypes() throws Exception {
 +        for (int i=0; i<10; i++) {
 +            {
 +                // One extra property, type int.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.INT);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(0, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("I", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +
 +            {
 +                // One extra property, type int.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME_2, 1, TypeDesc.INT);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME_2, layout.getStorableTypeName());
 +                assertEquals(0, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("I", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +
 +            {
 +                // One extra property, type String.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.STRING);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME, layout.getStorableTypeName());
 +                assertEquals(1, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("Ljava/lang/String;", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +
 +            {
 +                // One extra property, type String.
 +                Class<? extends Storable> type =
 +                    defineStorable(TEST_STORABLE_NAME_2, 1, TypeDesc.STRING);
 +                Layout layout = mFactory.layoutFor(type);
 +                assertEquals(TEST_STORABLE_NAME_2, layout.getStorableTypeName());
 +                assertEquals(1, layout.getGeneration());
 +                List<LayoutProperty> properties = layout.getAllProperties();
 +                assertEquals(2, properties.size());
 +                assertEquals("prop0", properties.get(1).getPropertyName());
 +                assertEquals("Ljava/lang/String;", properties.get(1).getPropertyTypeDescriptor());
 +            }
 +        }
 +    }
 +
 +    public void testReconstruct() throws Exception {
 +        // Run test twice: First time, records get inserted. Second time, they are loaded.
 +        for (int i=0; i<2; i++) {
 +            Layout layout = mFactory.layoutFor(StorableDateIndex.class);
 +            
 +            Class<? extends Storable> recon = layout.reconstruct();
 +
 +            assertTrue(recon != StorableDateIndex.class);
 +
 +            Layout reconLayout = mFactory.layoutFor(recon);
 +            assertTrue(layout == reconLayout);
 +
 +            compareInfo(StorableIntrospector.examine(StorableDateIndex.class),
 +                        StorableIntrospector.examine(recon));
 +        }
 +    }
 +
 +    public void testEvolution_addProperties() throws Exception {
 +        {
 +            // Zero extra properties.
 +            Class<? extends StorableTestMinimal> type =
 +                defineStorable(TEST_STORABLE_NAME, 0, TypeDesc.INT);
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(type);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            stm.insert();
 +        }
 +
 +        {
 +            // One extra property.
 +            Class<? extends StorableTestMinimal> type =
 +                defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.INT);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(type);
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(type);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(2);
 +            bean.setPropertyValue(stm, "prop0", 200);
 +            stm.insert();
 +
 +            // Now load old generation.
 +            stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(0, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +
 +            // Verify load of new generation.
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals(200, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +        }
 +
 +        {
 +            // Two extra properties.
 +            Class<? extends StorableTestMinimal> type =
 +                defineStorable(TEST_STORABLE_NAME, 2, TypeDesc.INT);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(type);
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(type);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(3);
 +            bean.setPropertyValue(stm, "prop0", 300);
 +            bean.setPropertyValue(stm, "prop1", 301);
 +            stm.insert();
 +
 +            // Now load old generations.
 +            stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(0, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +            assertEquals(0, ((Integer) bean.getPropertyValue(stm, "prop1")).intValue());
 +
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals(200, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +            assertEquals(0, ((Integer) bean.getPropertyValue(stm, "prop1")).intValue());
 +
 +            // Verify load of new generation.
 +            stm = storage.prepare();
 +            stm.setId(3);
 +            stm.load();
 +            assertEquals(300, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +            assertEquals(301, ((Integer) bean.getPropertyValue(stm, "prop1")).intValue());
 +        }
 +
 +        {
 +            // Test incrementally with over 130 added properties, to ensure
 +            // generation is encoded in 1 and 4 bytes formats.
 +            for (int i=3; i<=130; i++) {
 +                Class<? extends StorableTestMinimal> type =
 +                    defineStorable(TEST_STORABLE_NAME, i, TypeDesc.INT);
 +                BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(type);
 +                Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(type);
 +                StorableTestMinimal stm = storage.prepare();
 +                stm.setId(i + 3);
 +                for (int j=0; j<i; j++) {
 +                    bean.setPropertyValue(stm, "prop" + j, 400 + j);
 +                }
 +                stm.insert();
 +
 +                // Now load an old generation.
 +                stm = storage.prepare();
 +                stm.setId(3);
 +                stm.load();
 +                assertEquals(300, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +                assertEquals(301, ((Integer) bean.getPropertyValue(stm, "prop1")).intValue());
 +                assertEquals(0, ((Integer) bean.getPropertyValue(stm, "prop2")).intValue());
 +            }
 +        }
 +    }
 +
 +    public void testEvolution_removeProperties() throws Exception {
 +        {
 +            // Two extra properties.
 +            Class<? extends StorableTestMinimal> type =
 +                defineStorable(TEST_STORABLE_NAME, 2, TypeDesc.INT);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(type);
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(type);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            bean.setPropertyValue(stm, "prop0", 100);
 +            bean.setPropertyValue(stm, "prop1", 101);
 +            stm.insert();
 +        }
 +
 +        {
 +            // One extra property.
 +            Class<? extends StorableTestMinimal> type =
 +                defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.INT);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(type);
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(type);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(2);
 +            bean.setPropertyValue(stm, "prop0", 200);
 +            stm.insert();
 +
 +            // Now load old generation.
 +            stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(100, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +
 +            // Verify load of new generation.
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals(200, ((Integer) bean.getPropertyValue(stm, "prop0")).intValue());
 +        }
 +
 +        {
 +            // Zero extra properties.
 +            Class<? extends StorableTestMinimal> type =
 +                defineStorable(TEST_STORABLE_NAME, 0, TypeDesc.INT);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(type);
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(type);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(3);
 +            stm.insert();
 +
 +            // Now load old generations.
 +            stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +
 +            // Verify load of new generation.
 +            stm = storage.prepare();
 +            stm.setId(3);
 +            stm.load();
 +        }
 +    }
 +
 +    public void testEvolution_changeProperties() throws Exception {
 +        // Define various generations.
 +        Class<? extends StorableTestMinimal> typeWithInt =
 +            defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.INT);
 +        Class<? extends StorableTestMinimal> typeWithStr =
 +            defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.STRING);
 +        Class<? extends StorableTestMinimal> typeWithLong =
 +            defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.LONG);
 +        Class<? extends StorableTestMinimal> typeWithDate =
 +            defineStorable(TEST_STORABLE_NAME, 1, TypeDesc.forClass(DateTime.class));
 +        Class<? extends StorableTestMinimal> typeWithIntAndStr =
 +            defineStorable(TEST_STORABLE_NAME, TypeDesc.INT, TypeDesc.STRING);
 +
 +        DateTime date = new DateTime();
 +
 +        {
 +            // Insert int prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithInt);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithInt);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            bean.setPropertyValue(stm, "prop0", 100);
 +            stm.insert();
 +        }
 +
 +        {
 +            // Insert String prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithStr);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithStr);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(2);
 +            bean.setPropertyValue(stm, "prop0", "hello");
 +            stm.insert();
 +        }
 +
 +        {
 +            // Insert long prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithLong);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithLong);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(3);
 +            bean.setPropertyValue(stm, "prop0", 0x100000001L);
 +            stm.insert();
 +        }
 +
 +        {
 +            // Insert date prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithDate);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithDate);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(4);
 +            bean.setPropertyValue(stm, "prop0", date);
 +            stm.insert();
 +        }
 +
 +        {
 +            // Insert int prop0, String prop1.
 +            Storage<? extends StorableTestMinimal> storage =
 +                mRepository.storageFor(typeWithIntAndStr);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithIntAndStr);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(5);
 +            bean.setPropertyValue(stm, "prop0", 500);
 +            bean.setPropertyValue(stm, "prop1", "world");
 +            stm.insert();
 +        }
 +
 +        // Load using int property generation.
 +        {
 +            // Load against int prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithInt);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithInt);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(100, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against String prop0.
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals(0, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against long prop0.
 +            stm = storage.prepare();
 +            stm.setId(3);
 +            stm.load();
 +            // Cast of 0x100000001L to int yields 1.
 +            assertEquals(1, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against date prop0.
 +            stm = storage.prepare();
 +            stm.setId(4);
 +            stm.load();
 +            assertEquals(0, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against int prop0, String prop1.
 +            stm = storage.prepare();
 +            stm.setId(5);
 +            stm.load();
 +            assertEquals(500, bean.getPropertyValue(stm, "prop0"));
 +        }
 +
 +        // Load using String property generation.
 +        {
 +            // Load against int prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithStr);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithStr);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against String prop0.
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals("hello", bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against long prop0.
 +            stm = storage.prepare();
 +            stm.setId(3);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against date prop0.
 +            stm = storage.prepare();
 +            stm.setId(4);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against int prop0, String prop1.
 +            stm = storage.prepare();
 +            stm.setId(5);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +        }
 +
 +        // Load using long property generation.
 +        {
 +            // Load against int prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithLong);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithLong);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(100L, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against String prop0.
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals(0L, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against long prop0.
 +            stm = storage.prepare();
 +            stm.setId(3);
 +            stm.load();
 +            assertEquals(0x100000001L, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against date prop0.
 +            stm = storage.prepare();
 +            stm.setId(4);
 +            stm.load();
 +            assertEquals(0L, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against int prop0, String prop1.
 +            stm = storage.prepare();
 +            stm.setId(5);
 +            stm.load();
 +            assertEquals(500L, bean.getPropertyValue(stm, "prop0"));
 +        }
 +
 +        // Load using date property generation.
 +        {
 +            // Load against int prop0.
 +            Storage<? extends StorableTestMinimal> storage = mRepository.storageFor(typeWithDate);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithDate);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against String prop0.
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against long prop0.
 +            stm = storage.prepare();
 +            stm.setId(3);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against date prop0.
 +            stm = storage.prepare();
 +            stm.setId(4);
 +            stm.load();
 +            assertEquals(date, bean.getPropertyValue(stm, "prop0"));
 +
 +            // Load against int prop0, String prop1.
 +            stm = storage.prepare();
 +            stm.setId(5);
 +            stm.load();
 +            assertEquals(null, bean.getPropertyValue(stm, "prop0"));
 +        }
 +
 +        // Load using int and String property generation.
 +        {
 +            // Load against int prop0.
 +            Storage<? extends StorableTestMinimal> storage =
 +                mRepository.storageFor(typeWithIntAndStr);
 +            BeanPropertyAccessor bean = BeanPropertyAccessor.forClass(typeWithIntAndStr);
 +            StorableTestMinimal stm = storage.prepare();
 +            stm.setId(1);
 +            stm.load();
 +            assertEquals(100, bean.getPropertyValue(stm, "prop0"));
 +            assertEquals(null, bean.getPropertyValue(stm, "prop1"));
 +
 +            // Load against String prop0.
 +            stm = storage.prepare();
 +            stm.setId(2);
 +            stm.load();
 +            assertEquals(0, bean.getPropertyValue(stm, "prop0"));
 +            assertEquals(null, bean.getPropertyValue(stm, "prop1"));
 +
 +            // Load against long prop0.
 +            stm = storage.prepare();
 +            stm.setId(3);
 +            stm.load();
 +            // Cast of 0x100000001L to int yields 1.
 +            assertEquals(1, bean.getPropertyValue(stm, "prop0"));
 +            assertEquals(null, bean.getPropertyValue(stm, "prop1"));
 +
 +            // Load against date prop0.
 +            stm = storage.prepare();
 +            stm.setId(4);
 +            stm.load();
 +            assertEquals(0, bean.getPropertyValue(stm, "prop0"));
 +            assertEquals(null, bean.getPropertyValue(stm, "prop1"));
 +
 +            // Load against int prop0, String prop1.
 +            stm = storage.prepare();
 +            stm.setId(5);
 +            stm.load();
 +            assertEquals(500, bean.getPropertyValue(stm, "prop0"));
 +            assertEquals("world", bean.getPropertyValue(stm, "prop1"));
 +        }
 +    }
 +
 +    private void compareInfo(StorableInfo expected, StorableInfo actual) {
 +        Map<String, StorableProperty> expectedProps = expected.getAllProperties();
 +        Map<String, StorableProperty> actualProps = actual.getAllProperties();
 +
 +        for (StorableProperty expectedProp : expectedProps.values()) {
 +            StorableProperty actualProp = actualProps.get(expectedProp.getName());
 +            if (actualProp == null) {
 +                fail("Missing property: " + expectedProp.getName());
 +            }
 +            assertEquals(expectedProp.getType(), actualProp.getType());
 +
 +            assertEquals(expectedProp.isPrimaryKeyMember(), actualProp.isPrimaryKeyMember());
 +
 +            StorablePropertyAdapter expectedAdapter = expectedProp.getAdapter();
 +            StorablePropertyAdapter actualAdapter = actualProp.getAdapter();
 +
 +            if (expectedAdapter == null) {
 +                assertNull(actualAdapter);
 +            } else {
 +                assertNotNull(actualAdapter);
 +                assertEquals(expectedAdapter.getAnnotation().getAnnotation(),
 +                             actualAdapter.getAnnotation().getAnnotation());
 +            }
 +        }
 +    }
 +
 +    private Class<? extends StorableTestMinimal> defineStorable(String name,
 +                                                                int extraPropCount,
 +                                                                TypeDesc propType)
 +    {
 +        ClassInjector ci = ClassInjector.createExplicit(name, null);
 +        ClassFile cf = new ClassFile(ci.getClassName());
 +        cf.setTarget("1.5");
 +        cf.setModifiers(cf.getModifiers().toInterface(true));
 +        cf.addInterface(StorableTestMinimal.class);
 +
 +        definePrimaryKey(cf);
 +
 +        for (int i=0; i<extraPropCount; i++) {
 +            cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "getProp" + i, propType, null);
 +            cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "setProp" + i, null, new TypeDesc[]{propType});
 +        }
 +
 +        return ci.defineClass(cf);
 +    }
 +
 +    private Class<? extends StorableTestMinimal> defineStorable(String name,
 +                                                                TypeDesc propType,
 +                                                                TypeDesc propType2)
 +    {
 +        ClassInjector ci = ClassInjector.createExplicit(name, null);
 +        ClassFile cf = new ClassFile(ci.getClassName());
 +        cf.setTarget("1.5");
 +        cf.setModifiers(cf.getModifiers().toInterface(true));
 +        cf.addInterface(StorableTestMinimal.class);
 +
 +        definePrimaryKey(cf);
 +
 +        int i = 0;
 +        cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "getProp" + i, propType, null);
 +        cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "setProp" + i, null, new TypeDesc[]{propType});
 +
 +        i++;
 +        cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "getProp" + i, propType2, null);
 +        cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "setProp" + i, null, new TypeDesc[]{propType2});
 +
 +        return ci.defineClass(cf);
 +    }
 +
 +    private void definePrimaryKey(ClassFile cf) {
 +        // Add primary key on inherited "id" property.
 +        // @PrimaryKey(value={"id"})
 +        Annotation pk = cf.addRuntimeVisibleAnnotation(TypeDesc.forClass(PrimaryKey.class));
 +        Annotation.MemberValue[] props = new Annotation.MemberValue[1];
 +        props[0] = pk.makeMemberValue("id");
 +        pk.putMemberValue("value", props);
 +    }
 +}
  | 
