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