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