1 /*
2  * Copyright (C) 2015 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.providers.contacts;
17 
18 import android.database.Cursor;
19 import android.database.sqlite.SQLiteDatabase;
20 
21 import junit.framework.AssertionFailedError;
22 
23 import java.util.HashMap;
24 
25 /**
26  * Unit tests for database create/upgrade operations.
27  *
28  * Run the test like this: <code> runtest -c com.android.providers.contacts.BaseDatabaseHelperUpgradeTest
29  * contactsprov </code>
30  */
31 public abstract class BaseDatabaseHelperUpgradeTest extends FixedAndroidTestCase {
32 
33     protected static final String INTEGER = "INTEGER";
34     protected static final String TEXT = "TEXT";
35     protected static final String STRING = "STRING";
36     protected static final String BLOB = "BLOB";
37 
38     protected SQLiteDatabase mDb;
39 
40     /**
41      * The column info returned by PRAGMA table_info()
42      */
43     protected static class TableColumn {
44 
45         public int cid;
46         public String name;
47         public String type;
48         public boolean notnull;
49         // default value
50         public String dflt_value;
51         // primary key. Not tested.
52         public int pk;
53 
TableColumn()54         public TableColumn() {
55 
56         }
57 
TableColumn(String name, String type, boolean notnull, String defaultValue)58         public TableColumn(String name, String type, boolean notnull, String defaultValue) {
59             this.name = name;
60             this.type = type;
61             this.notnull = notnull;
62             this.dflt_value = defaultValue;
63         }
64     }
65 
66     protected static class TableStructure {
67 
68         private final HashMap<String, TableColumn> mColumns = new HashMap<String, TableColumn>();
69         private final String mName;
70 
TableStructure(SQLiteDatabase db, String tableName)71         public TableStructure(SQLiteDatabase db, String tableName) {
72             mName = tableName;
73             try (final Cursor cursor = db.rawQuery("PRAGMA table_info(" + tableName + ");", null)) {
74                 final int cidIndex = cursor.getColumnIndex("cid");
75                 final int nameIndex = cursor.getColumnIndex("name");
76                 final int typeIndex = cursor.getColumnIndex("type");
77                 final int notNullIndex = cursor.getColumnIndex("notnull");
78                 final int dfltValueIndex = cursor.getColumnIndex("dflt_value");
79                 final int pkIndex = cursor.getColumnIndex("pk");
80                 cursor.moveToPosition(-1);
81                 while (cursor.moveToNext()) {
82                     TableColumn column = new TableColumn();
83                     column.cid = cursor.getInt(cidIndex);
84                     column.name = cursor.getString(nameIndex);
85                     column.type = cursor.getString(typeIndex);
86                     column.notnull = cursor.getInt(notNullIndex) != 0;
87                     column.dflt_value = cursor.getString(dfltValueIndex);
88                     column.pk = cursor.getInt(pkIndex);
89 
90                     addColumn(column);
91                 }
92             }
93         }
94 
TableStructure()95         private TableStructure() {
96             mName = "";
97         }
98 
addColumn(TableColumn column)99         private void addColumn(TableColumn column) {
100             mColumns.put(column.name, column);
101         }
102 
assertHasColumn(String name, String type, boolean notnull, String defaultValue)103         public void assertHasColumn(String name, String type, boolean notnull,
104                 String defaultValue) {
105             final TableColumn column = mColumns.get(name);
106             if (column == null) {
107                 throw new AssertionFailedError("Table " + mName + ": Column missing: " + name);
108             }
109             if (!type.equals(column.type)) {
110                 throw new AssertionFailedError("Table " + mName + ": Column " + name + " type:"
111                         + column.type + ", " + type + " expected");
112             }
113             if (!notnull == column.notnull) {
114                 throw new AssertionFailedError("Table " + mName + ": Column " + name + " notnull:"
115                         + column.notnull + ", " + notnull + " expected");
116             }
117             if (defaultValue == null) {
118                 if (column.dflt_value != null) {
119                     throw new AssertionFailedError("Table " + mName + ": Column " + name
120                             + " defaultValue: " + column.dflt_value + ", null expected");
121                 }
122             } else if (!defaultValue.equals(column.dflt_value)) {
123                 throw new AssertionFailedError("Table " + mName + ": Column " + name
124                         + " defaultValue:" + column.dflt_value + ", " + defaultValue + " expected");
125             }
126         }
127 
128 
assertHasColumns(TableColumn[] columns)129         public void assertHasColumns(TableColumn[] columns) {
130             for (final TableColumn column : columns) {
131                 assertHasColumn(column.name, column.type, column.notnull, column.dflt_value);
132             }
133         }
134 
135         /**
136          * Assert the TableStructure has every column in @param columns, and nothing else.
137          */
assertSame(TableColumn[] columns)138         public void assertSame(TableColumn[] columns) {
139             assertHasColumns(columns);
140             if (columns.length != mColumns.size()) {
141                 throw new RuntimeException("column count mismatch");
142             }
143         }
144 
145     }
146 
147     /**
148      * Used to store a tables' name and its' current structure in a array.
149      */
150     protected static class TableListEntry {
151 
152         public final String name;
153         public final TableColumn[] columns;
154         public final boolean shouldBeInNewDb;
155 
TableListEntry(String name, TableColumn[] columns)156         public TableListEntry(String name, TableColumn[] columns) {
157             this(name, columns, /* shouldBeInNewDb = */ true);
158         }
159 
TableListEntry(String name, TableColumn[] columns, boolean shouldBeInNewDb)160         public TableListEntry(String name, TableColumn[] columns, boolean shouldBeInNewDb) {
161             this.name = name;
162             this.columns = columns;
163             this.shouldBeInNewDb = shouldBeInNewDb;
164         }
165     }
166 
167     @Override
setUp()168     protected void setUp() throws Exception {
169         super.setUp();
170 
171         final String filename = getDatabaseFilename();
172         if (filename == null) {
173             mDb = SQLiteDatabase.create(null);
174         } else {
175             getContext().deleteDatabase(filename);
176             mDb = SQLiteDatabase.openOrCreateDatabase(filename, null);
177         }
178     }
179 
180     @Override
tearDown()181     protected void tearDown() throws Exception {
182         mDb.close();
183         super.tearDown();
184     }
185 
getDatabaseFilename()186     protected abstract String getDatabaseFilename();
187 
assertDatabaseStructureSameAsList(TableListEntry[] list, boolean isNewDatabase)188     protected void assertDatabaseStructureSameAsList(TableListEntry[] list, boolean isNewDatabase) {
189         for (TableListEntry entry : list) {
190             if (!entry.shouldBeInNewDb) {
191                 if (isNewDatabase) {
192                     continue;
193                 }
194             }
195             TableStructure structure = new TableStructure(mDb, entry.name);
196             structure.assertSame(entry.columns);
197         }
198     }
199 
testAssertHasColumn_Match()200     public void testAssertHasColumn_Match() {
201         TableStructure table = createOneColumnTable("foo", INTEGER, false, null);
202         table.assertHasColumn("foo", INTEGER, false, null);
203     }
204 
testAssertHasColumn_Empty()205     public void testAssertHasColumn_Empty() {
206         TableStructure table = new TableStructure();
207 
208         try {
209             table.assertHasColumn("bar", INTEGER, false, null);
210             throw new AssertionError("Assert should fail");
211         } catch (AssertionFailedError e) {
212             // Should fail
213         }
214     }
215 
testAssertHasColumn_ColumnNotExist()216     public void testAssertHasColumn_ColumnNotExist() {
217         TableStructure table = createOneColumnTable("foo", INTEGER, false, null);
218 
219         try {
220             table.assertHasColumn("bar", INTEGER, false, null);
221             throw new AssertionError("Assert should fail");
222         } catch (AssertionFailedError e) {
223             // Should fail
224         }
225     }
226 
testAssertHasColumn_TypeMismatch()227     public void testAssertHasColumn_TypeMismatch() {
228         TableStructure table = createOneColumnTable("foo", INTEGER, false, null);
229 
230         try {
231             table.assertHasColumn("foo", TEXT, false, null);
232             throw new AssertionError("Assert should fail");
233         } catch (AssertionFailedError e) {
234             // Should fail
235         }
236     }
237 
testAssertHasColumn_NotNullMismatch()238     public void testAssertHasColumn_NotNullMismatch() {
239         TableStructure table = createOneColumnTable("foo", INTEGER, false, null);
240 
241         try {
242             table.assertHasColumn("foo", INTEGER, true, null);
243             throw new AssertionError("Assert should fail");
244         } catch (AssertionFailedError e) {
245             // Should fail
246         }
247     }
248 
testAssertHasColumn_DefaultMatch()249     public void testAssertHasColumn_DefaultMatch() {
250         TableStructure table = createOneColumnTable("foo", INTEGER, false, "baz");
251         table.assertHasColumn("foo", INTEGER, false, "baz");
252     }
253 
testAssertHasColumn_DefaultMismatch()254     public void testAssertHasColumn_DefaultMismatch() {
255         TableStructure table = createOneColumnTable("foo", INTEGER, false, "bar");
256 
257         try {
258             table.assertHasColumn("foo", INTEGER, false, "baz");
259             throw new AssertionError("Assert should fail");
260         } catch (AssertionFailedError e) {
261             // Should fail
262         }
263     }
264 
testAssertHasColumn_DefaultMismatch_Null1()265     public void testAssertHasColumn_DefaultMismatch_Null1() {
266         TableStructure table = createOneColumnTable("foo", INTEGER, false, null);
267 
268         try {
269             table.assertHasColumn("foo", INTEGER, false, "baz");
270             throw new AssertionError("Assert should fail");
271         } catch (AssertionFailedError e) {
272             // Should fail
273         }
274     }
275 
testAssertHasColumn_DefaultMismatch_Null2()276     public void testAssertHasColumn_DefaultMismatch_Null2() {
277         TableStructure table = createOneColumnTable("foo", INTEGER, false, "baz");
278 
279         try {
280             table.assertHasColumn("foo", INTEGER, false, null);
281             throw new AssertionError("Assert should fail");
282         } catch (AssertionFailedError e) {
283             // Should fail
284         }
285     }
286 
createOneColumnTable(String name, String type, boolean notnull, String defaultValue)287     private TableStructure createOneColumnTable(String name, String type, boolean notnull,
288             String defaultValue) {
289         TableStructure table = new TableStructure();
290         table.addColumn(new TableColumn(name, type, notnull, defaultValue));
291         return table;
292     }
293 
294 }
295