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