1 /* 2 * Copyright (C) 2013 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 17 package android.provider.cts.contacts; 18 19 import android.accounts.Account; 20 import android.content.ContentResolver; 21 import android.content.ContentValues; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.provider.BaseColumns; 25 import android.provider.ContactsContract.Contacts; 26 import android.provider.cts.contacts.account.StaticAccountAuthenticator; 27 import android.test.MoreAsserts; 28 29 import junit.framework.Assert; 30 31 import java.util.BitSet; 32 33 /** 34 * Common methods for asserting database related operations. 35 */ 36 public class DatabaseAsserts { 37 assertDeleteIsUnsupported(ContentResolver resolver, Uri uri)38 public static void assertDeleteIsUnsupported(ContentResolver resolver, Uri uri) { 39 try { 40 resolver.delete(uri, null, null); 41 Assert.fail("delete operation should have failed with UnsupportedOperationException on" 42 + uri); 43 } catch (UnsupportedOperationException e) { 44 // pass 45 } 46 } 47 assertInsertIsUnsupported(ContentResolver resolver, Uri uri)48 public static void assertInsertIsUnsupported(ContentResolver resolver, Uri uri) { 49 try { 50 ContentValues values = new ContentValues(); 51 resolver.insert(uri, values); 52 Assert.fail("insert operation should have failed with UnsupportedOperationException on" 53 + uri); 54 } catch (UnsupportedOperationException e) { 55 // pass 56 } 57 } 58 59 /** 60 * Create a contact in account 1 and assert that the record exists. 61 * 62 * @return The created contact id pair. 63 */ assertAndCreateContact(ContentResolver resolver)64 public static ContactIdPair assertAndCreateContact(ContentResolver resolver) { 65 return assertAndCreateContact(resolver, StaticAccountAuthenticator.ACCOUNT_1); 66 } 67 68 /** 69 * Create a contact in a specified account and assert that the record exists. 70 * 71 * @return The created contact id pair. 72 */ assertAndCreateContactWithName(ContentResolver resolver, Account account, String name)73 public static ContactIdPair assertAndCreateContactWithName(ContentResolver resolver, 74 Account account, String name) { 75 long rawContactId = RawContactUtil.createRawContactWithName(resolver, account, name); 76 77 long contactId = RawContactUtil.queryContactIdByRawContactId(resolver, rawContactId); 78 MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId); 79 80 return new ContactIdPair(contactId, rawContactId); 81 } 82 83 /** 84 * Create a contact in a specified account and assert that the record exists. 85 * 86 * @return The created contact id pair. 87 */ assertAndCreateContact(ContentResolver resolver, Account account)88 public static ContactIdPair assertAndCreateContact(ContentResolver resolver, Account account) { 89 long rawContactId = RawContactUtil.createRawContactWithAutoGeneratedName(resolver, account); 90 91 long contactId = RawContactUtil.queryContactIdByRawContactId(resolver, rawContactId); 92 MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId); 93 94 return new ContactIdPair(contactId, rawContactId); 95 } 96 97 /** 98 * Asserts that a contact id was deleted, has a delete log, and that log has a timestamp greater 99 * than the given timestamp. 100 * 101 * @param contactId The contact id to check. 102 * @param start The timestamp that the delete log should be greater than. 103 */ assertHasDeleteLogGreaterThan(ContentResolver resolver, long contactId, long start)104 public static void assertHasDeleteLogGreaterThan(ContentResolver resolver, long contactId, 105 long start) { 106 Assert.assertFalse(ContactUtil.recordExistsForContactId(resolver, contactId)); 107 108 long deletedTimestamp = DeletedContactUtil.queryDeletedTimestampForContactId(resolver, 109 contactId); 110 MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, deletedTimestamp); 111 Assert.assertTrue(deletedTimestamp > start); 112 } 113 114 /** 115 * Holds a single contact id and raw contact id relationship. 116 */ 117 public static class ContactIdPair { 118 public long mContactId; 119 public long mRawContactId; 120 ContactIdPair(long contactId, long rawContactId)121 public ContactIdPair(long contactId, long rawContactId) { 122 this.mContactId = contactId; 123 this.mRawContactId = rawContactId; 124 } 125 126 @Override toString()127 public String toString() { 128 return "ContactIdPair{" + 129 "mContactId=" + mContactId + 130 ", mRawContactId=" + mRawContactId + 131 '}'; 132 } 133 } 134 135 /** 136 * Queries for a given {@link Uri} against a provided {@link ContentResolver}, and 137 * ensures that the returned cursor contains exactly the expected values. 138 * 139 * @param resolver - ContentResolver to query against 140 * @param uri - {@link Uri} to perform the query for 141 * contained in <code>expectedValues</code> in order for the assert to pass. 142 * @param expectedValues - Array of {@link ContentValues} which the cursor returned from the 143 * query should contain. 144 */ assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri, ContentValues... expectedValues)145 public static void assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri, 146 ContentValues... expectedValues) { 147 assertStoredValuesInUriMatchExactly(resolver, uri, null, null, null, null, false, expectedValues); 148 } 149 150 /** 151 * Queries for a given {@link Uri} against a provided {@link ContentResolver}, and 152 * ensures that the returned cursor contains exactly the expected values. 153 * 154 * @param resolver - ContentResolver to query against 155 * @param uri - {@link Uri} to perform the query for 156 * @param projection - Projection to use for the query. Must contain at least the columns 157 * contained in <code>expectedValues</code> in order for the assert to pass. 158 * @param selection - Selection string to use for the query. 159 * @param selectionArgs - Selection arguments to use for the query. 160 * @param sortOrder - Sort order to use for the query. 161 * @param inOrder Whether or not the returned rows in the cursor should correspond to the 162 * order of the provided ContentValues 163 * @param expectedValues - Array of {@link ContentValues} which the cursor returned from the 164 * query should contain. 165 */ assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, boolean inOrder, ContentValues... expectedValues)166 public static void assertStoredValuesInUriMatchExactly(ContentResolver resolver, Uri uri, 167 String[] projection, String selection, String[] selectionArgs, String sortOrder, 168 boolean inOrder, ContentValues... expectedValues) { 169 final Cursor cursor = resolver.query(uri, projection, selection, selectionArgs, sortOrder); 170 try { 171 if (inOrder) { 172 assertCursorValuesMatchExactlyInOrder(cursor, expectedValues); 173 } else { 174 assertCursorValuesMatchExactly(cursor, expectedValues); 175 } 176 } finally { 177 cursor.close(); 178 } 179 } 180 181 /** 182 * Ensures that the rows in the cursor match the rows in the expected values exactly. However, 183 * does not require that the rows in the cursor are ordered the same way as those in the 184 * expected values. 185 * 186 * @param cursor - Cursor containing the values to check for 187 * @param expectedValues - Array of ContentValues that the cursor should be expected to 188 * contain. 189 */ assertCursorValuesMatchExactly(Cursor cursor, ContentValues... expectedValues)190 public static void assertCursorValuesMatchExactly(Cursor cursor, 191 ContentValues... expectedValues) { 192 Assert.assertEquals("Cursor does not contain the number of expected rows", 193 expectedValues.length, cursor.getCount()); 194 StringBuilder message = new StringBuilder(); 195 // In case if expectedValues contains multiple identical values, remember which cursor 196 // rows are "consumed" to prevent multiple ContentValues from hitting the same row. 197 final BitSet used = new BitSet(cursor.getCount()); 198 199 for (ContentValues v : expectedValues) { 200 boolean found = false; 201 cursor.moveToPosition(-1); 202 while (cursor.moveToNext()) { 203 final int pos = cursor.getPosition(); 204 if (used.get(pos)) continue; 205 found = equalsWithExpectedValues(cursor, v, message); 206 if (found) { 207 used.set(pos); 208 break; 209 } 210 } 211 Assert.assertTrue("Expected values can not be found " + v + "," + message.toString(), 212 found); 213 } 214 } 215 216 /** 217 * Ensures that the rows in the cursor match the rows in the expected values exactly. Requires 218 * that the rows in the cursor are ordered the same way as those in the expected values. 219 * 220 * @param cursor - Cursor containing the values to check for 221 * @param expectedValues - Array of ContentValues that the cursor should be expected to 222 * contain. 223 */ assertCursorValuesMatchExactlyInOrder(Cursor cursor, ContentValues... expectedValues)224 public static void assertCursorValuesMatchExactlyInOrder(Cursor cursor, 225 ContentValues... expectedValues) { 226 Assert.assertEquals("Cursor does not contain the number of expected rows", 227 expectedValues.length, cursor.getCount()); 228 StringBuilder message = new StringBuilder(); 229 230 cursor.moveToPosition(-1); 231 for (ContentValues v : expectedValues) { 232 cursor.moveToNext(); 233 Assert.assertTrue("Expected values can not be found " + v + "," + message.toString(), 234 equalsWithExpectedValues(cursor, v, message)); 235 } 236 } 237 238 equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues, StringBuilder msgBuffer)239 private static boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues, 240 StringBuilder msgBuffer) { 241 for (String column : expectedValues.keySet()) { 242 int index = cursor.getColumnIndex(column); 243 if (index == -1) { 244 msgBuffer.append(" No such column: ").append(column); 245 return false; 246 } 247 Object expectedValue = expectedValues.get(column); 248 String value; 249 expectedValue = expectedValues.getAsString(column); 250 value = cursor.getString(cursor.getColumnIndex(column)); 251 if (expectedValue != null && !expectedValue.equals(value) || value != null 252 && !value.equals(expectedValue)) { 253 msgBuffer 254 .append(" Column value ") 255 .append(column) 256 .append(" expected <") 257 .append(expectedValue) 258 .append(">, but was <") 259 .append(value) 260 .append('>'); 261 return false; 262 } 263 } 264 return true; 265 } 266 buildIdSelection(long[] ids)267 public static String buildIdSelection(long[] ids) { 268 StringBuilder selection = new StringBuilder(); 269 selection.append(BaseColumns._ID + " in "); 270 selection.append("("); 271 for (int i = 0; i < ids.length; i++) { 272 if (i != 0) selection.append(","); 273 selection.append(ids[i]); 274 } 275 selection.append(")"); 276 return selection.toString(); 277 } 278 279 /** 280 * Check if a query accepts all columns in the projection, and a resulting cursor contains 281 * all expected columns. This also checks the number of resulting rows, but don't check 282 * actual content in a returned cursor. 283 */ checkProjection(ContentResolver resolver, Uri uri, String[] allColumns, long[] ids)284 public static void checkProjection(ContentResolver resolver, 285 Uri uri, String[] allColumns, long[] ids) { 286 final String selection = buildIdSelection(ids); 287 288 // First, check the null projection (i.e. all columns). 289 checkProjectionInner(resolver, uri, null, allColumns, selection, ids.length, 290 /* keepPosition =*/ false); 291 292 // All columns. 293 checkProjectionInner(resolver, uri, allColumns, allColumns, selection, ids.length, 294 /* keepPosition =*/ true); 295 296 // Select each column one by one. 297 for (int i = 0; i < allColumns.length; i++) { 298 final String[] columns = new String[] {allColumns[i]}; 299 checkProjectionInner(resolver, uri, columns, columns, selection, ids.length, 300 /* keepPosition =*/ true); 301 } 302 303 // Select two columns. 304 for (int i = 0; i < allColumns.length; i++) { 305 for (int j = 0; j < allColumns.length; j++) { 306 // Requesting the same column multiple times is okay, but it'd make the column 307 // order check harder. 308 if (i == j) continue; 309 final String[] columns = new String[] {allColumns[i], allColumns[j]}; 310 checkProjectionInner(resolver, uri, columns, columns, selection, ids.length, 311 /* keepPosition =*/ true); 312 } 313 } 314 } 315 checkProjectionInner(ContentResolver resolver, Uri uri, String[] projection, String[] expectedColumns, String selection, int expectedRowCount, boolean keepPosition)316 private static void checkProjectionInner(ContentResolver resolver, 317 Uri uri, String[] projection, String[] expectedColumns, 318 String selection, int expectedRowCount, boolean keepPosition) { 319 final Cursor c = resolver.query(uri, projection, selection, 320 /* args =*/ null, /* sort =*/ null); 321 Assert.assertNotNull(c); 322 try { 323 Assert.assertEquals("# of rows", expectedRowCount, c.getCount()); 324 325 // Make sure expected columns exist. 326 for (int i = 0; i < expectedColumns.length; i++) { 327 final String column = expectedColumns[i]; 328 if (keepPosition) { 329 Assert.assertEquals(column, c.getColumnName(i)); 330 } else { 331 Assert.assertTrue(column, c.getColumnIndex(column) >= 0); 332 } 333 } 334 } finally { 335 c.close(); 336 } 337 } 338 } 339