Home | History | Annotate | Download | only in provider
      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 
     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