blob: aa297ed43786cdef036d6c43c4e3b19ca032f8d0 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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 android.arch.persistence.room.integration.testapp.migration;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.migration.Migration;
import android.arch.persistence.room.testing.MigrationTestHelper;
import android.arch.persistence.room.util.TableInfo;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
/**
* Test custom database migrations.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MigrationTest {
private static final String TEST_DB = "migration-test";
@Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
MigrationDb.class.getCanonicalName());
}
@Test
public void giveBadResource() throws IOException {
MigrationTestHelper helper = new MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
"foo", new FrameworkSQLiteOpenHelperFactory());
try {
helper.createDatabase(TEST_DB, 1);
throw new AssertionError("must have failed with missing file exception");
} catch (FileNotFoundException exception) {
assertThat(exception.getMessage(), containsString("Cannot find"));
}
}
@Test
public void startInCurrentVersion() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB,
MigrationDb.LATEST_VERSION);
final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
dao.insertIntoEntity1(2, "x");
db.close();
MigrationDb migrationDb = getLatestDb();
List<MigrationDb.Entity1> items = migrationDb.dao().loadAllEntity1s();
helper.closeWhenFinished(migrationDb);
assertThat(items.size(), is(1));
}
@Test
public void addTable() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
dao.insertIntoEntity1(2, "foo");
dao.insertIntoEntity1(3, "bar");
db.close();
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
new MigrationDb.Dao_V2(db).insertIntoEntity2(3, "blah");
db.close();
MigrationDb migrationDb = getLatestDb();
List<MigrationDb.Entity1> entity1s = migrationDb.dao().loadAllEntity1s();
assertThat(entity1s.size(), is(2));
MigrationDb.Entity2 entity2 = new MigrationDb.Entity2();
entity2.id = 2;
entity2.name = "bar";
// assert no error happens
migrationDb.dao().insert(entity2);
List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
assertThat(entity2s.size(), is(2));
}
private MigrationDb getLatestDb() {
MigrationDb db = Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().getTargetContext(),
MigrationDb.class, TEST_DB).addMigrations(ALL_MIGRATIONS).build();
// trigger open
db.beginTransaction();
db.endTransaction();
helper.closeWhenFinished(db);
return db;
}
@Test
public void addTableFailure() throws IOException {
testFailure(1, 2);
}
@Test
public void addColumnFailure() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
db.close();
IllegalStateException caught = null;
try {
helper.runMigrationsAndValidate(TEST_DB, 3, true, new EmptyMigration(2, 3));
} catch (IllegalStateException ex) {
caught = ex;
}
assertThat(caught, instanceOf(IllegalStateException.class));
}
@Test
public void addColumn() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
MigrationDb.Dao_V2 v2Dao = new MigrationDb.Dao_V2(db);
v2Dao.insertIntoEntity2(7, "blah");
db.close();
helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3);
// trigger open.
MigrationDb migrationDb = getLatestDb();
List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
assertThat(entity2s.size(), is(1));
assertThat(entity2s.get(0).name, is("blah"));
assertThat(entity2s.get(0).addedInV3, is(nullValue()));
List<MigrationDb.Entity2Pojo> entity2Pojos = migrationDb.dao().loadAllEntity2sAsPojo();
assertThat(entity2Pojos.size(), is(1));
assertThat(entity2Pojos.get(0).name, is("blah"));
assertThat(entity2Pojos.get(0).addedInV3, is(nullValue()));
}
@Test
public void failedToRemoveColumn() throws IOException {
testFailure(4, 5);
}
@Test
public void removeColumn() throws IOException {
helper.createDatabase(TEST_DB, 4);
final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
5, true, MIGRATION_4_5);
final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
assertThat(info.columns.size(), is(2));
}
@Test
public void dropTable() throws IOException {
helper.createDatabase(TEST_DB, 5);
final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
6, true, MIGRATION_5_6);
final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
assertThat(info.columns.size(), is(0));
}
@Test
public void failedToDropTable() throws IOException {
testFailure(5, 6);
}
@Test
public void failedToDropTableDontVerify() throws IOException {
helper.createDatabase(TEST_DB, 5);
final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
6, false, new EmptyMigration(5, 6));
final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
assertThat(info.columns.size(), is(2));
}
@Test
public void failedForeignKey() throws IOException {
final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 6);
db.close();
Throwable throwable = null;
try {
helper.runMigrationsAndValidate(TEST_DB,
7, false, new Migration(6, 7) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE Entity4 (`id` INTEGER NOT NULL,"
+ " `name` TEXT, PRIMARY KEY(`id`))");
}
});
} catch (Throwable t) {
throwable = t;
}
assertThat(throwable, instanceOf(IllegalStateException.class));
//noinspection ConstantConditions
assertThat(throwable.getMessage(), containsString("Migration failed"));
}
@Test
public void newTableWithForeignKey() throws IOException {
helper.createDatabase(TEST_DB, 6);
final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
7, false, MIGRATION_6_7);
final TableInfo info = TableInfo.read(db, MigrationDb.Entity4.TABLE_NAME);
assertThat(info.foreignKeys.size(), is(1));
}
@Test
public void missingMigration() throws IOException {
SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
database.close();
try {
Context targetContext = InstrumentationRegistry.getTargetContext();
MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
.build();
db.dao().loadAllEntity1s();
throw new AssertionError("Should've failed :/");
} catch (IllegalStateException ignored) {
}
}
@Test
public void missingMigrationNuke() throws IOException {
SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(database);
dao.insertIntoEntity1(2, "foo");
dao.insertIntoEntity1(3, "bar");
database.close();
Context targetContext = InstrumentationRegistry.getTargetContext();
MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
.fallbackToDestructiveMigration()
.build();
assertThat(db.dao().loadAllEntity1s().size(), is(0));
db.close();
}
private void testFailure(int startVersion, int endVersion) throws IOException {
final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, startVersion);
db.close();
Throwable throwable = null;
try {
helper.runMigrationsAndValidate(TEST_DB, endVersion, true,
new EmptyMigration(startVersion, endVersion));
} catch (Throwable t) {
throwable = t;
}
assertThat(throwable, instanceOf(IllegalStateException.class));
//noinspection ConstantConditions
assertThat(throwable.getMessage(), containsString("Migration failed"));
}
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `Entity2` ("
+ "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+ " `name` TEXT)");
}
};
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE " + MigrationDb.Entity2.TABLE_NAME
+ " ADD COLUMN addedInV3 TEXT");
}
};
private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3` (`id` INTEGER NOT NULL,"
+ " `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))");
}
};
private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3_New` (`id` INTEGER NOT NULL,"
+ " `name` TEXT, PRIMARY KEY(`id`))");
database.execSQL("INSERT INTO Entity3_New(`id`, `name`) "
+ "SELECT `id`, `name` FROM Entity3");
database.execSQL("DROP TABLE Entity3");
database.execSQL("ALTER TABLE Entity3_New RENAME TO Entity3");
}
};
private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE " + MigrationDb.Entity3.TABLE_NAME);
}
};
private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS " + MigrationDb.Entity4.TABLE_NAME
+ " (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`),"
+ " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
+ " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
}
};
private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_1_2,
MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7};
static final class EmptyMigration extends Migration {
EmptyMigration(int startVersion, int endVersion) {
super(startVersion, endVersion);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
// do nothing
}
}
}