• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.provider;
18 
19 import android.app.SearchManager;
20 import android.content.ContentResolver;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.test.ProviderTestCase2;
24 import android.test.suitebuilder.annotation.MediumTest;
25 
26 /**
27  * ProviderTestCase that performs unit tests of SearchRecentSuggestionsProvider.
28  *
29  * You can run this test in isolation via the commands:
30  *
31  * $ (cd tests/FrameworkTests/ && mm) && adb sync
32  * $ adb shell am instrument -w \
33  *     -e class android.provider.SearchRecentSuggestionsProviderTest
34  *     com.android.frameworktest.tests/android.test.InstrumentationTestRunner
35  */
36 @MediumTest
37 public class SearchRecentSuggestionsProviderTest extends ProviderTestCase2<TestProvider> {
38 
39     // Elements prepared by setUp()
40     SearchRecentSuggestions mSearchHelper;
41 
SearchRecentSuggestionsProviderTest()42     public SearchRecentSuggestionsProviderTest() {
43         super(TestProvider.class, TestProvider.AUTHORITY);
44     }
45 
46     /**
47      * During setup, grab a helper for DB access
48      */
49     @Override
setUp()50     public void setUp() throws Exception {
51         super.setUp();
52 
53         // Use the recent suggestions helper.  As long as we pass in our isolated context,
54         // it should correctly access the provider under test.
55         mSearchHelper = new SearchRecentSuggestions(getMockContext(),
56                 TestProvider.AUTHORITY, TestProvider.MODE);
57 
58         // test for empty database at setup time
59         checkOpenCursorCount(0);
60     }
61 
62     /**
63      * Simple test to see if we can instantiate the whole mess.
64      */
testSetup()65     public void testSetup() {
66         assertTrue(true);
67     }
68 
69     /**
70      * Simple test to see if we can write and read back a single query
71      */
testOneQuery()72     public void testOneQuery() {
73         final String TEST_LINE1 = "test line 1";
74         final String TEST_LINE2 = "test line 2";
75         mSearchHelper.saveRecentQuery(TEST_LINE1, TEST_LINE2);
76         mSearchHelper.waitForSave();
77 
78         // make sure that there are is exactly one entry returned by a non-filtering cursor
79         checkOpenCursorCount(1);
80 
81         // test non-filtering cursor for correct entry
82         checkResultCounts(null, 1, 1, TEST_LINE1, TEST_LINE2);
83 
84         // test filtering cursor for correct entry
85         checkResultCounts(TEST_LINE1, 1, 1, TEST_LINE1, TEST_LINE2);
86         checkResultCounts(TEST_LINE2, 1, 1, TEST_LINE1, TEST_LINE2);
87 
88         // test that a different filter returns zero results
89         checkResultCounts("bad filter", 0, 0, null, null);
90     }
91 
92     /**
93      * Simple test to see if we can write and read back a diverse set of queries
94      */
testMixedQueries()95     public void testMixedQueries() {
96         // we'll make 10 queries named "query x" and 10 queries named "test x"
97         final String TEST_GROUP_1 = "query ";
98         final String TEST_GROUP_2 = "test ";
99         final String TEST_LINE2 = "line2 ";
100         final int GROUP_COUNT = 10;
101 
102         writeEntries(GROUP_COUNT, TEST_GROUP_1, TEST_LINE2);
103         writeEntries(GROUP_COUNT, TEST_GROUP_2, TEST_LINE2);
104 
105         // check counts
106         checkOpenCursorCount(2 * GROUP_COUNT);
107 
108         // check that each query returns the right result counts
109         checkResultCounts(TEST_GROUP_1, GROUP_COUNT, GROUP_COUNT, null, null);
110         checkResultCounts(TEST_GROUP_2, GROUP_COUNT, GROUP_COUNT, null, null);
111         checkResultCounts(TEST_LINE2, 2 * GROUP_COUNT, 2 * GROUP_COUNT, null, null);
112     }
113 
114     /**
115      * Test that the reordering code works properly.  The most recently injected queries
116      * should replace existing queries and be sorted to the top of the list.
117      */
testReordering()118     public void testReordering() {
119         // first we'll make 10 queries named "group1 x"
120         final int GROUP_1_COUNT = 10;
121         final String GROUP_1_QUERY = "group1 ";
122         final String GROUP_1_LINE2 = "line2 ";
123         writeEntries(GROUP_1_COUNT, GROUP_1_QUERY, GROUP_1_LINE2);
124 
125         // check totals
126         checkOpenCursorCount(GROUP_1_COUNT);
127 
128         // guarantee that group 1 has older timestamps
129         writeDelay();
130 
131         // next we'll add 10 entries named "group2 x"
132         final int GROUP_2_COUNT = 10;
133         final String GROUP_2_QUERY = "group2 ";
134         final String GROUP_2_LINE2 = "line2 ";
135         writeEntries(GROUP_2_COUNT, GROUP_2_QUERY, GROUP_2_LINE2);
136 
137         // check totals
138         checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
139 
140         // guarantee that group 2 has older timestamps
141         writeDelay();
142 
143         // now refresh 5 of the 10 from group 1
144         // change line2 so they can be more easily tracked
145         final int GROUP_3_COUNT = 5;
146         final String GROUP_3_QUERY = GROUP_1_QUERY;
147         final String GROUP_3_LINE2 = "refreshed ";
148         writeEntries(GROUP_3_COUNT, GROUP_3_QUERY, GROUP_3_LINE2);
149 
150         // confirm that the total didn't change (those were replacements, not adds)
151         checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
152 
153         // confirm that the are now 5 in group 1, 10 in group 2, and 5 in group 3
154         int newGroup1Count = GROUP_1_COUNT - GROUP_3_COUNT;
155         checkResultCounts(GROUP_1_QUERY, newGroup1Count, newGroup1Count, null, GROUP_1_LINE2);
156         checkResultCounts(GROUP_2_QUERY, GROUP_2_COUNT, GROUP_2_COUNT, null, null);
157         checkResultCounts(GROUP_3_QUERY, GROUP_3_COUNT, GROUP_3_COUNT, null, GROUP_3_LINE2);
158 
159         // finally, spot check that the right groups are in the right places
160         // the ordering should be group 3 (newest), group 2, group 1 (oldest)
161         Cursor c = getQueryCursor(null);
162         int colQuery = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_QUERY);
163         int colDisplay1 = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1);
164         int colDisplay2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
165 
166         // Spot check the first and last expected entries of group 3
167         c.moveToPosition(0);
168         assertTrue("group 3 did not properly reorder to head of list",
169                 checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_3_QUERY, GROUP_3_LINE2));
170         c.move(GROUP_3_COUNT - 1);
171         assertTrue("group 3 did not properly reorder to head of list",
172                 checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_3_QUERY, GROUP_3_LINE2));
173 
174         // Spot check the first and last expected entries of group 2
175         c.move(1);
176         assertTrue("group 2 not in expected position after reordering",
177                 checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_2_QUERY, GROUP_2_LINE2));
178         c.move(GROUP_2_COUNT - 1);
179         assertTrue("group 2 not in expected position after reordering",
180                 checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_2_QUERY, GROUP_2_LINE2));
181 
182         // Spot check the first and last expected entries of group 1
183         c.move(1);
184         assertTrue("group 1 not in expected position after reordering",
185                 checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_1_QUERY, GROUP_1_LINE2));
186         c.move(newGroup1Count - 1);
187         assertTrue("group 1 not in expected position after reordering",
188                 checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_1_QUERY, GROUP_1_LINE2));
189 
190         c.close();
191     }
192 
193     /**
194      * Test that the pruning code works properly,  The database should not go beyond 250 entries,
195      * and the oldest entries should always be discarded first.
196      *
197      * TODO:  This is a slow test, do we have annotation for that?
198      */
testPruning()199     public void testPruning() {
200         // first we'll make 50 queries named "group1 x"
201         final int GROUP_1_COUNT = 50;
202         final String GROUP_1_QUERY = "group1 ";
203         final String GROUP_1_LINE2 = "line2 ";
204         writeEntries(GROUP_1_COUNT, GROUP_1_QUERY, GROUP_1_LINE2);
205 
206         // check totals
207         checkOpenCursorCount(GROUP_1_COUNT);
208 
209         // guarantee that group 1 has older timestamps (and will be pruned first)
210         writeDelay();
211 
212         // next we'll add 200 entries named "group2 x"
213         final int GROUP_2_COUNT = 200;
214         final String GROUP_2_QUERY = "group2 ";
215         final String GROUP_2_LINE2 = "line2 ";
216         writeEntries(GROUP_2_COUNT, GROUP_2_QUERY, GROUP_2_LINE2);
217 
218         // check totals
219         checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
220 
221         // Finally we'll add 10 more entries named "group3 x"
222         // These should push out 10 entries from group 1
223         final int GROUP_3_COUNT = 10;
224         final String GROUP_3_QUERY = "group3 ";
225         final String GROUP_3_LINE2 = "line2 ";
226         writeEntries(GROUP_3_COUNT, GROUP_3_QUERY, GROUP_3_LINE2);
227 
228         // total should still be 250
229         checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
230 
231         // there should be 40 group 1, 200 group 2, and 10 group 3
232         int group1NewCount = GROUP_1_COUNT-GROUP_3_COUNT;
233         checkResultCounts(GROUP_1_QUERY, group1NewCount, group1NewCount, null, null);
234         checkResultCounts(GROUP_2_QUERY, GROUP_2_COUNT, GROUP_2_COUNT, null, null);
235         checkResultCounts(GROUP_3_QUERY, GROUP_3_COUNT, GROUP_3_COUNT, null, null);
236     }
237 
238     /**
239      * Test that the clear history code works properly.
240      */
testClear()241     public void testClear() {
242         // first we'll make 10 queries named "group1 x"
243         final int GROUP_1_COUNT = 10;
244         final String GROUP_1_QUERY = "group1 ";
245         final String GROUP_1_LINE2 = "line2 ";
246         writeEntries(GROUP_1_COUNT, GROUP_1_QUERY, GROUP_1_LINE2);
247 
248         // next we'll add 10 entries named "group2 x"
249         final int GROUP_2_COUNT = 10;
250         final String GROUP_2_QUERY = "group2 ";
251         final String GROUP_2_LINE2 = "line2 ";
252         writeEntries(GROUP_2_COUNT, GROUP_2_QUERY, GROUP_2_LINE2);
253 
254         // check totals
255         checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
256 
257         // delete all
258         mSearchHelper.clearHistory();
259 
260         // check totals
261         checkOpenCursorCount(0);
262     }
263 
264     /**
265      * Write a sequence of queries into the database, with incrementing counters in the strings.
266      */
writeEntries(int groupCount, String line1Base, String line2Base)267     private void writeEntries(int groupCount, String line1Base, String line2Base) {
268         for (int i = 0; i < groupCount; i++) {
269             final String line1 = line1Base + i;
270             final String line2 = line2Base + i;
271             mSearchHelper.saveRecentQuery(line1, line2);
272             mSearchHelper.waitForSave();
273         }
274     }
275 
276     /**
277      * A very slight delay to ensure that successive groups of queries in the DB cannot
278      * have the same timestamp.
279      */
writeDelay()280     private void writeDelay() {
281         try {
282             Thread.sleep(10);
283         } catch (InterruptedException e) {
284             fail("Interrupted sleep.");
285         }
286     }
287 
288     /**
289      * Access an "open" (no selection) suggestions cursor and confirm that it has the specified
290      * number of entries.
291      *
292      * @param expectCount The expected number of entries returned by the cursor.
293      */
checkOpenCursorCount(int expectCount)294     private void checkOpenCursorCount(int expectCount) {
295         Cursor c = getQueryCursor(null);
296         assertEquals(expectCount, c.getCount());
297         c.close();
298     }
299 
300     /**
301      * Set up a filter cursor and then scan it for specific results.
302      *
303      * @param queryString The query string to apply.
304      * @param minRows The minimum number of matching rows that must be found.
305      * @param maxRows The maximum number of matching rows that must be found.
306      * @param matchDisplay1 If non-null, must match DISPLAY1 column if row counts as match
307      * @param matchDisplay2 If non-null, must match DISPLAY2 column if row counts as match
308      */
checkResultCounts(String queryString, int minRows, int maxRows, String matchDisplay1, String matchDisplay2)309     private void checkResultCounts(String queryString, int minRows, int maxRows,
310             String matchDisplay1, String matchDisplay2) {
311 
312         // get the cursor and apply sanity checks to result
313         Cursor c = getQueryCursor(queryString);
314         assertNotNull(c);
315         assertTrue("Insufficient rows in filtered cursor", c.getCount() >= minRows);
316 
317         // look for minimum set of columns (note, display2 is optional)
318         int colQuery = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_QUERY);
319         int colDisplay1 = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1);
320         int colDisplay2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
321 
322         // now loop through rows and look for desired rows
323         int foundRows = 0;
324         c.moveToFirst();
325         while (!c.isAfterLast()) {
326             if (checkRow(c, colQuery, colDisplay1, colDisplay2, matchDisplay1, matchDisplay2)) {
327                 foundRows++;
328             }
329             c.moveToNext();
330         }
331 
332         // now check the results
333         assertTrue(minRows <= foundRows);
334         assertTrue(foundRows <= maxRows);
335 
336         c.close();
337     }
338 
339     /**
340      * Check a single row for equality with target strings.
341      *
342      * @param c The cursor, already moved to the row
343      * @param colQuery The column # containing the query.  The query must match display1.
344      * @param colDisp1 The column # containing display line 1.
345      * @param colDisp2 The column # containing display line 2, or -1 if no column
346      * @param matchDisplay1 If non-null, this must be the prefix of display1
347      * @param matchDisplay2 If non-null, this must be the prefix of display2
348      * @return Returns true if the row is a "match"
349      */
checkRow(Cursor c, int colQuery, int colDisp1, int colDisp2, String matchDisplay1, String matchDisplay2)350     private boolean checkRow(Cursor c, int colQuery, int colDisp1, int colDisp2,
351             String matchDisplay1, String matchDisplay2) {
352         // Get the data from the row
353         String query = c.getString(colQuery);
354         String display1 = c.getString(colDisp1);
355         String display2 = (colDisp2 >= 0) ? c.getString(colDisp2) : null;
356 
357         assertEquals(query, display1);
358         boolean result = true;
359         if (matchDisplay1 != null) {
360             result = result && (display1 != null) && display1.startsWith(matchDisplay1);
361         }
362         if (matchDisplay2 != null) {
363             result = result && (display2 != null) && display2.startsWith(matchDisplay2);
364         }
365 
366         return result;
367     }
368 
369     /**
370      * Generate a query cursor in a manner like the search dialog would.
371      *
372      * @param queryString The search string, or, null for "all"
373      * @return Returns a cursor, or null if there was some problem.  Be sure to close the cursor
374      * when done with it.
375      */
getQueryCursor(String queryString)376     private Cursor getQueryCursor(String queryString) {
377         ContentResolver cr = getMockContext().getContentResolver();
378 
379         String uriStr = "content://" + TestProvider.AUTHORITY +
380         '/' + SearchManager.SUGGEST_URI_PATH_QUERY;
381         Uri contentUri = Uri.parse(uriStr);
382 
383         String[] selArgs = new String[] {queryString};
384 
385         Cursor c = cr.query(contentUri, null, null, selArgs, null);
386 
387         assertNotNull(c);
388         return c;
389     }
390 }
391