| /* |
| * Copyright 2019 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 androidx.room.integration.testapp.test; |
| |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.hamcrest.CoreMatchers.instanceOf; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.fail; |
| |
| import android.content.Context; |
| |
| import androidx.annotation.NonNull; |
| import androidx.core.content.ContextCompat; |
| import androidx.room.Database; |
| import androidx.room.Room; |
| import androidx.room.RoomDatabase; |
| import androidx.room.integration.testapp.dao.ProductDao; |
| import androidx.room.integration.testapp.vo.Product; |
| import androidx.room.migration.Migration; |
| import androidx.sqlite.db.SupportSQLiteDatabase; |
| import androidx.test.core.app.ApplicationProvider; |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.MediumTest; |
| |
| import org.junit.Ignore; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.concurrent.Callable; |
| import java.util.zip.GZIPInputStream; |
| import java.util.zip.GZIPOutputStream; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class PrepackageTest { |
| |
| @Test |
| public void createFromAsset() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromZippedAsset() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| |
| final Callable<InputStream> inputStreamCallable = () -> { |
| final ZipInputStream zipInputStream = |
| new ZipInputStream( |
| context.getAssets().open("databases/products_v1.db.zip")); |
| zipInputStream.getNextEntry(); |
| return zipInputStream; |
| }; |
| |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromInputStream(inputStreamCallable) |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_badSchema() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_badSchema.db"); |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_badSchema.db") |
| .createFromAsset("databases/products_badSchema.db") |
| .build(); |
| |
| Throwable throwable = null; |
| try { |
| database.getProductDao().countProducts(); |
| fail("Opening database should fail due to bad schema."); |
| } catch (Throwable t) { |
| throwable = t; |
| } |
| assertThat(throwable, instanceOf(IllegalStateException.class)); |
| assertThat(throwable.getMessage(), |
| containsString("Pre-packaged database has an invalid schema")); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_notFound() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_notFound.db"); |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_notFound.db") |
| .createFromAsset("databases/products_notFound.db") |
| .build(); |
| |
| Throwable throwable = null; |
| try { |
| database.getProductDao().countProducts(); |
| fail("Opening database should fail due to asset file not found."); |
| } catch (Throwable t) { |
| throwable = t; |
| } |
| assertThat(throwable, instanceOf(RuntimeException.class)); |
| assertThat(throwable.getCause(), instanceOf(FileNotFoundException.class)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_versionZero() { |
| // A 0 version DB goes through the create path because SQLiteOpenHelper thinks the opened |
| // DB was created from scratch. Therefore our onCreate callbacks will be called and we need |
| // to validate the schema before completely opening the DB. |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_v0.db"); |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_v0.db") |
| .createFromAsset("databases/products_v0.db") |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_versionZero_badSchema() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_v0_badSchema.db"); |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_v0_badSchema.db") |
| .createFromAsset("databases/products_v0_badSchema.db") |
| .build(); |
| |
| Throwable throwable = null; |
| try { |
| database.getProductDao().countProducts(); |
| fail("Opening database should fail due to bad schema."); |
| } catch (Throwable t) { |
| throwable = t; |
| } |
| assertThat(throwable, instanceOf(IllegalStateException.class)); |
| assertThat(throwable.getMessage(), |
| containsString("Pre-packaged database has an invalid schema")); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_closeAndReOpen() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| ProductsDatabase database; |
| ProductDao dao; |
| |
| database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .build(); |
| dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| dao.insert("a new product"); |
| assertThat(dao.countProducts(), is(3)); |
| |
| database.close(); |
| |
| database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .build(); |
| dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(3)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_badDatabaseFile() { |
| // A bad database file is a 'corrupted' database, it'll get deleted and a new file will be |
| // created, the usual corrupted db recovery process. |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_badFile.db"); |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_badFile.db") |
| .createFromAsset("databases/products_badFile.db") |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(0)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_upgrade() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| ProductsDatabase_v2 database = Room.databaseBuilder( |
| context, ProductsDatabase_v2.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .addMigrations(new Migration(1, 2) { |
| @Override |
| public void migrate(@NonNull SupportSQLiteDatabase database) { |
| database.execSQL( |
| "INSERT INTO Products (id, name) VALUES (null, 'Mofongo')"); |
| } |
| }) |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(3)); |
| assertThat(dao.getProductById(3).name, is("Mofongo")); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_upgrade_destructiveMigration() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| ProductsDatabase_v2 database = Room.databaseBuilder( |
| context, ProductsDatabase_v2.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .fallbackToDestructiveMigration() |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(0)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromAsset_copyOnDestructiveMigration() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| ProductDao dao; |
| |
| ProductsDatabase database_v1 = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .build(); |
| dao = database_v1.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database_v1.close(); |
| |
| ProductsDatabase_v2 database_v2 = Room.databaseBuilder( |
| context, ProductsDatabase_v2.class, "products.db") |
| .createFromAsset("databases/products_v2.db") |
| .fallbackToDestructiveMigration() |
| .build(); |
| dao = database_v2.getProductDao(); |
| assertThat(dao.countProducts(), is(3)); |
| |
| database_v2.close(); |
| } |
| |
| @Test |
| public void createFromAsset_copyOnDestructiveMigration_noRecursion() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| ProductDao dao; |
| |
| ProductsDatabase database_v1 = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .build(); |
| dao = database_v1.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database_v1.close(); |
| |
| ProductsDatabase_v2 database_v2 = Room.databaseBuilder( |
| context, ProductsDatabase_v2.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .fallbackToDestructiveMigration() |
| .build(); |
| dao = database_v2.getProductDao(); |
| assertThat(dao.countProducts(), is(0)); |
| |
| database_v2.close(); |
| } |
| |
| @Test |
| public void createFromAsset_copyOnDestructiveMigration_migrationProvided() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| ProductDao dao; |
| |
| ProductsDatabase database_v1 = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .build(); |
| dao = database_v1.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database_v1.close(); |
| |
| ProductsDatabase_v2 database_v2 = Room.databaseBuilder( |
| context, ProductsDatabase_v2.class, "products.db") |
| .createFromAsset("databases/products_v1.db") |
| .addMigrations(new Migration(1, 2) { |
| @Override |
| public void migrate(@NonNull SupportSQLiteDatabase database) { |
| database.execSQL( |
| "INSERT INTO Products (id, name) VALUES (null, 'Mofongo')"); |
| } |
| }) |
| .fallbackToDestructiveMigration() |
| .build(); |
| dao = database_v2.getProductDao(); |
| assertThat(dao.countProducts(), is(3)); |
| assertThat(dao.getProductById(3).name, is("Mofongo")); |
| |
| database_v2.close(); |
| } |
| |
| @Test |
| @Ignore("Flaky test, see b/149072706") |
| public void createFromAssert_multiInstanceCopy() throws InterruptedException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products.db"); |
| |
| ProductsDatabase database1 = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_big.db") |
| .build(); |
| |
| ProductsDatabase database2 = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products.db") |
| .createFromAsset("databases/products_big.db") |
| .build(); |
| |
| Thread t1 = new Thread("DB Thread A") { |
| @Override |
| public void run() { |
| database1.getProductDao().countProducts(); |
| } |
| }; |
| Thread t2 = new Thread("DB Thread B") { |
| @Override |
| public void run() { |
| database2.getProductDao().countProducts(); |
| } |
| }; |
| |
| t1.start(); |
| t2.start(); |
| |
| t1.join(); |
| t2.join(); |
| |
| database1.close(); |
| database2.close(); |
| } |
| |
| @Test |
| public void createFromFile() throws IOException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_external.db"); |
| File dataDbFile = new File(ContextCompat.getDataDir(context), "products_external.db"); |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| |
| InputStream toCopyInput = context.getAssets().open("databases/products_v1.db"); |
| copyAsset(toCopyInput, dataDbFile); |
| |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_external.db") |
| .createFromFile(dataDbFile) |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void createFromFile_copyOnDestructiveMigration_fileNotFound() throws IOException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_external.db"); |
| ProductDao dao; |
| |
| File dataDbFile = new File(ContextCompat.getDataDir(context), "products_external.db"); |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| InputStream toCopyInput = context.getAssets().open("databases/products_v1.db"); |
| copyAsset(toCopyInput, dataDbFile); |
| |
| ProductsDatabase database_v1 = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_external.db") |
| .createFromFile(dataDbFile) |
| .build(); |
| dao = database_v1.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database_v1.close(); |
| |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| assertThat(dataDbFile.exists(), is(false)); |
| |
| ProductsDatabase_v2 database_v2 = Room.databaseBuilder( |
| context, ProductsDatabase_v2.class, "products_external.db") |
| .createFromFile(dataDbFile) |
| .fallbackToDestructiveMigration() |
| .build(); |
| dao = database_v2.getProductDao(); |
| assertThat(dao.countProducts(), is(0)); |
| |
| database_v2.close(); |
| } |
| |
| @Test |
| public void createFromInputStream() throws IOException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| context.deleteDatabase("products_external.db"); |
| File dataDbFile = new File(ContextCompat.getDataDir(context), "products_external.db.gz"); |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| |
| InputStream toCopyInput = context.getAssets().open("databases/products_v1.db"); |
| |
| // gzip the file while copying it - note that gzipping files in assets doesn't work because |
| // aapt drops the gz extension and makes them available without requiring a GZip stream. |
| final OutputStream output = new GZIPOutputStream(new FileOutputStream(dataDbFile)); |
| copyStream(toCopyInput, output); |
| |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, "products_external.db") |
| .createFromInputStream(() -> new GZIPInputStream(new FileInputStream(dataDbFile))) |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void openDataDirDatabase() throws IOException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| |
| File dataDbFile = new File(ContextCompat.getDataDir(context), "products.db"); |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| |
| InputStream toCopyInput = context.getAssets().open("databases/products_v1.db"); |
| copyAsset(toCopyInput, dataDbFile); |
| |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, dataDbFile.getAbsolutePath()) |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void openDataDirDatabase_badSchema() throws IOException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| |
| File dataDbFile = new File(ContextCompat.getDataDir(context), "products.db"); |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| |
| InputStream toCopyInput = context.getAssets().open("databases/products_badSchema.db"); |
| copyAsset(toCopyInput, dataDbFile); |
| |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, dataDbFile.getAbsolutePath()) |
| .build(); |
| |
| Throwable throwable = null; |
| try { |
| database.getProductDao().countProducts(); |
| fail("Opening database should fail due to bad schema."); |
| } catch (Throwable t) { |
| throwable = t; |
| } |
| assertThat(throwable, instanceOf(IllegalStateException.class)); |
| assertThat(throwable.getMessage(), |
| containsString("Pre-packaged database has an invalid schema")); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void openDataDirDatabase_versionZero() throws IOException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| |
| File dataDbFile = new File(ContextCompat.getDataDir(context), "products.db"); |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| |
| InputStream toCopyInput = context.getAssets().open("databases/products_v0.db"); |
| copyAsset(toCopyInput, dataDbFile); |
| |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, dataDbFile.getAbsolutePath()) |
| .build(); |
| |
| ProductDao dao = database.getProductDao(); |
| assertThat(dao.countProducts(), is(2)); |
| |
| database.close(); |
| } |
| |
| @Test |
| public void openDataDirDatabase_versionZero_badSchema() throws IOException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| |
| File dataDbFile = new File(ContextCompat.getDataDir(context), "products.db"); |
| context.deleteDatabase(dataDbFile.getAbsolutePath()); |
| |
| InputStream toCopyInput = context.getAssets().open("databases/products_v0_badSchema.db"); |
| copyAsset(toCopyInput, dataDbFile); |
| |
| ProductsDatabase database = Room.databaseBuilder( |
| context, ProductsDatabase.class, dataDbFile.getAbsolutePath()) |
| .build(); |
| |
| Throwable throwable = null; |
| try { |
| database.getProductDao().countProducts(); |
| fail("Opening database should fail due to bad schema."); |
| } catch (Throwable t) { |
| throwable = t; |
| } |
| assertThat(throwable, instanceOf(IllegalStateException.class)); |
| assertThat(throwable.getMessage(), |
| containsString("Pre-packaged database has an invalid schema")); |
| |
| database.close(); |
| } |
| |
| @Database(entities = Product.class, version = 1, exportSchema = false) |
| abstract static class ProductsDatabase extends RoomDatabase { |
| abstract ProductDao getProductDao(); |
| } |
| |
| @Database(entities = Product.class, version = 2, exportSchema = false) |
| abstract static class ProductsDatabase_v2 extends RoomDatabase { |
| abstract ProductDao getProductDao(); |
| } |
| |
| private static void copyAsset(InputStream input, File outputFile) throws IOException { |
| OutputStream output = new FileOutputStream(outputFile); |
| copyStream(input, output); |
| } |
| |
| private static void copyStream(InputStream input, OutputStream output) throws IOException { |
| try { |
| int length; |
| byte[] buffer = new byte[1024 * 4]; |
| while ((length = input.read(buffer)) > 0) { |
| output.write(buffer, 0, length); |
| } |
| } finally { |
| input.close(); |
| output.close(); |
| } |
| } |
| } |