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