SearchRecentSuggestionsProviderTest.java revision 553a53ef9ff789dff8b5a74dfea4d6f37feeb263
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
17package android.provider;
18
19import android.app.SearchManager;
20import android.content.ContentResolver;
21import android.database.Cursor;
22import android.net.Uri;
23import android.test.ProviderTestCase2;
24import 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
37public class SearchRecentSuggestionsProviderTest extends ProviderTestCase2<TestProvider> {
38
39    // Elements prepared by setUp()
40    SearchRecentSuggestions mSearchHelper;
41
42    public SearchRecentSuggestionsProviderTest() {
43        super(TestProvider.class, TestProvider.AUTHORITY);
44    }
45
46    /**
47     * During setup, grab a helper for DB access
48     */
49    @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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