Home | History | Annotate | Download | only in migration
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.room.integration.testapp.migration;
     18 
     19 import static org.hamcrest.CoreMatchers.containsString;
     20 import static org.hamcrest.CoreMatchers.endsWith;
     21 import static org.hamcrest.CoreMatchers.instanceOf;
     22 import static org.hamcrest.CoreMatchers.is;
     23 import static org.hamcrest.CoreMatchers.not;
     24 import static org.hamcrest.CoreMatchers.notNullValue;
     25 import static org.hamcrest.CoreMatchers.nullValue;
     26 import static org.hamcrest.CoreMatchers.startsWith;
     27 import static org.hamcrest.MatcherAssert.assertThat;
     28 
     29 import android.content.Context;
     30 import android.support.test.InstrumentationRegistry;
     31 import android.support.test.filters.SmallTest;
     32 import android.support.test.runner.AndroidJUnit4;
     33 
     34 import androidx.room.Room;
     35 import androidx.room.migration.Migration;
     36 import androidx.room.testing.MigrationTestHelper;
     37 import androidx.room.util.TableInfo;
     38 import androidx.sqlite.db.SupportSQLiteDatabase;
     39 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
     40 
     41 import org.hamcrest.MatcherAssert;
     42 import org.junit.Rule;
     43 import org.junit.Test;
     44 import org.junit.runner.RunWith;
     45 
     46 import java.io.FileNotFoundException;
     47 import java.io.IOException;
     48 import java.util.List;
     49 
     50 /**
     51  * Test custom database migrations.
     52  */
     53 @RunWith(AndroidJUnit4.class)
     54 @SmallTest
     55 public class MigrationTest {
     56     private static final String TEST_DB = "migration-test";
     57     @Rule
     58     public MigrationTestHelper helper;
     59 
     60     public MigrationTest() {
     61         helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
     62                 MigrationDb.class.getCanonicalName());
     63     }
     64 
     65     @Test
     66     public void giveBadResource() throws IOException {
     67         MigrationTestHelper helper = new MigrationTestHelper(
     68                 InstrumentationRegistry.getInstrumentation(),
     69                 "foo", new FrameworkSQLiteOpenHelperFactory());
     70         try {
     71             helper.createDatabase(TEST_DB, 1);
     72             throw new AssertionError("must have failed with missing file exception");
     73         } catch (FileNotFoundException exception) {
     74             assertThat(exception.getMessage(), containsString("Cannot find"));
     75         }
     76     }
     77 
     78     @Test
     79     public void startInCurrentVersion() throws IOException {
     80         SupportSQLiteDatabase db = helper.createDatabase(TEST_DB,
     81                 MigrationDb.LATEST_VERSION);
     82         final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
     83         dao.insertIntoEntity1(2, "x");
     84         db.close();
     85         MigrationDb migrationDb = getLatestDb();
     86         List<MigrationDb.Entity1> items = migrationDb.dao().loadAllEntity1s();
     87         helper.closeWhenFinished(migrationDb);
     88         assertThat(items.size(), is(1));
     89     }
     90 
     91     @Test
     92     public void addTable() throws IOException {
     93         SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
     94         final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
     95         dao.insertIntoEntity1(2, "foo");
     96         dao.insertIntoEntity1(3, "bar");
     97         db.close();
     98         db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
     99         new MigrationDb.Dao_V2(db).insertIntoEntity2(3, "blah");
    100         db.close();
    101         MigrationDb migrationDb = getLatestDb();
    102         List<MigrationDb.Entity1> entity1s = migrationDb.dao().loadAllEntity1s();
    103 
    104         assertThat(entity1s.size(), is(2));
    105         MigrationDb.Entity2 entity2 = new MigrationDb.Entity2();
    106         entity2.id = 2;
    107         entity2.name = "bar";
    108         // assert no error happens
    109         migrationDb.dao().insert(entity2);
    110         List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
    111         assertThat(entity2s.size(), is(2));
    112     }
    113 
    114     private MigrationDb getLatestDb() {
    115         MigrationDb db = Room.databaseBuilder(
    116                 InstrumentationRegistry.getInstrumentation().getTargetContext(),
    117                 MigrationDb.class, TEST_DB).addMigrations(ALL_MIGRATIONS).build();
    118         // trigger open
    119         db.beginTransaction();
    120         db.endTransaction();
    121         helper.closeWhenFinished(db);
    122         return db;
    123     }
    124 
    125     @Test
    126     public void addTableFailure() throws IOException {
    127         testFailure(1, 2);
    128     }
    129 
    130     @Test
    131     public void addColumnFailure() throws IOException {
    132         SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
    133         db.close();
    134         IllegalStateException caught = null;
    135         try {
    136             helper.runMigrationsAndValidate(TEST_DB, 3, true, new EmptyMigration(2, 3));
    137         } catch (IllegalStateException ex) {
    138             caught = ex;
    139         }
    140         assertThat(caught, instanceOf(IllegalStateException.class));
    141     }
    142 
    143     @Test
    144     public void addColumn() throws IOException {
    145         SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
    146         MigrationDb.Dao_V2 v2Dao = new MigrationDb.Dao_V2(db);
    147         v2Dao.insertIntoEntity2(7, "blah");
    148         db.close();
    149         helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3);
    150         // trigger open.
    151         MigrationDb migrationDb = getLatestDb();
    152         List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
    153         assertThat(entity2s.size(), is(1));
    154         assertThat(entity2s.get(0).name, is("blah"));
    155         assertThat(entity2s.get(0).addedInV3, is(nullValue()));
    156 
    157         List<MigrationDb.Entity2Pojo> entity2Pojos = migrationDb.dao().loadAllEntity2sAsPojo();
    158         assertThat(entity2Pojos.size(), is(1));
    159         assertThat(entity2Pojos.get(0).name, is("blah"));
    160         assertThat(entity2Pojos.get(0).addedInV3, is(nullValue()));
    161     }
    162 
    163     @Test
    164     public void failedToRemoveColumn() throws IOException {
    165         testFailure(4, 5);
    166     }
    167 
    168     @Test
    169     public void removeColumn() throws IOException {
    170         helper.createDatabase(TEST_DB, 4);
    171         final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
    172                 5, true, MIGRATION_4_5);
    173         final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
    174         assertThat(info.columns.size(), is(2));
    175     }
    176 
    177     @Test
    178     public void dropTable() throws IOException {
    179         helper.createDatabase(TEST_DB, 5);
    180         final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
    181                 6, true, MIGRATION_5_6);
    182         final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
    183         assertThat(info.columns.size(), is(0));
    184     }
    185 
    186     @Test
    187     public void failedToDropTable() throws IOException {
    188         testFailure(5, 6);
    189     }
    190 
    191     @Test
    192     public void failedToDropTableDontVerify() throws IOException {
    193         helper.createDatabase(TEST_DB, 5);
    194         final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
    195                 6, false, new EmptyMigration(5, 6));
    196         final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
    197         assertThat(info.columns.size(), is(2));
    198     }
    199 
    200     @Test
    201     public void failedForeignKey() throws IOException {
    202         final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 6);
    203         db.close();
    204         Throwable throwable = null;
    205         try {
    206             helper.runMigrationsAndValidate(TEST_DB,
    207                     7, false, new Migration(6, 7) {
    208                         @Override
    209                         public void migrate(SupportSQLiteDatabase database) {
    210                             database.execSQL("CREATE TABLE Entity4 (`id` INTEGER NOT NULL,"
    211                                     + " `name` TEXT, PRIMARY KEY(`id`))");
    212                         }
    213                     });
    214         } catch (Throwable t) {
    215             throwable = t;
    216         }
    217         assertThat(throwable, instanceOf(IllegalStateException.class));
    218         //noinspection ConstantConditions
    219         assertThat(throwable.getMessage(), containsString("Migration failed"));
    220     }
    221 
    222     @Test
    223     public void newTableWithForeignKey() throws IOException {
    224         helper.createDatabase(TEST_DB, 6);
    225         final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
    226                 7, false, MIGRATION_6_7);
    227         final TableInfo info = TableInfo.read(db, MigrationDb.Entity4.TABLE_NAME);
    228         assertThat(info.foreignKeys.size(), is(1));
    229     }
    230 
    231     @Test
    232     public void missingMigration() throws IOException {
    233         SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
    234         database.close();
    235         try {
    236             Context targetContext = InstrumentationRegistry.getTargetContext();
    237             MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
    238                     .build();
    239             db.dao().loadAllEntity1s();
    240             throw new AssertionError("Should've failed :/");
    241         } catch (IllegalStateException ignored) {
    242         }
    243     }
    244 
    245     @Test
    246     public void missingMigrationNuke() throws IOException {
    247         SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
    248         final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(database);
    249         dao.insertIntoEntity1(2, "foo");
    250         dao.insertIntoEntity1(3, "bar");
    251         database.close();
    252 
    253         Context targetContext = InstrumentationRegistry.getTargetContext();
    254         MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
    255                 .fallbackToDestructiveMigration()
    256                 .build();
    257         assertThat(db.dao().loadAllEntity1s().size(), is(0));
    258         db.close();
    259     }
    260 
    261     @Test
    262     public void failWithIdentityCheck() throws IOException {
    263         for (int i = 1; i < MigrationDb.LATEST_VERSION; i++) {
    264             String name = "test_" + i;
    265             helper.createDatabase(name, i).close();
    266             IllegalStateException exception = null;
    267             try {
    268                 MigrationDb db = Room.databaseBuilder(
    269                         InstrumentationRegistry.getInstrumentation().getTargetContext(),
    270                         MigrationDb.class, name).build();
    271                 db.runInTransaction(new Runnable() {
    272                     @Override
    273                     public void run() {
    274                         // do nothing
    275                     }
    276                 });
    277             } catch (IllegalStateException ex) {
    278                 exception = ex;
    279             }
    280             MatcherAssert.assertThat("identity detection should've failed",
    281                     exception, notNullValue());
    282         }
    283     }
    284 
    285     @Test
    286     public void fallbackToDestructiveMigrationFrom_destructiveMigrationOccursForSuppliedVersion()
    287             throws IOException {
    288         SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 6);
    289         final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(database);
    290         dao.insertIntoEntity1(2, "foo");
    291         dao.insertIntoEntity1(3, "bar");
    292         database.close();
    293         Context targetContext = InstrumentationRegistry.getTargetContext();
    294 
    295         MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
    296                 .fallbackToDestructiveMigrationFrom(6)
    297                 .build();
    298 
    299         assertThat(db.dao().loadAllEntity1s().size(), is(0));
    300     }
    301 
    302     @Test
    303     public void fallbackToDestructiveMigrationFrom_suppliedValueIsMigrationStartVersion_exception()
    304             throws IOException {
    305         SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 6);
    306         database.close();
    307         Context targetContext = InstrumentationRegistry.getTargetContext();
    308 
    309         Throwable throwable = null;
    310         try {
    311             Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
    312                     .addMigrations(MIGRATION_6_7)
    313                     .fallbackToDestructiveMigrationFrom(6)
    314                     .build();
    315         } catch (Throwable t) {
    316             throwable = t;
    317         }
    318 
    319         assertThat(throwable, is(not(nullValue())));
    320         //noinspection ConstantConditions
    321         assertThat(throwable.getMessage(),
    322                 startsWith("Inconsistency detected. A Migration was supplied to"));
    323         assertThat(throwable.getMessage(), endsWith("6"));
    324     }
    325 
    326     @Test
    327     public void fallbackToDestructiveMigrationFrom_suppliedValueIsMigrationEndVersion_exception()
    328             throws IOException {
    329         SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 5);
    330         database.close();
    331         Context targetContext = InstrumentationRegistry.getTargetContext();
    332 
    333         Throwable throwable = null;
    334         try {
    335             Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
    336                     .addMigrations(MIGRATION_5_6)
    337                     .fallbackToDestructiveMigrationFrom(6)
    338                     .build();
    339         } catch (Throwable t) {
    340             throwable = t;
    341         }
    342 
    343         assertThat(throwable, is(not(nullValue())));
    344         //noinspection ConstantConditions
    345         assertThat(throwable.getMessage(),
    346                 startsWith("Inconsistency detected. A Migration was supplied to"));
    347         assertThat(throwable.getMessage(), endsWith("6"));
    348     }
    349 
    350     private void testFailure(int startVersion, int endVersion) throws IOException {
    351         final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, startVersion);
    352         db.close();
    353         Throwable throwable = null;
    354         try {
    355             helper.runMigrationsAndValidate(TEST_DB, endVersion, true,
    356                     new EmptyMigration(startVersion, endVersion));
    357         } catch (Throwable t) {
    358             throwable = t;
    359         }
    360         assertThat(throwable, instanceOf(IllegalStateException.class));
    361         //noinspection ConstantConditions
    362         assertThat(throwable.getMessage(), containsString("Migration failed"));
    363     }
    364 
    365     private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    366         @Override
    367         public void migrate(SupportSQLiteDatabase database) {
    368             database.execSQL("CREATE TABLE IF NOT EXISTS `Entity2` ("
    369                     + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    370                     + " `name` TEXT)");
    371         }
    372     };
    373 
    374     private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    375         @Override
    376         public void migrate(SupportSQLiteDatabase database) {
    377             database.execSQL("ALTER TABLE " + MigrationDb.Entity2.TABLE_NAME
    378                     + " ADD COLUMN addedInV3 TEXT");
    379         }
    380     };
    381 
    382     private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
    383         @Override
    384         public void migrate(SupportSQLiteDatabase database) {
    385             database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3` (`id` INTEGER NOT NULL,"
    386                     + " `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))");
    387         }
    388     };
    389 
    390     private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
    391         @Override
    392         public void migrate(SupportSQLiteDatabase database) {
    393             database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3_New` (`id` INTEGER NOT NULL,"
    394                     + " `name` TEXT, PRIMARY KEY(`id`))");
    395             database.execSQL("INSERT INTO Entity3_New(`id`, `name`) "
    396                     + "SELECT `id`, `name` FROM Entity3");
    397             database.execSQL("DROP TABLE Entity3");
    398             database.execSQL("ALTER TABLE Entity3_New RENAME TO Entity3");
    399         }
    400     };
    401 
    402     private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
    403         @Override
    404         public void migrate(SupportSQLiteDatabase database) {
    405             database.execSQL("DROP TABLE " + MigrationDb.Entity3.TABLE_NAME);
    406         }
    407     };
    408 
    409     private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
    410         @Override
    411         public void migrate(SupportSQLiteDatabase database) {
    412             database.execSQL("CREATE TABLE IF NOT EXISTS " + MigrationDb.Entity4.TABLE_NAME
    413                     + " (`id` INTEGER NOT NULL, `name` TEXT COLLATE NOCASE, PRIMARY KEY(`id`),"
    414                     + " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
    415                     + " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
    416             database.execSQL("CREATE UNIQUE INDEX `index_entity1` ON "
    417                     + MigrationDb.Entity1.TABLE_NAME + " (`name`)");
    418         }
    419     };
    420 
    421     private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_1_2,
    422             MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7};
    423 
    424     static final class EmptyMigration extends Migration {
    425         EmptyMigration(int startVersion, int endVersion) {
    426             super(startVersion, endVersion);
    427         }
    428 
    429         @Override
    430         public void migrate(SupportSQLiteDatabase database) {
    431             // do nothing
    432         }
    433     }
    434 }
    435