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