/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.quicksearchbox; import com.android.quicksearchbox.util.MockExecutor; import com.android.quicksearchbox.util.Util; import org.json.JSONArray; import android.app.SearchManager; import android.content.Intent; import android.test.AndroidTestCase; import android.test.MoreAsserts; import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * Abstract base class for tests of {@link ShortcutRepository} * implementations. Most importantly, verifies the * stuff we are doing with sqlite works how we expect it to. * * Attempts to test logic independent of the (sql) details of the implementation, so these should * be useful even in the face of a schema change. */ @MediumTest public class ShortcutRepositoryTest extends AndroidTestCase { private static final String TAG = "ShortcutRepositoryTest"; static final long NOW = 1239841162000L; // millis since epoch. some time in 2009 static final Source APP_SOURCE = new MockSource("com.example.app/.App"); static final Source APP_SOURCE_V2 = new MockSource("com.example.app/.App", 2); static final Source CONTACTS_SOURCE = new MockSource("com.android.contacts/.Contacts"); static final Source BOOKMARKS_SOURCE = new MockSource("com.android.browser/.Bookmarks"); static final Source HISTORY_SOURCE = new MockSource("com.android.browser/.History"); static final Source MUSIC_SOURCE = new MockSource("com.android.music/.Music"); static final Source MARKET_SOURCE = new MockSource("com.android.vending/.Market"); static final Corpus APP_CORPUS = new MockCorpus(APP_SOURCE); static final Corpus CONTACTS_CORPUS = new MockCorpus(CONTACTS_SOURCE); static final Corpus WEB_CORPUS = new MockCorpus(MockSource.WEB_SOURCE); static final int MAX_SHORTCUTS = 8; protected Config mConfig; protected MockCorpora mCorpora; protected MockExecutor mLogExecutor; protected ShortcutRefresher mRefresher; protected List<Corpus> mAllowedCorpora; protected ShortcutRepositoryImplLog mRepo; protected ListSuggestionCursor mAppSuggestions; protected ListSuggestionCursor mContactSuggestions; protected SuggestionData mApp1; protected SuggestionData mApp2; protected SuggestionData mApp3; protected SuggestionData mContact1; protected SuggestionData mContact2; protected SuggestionData mWeb1; protected ShortcutRepositoryImplLog createShortcutRepository() { return new ShortcutRepositoryImplLog(getContext(), mConfig, mCorpora, mRefresher, new MockHandler(), mLogExecutor, "test-shortcuts-log.db"); } @Override protected void setUp() throws Exception { super.setUp(); mConfig = new Config(getContext()); mCorpora = new MockCorpora(); mCorpora.addCorpus(APP_CORPUS); mCorpora.addCorpus(CONTACTS_CORPUS); mCorpora.addCorpus(WEB_CORPUS); mRefresher = new MockShortcutRefresher(); mLogExecutor = new MockExecutor(); mRepo = createShortcutRepository(); mAllowedCorpora = new ArrayList<Corpus>(mCorpora.getAllCorpora()); mApp1 = makeApp("app1"); mApp2 = makeApp("app2"); mApp3 = makeApp("app3"); mAppSuggestions = new ListSuggestionCursor("foo", mApp1, mApp2, mApp3); mContact1 = new SuggestionData(CONTACTS_SOURCE) .setText1("Joe Blow") .setIntentAction("view") .setIntentData("contacts/joeblow") .setShortcutId("j-blow"); mContact2 = new SuggestionData(CONTACTS_SOURCE) .setText1("Mike Johnston") .setIntentAction("view") .setIntentData("contacts/mikeJ") .setShortcutId("mo-jo"); mWeb1 = new SuggestionData(MockSource.WEB_SOURCE) .setText1("foo") .setIntentAction(Intent.ACTION_WEB_SEARCH) .setSuggestionQuery("foo"); mContactSuggestions = new ListSuggestionCursor("foo", mContact1, mContact2); } private SuggestionData makeApp(String name) { return new SuggestionData(APP_SOURCE) .setText1(name) .setIntentAction("view") .setIntentData("apps/" + name) .setShortcutId("shorcut_" + name); } private SuggestionData makeContact(String name) { return new SuggestionData(CONTACTS_SOURCE) .setText1(name) .setIntentAction("view") .setIntentData("contacts/" + name) .setShortcutId("shorcut_" + name); } @Override protected void tearDown() throws Exception { super.tearDown(); mRepo.deleteRepository(); } public void testHasHistory() { assertHasHistory(false); reportClickAtTime(mAppSuggestions, 0, NOW); assertHasHistory(true); mRepo.clearHistory(); mLogExecutor.runNext(); assertHasHistory(false); } public void testRemoveFromHistory() { SuggestionData john = new SuggestionData(CONTACTS_SOURCE) .setText1("john doe") .setIntentAction("view") .setIntentData("john_doe"); SuggestionData jane = new SuggestionData(CONTACTS_SOURCE) .setText1("jane doe") .setIntentAction("view") .setIntentData("jane_doe"); reportClick("j", john); reportClick("j", john); reportClick("j", jane); assertShortcuts("j", john, jane); removeFromHistory(new ListSuggestionCursor("j", jane, john), 1); assertShortcuts("j", jane); } public void testRemoveFromHistoryNonExisting() { SuggestionData john = new SuggestionData(CONTACTS_SOURCE) .setText1("john doe") .setIntentAction("view") .setIntentData("john_doe"); SuggestionData jane = new SuggestionData(CONTACTS_SOURCE) .setText1("jane doe") .setIntentAction("view") .setIntentData("jane_doe"); reportClick("j", john); assertShortcuts("j", john); removeFromHistory(new ListSuggestionCursor("j", jane), 0); assertShortcuts("j", john); } public void testNoMatch() { SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) .setText1("bob smith") .setIntentAction("action") .setIntentData("data"); reportClick("bob smith", clicked); assertNoShortcuts("joe"); } public void testFullPackingUnpacking() { SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) .setFormat("<i>%s</i>") .setText1("title") .setText2("description") .setText2Url("description_url") .setIcon1("android.resource://system/drawable/foo") .setIcon2("content://test/bar") .setIntentAction("action") .setIntentData("data") .setSuggestionQuery("query") .setIntentExtraData("extradata") .setShortcutId("idofshortcut") .setSuggestionLogType("logtype"); reportClick("q", clicked); assertShortcuts("q", clicked); assertShortcuts("", clicked); } public void testSpinnerWhileRefreshing() { SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) .setText1("title") .setText2("description") .setIcon2("icon2") .setSuggestionQuery("query") .setIntentExtraData("extradata") .setShortcutId("idofshortcut") .setSpinnerWhileRefreshing(true); reportClick("q", clicked); String spinnerUri = Util.getResourceUri(mContext, R.drawable.search_spinner).toString(); SuggestionData expected = new SuggestionData(CONTACTS_SOURCE) .setText1("title") .setText2("description") .setIcon2(spinnerUri) .setSuggestionQuery("query") .setIntentExtraData("extradata") .setShortcutId("idofshortcut") .setSpinnerWhileRefreshing(true); assertShortcuts("q", expected); } public void testPrefixesMatch() { assertNoShortcuts("bob"); SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) .setText1("bob smith the third") .setIntentAction("action") .setIntentData("intentdata"); reportClick("bob smith", clicked); assertShortcuts("bob smith", clicked); assertShortcuts("bob s", clicked); assertShortcuts("b", clicked); } public void testMatchesOneAndNotOthers() { SuggestionData bob = new SuggestionData(CONTACTS_SOURCE) .setText1("bob smith the third") .setIntentAction("action") .setIntentData("intentdata/bob"); reportClick("bob", bob); SuggestionData george = new SuggestionData(CONTACTS_SOURCE) .setText1("george jones") .setIntentAction("action") .setIntentData("intentdata/george"); reportClick("geor", george); assertShortcuts("b for bob", "b", bob); assertShortcuts("g for george", "g", george); } public void testDifferentPrefixesMatchSameEntity() { SuggestionData clicked = new SuggestionData(CONTACTS_SOURCE) .setText1("bob smith the third") .setIntentAction("action") .setIntentData("intentdata"); reportClick("bob", clicked); reportClick("smith", clicked); assertShortcuts("b", clicked); assertShortcuts("s", clicked); } public void testMoreClicksWins() { reportClick("app", mApp1); reportClick("app", mApp2); reportClick("app", mApp1); assertShortcuts("expected app1 to beat app2 since it has more hits", "app", mApp1, mApp2); reportClick("app", mApp2); reportClick("app", mApp2); assertShortcuts("query 'app': expecting app2 to beat app1 since it has more hits", "app", mApp2, mApp1); assertShortcuts("query 'a': expecting app2 to beat app1 since it has more hits", "a", mApp2, mApp1); } public void testMostRecentClickWins() { // App 1 has 3 clicks reportClick("app", mApp1, NOW - 5); reportClick("app", mApp1, NOW - 5); reportClick("app", mApp1, NOW - 5); // App 2 has 2 clicks reportClick("app", mApp2, NOW - 2); reportClick("app", mApp2, NOW - 2); // App 3 only has 1, but it's most recent reportClick("app", mApp3, NOW - 1); assertShortcuts("expected app3 to beat app1 and app2 because it's clicked last", "app", mApp3, mApp1, mApp2); reportClick("app", mApp2, NOW); assertShortcuts("query 'app': expecting app2 to beat app1 since it's clicked last", "app", mApp2, mApp1, mApp3); assertShortcuts("query 'a': expecting app2 to beat app1 since it's clicked last", "a", mApp2, mApp1, mApp3); assertShortcuts("query '': expecting app2 to beat app1 since it's clicked last", "", mApp2, mApp1, mApp3); } public void testMostRecentClickWinsOnEmptyQuery() { reportClick("app", mApp1, NOW - 3); reportClick("app", mApp1, NOW - 2); reportClick("app", mApp2, NOW - 1); assertShortcuts("expected app2 to beat app1 since it's clicked last", "", mApp2, mApp1); } public void testMostRecentClickWinsEvenWithMoreThanLimitShortcuts() { for (int i = 0; i < MAX_SHORTCUTS; i++) { SuggestionData app = makeApp("TestApp" + i); // Each of these shortcuts has two clicks reportClick("app", app, NOW - 2); reportClick("app", app, NOW - 1); } // mApp1 has only one click, but is more recent reportClick("app", mApp1, NOW); assertShortcutAtPosition( "expecting app1 to beat all others since it's clicked last", "app", 0, mApp1); } /** * similar to {@link #testMoreClicksWins()} but clicks are reported with prefixes of the * original query. we want to make sure a match on query 'a' updates the stats for the * entry it matched against, 'app'. */ public void testPrefixMatchUpdatesSameEntry() { reportClick("app", mApp1, NOW); reportClick("app", mApp2, NOW); reportClick("app", mApp1, NOW); assertShortcuts("expected app1 to beat app2 since it has more hits", "app", mApp1, mApp2); } private static final long DAY_MILLIS = 86400000L; // just ask the google private static final long HOUR_MILLIS = 3600000L; public void testMoreRecentlyClickedWins() { reportClick("app", mApp1, NOW - DAY_MILLIS*2); reportClick("app", mApp2, NOW); reportClick("app", mApp3, NOW - DAY_MILLIS*4); assertShortcuts("expecting more recently clicked app to rank higher", "app", mApp2, mApp1, mApp3); } public void testMoreRecentlyClickedWinsSeconds() { reportClick("app", mApp1, NOW - 10000); reportClick("app", mApp2, NOW - 5000); reportClick("app", mApp3, NOW); assertShortcuts("expecting more recently clicked app to rank higher", "app", mApp3, mApp2, mApp1); } public void testRecencyOverridesClicks() { // 5 clicks, most recent half way through age limit long halfWindow = mConfig.getMaxStatAgeMillis() / 2; reportClick("app", mApp1, NOW - halfWindow); reportClick("app", mApp1, NOW - halfWindow); reportClick("app", mApp1, NOW - halfWindow); reportClick("app", mApp1, NOW - halfWindow); reportClick("app", mApp1, NOW - halfWindow); // 3 clicks, the most recent very recent reportClick("app", mApp2, NOW - HOUR_MILLIS); reportClick("app", mApp2, NOW - HOUR_MILLIS); reportClick("app", mApp2, NOW - HOUR_MILLIS); assertShortcuts("expecting 3 recent clicks to beat 5 clicks long ago", "app", mApp2, mApp1); } public void testEntryOlderThanAgeLimitFiltered() { reportClick("app", mApp1); long pastWindow = mConfig.getMaxStatAgeMillis() + 1000; reportClick("app", mApp2, NOW - pastWindow); assertShortcuts("expecting app2 not clicked on recently enough to be filtered", "app", mApp1); } public void testZeroQueryResults_MoreClicksWins() { reportClick("app", mApp1); reportClick("app", mApp1); reportClick("foo", mApp2); assertShortcuts("", mApp1, mApp2); reportClick("foo", mApp2); reportClick("foo", mApp2); assertShortcuts("", mApp2, mApp1); } public void testZeroQueryResults_DifferentQueryhitsCreditSameShortcut() { reportClick("app", mApp1); reportClick("foo", mApp2); reportClick("bar", mApp2); assertShortcuts("hits for 'foo' and 'bar' on app2 should have combined to rank it " + "ahead of app1, which only has one hit.", "", mApp2, mApp1); reportClick("z", mApp1); reportClick("2", mApp1); assertShortcuts("", mApp1, mApp2); } public void testZeroQueryResults_zeroQueryHitCounts() { reportClick("app", mApp1); reportClick("", mApp2); reportClick("", mApp2); assertShortcuts("hits for '' on app2 should have combined to rank it " + "ahead of app1, which only has one hit.", "", mApp2, mApp1); reportClick("", mApp1); reportClick("", mApp1); assertShortcuts("zero query hits for app1 should have made it higher than app2.", "", mApp1, mApp2); assertShortcuts("query for 'a' should only match app1.", "a", mApp1); } public void testRefreshShortcut() { final SuggestionData app1 = new SuggestionData(APP_SOURCE) .setFormat("format") .setText1("app1") .setText2("cool app") .setShortcutId("app1_id"); reportClick("app", app1); final SuggestionData updated = new SuggestionData(APP_SOURCE) .setFormat("format (updated)") .setText1("app1 (updated)") .setText2("cool app") .setShortcutId("app1_id"); refreshShortcut(APP_SOURCE, "app1_id", updated); assertShortcuts("expected updated properties in match", "app", updated); } public void testRefreshShortcutChangedIntent() { final SuggestionData app1 = new SuggestionData(APP_SOURCE) .setIntentData("data") .setFormat("format") .setText1("app1") .setText2("cool app") .setShortcutId("app1_id"); reportClick("app", app1); final SuggestionData updated = new SuggestionData(APP_SOURCE) .setIntentData("data-updated") .setFormat("format (updated)") .setText1("app1 (updated)") .setText2("cool app") .setShortcutId("app1_id"); refreshShortcut(APP_SOURCE, "app1_id", updated); assertShortcuts("expected updated properties in match", "app", updated); } public void testInvalidateShortcut() { final SuggestionData app1 = new SuggestionData(APP_SOURCE) .setText1("app1") .setText2("cool app") .setShortcutId("app1_id"); reportClick("app", app1); invalidateShortcut(APP_SOURCE, "app1_id"); assertNoShortcuts("should be no matches since shortcut is invalid.", "app"); } public void testInvalidateShortcut_sameIdDifferentSources() { final String sameid = "same_id"; final SuggestionData app = new SuggestionData(APP_SOURCE) .setText1("app1") .setText2("cool app") .setShortcutId(sameid); reportClick("app", app); assertShortcuts("app should be there", "", app); final SuggestionData contact = new SuggestionData(CONTACTS_SOURCE) .setText1("joe blow") .setText2("a good pal") .setShortcutId(sameid); reportClick("joe", contact); reportClick("joe", contact); assertShortcuts("app and contact should be there.", "", contact, app); refreshShortcut(APP_SOURCE, sameid, null); assertNoShortcuts("app should not be there.", "app"); assertShortcuts("contact with same shortcut id should still be there.", "joe", contact); assertShortcuts("contact with same shortcut id should still be there.", "", contact); } public void testNeverMakeShortcut() { final SuggestionData contact = new SuggestionData(CONTACTS_SOURCE) .setText1("unshortcuttable contact") .setText2("you didn't want to call them again anyway") .setShortcutId(SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT); reportClick("unshortcuttable", contact); assertNoShortcuts("never-shortcutted suggestion should not be there.", "unshortcuttable"); } public void testCountResetAfterShortcutDeleted() { reportClick("app", mApp1); reportClick("app", mApp1); reportClick("app", mApp1); reportClick("app", mApp1); reportClick("app", mApp2); reportClick("app", mApp2); // app1 wins 4 - 2 assertShortcuts("app", mApp1, mApp2); // reset to 1 invalidateShortcut(APP_SOURCE, mApp1.getShortcutId()); reportClick("app", mApp1); // app2 wins 2 - 1 assertShortcuts("expecting app1's click count to reset after being invalidated.", "app", mApp2, mApp1); } public void testShortcutsAllowedCorpora() { reportClick("a", mApp1); reportClick("a", mContact1); assertShortcuts("only allowed shortcuts should be returned", "a", Arrays.asList(APP_CORPUS), mApp1); } // // SOURCE RANKING TESTS BELOW // public void testSourceRanking_moreClicksWins() { assertCorpusRanking("expected no ranking"); int minClicks = mConfig.getMinClicksForSourceRanking(); // click on an app for (int i = 0; i < minClicks + 1; i++) { reportClick("a", mApp1); } // fewer clicks on a contact for (int i = 0; i < minClicks; i++) { reportClick("a", mContact1); } assertCorpusRanking("expecting apps to rank ahead of contacts (more clicks)", APP_CORPUS, CONTACTS_CORPUS); // more clicks on a contact reportClick("a", mContact1); reportClick("a", mContact1); assertCorpusRanking("expecting contacts to rank ahead of apps (more clicks)", CONTACTS_CORPUS, APP_CORPUS); } public void testOldSourceStatsDontCount() { // apps were popular back in the day final long toOld = mConfig.getMaxStatAgeMillis() + 1; int minClicks = mConfig.getMinClicksForSourceRanking(); for (int i = 0; i < minClicks; i++) { reportClick("app", mApp1, NOW - toOld); } // and contacts is 1/2 for (int i = 0; i < minClicks; i++) { reportClick("bob", mContact1, NOW); } assertCorpusRanking("old clicks for apps shouldn't count.", CONTACTS_CORPUS); } public void testSourceRanking_filterSourcesWithInsufficientData() { int minClicks = mConfig.getMinClicksForSourceRanking(); // not enough for (int i = 0; i < minClicks - 1; i++) { reportClick("app", mApp1); } // just enough for (int i = 0; i < minClicks; i++) { reportClick("bob", mContact1); } assertCorpusRanking( "ordering should only include sources with at least " + minClicks + " clicks.", CONTACTS_CORPUS); } // App upgrade tests public void testAppUpgradeClearsShortcuts() { reportClick("a", mApp1); reportClick("add", mApp1); reportClick("a", mContact1); assertShortcuts("all shortcuts should be returned", "a", mAllowedCorpora, mApp1, mContact1); // Upgrade an existing corpus MockCorpus upgradedCorpus = new MockCorpus(APP_SOURCE_V2); mCorpora.addCorpus(upgradedCorpus); List<Corpus> newAllowedCorpora = new ArrayList<Corpus>(mCorpora.getAllCorpora()); assertShortcuts("app shortcuts should be removed when the source was upgraded", "a", newAllowedCorpora, mContact1); } public void testAppUpgradePromotesLowerRanked() { ListSuggestionCursor expected = new ListSuggestionCursor("a"); for (int i = 0; i < MAX_SHORTCUTS + 1; i++) { reportClick("app", mApp1, NOW); } expected.add(mApp1); // Enough contact clicks to make one more shortcut than getMaxShortcutsReturned() for (int i = 0; i < MAX_SHORTCUTS; i++) { SuggestionData contact = makeContact("andy" + i); int numClicks = MAX_SHORTCUTS - i; // use click count to get shortcuts in order for (int j = 0; j < numClicks; j++) { reportClick("and", contact, NOW); } expected.add(contact); } // Expect the app, and then all contacts assertShortcuts("app and all contacts should be returned", "a", mAllowedCorpora, expected); // Upgrade app corpus MockCorpus upgradedCorpus = new MockCorpus(APP_SOURCE_V2); mCorpora.addCorpus(upgradedCorpus); // Expect all contacts List<Corpus> newAllowedCorpora = new ArrayList<Corpus>(mCorpora.getAllCorpora()); assertShortcuts("app shortcuts should be removed when the source was upgraded " + "and a contact should take its place", "a", newAllowedCorpora, SuggestionCursorUtil.slice(expected, 1)); } public void testIrrelevantAppUpgrade() { reportClick("a", mApp1); reportClick("add", mApp1); reportClick("a", mContact1); assertShortcuts("all shortcuts should be returned", "a", mAllowedCorpora, mApp1, mContact1); // Fire a corpus set update that affect no shortcuts corpus MockCorpus newCorpus = new MockCorpus(new MockSource("newsource")); mCorpora.addCorpus(newCorpus); assertShortcuts("all shortcuts should be returned", "a", mAllowedCorpora, mApp1, mContact1); } public void testAllowWebSearchShortcuts() { reportClick("a", mApp1); reportClick("a", mApp1); reportClick("a", mWeb1); assertShortcuts("web shortcuts should be included", "a", mAllowedCorpora, true, mApp1, mWeb1); assertShortcuts("web shortcuts should not be included", "a", mAllowedCorpora, false, mApp1); } public void testExtraDataNull() { assertExtra("Null extra", "extra_null", null); } public void testExtraDataString() { assertExtra("String extra", "extra_string", "stringy-stringy-string"); } public void testExtraDataInteger() { assertExtra("Integer extra", "extra_int", new Integer(42)); } public void testExtraDataFloat() { assertExtra("Float extra", "extra_float", new Float(Math.PI)); } public void testExtraDataStringWithDodgyChars() { assertExtra("String extra with newlines", "extra_string", "line\nline\nline\n"); JSONArray a = new JSONArray(); a.put(true); a.put(42); a.put("hello"); a.put("hello \"again\""); assertExtra("String extra with JSON", "extra_string", a.toString()); assertExtra("String extra with control chars", "extra_string", "\0\b\t\f\r"); } // Utilities protected ListSuggestionCursor makeCursor(String query, SuggestionData... suggestions) { ListSuggestionCursor cursor = new ListSuggestionCursor(query); for (SuggestionData suggestion : suggestions) { cursor.add(suggestion); } return cursor; } protected void reportClick(String query, SuggestionData suggestion) { reportClick(new ListSuggestionCursor(query, suggestion), 0); } protected void reportClick(String query, SuggestionData suggestion, long now) { reportClickAtTime(new ListSuggestionCursor(query, suggestion), 0, now); } protected void reportClick(SuggestionCursor suggestions, int position) { reportClickAtTime(suggestions, position, NOW); } protected void reportClickAtTime(SuggestionCursor suggestions, int position, long now) { mRepo.reportClickAtTime(suggestions, position, now); mLogExecutor.runNext(); } protected void removeFromHistory(SuggestionCursor suggestions, int position) { mRepo.removeFromHistory(suggestions, position); mLogExecutor.runNext(); } protected void invalidateShortcut(Source source, String shortcutId) { refreshShortcut(source, shortcutId, null); } protected void refreshShortcut(Source source, String shortcutId, SuggestionData suggestion) { SuggestionCursor refreshed = suggestion == null ? null : new ListSuggestionCursor(null, suggestion); mRepo.refreshShortcut(source, shortcutId, refreshed); mLogExecutor.runNext(); } protected void sourceImpressions(Source source, int clicks, int impressions) { if (clicks > impressions) throw new IllegalArgumentException("ya moran!"); for (int i = 0; i < impressions; i++, clicks--) { sourceImpression(source, clicks > 0); } } /** * Simulate an impression, and optionally a click, on a source. * * @param source The name of the source. * @param click Whether to register a click in addition to the impression. */ protected void sourceImpression(Source source, boolean click) { sourceImpression(source, click, NOW); } protected SuggestionData sourceSuggestion(Source source) { return new SuggestionData(source) .setIntentAction("view") .setIntentData("data/id") .setShortcutId("shortcutid"); } /** * Simulate an impression, and optionally a click, on a source. * * @param source The name of the source. * @param click Whether to register a click in addition to the impression. */ protected void sourceImpression(Source source, boolean click, long now) { SuggestionData suggestionClicked = !click ? null : sourceSuggestion(source); reportClick("a", suggestionClicked); } void assertNoShortcuts(String query) { assertNoShortcuts("", query); } void assertNoShortcuts(String message, String query) { SuggestionCursor cursor = getShortcuts(query, mAllowedCorpora); try { assertNull(message + ", got shortcuts", cursor); } finally { if (cursor != null) cursor.close(); } } void assertShortcuts(String query, SuggestionData... expected) { assertShortcuts("", query, expected); } void assertShortcutAtPosition(String message, String query, int position, SuggestionData expected) { SuggestionCursor cursor = getShortcuts(query, mAllowedCorpora); try { SuggestionCursor expectedCursor = new ListSuggestionCursor(query, expected); SuggestionCursorUtil.assertSameSuggestion(message, position, expectedCursor, cursor); } finally { if (cursor != null) cursor.close(); } } void assertShortcutCount(String message, String query, int expectedCount) { SuggestionCursor cursor = getShortcuts(query, mAllowedCorpora); try { assertEquals(message, expectedCount, cursor.getCount()); } finally { if (cursor != null) cursor.close(); } } void assertShortcuts(String message, String query, Collection<Corpus> allowedCorpora, boolean allowWebSearchShortcuts, SuggestionCursor expected) { SuggestionCursor cursor = mRepo.getShortcutsForQuery(query, allowedCorpora, allowWebSearchShortcuts, NOW); try { SuggestionCursorUtil.assertSameSuggestions(message, expected, cursor); } finally { if (cursor != null) cursor.close(); } } void assertShortcuts(String message, String query, Collection<Corpus> allowedCorpora, SuggestionCursor expected) { assertShortcuts(message, query, allowedCorpora, true, expected); } SuggestionCursor getShortcuts(String query, Collection<Corpus> allowedCorpora) { return mRepo.getShortcutsForQuery(query, allowedCorpora, true, NOW); } void assertShortcuts(String message, String query, Collection<Corpus> allowedCorpora, boolean allowWebSearchShortcuts, SuggestionData... expected) { assertShortcuts(message, query, allowedCorpora, allowWebSearchShortcuts, new ListSuggestionCursor(query, expected)); } void assertShortcuts(String message, String query, Collection<Corpus> allowedCorpora, SuggestionData... expected) { assertShortcuts(message, query, allowedCorpora, new ListSuggestionCursor(query, expected)); } void assertShortcuts(String message, String query, SuggestionData... expected) { assertShortcuts(message, query, mAllowedCorpora, expected); } private void assertHasHistory(boolean expected) { ConsumerTrap<Boolean> trap = new ConsumerTrap<Boolean>(); mRepo.hasHistory(trap); mLogExecutor.runNext(); assertEquals("hasHistory() returned bad value", expected, (boolean) trap.getValue()); } void assertCorpusRanking(String message, Corpus... expected) { String[] expectedNames = new String[expected.length]; for (int i = 0; i < expected.length; i++) { expectedNames[i] = expected[i].getName(); } Map<String,Integer> scores = getCorpusScores(); List<String> observed = sortByValues(scores); // Highest scores should come first Collections.reverse(observed); Log.d(TAG, "scores=" + scores); MoreAsserts.assertContentsInOrder(message, observed, (Object[]) expectedNames); } private Map<String,Integer> getCorpusScores() { ConsumerTrap<Map<String,Integer>> trap = new ConsumerTrap<Map<String,Integer>>(); mRepo.getCorpusScores(trap); mLogExecutor.runNext(); return trap.getValue(); } static <A extends Comparable<A>, B extends Comparable<B>> List<A> sortByValues(Map<A,B> map) { Comparator<Map.Entry<A,B>> comp = new Comparator<Map.Entry<A,B>>() { public int compare(Entry<A, B> object1, Entry<A, B> object2) { int diff = object1.getValue().compareTo(object2.getValue()); if (diff != 0) { return diff; } else { return object1.getKey().compareTo(object2.getKey()); } } }; ArrayList<Map.Entry<A,B>> sorted = new ArrayList<Map.Entry<A,B>>(map.size()); sorted.addAll(map.entrySet()); Collections.sort(sorted, comp); ArrayList<A> out = new ArrayList<A>(sorted.size()); for (Map.Entry<A,B> e : sorted) { out.add(e.getKey()); } return out; } static void assertContentsInOrder(Iterable<?> actual, Object... expected) { MoreAsserts.assertContentsInOrder(null, actual, expected); } void assertExtra(String message, String extraColumn, Object extraValue) { SuggestionData s = sourceSuggestion(APP_SOURCE); s.setExtras(new MockSuggestionExtras().put(extraColumn, extraValue)); reportClick("a", s); assertShortcutExtra(message, "a", extraColumn, extraValue); } void assertShortcutExtra(String message, String query, String extraColumn, Object extraValue) { SuggestionCursor cursor = getShortcuts(query, mAllowedCorpora); try { SuggestionCursorUtil.assertSuggestionExtras(message, cursor, extraColumn, extraValue); } finally { if (cursor != null) cursor.close(); } } }