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