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 static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertThrows; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 27 import android.database.CharArrayBuffer; 28 import android.database.CursorWindow; 29 import android.database.CursorWindowAllocationException; 30 import android.database.MatrixCursor; 31 import android.database.sqlite.SQLiteException; 32 import android.os.Parcel; 33 import android.util.Log; 34 35 import androidx.test.filters.SmallTest; 36 import androidx.test.runner.AndroidJUnit4; 37 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Random; 44 45 @RunWith(AndroidJUnit4.class) 46 @SmallTest 47 public class CursorWindowTest { 48 private static final String TAG = "CursorWindowTest"; 49 50 private static final String TEST_STRING = "Test String"; 51 52 @Test testWriteCursorToWindow()53 public void testWriteCursorToWindow() throws Exception { 54 // create cursor 55 String[] colNames = new String[]{"_id", "name", "number", "profit"}; 56 int colsize = colNames.length; 57 ArrayList<ArrayList<Integer>> list = createTestList(10, colsize); 58 MatrixCursor cursor = new MatrixCursor(colNames, list.size()); 59 for (ArrayList<Integer> row : list) { 60 cursor.addRow(row); 61 } 62 63 // fill window 64 CursorWindow window = new CursorWindow(false); 65 cursor.fillWindow(0, window); 66 67 // read from cursor window 68 for (int i = 0; i < list.size(); i++) { 69 ArrayList<Integer> col = list.get(i); 70 for (int j = 0; j < colsize; j++) { 71 String s = window.getString(i, j); 72 int r2 = col.get(j); 73 int r1 = Integer.parseInt(s); 74 assertEquals(r2, r1); 75 } 76 } 77 78 // test cursor window handle startpos != 0 79 window.clear(); 80 cursor.fillWindow(1, window); 81 // read from cursor from window 82 for (int i = 1; i < list.size(); i++) { 83 ArrayList<Integer> col = list.get(i); 84 for (int j = 0; j < colsize; j++) { 85 String s = window.getString(i, j); 86 int r2 = col.get(j); 87 int r1 = Integer.parseInt(s); 88 assertEquals(r2, r1); 89 } 90 } 91 92 // Clear the window and make sure it's empty 93 window.clear(); 94 assertEquals(0, window.getNumRows()); 95 } 96 97 @Test testNull()98 public void testNull() { 99 CursorWindow window = getOneByOneWindow(); 100 101 // Put in a null value and read it back as various types 102 assertTrue(window.putNull(0, 0)); 103 assertNull(window.getString(0, 0)); 104 assertEquals(0, window.getLong(0, 0)); 105 assertEquals(0.0, window.getDouble(0, 0), 0.0); 106 assertNull(window.getBlob(0, 0)); 107 } 108 109 @Test testEmptyString()110 public void testEmptyString() { 111 CursorWindow window = getOneByOneWindow(); 112 113 // put size 0 string and read it back as various types 114 assertTrue(window.putString("", 0, 0)); 115 assertEquals("", window.getString(0, 0)); 116 assertEquals(0, window.getLong(0, 0)); 117 assertEquals(0.0, window.getDouble(0, 0), 0.0); 118 } 119 120 @Test testConstructors()121 public void testConstructors() { 122 int TEST_NUMBER = 5; 123 CursorWindow cursorWindow; 124 125 // Test constructor with 'true' input, and getStartPosition should return 0 126 cursorWindow = new CursorWindow(true); 127 assertEquals(0, cursorWindow.getStartPosition()); 128 129 // Test constructor with 'false' input 130 cursorWindow = new CursorWindow(false); 131 assertEquals(0, cursorWindow.getStartPosition()); 132 133 // Test newFromParcel 134 Parcel parcel = Parcel.obtain(); 135 cursorWindow = new CursorWindow(true); 136 cursorWindow.setStartPosition(TEST_NUMBER); 137 cursorWindow.setNumColumns(1); 138 cursorWindow.allocRow(); 139 cursorWindow.putString(TEST_STRING, TEST_NUMBER, 0); 140 cursorWindow.writeToParcel(parcel, 0); 141 142 parcel.setDataPosition(0); 143 cursorWindow = CursorWindow.CREATOR.createFromParcel(parcel); 144 assertEquals(TEST_NUMBER, cursorWindow.getStartPosition()); 145 assertEquals(TEST_STRING, cursorWindow.getString(TEST_NUMBER, 0)); 146 147 parcel.recycle(); 148 } 149 150 @Test testDataStructureOperations()151 public void testDataStructureOperations() { 152 CursorWindow cursorWindow = new CursorWindow(true); 153 154 // Test with normal values 155 assertTrue(cursorWindow.setNumColumns(0)); 156 // If the column has been set to zero, can't put String. 157 assertFalse(cursorWindow.putString(TEST_STRING, 0, 0)); 158 159 // Test allocRow(). 160 assertTrue(cursorWindow.allocRow()); 161 assertEquals(1, cursorWindow.getNumRows()); 162 assertTrue(cursorWindow.allocRow()); 163 assertEquals(2, cursorWindow.getNumRows()); 164 // Though allocate a row, but the column number is still 0, so can't putString. 165 assertFalse(cursorWindow.putString(TEST_STRING, 0, 0)); 166 167 // Test freeLstRow 168 cursorWindow.freeLastRow(); 169 assertEquals(1, cursorWindow.getNumRows()); 170 cursorWindow.freeLastRow(); 171 assertEquals(0, cursorWindow.getNumRows()); 172 173 cursorWindow = new CursorWindow(true); 174 assertTrue(cursorWindow.setNumColumns(6)); 175 assertTrue(cursorWindow.allocRow()); 176 // Column number set to negative number, so now can put values. 177 assertTrue(cursorWindow.putString(TEST_STRING, 0, 0)); 178 assertEquals(TEST_STRING, cursorWindow.getString(0, 0)); 179 180 // Test with negative value 181 assertFalse(cursorWindow.setNumColumns(-1)); 182 183 // Test with reference limitation 184 cursorWindow.releaseReference(); 185 try { 186 cursorWindow.setNumColumns(5); 187 fail("setNumColumns() should throws IllegalStateException here."); 188 } catch (IllegalStateException e) { 189 // expected 190 } 191 192 // Test close(), close will also minus references, that will lead acquireReference() 193 // related operation failed. 194 cursorWindow.close(); 195 try { 196 cursorWindow.acquireReference(); 197 fail("setNumColumns() should throws IllegalStateException here."); 198 } catch (IllegalStateException e) { 199 // expected 200 } 201 } 202 203 @Test testAccessDataValues()204 public void testAccessDataValues() { 205 final long NUMBER_LONG_INTEGER = (long) 0xaabbccddffL; 206 final long NUMBER_INTEGER = (int) NUMBER_LONG_INTEGER; 207 final long NUMBER_SHORT = (short) NUMBER_INTEGER; 208 final float NUMBER_FLOAT_SCIENCE = 7.332952E11f; 209 final double NUMBER_DOUBLE_SCIENCE = 7.33295205887E11; 210 final String NUMBER_FLOAT_SCIENCE_STRING = "7.332952E11"; 211 final String NUMBER_DOUBLE_SCIENCE_STRING = "7.33295205887E11"; 212 final String NUMBER_FLOAT_SCIENCE_STRING2 = "7.33295e+11"; 213 214 byte[] originalBlob = new byte[Byte.MAX_VALUE]; 215 for (int i = 0; i < Byte.MAX_VALUE; i++) { 216 originalBlob[i] = (byte) i; 217 } 218 219 CursorWindow cursorWindow = new CursorWindow(true); 220 cursorWindow.setNumColumns(5); 221 cursorWindow.allocRow(); 222 223 // Test putString, getString, getLong, getInt, isBlob 224 assertTrue(cursorWindow.putString(Long.toString(NUMBER_LONG_INTEGER), 0, 0)); 225 assertEquals(Long.toString(NUMBER_LONG_INTEGER), cursorWindow.getString(0, 0)); 226 assertEquals(NUMBER_LONG_INTEGER, cursorWindow.getLong(0, 0)); 227 assertEquals(NUMBER_INTEGER, cursorWindow.getInt(0, 0)); 228 assertEquals(NUMBER_SHORT, cursorWindow.getShort(0, 0)); 229 // Converting of Float, there would be some little precision differences. So just compare 230 // first 6 digits. 231 assertEquals(NUMBER_FLOAT_SCIENCE_STRING.substring(0, 6), Float.toString( 232 cursorWindow.getFloat(0, 0)).substring(0, 6)); 233 assertEquals(NUMBER_DOUBLE_SCIENCE_STRING, Double.toString(cursorWindow.getDouble(0, 0))); 234 assertFalse(cursorWindow.isNull(0, 0)); 235 assertFalse(cursorWindow.isBlob(0, 0)); 236 237 // Test null String 238 assertTrue(cursorWindow.putString("", 0, 0)); 239 assertEquals("", cursorWindow.getString(0, 0)); 240 assertEquals(0, cursorWindow.getLong(0, 0)); 241 assertEquals(0, cursorWindow.getInt(0, 0)); 242 assertEquals(0, cursorWindow.getShort(0, 0)); 243 assertEquals(0.0, cursorWindow.getDouble(0, 0), 0.0); 244 assertEquals(0.0f, cursorWindow.getFloat(0, 0), 0.0); 245 assertFalse(cursorWindow.isNull(0, 0)); 246 assertFalse(cursorWindow.isBlob(0, 0)); 247 248 // Test putNull, getString, getLong, getDouble, getBlob, getInd, getShort, getFloat, 249 // isBlob. 250 assertTrue(cursorWindow.putNull(0, 1)); 251 assertNull(cursorWindow.getString(0, 1)); 252 assertEquals(0, cursorWindow.getLong(0, 1)); 253 assertEquals(0, cursorWindow.getInt(0, 1)); 254 assertEquals(0, cursorWindow.getShort(0, 1)); 255 assertEquals(0.0, cursorWindow.getDouble(0, 1), 0.0); 256 assertEquals(0.0f, cursorWindow.getFloat(0, 1), 0.0); 257 assertNull(cursorWindow.getBlob(0, 1)); 258 assertTrue(cursorWindow.isNull(0, 1)); 259 // If the field is null, isBlob will return true. 260 assertTrue(cursorWindow.isBlob(0, 1)); 261 262 // Test putLong, getLong, getInt, getString , getShort, getFloat, getDouble, isBlob. 263 assertTrue(cursorWindow.putLong(NUMBER_LONG_INTEGER, 0, 2)); 264 assertEquals(NUMBER_LONG_INTEGER, cursorWindow.getLong(0, 2)); 265 assertEquals(NUMBER_INTEGER, cursorWindow.getInt(0, 2)); 266 assertEquals(Long.toString(NUMBER_LONG_INTEGER), cursorWindow.getString(0, 2)); 267 assertEquals(NUMBER_SHORT, cursorWindow.getShort(0, 2)); 268 assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 2), 0.0); 269 assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 2), 0.0); 270 try { 271 cursorWindow.getBlob(0, 2); 272 fail("Can't get Blob from a Integer value."); 273 } catch (SQLiteException e) { 274 // expected 275 } 276 assertFalse(cursorWindow.isNull(0, 2)); 277 assertFalse(cursorWindow.isBlob(0, 2)); 278 279 // Test putDouble 280 assertTrue(cursorWindow.putDouble(NUMBER_DOUBLE_SCIENCE, 0, 3)); 281 assertEquals(NUMBER_LONG_INTEGER, cursorWindow.getLong(0, 3)); 282 assertEquals(NUMBER_INTEGER, cursorWindow.getInt(0, 3)); 283 // Converting from Double to String, there would be some little precision differences. So 284 // Just compare first 6 digits. 285 assertEquals(NUMBER_FLOAT_SCIENCE_STRING2.substring(0, 6), cursorWindow.getString(0, 3) 286 .substring(0, 6)); 287 assertEquals(NUMBER_SHORT, cursorWindow.getShort(0, 3)); 288 assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 3), 0.0); 289 assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 3), 0.0); 290 try { 291 cursorWindow.getBlob(0, 3); 292 fail("Can't get Blob from a Double value."); 293 } catch (SQLiteException e) { 294 // expected 295 } 296 assertFalse(cursorWindow.isNull(0, 3)); 297 assertFalse(cursorWindow.isBlob(0, 3)); 298 299 // Test putBlob 300 assertTrue(cursorWindow.putBlob(originalBlob, 0, 4)); 301 byte[] targetBlob = cursorWindow.getBlob(0, 4); 302 assertTrue(Arrays.equals(originalBlob, targetBlob)); 303 assertFalse(cursorWindow.isNull(0, 4)); 304 // Test isBlob 305 assertTrue(cursorWindow.isBlob(0, 4)); 306 } 307 308 @Test testCopyStringToBuffer()309 public void testCopyStringToBuffer() { 310 int DEFAULT_ARRAY_LENGTH = 64; 311 String baseString = "0123456789"; 312 String expectedString = ""; 313 // Create a 60 characters string. 314 for (int i = 0; i < 6; i++) { 315 expectedString += baseString; 316 } 317 CharArrayBuffer charArrayBuffer = new CharArrayBuffer(null); 318 CursorWindow cursorWindow = new CursorWindow(true); 319 cursorWindow.setNumColumns(2); 320 cursorWindow.allocRow(); 321 322 assertEquals(null, charArrayBuffer.data); 323 cursorWindow.putString(expectedString, 0, 0); 324 cursorWindow.copyStringToBuffer(0, 0, charArrayBuffer); 325 assertNotNull(charArrayBuffer.data); 326 // By default, if the field's string is shorter than 64, array will be allocated as 64. 327 assertEquals(DEFAULT_ARRAY_LENGTH, charArrayBuffer.data.length); 328 assertEquals(expectedString, 329 new String(charArrayBuffer.data, 0, charArrayBuffer.sizeCopied)); 330 331 // Test in case of string is longer than 64, 332 expectedString += baseString; 333 charArrayBuffer = new CharArrayBuffer(null); 334 cursorWindow.putString(expectedString, 0, 1); 335 cursorWindow.copyStringToBuffer(0, 1, charArrayBuffer); 336 assertNotNull(charArrayBuffer.data); 337 // If the string is longer than 64, array will be allocated as needed(longer than 64). 338 assertEquals(expectedString, 339 new String(charArrayBuffer.data, 0, charArrayBuffer.sizeCopied)); 340 assertEquals(70, expectedString.length()); 341 assertEquals(expectedString.length(), charArrayBuffer.data.length); 342 } 343 344 @Test testAccessStartPosition()345 public void testAccessStartPosition() { 346 final int TEST_POSITION_1 = 0; 347 final int TEST_POSITION_2 = 3; 348 349 CursorWindow cursorWindow = new CursorWindow(true); 350 fillCursorTestContents(cursorWindow, 5); 351 352 // Test setStartPosition 353 assertEquals(TEST_POSITION_1, cursorWindow.getStartPosition()); 354 assertEquals(3, cursorWindow.getInt(3, 0)); 355 assertEquals(TEST_STRING + "3", cursorWindow.getString(3, 1)); 356 assertEquals(4, cursorWindow.getInt(4, 0)); 357 assertEquals(TEST_STRING + "4", cursorWindow.getString(4, 1)); 358 cursorWindow.setStartPosition(TEST_POSITION_2); 359 360 assertEquals(TEST_POSITION_2, cursorWindow.getStartPosition()); 361 362 assertEquals(0, cursorWindow.getInt(3, 0)); 363 assertEquals(TEST_STRING + "0", cursorWindow.getString(3, 1)); 364 assertEquals(1, cursorWindow.getInt(4, 0)); 365 assertEquals(TEST_STRING + "1", cursorWindow.getString(4, 1)); 366 try { 367 cursorWindow.getBlob(0, 0); 368 fail("Row number is smaller than startPosition, will cause a IllegalStateException."); 369 } catch (IllegalStateException e) { 370 // expected 371 } 372 } 373 374 @Test testClearAndOnAllReferencesReleased()375 public void testClearAndOnAllReferencesReleased() { 376 MockCursorWindow cursorWindow = new MockCursorWindow(true); 377 378 assertEquals(0, cursorWindow.getNumRows()); 379 fillCursorTestContents(cursorWindow, 10); 380 assertEquals(10, cursorWindow.getNumRows()); 381 assertEquals(0, cursorWindow.getStartPosition()); 382 cursorWindow.setStartPosition(5); 383 assertEquals(5, cursorWindow.getStartPosition()); 384 385 // Test clear(). a complete calling process of cursorWindow has a perfect acquiring and 386 // releasing pair, so the references number will be equal at the begin and the end. 387 assertFalse(cursorWindow.hasReleasedAllReferences()); 388 cursorWindow.clear(); 389 assertEquals(0, cursorWindow.getNumRows()); 390 assertEquals(0, cursorWindow.getStartPosition()); 391 assertFalse(cursorWindow.hasReleasedAllReferences()); 392 393 // Test onAllReferencesReleased. 394 // By default, cursorWindow's reference is 1, when it reachs 0, onAllReferencesReleased 395 // be invoked. 396 cursorWindow = new MockCursorWindow(true); 397 cursorWindow.releaseReference(); 398 assertTrue(cursorWindow.hasReleasedAllReferences()); 399 } 400 401 @Test testDescribeContents()402 public void testDescribeContents() { 403 CursorWindow cursorWindow = new CursorWindow(true); 404 assertEquals(0, cursorWindow.describeContents()); 405 } 406 407 @Test testDefaultCursorWindowSize()408 public void testDefaultCursorWindowSize() { 409 CursorWindow cursorWindow = new CursorWindow("test"); 410 cursorWindow.setNumColumns(1); 411 byte[] bytes = new byte[1024]; 412 Arrays.fill(bytes, (byte) 1); 413 // Ensure that the default is not too small and it's possible to fill CursorWindow 414 // with ~2Mb of data 415 int testRowCount = 2016; 416 for (int i = 0; i < testRowCount; i++) { 417 assertTrue(cursorWindow.allocRow()); 418 assertTrue("Allocation failed for row " + i, cursorWindow.putBlob(bytes, i, 0)); 419 } 420 assertTrue(cursorWindow.allocRow()); 421 assertFalse("Allocation should fail for row " + testRowCount, 422 cursorWindow.putBlob(bytes, testRowCount, 0)); 423 } 424 425 @Test testCustomSize()426 public void testCustomSize() { 427 // Allocate CursorWindow with max size 10KB and test that restriction is enforced 428 CursorWindow cursorWindow = new CursorWindow("test", 10000); 429 cursorWindow.setNumColumns(1); 430 byte[] bytes = new byte[8000]; 431 Arrays.fill(bytes, (byte) 1); 432 assertTrue(cursorWindow.allocRow()); 433 assertTrue("Allocation of 1 row should succeed", cursorWindow.putBlob(bytes, 0, 0)); 434 assertTrue(cursorWindow.allocRow()); 435 assertFalse("Allocation of 2nd row should fail", cursorWindow.putBlob(bytes, 1, 0)); 436 } 437 438 @Test testCreateFailure()439 public void testCreateFailure() { 440 Exception actual = null; 441 try { 442 new CursorWindow("test", -1); 443 } catch (IllegalArgumentException caught) { 444 Log.i(TAG, "Received: " + caught); 445 return; 446 } 447 fail("Didn't catch IllegalArgumentException: actual=" + actual); 448 } 449 450 @Test testCreateFromParcelFailure()451 public void testCreateFromParcelFailure() { 452 Exception actual = null; 453 try { 454 CursorWindow.CREATOR.createFromParcel(Parcel.obtain()); 455 } catch (CursorWindowAllocationException caught) { 456 Log.i(TAG, "Received: " + caught); 457 return; 458 } 459 fail("Didn't catch CursorWindowAllocationException: actual=" + actual); 460 } 461 462 @Test testCursorWindowAllocationException()463 public void testCursorWindowAllocationException() { 464 String exceptionDescription = "description test"; 465 CursorWindowAllocationException newException = 466 new CursorWindowAllocationException(exceptionDescription); 467 assertEquals(exceptionDescription, newException.getMessage()); 468 try { 469 throw newException; 470 } catch (CursorWindowAllocationException exception) { 471 assertEquals(exceptionDescription, exception.getMessage()); 472 } 473 } 474 475 @Test putString_null()476 public void putString_null() { 477 CursorWindow cursorWindow = new CursorWindow("test"); 478 cursorWindow.allocRow(); 479 assertThrows(NullPointerException.class, () -> cursorWindow.putString(null, 0, 0)); 480 cursorWindow.close(); 481 } 482 483 @Test putBlob_null()484 public void putBlob_null() { 485 CursorWindow cursorWindow = new CursorWindow("test"); 486 cursorWindow.allocRow(); 487 assertThrows(NullPointerException.class, () -> cursorWindow.putBlob(null, 0, 0)); 488 cursorWindow.close(); 489 } 490 491 private class MockCursorWindow extends CursorWindow { 492 private boolean mHasReleasedAllReferences = false; 493 MockCursorWindow(boolean localWindow)494 public MockCursorWindow(boolean localWindow) { 495 super(localWindow); 496 } 497 498 @Override onAllReferencesReleased()499 protected void onAllReferencesReleased() { 500 super.onAllReferencesReleased(); 501 mHasReleasedAllReferences = true; 502 } 503 hasReleasedAllReferences()504 public boolean hasReleasedAllReferences() { 505 return mHasReleasedAllReferences; 506 } 507 resetStatus()508 public void resetStatus() { 509 mHasReleasedAllReferences = false; 510 } 511 } 512 fillCursorTestContents(CursorWindow cursorWindow, int length)513 private void fillCursorTestContents(CursorWindow cursorWindow, int length) { 514 cursorWindow.clear(); 515 cursorWindow.setStartPosition(0); 516 cursorWindow.setNumColumns(2); 517 for (int i = 0; i < length; i++) { 518 cursorWindow.allocRow(); 519 cursorWindow.putLong(i, i, 0); 520 cursorWindow.putString(TEST_STRING + i, i, 1); 521 } 522 } 523 createTestList(int rows, int cols)524 private static ArrayList<ArrayList<Integer>> createTestList(int rows, int cols) { 525 ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>(); 526 Random generator = new Random(); 527 528 for (int i = 0; i < rows; i++) { 529 ArrayList<Integer> col = new ArrayList<Integer>(); 530 list.add(col); 531 for (int j = 0; j < cols; j++) { 532 // generate random number 533 col.add(j == 0 ? i : generator.nextInt()); 534 } 535 } 536 return list; 537 } 538 539 /** 540 * The method comes from unit_test CursorWindowTest. 541 */ getOneByOneWindow()542 private CursorWindow getOneByOneWindow() { 543 CursorWindow window = new CursorWindow(false); 544 assertTrue(window.setNumColumns(1)); 545 assertTrue(window.allocRow()); 546 return window; 547 } 548 } 549