Add tests for onUpgrade logic
The new test databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb checks
that the table created through onUpgrade matches a cleanly created
table.
The test databaseHelperOnUpgrade_missingColumnsDetected checks that if
we don't go through a full onUpgrade, we should be missing columns, as
expected.
Bug: 79199985
Test: new unit tests pass
Change-Id: I6e68329e8a256bb2676d6eefe3a503be1305642b
Merged-In: I6e68329e8a256bb2676d6eefe3a503be1305642b
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index f809380..451584d 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -422,7 +422,8 @@
mInjector = injector;
}
- private static class DatabaseHelper extends SQLiteOpenHelper {
+ @VisibleForTesting
+ public static class DatabaseHelper extends SQLiteOpenHelper {
// Context to access resources with
private Context mContext;
@@ -438,10 +439,15 @@
setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
}
- private static int getVersion(Context context) {
+ @VisibleForTesting
+ public static int getVersion(Context context) {
if (VDBG) log("getVersion:+");
// Get the database version, combining a static schema version and the XML version
Resources r = context.getResources();
+ if (r == null) {
+ loge("resources=null, return version=" + Integer.toHexString(DATABASE_VERSION));
+ return DATABASE_VERSION;
+ }
XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
try {
XmlUtils.beginDocument(parser, "apns");
@@ -596,16 +602,20 @@
if (VDBG) log("dbh.initDatabase:+ db=" + db);
// Read internal APNS data
Resources r = mContext.getResources();
- XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
int publicversion = -1;
- try {
- XmlUtils.beginDocument(parser, "apns");
- publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
- loadApns(db, parser);
- } catch (Exception e) {
- loge("Got exception while loading APN database." + e);
- } finally {
- parser.close();
+ if (r != null) {
+ XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
+ try {
+ XmlUtils.beginDocument(parser, "apns");
+ publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
+ loadApns(db, parser);
+ } catch (Exception e) {
+ loge("Got exception while loading APN database." + e);
+ } finally {
+ parser.close();
+ }
+ } else {
+ loge("initDatabase: resources=null");
}
// Read external APNS data (partner-provided)
@@ -1062,6 +1072,8 @@
if (DBG) {
log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
}
+ // when adding fields to onUpgrade, also add a unit test to TelephonyDatabaseHelperTest
+ // and update the DATABASE_VERSION field
}
private void recreateSimInfoDB(Cursor c, SQLiteDatabase db, String[] proj) {
@@ -1456,82 +1468,87 @@
private void copyPreservedApnsToNewTable(SQLiteDatabase db, Cursor c) {
// Move entries from CARRIERS_TABLE to CARRIERS_TABLE_TMP
- if (c != null) {
- String[] persistApnsForPlmns = mContext.getResources().getStringArray(
- R.array.persist_apns_for_plmn);
- while (c.moveToNext()) {
- ContentValues cv = new ContentValues();
- String val;
- // Using V17 copy function for V15 upgrade. This should be fine since it handles
- // columns that may not exist properly (getStringValueFromCursor() and
- // getIntValueFromCursor() handle column index -1)
- copyApnValuesV17(cv, c);
- // Change bearer to a bitmask
- String bearerStr = c.getString(c.getColumnIndex(BEARER));
- if (!TextUtils.isEmpty(bearerStr)) {
- int bearer_bitmask = ServiceState.getBitmaskForTech(
- Integer.parseInt(bearerStr));
- cv.put(BEARER_BITMASK, bearer_bitmask);
+ if (c != null && mContext.getResources() != null) {
+ try {
+ String[] persistApnsForPlmns = mContext.getResources().getStringArray(
+ R.array.persist_apns_for_plmn);
+ while (c.moveToNext()) {
+ ContentValues cv = new ContentValues();
+ String val;
+ // Using V17 copy function for V15 upgrade. This should be fine since it handles
+ // columns that may not exist properly (getStringValueFromCursor() and
+ // getIntValueFromCursor() handle column index -1)
+ copyApnValuesV17(cv, c);
+ // Change bearer to a bitmask
+ String bearerStr = c.getString(c.getColumnIndex(BEARER));
+ if (!TextUtils.isEmpty(bearerStr)) {
+ int bearer_bitmask = ServiceState.getBitmaskForTech(
+ Integer.parseInt(bearerStr));
+ cv.put(BEARER_BITMASK, bearer_bitmask);
- int networkTypeBitmask = ServiceState.getBitmaskForTech(
- ServiceState.rilRadioTechnologyToNetworkType(
- Integer.parseInt(bearerStr)));
- cv.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
- }
-
- int userEditedColumnIdx = c.getColumnIndex("user_edited");
- if (userEditedColumnIdx != -1) {
- String user_edited = c.getString(userEditedColumnIdx);
- if (!TextUtils.isEmpty(user_edited)) {
- cv.put(EDITED, new Integer(user_edited));
+ int networkTypeBitmask = ServiceState.getBitmaskForTech(
+ ServiceState.rilRadioTechnologyToNetworkType(
+ Integer.parseInt(bearerStr)));
+ cv.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
}
- } else {
- cv.put(EDITED, CARRIER_EDITED);
- }
- // New EDITED column. Default value (UNEDITED) will
- // be used for all rows except for non-mvno entries for plmns indicated
- // by resource: those will be set to CARRIER_EDITED to preserve
- // their current values
- val = c.getString(c.getColumnIndex(NUMERIC));
- for (String s : persistApnsForPlmns) {
- if (!TextUtils.isEmpty(val) && val.equals(s) &&
- (!cv.containsKey(MVNO_TYPE) ||
- TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) {
- if (userEditedColumnIdx == -1) {
- cv.put(EDITED, CARRIER_EDITED);
- } else { // if (oldVersion == 14) -- if db had user_edited column
- if (cv.getAsInteger(EDITED) == USER_EDITED) {
- cv.put(EDITED, CARRIER_EDITED);
- }
+ int userEditedColumnIdx = c.getColumnIndex("user_edited");
+ if (userEditedColumnIdx != -1) {
+ String user_edited = c.getString(userEditedColumnIdx);
+ if (!TextUtils.isEmpty(user_edited)) {
+ cv.put(EDITED, new Integer(user_edited));
}
+ } else {
+ cv.put(EDITED, CARRIER_EDITED);
+ }
- break;
+ // New EDITED column. Default value (UNEDITED) will
+ // be used for all rows except for non-mvno entries for plmns indicated
+ // by resource: those will be set to CARRIER_EDITED to preserve
+ // their current values
+ val = c.getString(c.getColumnIndex(NUMERIC));
+ for (String s : persistApnsForPlmns) {
+ if (!TextUtils.isEmpty(val) && val.equals(s) &&
+ (!cv.containsKey(MVNO_TYPE) ||
+ TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) {
+ if (userEditedColumnIdx == -1) {
+ cv.put(EDITED, CARRIER_EDITED);
+ } else { // if (oldVersion == 14) -- if db had user_edited column
+ if (cv.getAsInteger(EDITED) == USER_EDITED) {
+ cv.put(EDITED, CARRIER_EDITED);
+ }
+ }
+
+ break;
+ }
+ }
+
+ try {
+ db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
+ SQLiteDatabase.CONFLICT_ABORT);
+ if (VDBG) {
+ log("dbh.copyPreservedApnsToNewTable: db.insert returned >= 0; " +
+ "insert successful for cv " + cv);
+ }
+ } catch (SQLException e) {
+ if (VDBG)
+ log("dbh.copyPreservedApnsToNewTable insertWithOnConflict exception " +
+ e + " for cv " + cv);
+ // Insertion failed which could be due to a conflict. Check if that is
+ // the case and merge the entries
+ Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
+ CARRIERS_TABLE_TMP, cv);
+ if (oldRow != null) {
+ ContentValues mergedValues = new ContentValues();
+ mergeFieldsAndUpdateDb(db, CARRIERS_TABLE_TMP, oldRow, cv,
+ mergedValues, true, mContext);
+ oldRow.close();
+ }
}
}
-
- try {
- db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
- SQLiteDatabase.CONFLICT_ABORT);
- if (VDBG) {
- log("dbh.copyPreservedApnsToNewTable: db.insert returned >= 0; " +
- "insert successful for cv " + cv);
- }
- } catch (SQLException e) {
- if (VDBG)
- log("dbh.copyPreservedApnsToNewTable insertWithOnConflict exception " +
- e + " for cv " + cv);
- // Insertion failed which could be due to a conflict. Check if that is
- // the case and merge the entries
- Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
- CARRIERS_TABLE_TMP, cv);
- if (oldRow != null) {
- ContentValues mergedValues = new ContentValues();
- mergeFieldsAndUpdateDb(db, CARRIERS_TABLE_TMP, oldRow, cv,
- mergedValues, true, mContext);
- oldRow.close();
- }
- }
+ } catch (Resources.NotFoundException e) {
+ loge("array.persist_apns_for_plmn is not found");
+ return;
}
}
}
@@ -1919,13 +1936,17 @@
boolean match = false;
// Check if APN falls under persist_apns_for_plmn
- String[] persistApnsForPlmns = context.getResources().getStringArray(
- R.array.persist_apns_for_plmn);
- for (String s : persistApnsForPlmns) {
- if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
- match = true;
- break;
+ if (context.getResources() != null) {
+ String[] persistApnsForPlmns = context.getResources().getStringArray(
+ R.array.persist_apns_for_plmn);
+ for (String s : persistApnsForPlmns) {
+ if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
+ match = true;
+ break;
+ }
}
+ } else {
+ loge("separateRowsNeeded: resources=null");
}
if (!match) return false;
diff --git a/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
new file mode 100644
index 0000000..d6b989a
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 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 com.android.providers.telephony;
+
+import static android.provider.Telephony.Carriers;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.test.InstrumentationRegistry;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class TelephonyDatabaseHelperTest {
+
+ private final static String TAG = TelephonyDatabaseHelperTest.class.getSimpleName();
+
+ private Context mContext;
+ private TelephonyProvider.DatabaseHelper mHelper; // the actual class being tested
+ private SQLiteOpenHelper mInMemoryDbHelper; // used to give us an in-memory db
+
+ @Before
+ public void setUp() {
+ Log.d(TAG, "setUp() +");
+ mContext = InstrumentationRegistry.getContext();
+ mHelper = new TelephonyProvider.DatabaseHelper(mContext);
+ mInMemoryDbHelper = new InMemoryTelephonyProviderV5DbHelper();
+ Log.d(TAG, "setUp() -");
+ }
+
+ @Test
+ public void databaseHelperOnUpgrade_hasApnSetIdField() {
+ Log.d(TAG, "databaseHelperOnUpgrade_hasApnSetIdField");
+ // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+ SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+ // the upgraded db must have the APN_SET_ID field
+ Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+ String[] upgradedColumns = cursor.getColumnNames();
+ Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+
+ assertTrue(Arrays.asList(upgradedColumns).contains(Carriers.APN_SET_ID));
+ }
+
+ @Test
+ public void databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb() {
+ Log.d(TAG, "databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb");
+ // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+ SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+ // compare upgraded carriers table to a carriers table created from scratch
+ db.execSQL(TelephonyProvider.getStringForCarrierTableCreation("carriers_full"));
+
+ Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+ String[] upgradedColumns = cursor.getColumnNames();
+ Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+
+ cursor = db.query("carriers_full", null, null, null, null, null, null);
+ String[] fullColumns = cursor.getColumnNames();
+ Log.d(TAG, "carriers_full colunmns: " + Arrays.toString(fullColumns));
+
+ assertArrayEquals("Carriers table from onUpgrade doesn't match full table",
+ fullColumns, upgradedColumns);
+
+ // compare upgraded siminfo table to siminfo table created from scratch
+ db.execSQL(TelephonyProvider.getStringForSimInfoTableCreation("siminfo_full"));
+
+ cursor = db.query("siminfo", null, null, null, null, null, null);
+ upgradedColumns = cursor.getColumnNames();
+ Log.d(TAG, "siminfo columns: " + Arrays.toString(upgradedColumns));
+
+ cursor = db.query("siminfo_full", null, null, null, null, null, null);
+ fullColumns = cursor.getColumnNames();
+ Log.d(TAG, "siminfo_full colunmns: " + Arrays.toString(fullColumns));
+
+ assertArrayEquals("Siminfo table from onUpgrade doesn't match full table",
+ fullColumns, upgradedColumns);
+ }
+
+ /**
+ * Helper for an in memory DB used to test the TelephonyProvider#DatabaseHelper.
+ *
+ * We pass this in-memory db to DatabaseHelper#onUpgrade so we can use the actual function
+ * without using the actual telephony db.
+ */
+ private static class InMemoryTelephonyProviderV5DbHelper extends SQLiteOpenHelper {
+
+ public InMemoryTelephonyProviderV5DbHelper() {
+ super(InstrumentationRegistry.getContext(),
+ null, // db file name is null for in-memory db
+ null, // CursorFactory is null by default
+ 1); // in-memory db version doesn't seem to matter
+ Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper creating in-memory database");
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // Set up the carriers table without any fields added in onUpgrade
+ // since these are the initial fields, there is no need to update this test fixture in
+ // the future
+ List<String> originalUniqueFields = new ArrayList<String>();
+ originalUniqueFields.add(Carriers.NUMERIC);
+ originalUniqueFields.add(Carriers.MCC);
+ originalUniqueFields.add(Carriers.MNC);
+ originalUniqueFields.add(Carriers.APN);
+ originalUniqueFields.add(Carriers.PROXY);
+ originalUniqueFields.add(Carriers.PORT);
+ originalUniqueFields.add(Carriers.MMSPROXY);
+ originalUniqueFields.add(Carriers.MMSPORT);
+ originalUniqueFields.add(Carriers.MMSC);
+ Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onCreate creating the carriers table");
+ db.execSQL(
+ "CREATE TABLE carriers" +
+ "(_id INTEGER PRIMARY KEY," +
+ Carriers.NAME + " TEXT DEFAULT ''," +
+ Carriers.NUMERIC + " TEXT DEFAULT ''," +
+ Carriers.MCC + " TEXT DEFAULT ''," +
+ Carriers.MNC + " TEXT DEFAULT ''," +
+ Carriers.APN + " TEXT DEFAULT ''," +
+ Carriers.USER + " TEXT DEFAULT ''," +
+ Carriers.SERVER + " TEXT DEFAULT ''," +
+ Carriers.PASSWORD + " TEXT DEFAULT ''," +
+ Carriers.PROXY + " TEXT DEFAULT ''," +
+ Carriers.PORT + " TEXT DEFAULT ''," +
+ Carriers.MMSPROXY + " TEXT DEFAULT ''," +
+ Carriers.MMSPORT + " TEXT DEFAULT ''," +
+ Carriers.MMSC + " TEXT DEFAULT ''," +
+ Carriers.TYPE + " TEXT DEFAULT ''," +
+ Carriers.CURRENT + " INTEGER," +
+ "UNIQUE (" + TextUtils.join(", ", originalUniqueFields) + "));");
+
+ // set up the siminfo table without any fields added in onUpgrade
+ // since these are the initial fields, there is no need to update this test fixture in
+ // the future
+ Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onCreate creating the siminfo table");
+ db.execSQL(
+ "CREATE TABLE siminfo ("
+ + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
+ + SubscriptionManager.SIM_SLOT_INDEX
+ + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
+ + SubscriptionManager.DISPLAY_NAME + " TEXT,"
+ + SubscriptionManager.NAME_SOURCE
+ + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","
+ + SubscriptionManager.COLOR
+ + " INTEGER DEFAULT " + SubscriptionManager.COLOR_DEFAULT + ","
+ + SubscriptionManager.NUMBER + " TEXT,"
+ + SubscriptionManager.DISPLAY_NUMBER_FORMAT + " INTEGER NOT NULL"
+ + " DEFAULT " + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","
+ + SubscriptionManager.DATA_ROAMING
+ + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
+ + SubscriptionManager.CARD_ID + " TEXT NOT NULL"
+ + ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onUpgrade doing nothing");
+ return;
+ }
+ }
+}