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