xref: /aosp_15_r20/cts/tests/tests/database/src/android/database/cts/CursorWindowTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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