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