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