1 /* 2 * Copyright (C) 2008 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.database.cts; 18 19 import dalvik.annotation.TestLevel; 20 import dalvik.annotation.TestTargetClass; 21 import dalvik.annotation.TestTargetNew; 22 import dalvik.annotation.TestTargets; 23 24 import android.content.Context; 25 import android.database.ContentObserver; 26 import android.database.Cursor; 27 import android.database.DataSetObserver; 28 import android.database.MergeCursor; 29 import android.database.StaleDataException; 30 import android.database.sqlite.SQLiteDatabase; 31 import android.test.AndroidTestCase; 32 33 import java.io.File; 34 import java.util.Arrays; 35 36 @TestTargetClass(android.database.MergeCursor.class) 37 public class MergeCursorTest extends AndroidTestCase { 38 private final int NUMBER_1_COLUMN_INDEX = 1; 39 private static final String TABLE1_NAME = "test1"; 40 private static final String TABLE2_NAME = "test2"; 41 private static final String TABLE3_NAME = "test3"; 42 private static final String TABLE4_NAME = "test4"; 43 private static final String TABLE5_NAME = "test5"; 44 private static final String COLUMN_FOR_NULL_TEST = "Null Field"; 45 46 private SQLiteDatabase mDatabase; 47 private File mDatabaseFile; 48 49 Cursor[] mCursors = null; 50 private static final String TABLE1_COLUMNS = " number_1 INTEGER"; 51 private static final String TABLE2_COLUMNS = " number_1 INTEGER, number_2 INTEGER"; 52 private static final String TABLE3_COLUMNS = " text_1 TEXT, number_3 INTEGER, number_4 REAL"; 53 private static final String TABLE2_COLUMN_NAMES = "_id,number_1,number_2"; 54 private static final String TABLE3_COLUMN_NAMES = "_id,text_1,number_3,number_4"; 55 private static final String TEXT_COLUMN_NAME = "text_1"; 56 private static final int TABLE2_COLUMN_COUNT = 3; 57 private static final int TABLE3_COLUMN_COUNT = 4; 58 private static final int DEFAULT_TABLE_VALUE_BEGINS = 1; 59 private static final int MAX_VALUE = 10; 60 private static final int HALF_VALUE = MAX_VALUE / 2; 61 62 @Override 63 protected void setUp() throws Exception { 64 super.setUp(); 65 setupDatabase(); 66 mCursors = new Cursor[2]; 67 } 68 69 @Override 70 protected void tearDown() throws Exception { 71 for (int i = 0; i < mCursors.length; i++) { 72 if (null != mCursors[i]) { 73 mCursors[i].close(); 74 } 75 } 76 mDatabase.close(); 77 mDatabaseFile.delete(); 78 super.tearDown(); 79 } 80 81 @TestTargets({ 82 @TestTargetNew( 83 level = TestLevel.COMPLETE, 84 method = "MergeCursor", 85 args = {android.database.Cursor[].class} 86 ), 87 @TestTargetNew( 88 level = TestLevel.COMPLETE, 89 method = "getCount", 90 args = {} 91 ) 92 }) 93 public void testConstructor() { 94 // If each item of mCursors are null, count will be zero. 95 MergeCursor mergeCursor = new MergeCursor(mCursors); 96 assertEquals(0, mergeCursor.getCount()); 97 98 createCursors(); 99 100 // if the items are not null, getCount() will return the sum of all cursors' count. 101 mergeCursor = new MergeCursor(mCursors); 102 assertEquals(mCursors[0].getCount() + mCursors[1].getCount(), mergeCursor.getCount()); 103 } 104 105 @TestTargetNew( 106 level = TestLevel.COMPLETE, 107 method = "onMove", 108 args = {int.class, int.class} 109 ) 110 public void testOnMove() { 111 createCursors(); 112 MergeCursor mergeCursor = new MergeCursor(mCursors); 113 for (int i = 0; i < MAX_VALUE; i++) { 114 mergeCursor.moveToNext(); 115 //From 1~5, mCursor should be in mCursors[0], larger than 5, it should be in 116 //mCursors[1]. 117 assertEquals(i + 1, mergeCursor.getInt(NUMBER_1_COLUMN_INDEX)); 118 } 119 } 120 121 @TestTargetNew( 122 level = TestLevel.COMPLETE, 123 notes = "Test method related to column infos during MergeCursor is switching" + 124 " between different single cursors", 125 method = "getColumnNames", 126 args = {} 127 ) 128 public void testCursorSwiching() { 129 mDatabase.execSQL("CREATE TABLE " + TABLE5_NAME + " (_id INTEGER PRIMARY KEY," 130 + TABLE3_COLUMNS + ");"); 131 String sql = "INSERT INTO " + TABLE5_NAME + " (" + TEXT_COLUMN_NAME + ") VALUES ('TEXT')"; 132 mDatabase.execSQL(sql); 133 134 Cursor[] cursors = new Cursor[2]; 135 cursors[0] = mDatabase.query(TABLE5_NAME, null, null, null, null, null, null); 136 assertEquals(1, cursors[0].getCount()); 137 createCursors(); 138 cursors[1] = mCursors[1]; 139 assertTrue(cursors[1].getCount() > 0); 140 MergeCursor mergeCursor = new MergeCursor(cursors); 141 // MergeCursor should points to cursors[0] after moveToFirst. 142 mergeCursor.moveToFirst(); 143 144 String[] tableColumns = TABLE3_COLUMN_NAMES.split("[,]"); 145 assertEquals(TABLE3_COLUMN_COUNT, mergeCursor.getColumnCount()); 146 assertTrue(Arrays.equals(tableColumns, mergeCursor.getColumnNames())); 147 148 // MergeCursor should points to cursors[1] moveToNext. 149 mergeCursor.moveToNext(); 150 tableColumns = TABLE2_COLUMN_NAMES.split("[,]"); 151 assertEquals(TABLE2_COLUMN_COUNT, mergeCursor.getColumnCount()); 152 assertTrue(Arrays.equals(tableColumns, mergeCursor.getColumnNames())); 153 } 154 155 @TestTargets({ 156 @TestTargetNew( 157 level = TestLevel.COMPLETE, 158 method = "getBlob", 159 args = {int.class} 160 ), 161 @TestTargetNew( 162 level = TestLevel.COMPLETE, 163 method = "getDouble", 164 args = {int.class} 165 ), 166 @TestTargetNew( 167 level = TestLevel.COMPLETE, 168 method = "getFloat", 169 args = {int.class} 170 ), 171 @TestTargetNew( 172 level = TestLevel.COMPLETE, 173 method = "getInt", 174 args = {int.class} 175 ), 176 @TestTargetNew( 177 level = TestLevel.COMPLETE, 178 method = "getLong", 179 args = {int.class} 180 ), 181 @TestTargetNew( 182 level = TestLevel.COMPLETE, 183 method = "getShort", 184 args = {int.class} 185 ), 186 @TestTargetNew( 187 level = TestLevel.COMPLETE, 188 method = "getString", 189 args = {int.class} 190 ), 191 @TestTargetNew( 192 level = TestLevel.COMPLETE, 193 method = "isNull", 194 args = {int.class} 195 ), 196 @TestTargetNew( 197 level = TestLevel.COMPLETE, 198 method = "onMove", 199 args = {int.class, int.class} 200 ), 201 @TestTargetNew( 202 level = TestLevel.COMPLETE, 203 method = "getColumnNames", 204 args = {} 205 ) 206 }) 207 public void testGetValues() { 208 byte NUMBER_BLOB_UNIT = 99; 209 String[] TEST_STRING = new String[] {"Test String1", "Test String2"}; 210 String[] tableNames = new String[] {TABLE3_NAME, TABLE4_NAME}; 211 212 final double NUMBER_DOUBLE = Double.MAX_VALUE; 213 final double NUMBER_FLOAT = (float) NUMBER_DOUBLE; 214 final long NUMBER_LONG_INTEGER = (long) 0xaabbccddffL; 215 final long NUMBER_INTEGER = (int) NUMBER_LONG_INTEGER; 216 final long NUMBER_SHORT = (short) NUMBER_INTEGER; 217 218 // create tables 219 byte[][] originalBlobs = new byte[2][]; 220 for (int i = 0; i < 2; i++) { 221 // insert blob and other values 222 originalBlobs[i] = new byte[1000]; 223 Arrays.fill(originalBlobs[i], (byte) (NUMBER_BLOB_UNIT - i)); 224 buildDatabaseWithTestValues(TEST_STRING[i], NUMBER_DOUBLE - i, NUMBER_LONG_INTEGER - i, 225 originalBlobs[i], tableNames[i]); 226 // Get cursors. 227 mCursors[i] = mDatabase.query(tableNames[i], null, null, null, null, null, null); 228 } 229 230 MergeCursor mergeCursor = new MergeCursor(mCursors); 231 assertEquals(4, mergeCursor.getCount()); 232 String[] testColumns = new String[] {"_id", "string_text", "double_number", "int_number", 233 "blob_data"}; 234 // Test getColumnNames(). 235 assertTrue(Arrays.equals(testColumns, mergeCursor.getColumnNames())); 236 237 int columnBlob = mCursors[0].getColumnIndexOrThrow("blob_data"); 238 int columnString = mCursors[0].getColumnIndexOrThrow("string_text"); 239 int columnDouble = mCursors[0].getColumnIndexOrThrow("double_number"); 240 int columnInteger = mCursors[0].getColumnIndexOrThrow("int_number"); 241 242 // Test values. 243 for (int i = 0; i < 2; i++) { 244 mergeCursor.moveToNext(); 245 assertEquals(5, mergeCursor.getColumnCount()); 246 247 // Test getting value methods. 248 byte[] targetBlob = mergeCursor.getBlob(columnBlob); 249 assertTrue(Arrays.equals(originalBlobs[i], targetBlob)); 250 251 assertEquals(TEST_STRING[i], mergeCursor.getString(columnString)); 252 assertEquals(NUMBER_DOUBLE - i, mergeCursor.getDouble(columnDouble), 0.000000000001); 253 assertEquals(NUMBER_FLOAT - i, mergeCursor.getFloat(columnDouble), 0.000000000001f); 254 assertEquals(NUMBER_LONG_INTEGER - i, mergeCursor.getLong(columnInteger)); 255 assertEquals(NUMBER_INTEGER - i, mergeCursor.getInt(columnInteger)); 256 assertEquals(NUMBER_SHORT - i, mergeCursor.getShort(columnInteger)); 257 258 // Test isNull(int). 259 assertFalse(mergeCursor.isNull(columnBlob)); 260 mergeCursor.moveToNext(); 261 assertEquals(COLUMN_FOR_NULL_TEST, mergeCursor.getString(columnString)); 262 assertTrue(mergeCursor.isNull(columnBlob)); 263 } 264 } 265 266 @TestTargets({ 267 @TestTargetNew( 268 level = TestLevel.COMPLETE, 269 method = "registerContentObserver", 270 args = {android.database.ContentObserver.class} 271 ), 272 @TestTargetNew( 273 level = TestLevel.COMPLETE, 274 method = "unregisterContentObserver", 275 args = {android.database.ContentObserver.class} 276 ) 277 }) 278 public void testContentObsererOperations() throws IllegalStateException { 279 createCursors(); 280 MergeCursor mergeCursor = new MergeCursor(mCursors); 281 ContentObserver observer = new ContentObserver(null) {}; 282 283 // Can't unregister a Observer before it has been registered. 284 try { 285 mergeCursor.unregisterContentObserver(observer); 286 fail("testUnregisterContentObserver failed"); 287 } catch (IllegalStateException e) { 288 // expected 289 } 290 291 mergeCursor.registerContentObserver(observer); 292 293 // Can't register a same observer twice before unregister it. 294 try { 295 mergeCursor.registerContentObserver(observer); 296 fail("testRegisterContentObserver failed"); 297 } catch (IllegalStateException e) { 298 // expected 299 } 300 301 mergeCursor.unregisterContentObserver(observer); 302 // one Observer can be registered again after it has been unregistered. 303 mergeCursor.registerContentObserver(observer); 304 305 mergeCursor.unregisterContentObserver(observer); 306 307 try { 308 mergeCursor.unregisterContentObserver(observer); 309 fail("testUnregisterContentObserver failed"); 310 } catch (IllegalStateException e) { 311 // expected 312 } 313 } 314 315 @TestTargets({ 316 @TestTargetNew( 317 level = TestLevel.COMPLETE, 318 method = "deactivate", 319 args = {} 320 ), 321 @TestTargetNew( 322 level = TestLevel.COMPLETE, 323 method = "registerDataSetObserver", 324 args = {android.database.DataSetObserver.class} 325 ), 326 @TestTargetNew( 327 level = TestLevel.COMPLETE, 328 method = "unregisterDataSetObserver", 329 args = {android.database.DataSetObserver.class} 330 ) 331 }) 332 public void testDeactivate() throws IllegalStateException { 333 createCursors(); 334 MergeCursor mergeCursor = new MergeCursor(mCursors); 335 MockObserver observer = new MockObserver(); 336 337 // one DataSetObserver can't unregistered before it had been registered. 338 try { 339 mergeCursor.unregisterDataSetObserver(observer); 340 fail("testUnregisterDataSetObserver failed"); 341 } catch (IllegalStateException e) { 342 // expected 343 } 344 345 // Before registering, observer can't be notified. 346 assertFalse(observer.hasInvalidated()); 347 mergeCursor.moveToLast(); 348 mergeCursor.deactivate(); 349 assertFalse(observer.hasInvalidated()); 350 351 // Test with registering DataSetObserver 352 assertTrue(mergeCursor.requery()); 353 mergeCursor.registerDataSetObserver(observer); 354 assertFalse(observer.hasInvalidated()); 355 mergeCursor.moveToLast(); 356 assertEquals(MAX_VALUE, mergeCursor.getInt(NUMBER_1_COLUMN_INDEX)); 357 mergeCursor.deactivate(); 358 // deactivate method can invoke invalidate() method, can be observed by DataSetObserver. 359 assertTrue(observer.hasInvalidated()); 360 // After deactivating, the cursor can not provide values from database record. 361 try { 362 mergeCursor.getInt(NUMBER_1_COLUMN_INDEX); 363 fail("After deactivating, cursor cannot execute getting value operations."); 364 } catch (StaleDataException e) { 365 // expected 366 } 367 368 // Can't register a same observer twice before unregister it. 369 try { 370 mergeCursor.registerDataSetObserver(observer); 371 fail("testRegisterDataSetObserver failed"); 372 } catch (IllegalStateException e) { 373 // expected 374 } 375 376 // After runegistering, observer can't be notified. 377 mergeCursor.unregisterDataSetObserver(observer); 378 observer.resetStatus(); 379 assertFalse(observer.hasInvalidated()); 380 mergeCursor.moveToLast(); 381 mergeCursor.deactivate(); 382 assertFalse(observer.hasInvalidated()); 383 384 // one DataSetObserver can't be unregistered twice continuously. 385 try { 386 mergeCursor.unregisterDataSetObserver(observer); 387 fail("testUnregisterDataSetObserver failed"); 388 } catch (IllegalStateException e) { 389 // expected 390 } 391 } 392 393 @TestTargets({ 394 @TestTargetNew( 395 level = TestLevel.COMPLETE, 396 method = "requery", 397 args = {} 398 ), 399 @TestTargetNew( 400 level = TestLevel.COMPLETE, 401 method = "getCount", 402 args = {} 403 ), 404 @TestTargetNew( 405 level = TestLevel.COMPLETE, 406 method = "close", 407 args = {} 408 ) 409 }) 410 public void testRequery() { 411 final String TEST_VALUE1 = Integer.toString(MAX_VALUE + 1); 412 final String TEST_VALUE2 = Integer.toString(MAX_VALUE + 2); 413 createCursors(); 414 MergeCursor mergeCursor = new MergeCursor(mCursors); 415 int cursor1Count = mCursors[0].getCount(); 416 int cursor2Count = mCursors[0].getCount(); 417 418 mDatabase.execSQL("INSERT INTO " + TABLE1_NAME + " (number_1) VALUES ('" + TEST_VALUE1 419 + "');"); 420 assertEquals(cursor1Count + cursor2Count, mergeCursor.getCount()); 421 assertTrue(mergeCursor.requery()); 422 cursor1Count += 1; 423 assertEquals(cursor1Count + cursor2Count, mergeCursor.getCount()); 424 mDatabase.execSQL("INSERT INTO " + TABLE2_NAME + " (number_1) VALUES ('" + TEST_VALUE2 425 + "');"); 426 cursor2Count += 1; 427 assertTrue(mergeCursor.requery()); 428 assertEquals(cursor1Count + cursor2Count, mergeCursor.getCount()); 429 430 mergeCursor.close(); 431 assertFalse(mergeCursor.requery()); 432 } 433 434 private void buildDatabaseWithTestValues(String text, double doubleNumber, long intNumber, 435 byte[] blob, String tablename) { 436 Object[] args = new Object[4]; 437 args[0] = text; 438 args[1] = doubleNumber; 439 args[2] = intNumber; 440 args[3] = blob; 441 mDatabase.execSQL("CREATE TABLE " + tablename + " (_id INTEGER PRIMARY KEY," 442 + "string_text TEXT, double_number REAL, int_number INTEGER, blob_data BLOB);"); 443 444 // Insert record in giving table. 445 String sql = "INSERT INTO " + tablename + " (string_text, double_number, int_number," 446 + " blob_data) VALUES (?,?,?,?)"; 447 mDatabase.execSQL(sql, args); 448 // insert null blob. 449 sql = "INSERT INTO " + tablename + " (string_text) VALUES ('" + COLUMN_FOR_NULL_TEST + "')"; 450 mDatabase.execSQL(sql); 451 } 452 453 private void setupDatabase() { 454 File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE); 455 mDatabaseFile = new File(dbDir, "database_test.db"); 456 if (mDatabaseFile.exists()) { 457 mDatabaseFile.delete(); 458 } 459 mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); 460 assertNotNull(mDatabaseFile); 461 createTable(TABLE1_NAME, TABLE1_COLUMNS); 462 createTable(TABLE2_NAME, TABLE2_COLUMNS); 463 addValuesIntoTable(TABLE1_NAME, DEFAULT_TABLE_VALUE_BEGINS, HALF_VALUE); 464 addValuesIntoTable(TABLE2_NAME, HALF_VALUE + 1, MAX_VALUE); 465 } 466 467 private void createTable(String tableName, String columnNames) { 468 String sql = "Create TABLE " + tableName + " (_id INTEGER PRIMARY KEY, " + columnNames 469 + " );"; 470 mDatabase.execSQL(sql); 471 } 472 473 private void addValuesIntoTable(String tableName, int start, int end) { 474 for (int i = start; i <= end; i++) { 475 mDatabase.execSQL("INSERT INTO " + tableName + "(number_1) VALUES ('" 476 + i + "');"); 477 } 478 } 479 480 private Cursor getCursor(String tableName, String selection, String[] columnNames) { 481 return mDatabase.query(tableName, columnNames, selection, null, null, null, "number_1"); 482 } 483 484 private void createCursors() { 485 mCursors[0] = getCursor(TABLE1_NAME, null, null); 486 mCursors[1] = getCursor(TABLE2_NAME, null, null); 487 } 488 489 private class MockObserver extends DataSetObserver { 490 private boolean mHasChanged = false; 491 private boolean mHasInvalidated = false; 492 493 @Override 494 public void onChanged() { 495 super.onChanged(); 496 mHasChanged = true; 497 } 498 499 @Override 500 public void onInvalidated() { 501 super.onInvalidated(); 502 mHasInvalidated = true; 503 } 504 505 public void resetStatus() { 506 mHasChanged = false; 507 mHasInvalidated = false; 508 } 509 510 public boolean hasChanged() { 511 return mHasChanged; 512 } 513 514 public boolean hasInvalidated () { 515 return mHasInvalidated; 516 } 517 } 518 } 519