blob: c881c7d6a0adfe8d2e19051e517535b653230691 [file] [log] [blame]
/*
* 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();
}
}
}