ShortcutRepositoryTest.java revision a48af083ff81555261f334a1e050eae3b02a746c
1/* 2 * Copyright (C) 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 com.android.quicksearchbox; 18 19import android.app.SearchManager; 20import android.content.ContentResolver; 21import android.test.AndroidTestCase; 22import android.test.MoreAsserts; 23import android.test.suitebuilder.annotation.MediumTest; 24import android.util.Log; 25 26import java.util.ArrayList; 27import java.util.Arrays; 28import java.util.Collections; 29import java.util.Comparator; 30import java.util.List; 31import java.util.Map; 32import java.util.Map.Entry; 33 34import junit.framework.Assert; 35 36/** 37 * Abstract base class for tests of {@link ShortcutRepository} 38 * implementations. Most importantly, verifies the 39 * stuff we are doing with sqlite works how we expect it to. 40 * 41 * Attempts to test logic independent of the (sql) details of the implementation, so these should 42 * be useful even in the face of a schema change. 43 */ 44@MediumTest 45public class ShortcutRepositoryTest extends AndroidTestCase { 46 47 private static final String TAG = "ShortcutRepositoryTest"; 48 49 static final long NOW = 1239841162000L; // millis since epoch. some time in 2009 50 51 static final Source APP_SOURCE = new MockSource("com.example.app/.App"); 52 53 static final Source CONTACTS_SOURCE = new MockSource("com.android.contacts/.Contacts"); 54 55 static final Source BOOKMARKS_SOURCE = new MockSource("com.android.browser/.Bookmarks"); 56 57 static final Source HISTORY_SOURCE = new MockSource("com.android.browser/.History"); 58 59 static final Source MUSIC_SOURCE = new MockSource("com.android.music/.Music"); 60 61 static final Source MARKET_SOURCE = new MockSource("com.android.vending/.Market"); 62 63 static final Corpus APP_CORPUS = new MockCorpus(APP_SOURCE); 64 65 static final Corpus CONTACTS_CORPUS = new MockCorpus(CONTACTS_SOURCE); 66 67 protected Config mConfig; 68 protected MockCorpora mCorpora; 69 protected ShortcutRefresher mRefresher; 70 71 protected ShortcutRepositoryImplLog mRepo; 72 73 protected DataSuggestionCursor mAppSuggestions; 74 protected DataSuggestionCursor mContactSuggestions; 75 76 protected SuggestionData mApp1; 77 protected SuggestionData mApp2; 78 protected SuggestionData mApp3; 79 80 protected SuggestionData mContact1; 81 protected SuggestionData mContact2; 82 83 protected ShortcutRepositoryImplLog createShortcutRepository() { 84 return new ShortcutRepositoryImplLog(getContext(), mConfig, mCorpora, 85 mRefresher, new MockHandler(), "test-shortcuts-log.db"); 86 } 87 88 @Override 89 protected void setUp() throws Exception { 90 super.setUp(); 91 92 mConfig = new Config(getContext()); 93 mCorpora = new MockCorpora(); 94 mCorpora.addCorpus(APP_CORPUS, APP_SOURCE); 95 mCorpora.addCorpus(CONTACTS_CORPUS, CONTACTS_SOURCE); 96 mRefresher = new MockShortcutRefresher(); 97 mRepo = createShortcutRepository(); 98 99 mApp1 = makeApp("app1"); 100 mApp2 = makeApp("app2"); 101 mApp3 = makeApp("app3"); 102 mAppSuggestions = new DataSuggestionCursor("foo", mApp1, mApp2, mApp3); 103 104 mContact1 = new SuggestionData(CONTACTS_SOURCE) 105 .setText1("Joe Blow") 106 .setIntentAction("view") 107 .setIntentData("contacts/joeblow") 108 .setShortcutId("j-blow"); 109 mContact2 = new SuggestionData(CONTACTS_SOURCE) 110 .setText1("Mike Johnston") 111 .setIntentAction("view") 112 .setIntentData("contacts/mikeJ") 113 .setShortcutId("mo-jo"); 114 115 mContactSuggestions = new DataSuggestionCursor("foo", mContact1, mContact2); 116 } 117 118 private SuggestionData makeApp(String name) { 119 return new SuggestionData(APP_SOURCE) 120 .setText1(name) 121 .setIntentAction("view") 122 .setIntentData("apps/" + name) 123 .setShortcutId("shorcut_" + name); 124 } 125 126 @Override 127 protected void tearDown() throws Exception { 128 super.tearDown(); 129 mRepo.deleteRepository(); 130 } 131 132 public void testHasHistory() { 133 assertFalse(mRepo.hasHistory()); 134 mRepo.reportClick(mAppSuggestions, 0); 135 assertTrue(mRepo.hasHistory()); 136 mRepo.clearHistory(); 137 assertFalse(mRepo.hasHistory()); 138 } 139 140 public void testNoMatch() { 141 SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) 142 .setText1("bob smith") 143 .setIntentAction("action") 144 .setIntentData("data"); 145 146 reportClick("bob smith", clicked); 147 assertNoShortcuts("joe"); 148 } 149 150 public void testFullPackingUnpacking() { 151 SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) 152 .setFormat("<i>%s</i>") 153 .setText1("title") 154 .setText2("description") 155 .setIcon1("icon1") 156 .setIcon2("icon2") 157 .setIntentAction("action") 158 .setIntentData("data") 159 .setSuggestionQuery("query") 160 .setIntentExtraData("extradata") 161 .setShortcutId("idofshortcut"); 162 reportClick("q", clicked); 163 164 assertShortcuts("q", clicked); 165 assertShortcuts("", clicked); 166 } 167 168 public void testSpinnerWhileRefreshing() { 169 SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) 170 .setFormat("<i>%s</i>") 171 .setText1("title") 172 .setText2("description") 173 .setIcon1("icon1") 174 .setIcon2("icon2") 175 .setIntentAction("action") 176 .setIntentData("data") 177 .setSuggestionQuery("query") 178 .setIntentExtraData("extradata") 179 .setShortcutId("idofshortcut") 180 .setSpinnerWhileRefreshing(true); 181 182 reportClick("q", clicked); 183 184 String spinnerUri = ContentResolver.SCHEME_ANDROID_RESOURCE 185 + "://" + mContext.getPackageName() + "/" + R.drawable.search_spinner; 186 SuggestionData expected = new SuggestionData(CONTACTS_SOURCE) 187 .setFormat("<i>%s</i>") 188 .setText1("title") 189 .setText2("description") 190 .setIcon1("icon1") 191 .setIcon2(spinnerUri) 192 .setIntentAction("action") 193 .setIntentData("data") 194 .setSuggestionQuery("query") 195 .setIntentExtraData("extradata") 196 .setShortcutId("idofshortcut") 197 .setSpinnerWhileRefreshing(true); 198 199 assertShortcuts("q", expected); 200 } 201 202 public void testPrefixesMatch() { 203 assertNoShortcuts("bob"); 204 205 SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) 206 .setText1("bob smith the third") 207 .setIntentAction("action") 208 .setIntentData("intentdata"); 209 210 reportClick("bob smith", clicked); 211 212 assertShortcuts("bob smith", clicked); 213 assertShortcuts("bob s", clicked); 214 assertShortcuts("b", clicked); 215 } 216 217 public void testMatchesOneAndNotOthers() { 218 SuggestionData bob = new SuggestionData(CONTACTS_SOURCE) 219 .setText1("bob smith the third") 220 .setIntentAction("action") 221 .setIntentData("intentdata/bob"); 222 223 reportClick("bob", bob); 224 225 SuggestionData george = new SuggestionData(CONTACTS_SOURCE) 226 .setText1("george jones") 227 .setIntentAction("action") 228 .setIntentData("intentdata/george"); 229 reportClick("geor", george); 230 231 assertShortcuts("b for bob", "b", bob); 232 assertShortcuts("g for george", "g", george); 233 } 234 235 public void testDifferentPrefixesMatchSameEntity() { 236 SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) 237 .setText1("bob smith the third") 238 .setIntentAction("action") 239 .setIntentData("intentdata"); 240 241 reportClick("bob", clicked); 242 reportClick("smith", clicked); 243 assertShortcuts("b", clicked); 244 assertShortcuts("s", clicked); 245 } 246 247 public void testMoreClicksWins() { 248 reportClick("app", mApp1); 249 reportClick("app", mApp2); 250 reportClick("app", mApp1); 251 252 assertShortcuts("expected app1 to beat app2 since it has more hits", 253 "app", mApp1, mApp2); 254 255 reportClick("app", mApp2); 256 reportClick("app", mApp2); 257 258 assertShortcuts("query 'app': expecting app2 to beat app1 since it has more hits", 259 "app", mApp2, mApp1); 260 assertShortcuts("query 'a': expecting app2 to beat app1 since it has more hits", 261 "a", mApp2, mApp1); 262 } 263 264 public void testMostRecentClickWins() { 265 // App 1 has 3 clicks 266 reportClick("app", mApp1, NOW - 5); 267 reportClick("app", mApp1, NOW - 5); 268 reportClick("app", mApp1, NOW - 5); 269 // App 2 has 2 clicks 270 reportClick("app", mApp2, NOW - 2); 271 reportClick("app", mApp2, NOW - 2); 272 // App 3 only has 1, but it's most recent 273 reportClick("app", mApp3, NOW - 1); 274 275 assertShortcuts("expected app3 to beat app1 and app2 because it's clicked last", 276 "app", mApp3, mApp1, mApp2); 277 278 reportClick("app", mApp2, NOW); 279 280 assertShortcuts("query 'app': expecting app2 to beat app1 since it's clicked last", 281 "app", mApp2, mApp1, mApp3); 282 assertShortcuts("query 'a': expecting app2 to beat app1 since it's clicked last", 283 "a", mApp2, mApp1, mApp3); 284 assertShortcuts("query '': expecting app2 to beat app1 since it's clicked last", 285 "", mApp2, mApp1, mApp3); 286 } 287 288 public void testMostRecentClickWinsOnEmptyQuery() { 289 reportClick("app", mApp1, NOW - 3); 290 reportClick("app", mApp1, NOW - 2); 291 reportClick("app", mApp2, NOW - 1); 292 293 assertShortcuts("expected app2 to beat app1 since it's clicked last", "", 294 mApp2, mApp1); 295 } 296 297 public void testMostRecentClickWinsEvenWithMoreThanLimitShortcuts() { 298 // Create MaxShortcutsReturned shortcuts 299 for (int i = 0; i < mConfig.getMaxShortcutsReturned(); i++) { 300 SuggestionData app = makeApp("TestApp" + i); 301 // Each of these shortcuts has two clicks 302 reportClick("app", app, NOW - 2); 303 reportClick("app", app, NOW - 1); 304 } 305 306 // mApp1 has only one click, but is more recent 307 reportClick("app", mApp1, NOW); 308 309 assertShortcutAtPosition( 310 "expecting app1 to beat all others since it's clicked last", 311 "app", 0, mApp1); 312 } 313 314 /** 315 * similar to {@link #testMoreClicksWins()} but clicks are reported with prefixes of the 316 * original query. we want to make sure a match on query 'a' updates the stats for the 317 * entry it matched against, 'app'. 318 */ 319 public void testPrefixMatchUpdatesSameEntry() { 320 reportClick("app", mApp1, NOW); 321 reportClick("app", mApp2, NOW); 322 reportClick("app", mApp1, NOW); 323 324 assertShortcuts("expected app1 to beat app2 since it has more hits", 325 "app", mApp1, mApp2); 326 } 327 328 private static final long DAY_MILLIS = 86400000L; // just ask the google 329 private static final long HOUR_MILLIS = 3600000L; 330 331 public void testMoreRecentlyClickedWins() { 332 reportClick("app", mApp1, NOW - DAY_MILLIS*2); 333 reportClick("app", mApp2, NOW); 334 reportClick("app", mApp3, NOW - DAY_MILLIS*4); 335 336 assertShortcuts("expecting more recently clicked app to rank higher", 337 "app", mApp2, mApp1, mApp3); 338 } 339 340 public void testRecencyOverridesClicks() { 341 342 // 5 clicks, most recent half way through age limit 343 long halfWindow = mConfig.getMaxStatAgeMillis() / 2; 344 reportClick("app", mApp1, NOW - halfWindow); 345 reportClick("app", mApp1, NOW - halfWindow); 346 reportClick("app", mApp1, NOW - halfWindow); 347 reportClick("app", mApp1, NOW - halfWindow); 348 reportClick("app", mApp1, NOW - halfWindow); 349 350 // 3 clicks, the most recent very recent 351 reportClick("app", mApp2, NOW - HOUR_MILLIS); 352 reportClick("app", mApp2, NOW - HOUR_MILLIS); 353 reportClick("app", mApp2, NOW - HOUR_MILLIS); 354 355 assertShortcuts("expecting 3 recent clicks to beat 5 clicks long ago", 356 "app", mApp2, mApp1); 357 } 358 359 public void testEntryOlderThanAgeLimitFiltered() { 360 reportClick("app", mApp1); 361 362 long pastWindow = mConfig.getMaxStatAgeMillis() + 1000; 363 reportClick("app", mApp2, NOW - pastWindow); 364 365 assertShortcuts("expecting app2 not clicked on recently enough to be filtered", 366 "app", mApp1); 367 } 368 369 public void testZeroQueryResults_MoreClicksWins() { 370 reportClick("app", mApp1); 371 reportClick("app", mApp1); 372 reportClick("foo", mApp2); 373 374 assertShortcuts("", mApp1, mApp2); 375 376 reportClick("foo", mApp2); 377 reportClick("foo", mApp2); 378 379 assertShortcuts("", mApp2, mApp1); 380 } 381 382 public void testZeroQueryResults_DifferentQueryhitsCreditSameShortcut() { 383 reportClick("app", mApp1); 384 reportClick("foo", mApp2); 385 reportClick("bar", mApp2); 386 387 assertShortcuts("hits for 'foo' and 'bar' on app2 should have combined to rank it " + 388 "ahead of app1, which only has one hit.", 389 "", mApp2, mApp1); 390 391 reportClick("z", mApp1); 392 reportClick("2", mApp1); 393 394 assertShortcuts("", mApp1, mApp2); 395 } 396 397 public void testZeroQueryResults_zeroQueryHitCounts() { 398 reportClick("app", mApp1); 399 reportClick("", mApp2); 400 reportClick("", mApp2); 401 402 assertShortcuts("hits for '' on app2 should have combined to rank it " + 403 "ahead of app1, which only has one hit.", 404 "", mApp2, mApp1); 405 406 reportClick("", mApp1); 407 reportClick("", mApp1); 408 409 assertShortcuts("zero query hits for app1 should have made it higher than app2.", 410 "", mApp1, mApp2); 411 412 assertShortcuts("query for 'a' should only match app1.", 413 "a", mApp1); 414 } 415 416 public void testRefreshShortcut() { 417 final SuggestionData app1 = new SuggestionData(APP_SOURCE) 418 .setFormat("format") 419 .setText1("app1") 420 .setText2("cool app") 421 .setShortcutId("app1_id"); 422 423 reportClick("app", app1); 424 425 final SuggestionData updated = new SuggestionData(APP_SOURCE) 426 .setFormat("format (updated)") 427 .setText1("app1 (updated)") 428 .setText2("cool app") 429 .setShortcutId("app1_id"); 430 431 refreshShortcut(APP_SOURCE, "app1_id", updated); 432 433 assertShortcuts("expected updated properties in match", 434 "app", updated); 435 } 436 437 public void testRefreshShortcutChangedIntent() { 438 439 final SuggestionData app1 = new SuggestionData(APP_SOURCE) 440 .setIntentData("data") 441 .setFormat("format") 442 .setText1("app1") 443 .setText2("cool app") 444 .setShortcutId("app1_id"); 445 446 reportClick("app", app1); 447 448 final SuggestionData updated = new SuggestionData(APP_SOURCE) 449 .setIntentData("data-updated") 450 .setFormat("format (updated)") 451 .setText1("app1 (updated)") 452 .setText2("cool app") 453 .setShortcutId("app1_id"); 454 455 refreshShortcut(APP_SOURCE, "app1_id", updated); 456 457 assertShortcuts("expected updated properties in match", 458 "app", updated); 459 } 460 461 public void testInvalidateShortcut() { 462 final SuggestionData app1 = new SuggestionData(APP_SOURCE) 463 .setText1("app1") 464 .setText2("cool app") 465 .setShortcutId("app1_id"); 466 467 reportClick("app", app1); 468 469 invalidateShortcut(APP_SOURCE, "app1_id"); 470 471 assertNoShortcuts("should be no matches since shortcut is invalid.", "app"); 472 } 473 474 public void testInvalidateShortcut_sameIdDifferentSources() { 475 final String sameid = "same_id"; 476 final SuggestionData app = new SuggestionData(APP_SOURCE) 477 .setText1("app1") 478 .setText2("cool app") 479 .setShortcutId(sameid); 480 reportClick("app", app); 481 assertShortcuts("app should be there", "", app); 482 483 final SuggestionData contact = new SuggestionData(CONTACTS_SOURCE) 484 .setText1("joe blow") 485 .setText2("a good pal") 486 .setShortcutId(sameid); 487 reportClick("joe", contact); 488 reportClick("joe", contact); 489 assertShortcuts("app and contact should be there.", "", contact, app); 490 491 mRepo.refreshShortcut(APP_SOURCE, sameid, null); 492 assertNoShortcuts("app should not be there.", "app"); 493 assertShortcuts("contact with same shortcut id should still be there.", 494 "joe", contact); 495 assertShortcuts("contact with same shortcut id should still be there.", 496 "", contact); 497 } 498 499 public void testNeverMakeShortcut() { 500 final SuggestionData contact = new SuggestionData(CONTACTS_SOURCE) 501 .setText1("unshortcuttable contact") 502 .setText2("you didn't want to call them again anyway") 503 .setShortcutId(SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT); 504 reportClick("unshortcuttable", contact); 505 assertNoShortcuts("never-shortcutted suggestion should not be there.", "unshortcuttable"); 506 } 507 508 public void testCountResetAfterShortcutDeleted() { 509 reportClick("app", mApp1); 510 reportClick("app", mApp1); 511 reportClick("app", mApp1); 512 reportClick("app", mApp1); 513 514 reportClick("app", mApp2); 515 reportClick("app", mApp2); 516 517 // app1 wins 4 - 2 518 assertShortcuts("app", mApp1, mApp2); 519 520 // reset to 1 521 invalidateShortcut(APP_SOURCE, mApp1.getShortcutId()); 522 reportClick("app", mApp1); 523 524 // app2 wins 2 - 1 525 assertShortcuts("expecting app1's click count to reset after being invalidated.", 526 "app", mApp2, mApp1); 527 } 528 529 public void testShortcutsLimitedCount() { 530 531 for (int i = 1; i <= 2 * mConfig.getMaxShortcutsReturned(); i++) { 532 reportClick("a", makeApp("app" + i)); 533 } 534 535 assertShortcutCount("number of shortcuts should be limited.", 536 "", mConfig.getMaxShortcutsReturned()); 537 } 538 539 // 540 // SOURCE RANKING TESTS BELOW 541 // 542 543 public void testSourceRanking_moreClicksWins() { 544 assertCorpusRanking("expected no ranking"); 545 546 int minClicks = mConfig.getMinClicksForSourceRanking(); 547 548 // click on an app 549 for (int i = 0; i < minClicks + 1; i++) { 550 reportClick("a", mApp1); 551 } 552 // fewer clicks on a contact 553 for (int i = 0; i < minClicks; i++) { 554 reportClick("a", mContact1); 555 } 556 557 assertCorpusRanking("expecting apps to rank ahead of contacts (more clicks)", 558 APP_CORPUS, CONTACTS_CORPUS); 559 560 // more clicks on a contact 561 reportClick("a", mContact1); 562 reportClick("a", mContact1); 563 564 assertCorpusRanking("expecting contacts to rank ahead of apps (more clicks)", 565 CONTACTS_CORPUS, APP_CORPUS); 566 } 567 568 public void testOldSourceStatsDontCount() { 569 // apps were popular back in the day 570 final long toOld = mConfig.getMaxSourceEventAgeMillis() + 1; 571 int minClicks = mConfig.getMinClicksForSourceRanking(); 572 for (int i = 0; i < minClicks; i++) { 573 reportClick("app", mApp1, NOW - toOld); 574 } 575 576 // and contacts is 1/2 577 for (int i = 0; i < minClicks; i++) { 578 reportClick("bob", mContact1, NOW); 579 } 580 581 assertCorpusRanking("old clicks for apps shouldn't count.", 582 CONTACTS_CORPUS); 583 } 584 585 586 public void testSourceRanking_filterSourcesWithInsufficientData() { 587 int minClicks = mConfig.getMinClicksForSourceRanking(); 588 // not enough 589 for (int i = 0; i < minClicks - 1; i++) { 590 reportClick("app", mApp1); 591 } 592 // just enough 593 for (int i = 0; i < minClicks; i++) { 594 reportClick("bob", mContact1); 595 } 596 597 assertCorpusRanking( 598 "ordering should only include sources with at least " + minClicks + " clicks.", 599 CONTACTS_CORPUS); 600 } 601 602 protected DataSuggestionCursor makeCursor(String query, SuggestionData... suggestions) { 603 DataSuggestionCursor cursor = new DataSuggestionCursor(query); 604 for (SuggestionData suggestion : suggestions) { 605 cursor.add(suggestion); 606 } 607 return cursor; 608 } 609 610 protected void reportClick(String query, SuggestionData suggestion) { 611 reportClick(new DataSuggestionCursor(query, suggestion), 0); 612 } 613 614 protected void reportClick(String query, SuggestionData suggestion, long now) { 615 mRepo.reportClickAtTime(new DataSuggestionCursor(query, suggestion), 0, now); 616 } 617 618 protected void reportClick(SuggestionCursor suggestions, int position) { 619 mRepo.reportClickAtTime(suggestions, position, NOW); 620 } 621 622 protected void invalidateShortcut(Source source, String shortcutId) { 623 mRepo.refreshShortcut(source, shortcutId, null); 624 } 625 626 protected void refreshShortcut(Source source, String shortcutId, SuggestionData suggestion) { 627 mRepo.refreshShortcut(source, shortcutId, new DataSuggestionCursor(null, suggestion)); 628 } 629 630 protected void sourceImpressions(Source source, int clicks, int impressions) { 631 if (clicks > impressions) throw new IllegalArgumentException("ya moran!"); 632 633 for (int i = 0; i < impressions; i++, clicks--) { 634 sourceImpression(source, clicks > 0); 635 } 636 } 637 638 /** 639 * Simulate an impression, and optionally a click, on a source. 640 * 641 * @param source The name of the source. 642 * @param click Whether to register a click in addition to the impression. 643 */ 644 protected void sourceImpression(Source source, boolean click) { 645 sourceImpression(source, click, NOW); 646 } 647 648 /** 649 * Simulate an impression, and optionally a click, on a source. 650 * 651 * @param source The name of the source. 652 * @param click Whether to register a click in addition to the impression. 653 */ 654 protected void sourceImpression(Source source, boolean click, long now) { 655 SuggestionData suggestionClicked = !click ? 656 null : 657 new SuggestionData(source) 658 .setIntentAction("view") 659 .setIntentData("data/id") 660 .setShortcutId("shortcutid"); 661 662 reportClick("a", suggestionClicked); 663 } 664 665 void assertNoShortcuts(String query) { 666 assertNoShortcuts("", query); 667 } 668 669 void assertNoShortcuts(String message, String query) { 670 SuggestionCursor cursor = mRepo.getShortcutsForQuery(query, NOW); 671 try { 672 assertNull(message + ", got shortcuts", cursor); 673 } finally { 674 if (cursor != null) cursor.close(); 675 } 676 } 677 678 void assertShortcuts(String query, SuggestionData... expected) { 679 assertShortcuts("", query, expected); 680 } 681 682 void assertShortcutAtPosition(String message, String query, 683 int position, SuggestionData expected) { 684 SuggestionCursor cursor = mRepo.getShortcutsForQuery(query, NOW); 685 try { 686 SuggestionCursor expectedCursor = new DataSuggestionCursor(query, expected); 687 assertSameSuggestion(message, position, expectedCursor, cursor); 688 } finally { 689 if (cursor != null) cursor.close(); 690 } 691 } 692 693 void assertShortcutCount(String message, String query, int expectedCount) { 694 SuggestionCursor cursor = mRepo.getShortcutsForQuery(query, NOW); 695 try { 696 assertEquals(message, expectedCount, cursor.getCount()); 697 } finally { 698 if (cursor != null) cursor.close(); 699 } 700 } 701 702 void assertShortcuts(String message, String query, SuggestionData... expected) { 703 SuggestionCursor cursor = mRepo.getShortcutsForQuery(query, NOW); 704 try { 705 assertSameSuggestions(message, 706 new DataSuggestionCursor(query, expected), 707 cursor); 708 } finally { 709 if (cursor != null) cursor.close(); 710 } 711 } 712 713 void assertCorpusRanking(String message, Corpus... expected) { 714 String[] expectedNames = new String[expected.length]; 715 for (int i = 0; i < expected.length; i++) { 716 expectedNames[i] = expected[i].getName(); 717 } 718 Map<String,Integer> scores = mRepo.getCorpusScores(); 719 List<String> observed = sortByValues(scores); 720 // Highest scores should come first 721 Collections.reverse(observed); 722 Log.d(TAG, "scores=" + scores); 723 assertContentsInOrder(message, observed, (Object[]) expectedNames); 724 } 725 726 static <A extends Comparable<A>, B extends Comparable<B>> List<A> sortByValues(Map<A,B> map) { 727 Comparator<Map.Entry<A,B>> comp = new Comparator<Map.Entry<A,B>>() { 728 public int compare(Entry<A, B> object1, Entry<A, B> object2) { 729 int diff = object1.getValue().compareTo(object2.getValue()); 730 if (diff != 0) { 731 return diff; 732 } else { 733 return object1.getKey().compareTo(object2.getKey()); 734 } 735 } 736 }; 737 ArrayList<Map.Entry<A,B>> sorted = new ArrayList<Map.Entry<A,B>>(map.size()); 738 sorted.addAll(map.entrySet()); 739 Collections.sort(sorted, comp); 740 ArrayList<A> out = new ArrayList<A>(sorted.size()); 741 for (Map.Entry<A,B> e : sorted) { 742 out.add(e.getKey()); 743 } 744 return out; 745 } 746 747 static void assertNoSuggestions(SuggestionCursor suggestions) { 748 assertNoSuggestions("", suggestions); 749 } 750 751 static void assertNoSuggestions(String message, SuggestionCursor suggestions) { 752 assertNotNull(suggestions); 753 assertEquals(message, 0, suggestions.getCount()); 754 } 755 756 static void assertSameSuggestion(String message, int position, 757 SuggestionCursor expected, SuggestionCursor observed) { 758 message += " at position " + position; 759 expected.moveTo(position); 760 observed.moveTo(position); 761 assertEquals(message + ", source", expected.getSuggestionSource(), 762 observed.getSuggestionSource()); 763 assertEquals(message + ", shortcutId", expected.getShortcutId(), 764 observed.getShortcutId()); 765 assertEquals(message + ", spinnerWhileRefreshing", expected.isSpinnerWhileRefreshing(), 766 observed.isSpinnerWhileRefreshing()); 767 assertEquals(message + ", format", expected.getSuggestionFormat(), 768 observed.getSuggestionFormat()); 769 assertEquals(message + ", icon1", expected.getSuggestionIcon1(), 770 observed.getSuggestionIcon1()); 771 assertEquals(message + ", icon2", expected.getSuggestionIcon2(), 772 observed.getSuggestionIcon2()); 773 assertEquals(message + ", text1", expected.getSuggestionText1(), 774 observed.getSuggestionText1()); 775 assertEquals(message + ", text2", expected.getSuggestionText2(), 776 observed.getSuggestionText2()); 777 assertEquals(message + ", action", expected.getSuggestionIntentAction(), 778 observed.getSuggestionIntentAction()); 779 assertEquals(message + ", data", expected.getSuggestionIntentDataString(), 780 observed.getSuggestionIntentDataString()); 781 assertEquals(message + ", extraData", expected.getSuggestionIntentExtraData(), 782 observed.getSuggestionIntentExtraData()); 783 assertEquals(message + ", query", expected.getSuggestionQuery(), 784 observed.getSuggestionQuery()); 785 assertEquals(message + ", displayQuery", expected.getSuggestionDisplayQuery(), 786 observed.getSuggestionDisplayQuery()); 787 assertEquals(message + ", logType", expected.getSuggestionLogType(), 788 observed.getSuggestionLogType()); 789 } 790 791 static void assertSameSuggestions(SuggestionCursor expected, SuggestionCursor observed) { 792 assertSameSuggestions("", expected, observed); 793 } 794 795 static void assertSameSuggestions( 796 String message, SuggestionCursor expected, SuggestionCursor observed) { 797 assertNotNull(expected); 798 assertNotNull(message, observed); 799 assertEquals(message + ", count", expected.getCount(), observed.getCount()); 800 assertEquals(message + ", userQuery", expected.getUserQuery(), observed.getUserQuery()); 801 int count = expected.getCount(); 802 for (int i = 0; i < count; i++) { 803 assertSameSuggestion(message, i, expected, observed); 804 } 805 } 806 807 static void assertContentsInOrder(Iterable<?> actual, Object... expected) { 808 assertContentsInOrder(null, actual, expected); 809 } 810 811 /** 812 * an implementation of {@link MoreAsserts#assertContentsInOrder(String, Iterable, Object[])} 813 * that isn't busted. a bug has been filed about that, but for now this works. 814 */ 815 static void assertContentsInOrder( 816 String message, Iterable<?> actual, Object... expected) { 817 ArrayList actualList = new ArrayList(); 818 for (Object o : actual) { 819 actualList.add(o); 820 } 821 Assert.assertEquals(message, Arrays.asList(expected), actualList); 822 } 823} 824