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 package com.android.launcher3.model; 17 18 import static androidx.test.InstrumentationRegistry.getContext; 19 20 import static junit.framework.Assert.assertEquals; 21 import static junit.framework.Assert.assertFalse; 22 import static junit.framework.Assert.assertNotSame; 23 import static junit.framework.Assert.assertTrue; 24 25 import static org.mockito.Mockito.doAnswer; 26 import static org.mockito.Mockito.spy; 27 import static org.mockito.Mockito.when; 28 29 import android.content.ContentValues; 30 import android.content.Context; 31 import android.content.res.Resources; 32 import android.database.Cursor; 33 import android.database.sqlite.SQLiteDatabase; 34 import android.database.sqlite.SQLiteOpenHelper; 35 36 import androidx.test.ext.junit.runners.AndroidJUnit4; 37 import androidx.test.filters.SmallTest; 38 import androidx.test.platform.app.InstrumentationRegistry; 39 40 import com.android.launcher3.LauncherProvider; 41 import com.android.launcher3.LauncherProvider.DatabaseHelper; 42 import com.android.launcher3.LauncherSettings.Favorites; 43 import com.android.launcher3.R; 44 45 import org.junit.Before; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 49 import java.io.File; 50 51 /** 52 * Tests for {@link DbDowngradeHelper} 53 */ 54 @SmallTest 55 @RunWith(AndroidJUnit4.class) 56 public class DbDowngradeHelperTest { 57 58 private static final String SCHEMA_FILE = "test_schema.json"; 59 private static final String DB_FILE = "test.db"; 60 61 private Context mContext; 62 private File mSchemaFile; 63 private File mDbFile; 64 65 @Before setup()66 public void setup() { 67 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 68 mSchemaFile = mContext.getFileStreamPath(SCHEMA_FILE); 69 mDbFile = mContext.getDatabasePath(DB_FILE); 70 } 71 72 @Test testDowngradeSchemaMatchesVersion()73 public void testDowngradeSchemaMatchesVersion() throws Exception { 74 mSchemaFile.delete(); 75 assertFalse(mSchemaFile.exists()); 76 DbDowngradeHelper.updateSchemaFile(mSchemaFile, 0, mContext); 77 assertEquals(LauncherProvider.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version); 78 } 79 80 @Test testUpdateSchemaFile()81 public void testUpdateSchemaFile() throws Exception { 82 // Setup mock resources 83 Resources res = spy(mContext.getResources()); 84 Resources myRes = getContext().getResources(); 85 doAnswer(i -> myRes.openRawResource( 86 myRes.getIdentifier("db_schema_v10", "raw", getContext().getPackageName()))) 87 .when(res).openRawResource(R.raw.downgrade_schema); 88 Context context = spy(mContext); 89 when(context.getResources()).thenReturn(res); 90 91 mSchemaFile.delete(); 92 assertFalse(mSchemaFile.exists()); 93 94 DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context); 95 assertTrue(mSchemaFile.exists()); 96 assertEquals(10, DbDowngradeHelper.parse(mSchemaFile).version); 97 98 // Schema is updated on version upgrade 99 assertTrue(mSchemaFile.setLastModified(0)); 100 DbDowngradeHelper.updateSchemaFile(mSchemaFile, 11, context); 101 assertNotSame(0, mSchemaFile.lastModified()); 102 103 // Schema is not updated when version is same 104 assertTrue(mSchemaFile.setLastModified(0)); 105 DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context); 106 assertEquals(0, mSchemaFile.lastModified()); 107 108 // Schema is not updated on version downgrade 109 DbDowngradeHelper.updateSchemaFile(mSchemaFile, 3, context); 110 assertEquals(0, mSchemaFile.lastModified()); 111 } 112 113 @Test testDowngrade_success_v24()114 public void testDowngrade_success_v24() throws Exception { 115 setupTestDb(); 116 117 TestOpenHelper helper = new TestOpenHelper(24); 118 assertEquals(24, helper.getReadableDatabase().getVersion()); 119 helper.close(); 120 } 121 122 @Test testDowngrade_success_v22()123 public void testDowngrade_success_v22() throws Exception { 124 setupTestDb(); 125 126 SQLiteOpenHelper helper = new TestOpenHelper(22); 127 assertEquals(22, helper.getWritableDatabase().getVersion()); 128 129 // Check column does not exist 130 try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME, 131 null, null, null, null, null, null)) { 132 assertEquals(-1, c.getColumnIndex(Favorites.OPTIONS)); 133 134 // Check data is present 135 assertEquals(10, c.getCount()); 136 } 137 helper.close(); 138 139 helper = new DatabaseHelper(mContext, DB_FILE, false) { 140 @Override 141 public void onOpen(SQLiteDatabase db) { } 142 }; 143 assertEquals(LauncherProvider.SCHEMA_VERSION, helper.getWritableDatabase().getVersion()); 144 145 try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME, 146 null, null, null, null, null, null)) { 147 // Check column exists 148 assertNotSame(-1, c.getColumnIndex(Favorites.OPTIONS)); 149 150 // Check data is present 151 assertEquals(10, c.getCount()); 152 } 153 helper.close(); 154 } 155 156 @Test(expected = DowngradeFailException.class) testDowngrade_fail_v20()157 public void testDowngrade_fail_v20() throws Exception { 158 setupTestDb(); 159 160 TestOpenHelper helper = new TestOpenHelper(20); 161 helper.getReadableDatabase().getVersion(); 162 } 163 setupTestDb()164 private void setupTestDb() throws Exception { 165 mSchemaFile.delete(); 166 mDbFile.delete(); 167 168 DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext); 169 170 DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) { 171 @Override 172 public void onOpen(SQLiteDatabase db) { } 173 }; 174 // Insert mock data 175 for (int i = 0; i < 10; i++) { 176 ContentValues values = new ContentValues(); 177 values.put(Favorites._ID, i); 178 values.put(Favorites.TITLE, "title " + i); 179 dbHelper.getWritableDatabase().insert(Favorites.TABLE_NAME, null, values); 180 } 181 dbHelper.close(); 182 } 183 184 private class TestOpenHelper extends SQLiteOpenHelper { 185 TestOpenHelper(int version)186 public TestOpenHelper(int version) { 187 super(mContext, DB_FILE, null, version); 188 } 189 190 @Override onCreate(SQLiteDatabase sqLiteDatabase)191 public void onCreate(SQLiteDatabase sqLiteDatabase) { 192 throw new RuntimeException("DB should already be created"); 193 } 194 195 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)196 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 197 throw new RuntimeException("Only downgrade supported"); 198 } 199 200 @Override onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)201 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 202 try { 203 DbDowngradeHelper.parse(mSchemaFile).onDowngrade(db, oldVersion, newVersion); 204 } catch (Exception e) { 205 throw new DowngradeFailException(e); 206 } 207 } 208 } 209 210 private static class DowngradeFailException extends RuntimeException { DowngradeFailException(Exception e)211 public DowngradeFailException(Exception e) { 212 super(e); 213 } 214 } 215 } 216