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