blob: 5f4e9304470f1a56f2465e11673b16e864964d5a [file] [log] [blame]
/*
* 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();
}
}
}