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