blob: d44bdf5f76b1bd3b4404e57e3e40da1377f028ba [file] [log] [blame]
/*
* Copyright 2018 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.work;
import static android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL;
import static androidx.work.impl.WorkDatabaseMigrations.MIGRATION_3_4;
import static androidx.work.impl.WorkDatabaseMigrations.MIGRATION_4_5;
import static androidx.work.impl.WorkDatabaseMigrations.MIGRATION_6_7;
import static androidx.work.impl.WorkDatabaseMigrations.VERSION_1;
import static androidx.work.impl.WorkDatabaseMigrations.VERSION_2;
import static androidx.work.impl.WorkDatabaseMigrations.VERSION_3;
import static androidx.work.impl.WorkDatabaseMigrations.VERSION_4;
import static androidx.work.impl.WorkDatabaseMigrations.VERSION_5;
import static androidx.work.impl.WorkDatabaseMigrations.VERSION_6;
import static androidx.work.impl.WorkDatabaseMigrations.VERSION_7;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.room.testing.MigrationTestHelper;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkDatabaseMigrations;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkTypeConverters;
import androidx.work.impl.utils.Preferences;
import androidx.work.worker.TestWorker;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RunWith(AndroidJUnit4.class)
public class WorkDatabaseMigrationTest {
private static final String TEST_DATABASE = "workdatabase-test";
private static final boolean VALIDATE_DROPPED_TABLES = true;
private static final String COLUMN_WORKSPEC_ID = "work_spec_id";
private static final String COLUMN_SYSTEM_ID = "system_id";
private static final String COLUMN_ALARM_ID = "alarm_id";
// Queries
private static final String INSERT_ALARM_INFO = "INSERT INTO alarmInfo VALUES (?, ?)";
private static final String INSERT_SYSTEM_ID_INFO = "INSERT INTO SystemIdInfo VALUES (?, ?)";
private static final String CHECK_SYSTEM_ID_INFO = "SELECT * FROM SystemIdInfo";
private static final String CHECK_ALARM_INFO = "SELECT * FROM alarmInfo";
private static final String CHECK_TABLE_NAME = "SELECT * FROM %s";
private static final String CHECK_TABLE_FIELD = "PRAGMA table_info(%s)";
private static final String TABLE_ALARM_INFO = "alarmInfo";
private static final String TABLE_SYSTEM_ID_INFO = "SystemIdInfo";
private static final String TABLE_WORKSPEC = "WorkSpec";
private static final String TABLE_WORKTAG = "WorkTag";
private static final String TABLE_WORKNAME = "WorkName";
private static final String TABLE_WORKPROGRESS = "WorkProgress";
private static final String NAME = "name";
private static final String TRIGGER_CONTENT_UPDATE_DELAY = "trigger_content_update_delay";
private static final String TRIGGER_MAX_CONTENT_DELAY = "trigger_max_content_delay";
private Context mContext;
private File mDatabasePath;
@Rule
public MigrationTestHelper mMigrationTestHelper = new MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
WorkDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
@Before
public void setUp() {
// Delete the database if it exists.
mContext = ApplicationProvider.getApplicationContext();
mDatabasePath = ApplicationProvider.getApplicationContext().getDatabasePath(TEST_DATABASE);
if (mDatabasePath.exists()) {
mDatabasePath.delete();
}
}
@Test
@MediumTest
public void testMigrationVersion1To2() throws IOException {
SupportSQLiteDatabase database =
mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_1);
String[] prepopulatedWorkSpecIds = new String[]{
UUID.randomUUID().toString(),
UUID.randomUUID().toString()
};
for (String workSpecId : prepopulatedWorkSpecIds) {
ContentValues contentValues = contentValues(workSpecId);
database.insert("workspec", CONFLICT_FAIL, contentValues);
if (workSpecId.equals(prepopulatedWorkSpecIds[0])) {
ContentValues tagValues = new ContentValues();
tagValues.put("tag", TestWorker.class.getName());
tagValues.put("work_spec_id", workSpecId);
database.insert("worktag", CONFLICT_FAIL, tagValues);
}
}
String workSpecId1 = UUID.randomUUID().toString();
String workSpecId2 = UUID.randomUUID().toString();
// insert alarmInfos
database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId1, 1});
database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId2, 2});
database.close();
database = mMigrationTestHelper.runMigrationsAndValidate(
TEST_DATABASE,
VERSION_2,
VALIDATE_DROPPED_TABLES,
WorkDatabaseMigrations.MIGRATION_1_2);
Cursor tagCursor = database.query("SELECT * FROM worktag");
assertThat(tagCursor.getCount(), is(prepopulatedWorkSpecIds.length));
boolean[] foundWorkSpecId = new boolean[prepopulatedWorkSpecIds.length];
for (int i = 0; i < prepopulatedWorkSpecIds.length; ++i) {
tagCursor.moveToPosition(i);
assertThat(tagCursor.getString(tagCursor.getColumnIndex("tag")),
is(TestWorker.class.getName()));
String currentId = tagCursor.getString(tagCursor.getColumnIndex("work_spec_id"));
for (int j = 0; j < prepopulatedWorkSpecIds.length; ++j) {
if (prepopulatedWorkSpecIds[j].equals(currentId)) {
foundWorkSpecId[j] = true;
break;
}
}
}
for (int i = 0; i < prepopulatedWorkSpecIds.length; ++i) {
assertThat(foundWorkSpecId[i], is(true));
}
tagCursor.close();
Cursor cursor = database.query(CHECK_SYSTEM_ID_INFO);
assertThat(cursor.getCount(), is(2));
cursor.moveToFirst();
assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId1));
assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_SYSTEM_ID)), is(1));
cursor.moveToNext();
assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId2));
assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_SYSTEM_ID)), is(2));
cursor.close();
assertThat(checkExists(database, TABLE_ALARM_INFO), is(false));
assertThat(checkExists(database, TABLE_WORKSPEC), is(true));
assertThat(checkExists(database, TABLE_WORKTAG), is(true));
assertThat(checkExists(database, TABLE_WORKNAME), is(true));
database.close();
}
@Test
@MediumTest
public void testMigrationVersion2To3() throws IOException {
SupportSQLiteDatabase database =
mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_2);
WorkDatabaseMigrations.WorkMigration migration2To3 =
new WorkDatabaseMigrations.WorkMigration(mContext, VERSION_2, VERSION_3);
database = mMigrationTestHelper.runMigrationsAndValidate(
TEST_DATABASE,
VERSION_3,
VALIDATE_DROPPED_TABLES,
migration2To3);
Preferences preferences = new Preferences(mContext);
assertThat(preferences.needsReschedule(), is(true));
database.close();
}
@Test
@MediumTest
public void testMigrationVersion3To4() throws IOException {
SupportSQLiteDatabase database =
mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_3);
String oneTimeWorkSpecId = UUID.randomUUID().toString();
long scheduleRequestedAt = System.currentTimeMillis();
ContentValues oneTimeWorkSpecContentValues = contentValues(oneTimeWorkSpecId);
oneTimeWorkSpecContentValues.put("schedule_requested_at", scheduleRequestedAt);
String periodicWorkSpecId = UUID.randomUUID().toString();
ContentValues periodicWorkSpecContentValues = contentValues(periodicWorkSpecId);
periodicWorkSpecContentValues.put("interval_duration", 15 * 60 * 1000L);
database.insert("workspec", CONFLICT_FAIL, oneTimeWorkSpecContentValues);
database.insert("workspec", CONFLICT_FAIL, periodicWorkSpecContentValues);
database = mMigrationTestHelper.runMigrationsAndValidate(
TEST_DATABASE,
VERSION_4,
VALIDATE_DROPPED_TABLES,
MIGRATION_3_4);
Cursor cursor = database.query("SELECT * from workspec");
assertThat(cursor.getCount(), is(2));
cursor.moveToFirst();
assertThat(cursor.getString(cursor.getColumnIndex("id")),
is(oneTimeWorkSpecId));
assertThat(cursor.getLong(cursor.getColumnIndex("schedule_requested_at")),
is(scheduleRequestedAt));
cursor.moveToNext();
assertThat(cursor.getString(cursor.getColumnIndex("id")),
is(periodicWorkSpecId));
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
assertThat(cursor.getLong(cursor.getColumnIndex("schedule_requested_at")),
is(0L));
} else {
assertThat(cursor.getLong(cursor.getColumnIndex("schedule_requested_at")),
is(WorkSpec.SCHEDULE_NOT_REQUESTED_YET));
}
database.close();
}
@Test
@MediumTest
public void testMigrationVersion4To5() throws IOException {
SupportSQLiteDatabase database =
mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_4);
database = mMigrationTestHelper.runMigrationsAndValidate(
TEST_DATABASE,
VERSION_5,
VALIDATE_DROPPED_TABLES,
MIGRATION_4_5);
assertThat(checkExists(database, TABLE_WORKSPEC), is(true));
assertThat(
checkColumnExists(database, TABLE_WORKSPEC, TRIGGER_CONTENT_UPDATE_DELAY),
is(true));
assertThat(
checkColumnExists(database, TABLE_WORKSPEC, TRIGGER_MAX_CONTENT_DELAY),
is(true));
database.close();
}
@Test
@MediumTest
public void testMigrationVersion5To6() throws IOException {
SupportSQLiteDatabase database =
mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_5);
WorkDatabaseMigrations.WorkMigration migration5To6 =
new WorkDatabaseMigrations.WorkMigration(mContext, VERSION_5, VERSION_6);
database = mMigrationTestHelper.runMigrationsAndValidate(
TEST_DATABASE,
VERSION_6,
VALIDATE_DROPPED_TABLES,
migration5To6);
Preferences preferences = new Preferences(mContext);
assertThat(preferences.needsReschedule(), is(true));
database.close();
}
@Test
@MediumTest
public void testMigrationVersion6To7() throws IOException {
SupportSQLiteDatabase database =
mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_6);
database = mMigrationTestHelper.runMigrationsAndValidate(
TEST_DATABASE,
VERSION_7,
VALIDATE_DROPPED_TABLES,
MIGRATION_6_7);
assertThat(checkExists(database, TABLE_WORKPROGRESS), is(true));
database.close();
}
@NonNull
private ContentValues contentValues(String workSpecId) {
ContentValues contentValues = new ContentValues();
contentValues.put("id", workSpecId);
contentValues.put("state", WorkTypeConverters.StateIds.ENQUEUED);
contentValues.put("worker_class_name", TestWorker.class.getName());
contentValues.put("input_merger_class_name", OverwritingInputMerger.class.getName());
contentValues.put("input", Data.toByteArray(Data.EMPTY));
contentValues.put("output", Data.toByteArray(Data.EMPTY));
contentValues.put("initial_delay", 0L);
contentValues.put("interval_duration", 0L);
contentValues.put("flex_duration", 0L);
contentValues.put("required_network_type", false);
contentValues.put("requires_charging", false);
contentValues.put("requires_device_idle", false);
contentValues.put("requires_battery_not_low", false);
contentValues.put("requires_storage_not_low", false);
contentValues.put("content_uri_triggers",
WorkTypeConverters.contentUriTriggersToByteArray(new ContentUriTriggers()));
contentValues.put("run_attempt_count", 0);
contentValues.put("backoff_policy",
WorkTypeConverters.backoffPolicyToInt(BackoffPolicy.EXPONENTIAL));
contentValues.put("backoff_delay_duration", WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS);
contentValues.put("period_start_time", 0L);
contentValues.put("minimum_retention_duration", 0L);
contentValues.put("schedule_requested_at", WorkSpec.SCHEDULE_NOT_REQUESTED_YET);
return contentValues;
}
private boolean checkExists(SupportSQLiteDatabase database, String tableName) {
Cursor cursor = null;
try {
cursor = database.query(String.format(CHECK_TABLE_NAME, tableName));
return true;
} catch (SQLiteException ignored) {
// Should fail with a SQLiteException (no such table: tableName)
return false;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
private boolean checkColumnExists(
SupportSQLiteDatabase database,
String tableName,
String columnName) {
Cursor cursor = null;
try {
cursor = database.query(String.format(CHECK_TABLE_FIELD, tableName));
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
// https://www.sqlite.org/pragma.html#pragma_table_info
// Columns are: (cid, name, type, notnull, dfit_value, pk)
String name = cursor.getString(cursor.getColumnIndex(NAME));
if (columnName.equals(name)) {
return true;
}
cursor.moveToNext();
}
return false;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}