• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.globalsearch;
18 
19 import junit.framework.TestCase;
20 import junit.framework.Assert;
21 import android.content.ComponentName;
22 import android.test.MoreAsserts;
23 import com.google.android.collect.Sets;
24 import com.google.android.collect.Lists;
25 
26 import static com.android.globalsearch.SourceSuggestionBacker.SourceStat;
27 
28 import java.util.List;
29 import java.util.ArrayList;
30 import java.util.HashSet;
31 import java.util.Arrays;
32 
33 /**
34  * Tests {@link SourceSuggestionBacker}
35  *
36  * see the {@link #setUp()} method for the default setup of the backer that is used by most
37  * test cases.
38  */
39 public class SourceSuggestionBackerTest extends TestCase
40         implements SourceSuggestionBacker.MoreExpanderFactory,
41         SourceSuggestionBacker.CorpusResultFactory {
42 
43     private ComponentName mName1;
44     private ComponentName mName2;
45     private ComponentName mName3;
46     private TestSuggestionSource mSource1;
47     private TestSuggestionSource mSource2;
48     private TestSuggestionSource mSource3;
49     private TestBacker mBacker;
50     private static final int MAX_PROMOTED_SHOWING = 6;
51     private static final long NOW = 700L;
52     private static final long DEADLINE = 2000L;
53     private SuggestionData mShortcut1;
54     private SuggestionData mGoToWebsite;
55     private SuggestionData mSearchTheWeb;
56     private SuggestionData mMoreNotExpanded;
57     private SuggestionData mMoreExpanded;
58     private static final String SOURCE1_LABEL = "source1 label";
59     private static final String SOURCE2_LABEL = "source2 label";
60     private static final String SOURCE3_LABEL = "source3 label";
61 
62 
63     @Override
setUp()64     protected void setUp() throws Exception {
65         super.setUp();
66 
67         mName1 = new ComponentName(
68                 "com.android.globalsearch", "com.android.globalsearch.One");
69 
70         mName2 = new ComponentName(
71                 "com.android.globalsearch", "com.android.globalsearch.Two");
72 
73         mName3 = new ComponentName(
74                 "com.android.globalsearch", "com.android.globalsearch.Three");
75 
76         mSource1 = new TestSuggestionSource.Builder()
77                 .setComponent(mName1)
78                 .setLabel(SOURCE1_LABEL)
79                 .create();
80 
81         mSource2 = new TestSuggestionSource.Builder()
82                 .setComponent(mName2)
83                 .setLabel(SOURCE2_LABEL)
84                 .create();
85 
86         mSource3 = new TestSuggestionSource.Builder()
87                 .setComponent(mName3)
88                 .setLabel(SOURCE3_LABEL)
89                 .create();
90 
91         mShortcut1 = new SuggestionData.Builder(mName1)
92                 .title("shortcut")
93                 .description("description")
94                 .shortcutId("shortcutid")
95                 .intentAction("action.VIEW")
96                 .intentData("shorctut1")
97                 .build();
98 
99         // Normally we pass null in for this value; we only use this value to
100         // explicitly test the "go to website" functionality.
101         mGoToWebsite = new SuggestionData.Builder(mName1)
102                 .title("go to website")
103                 .description("google.com")
104                 .build();
105 
106         mSearchTheWeb = new SuggestionData.Builder(mName1)
107                 .title("search the web for 'yo'")
108                 .description("description")
109                 .build();
110 
111         mBacker = new TestBacker(
112                 "query",
113                 Lists.newArrayList(mShortcut1),
114                 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3),
115                 Sets.newHashSet(mName1, mName2), // promoted sources
116                 mSource1,             // source1 is the web souce
117                 Lists.<SuggestionResult>newArrayList(),
118                 null,                 // no "go to website" suggestion
119                 mSearchTheWeb,        // the "search the web" suggestion
120                 MAX_PROMOTED_SHOWING,
121                 DEADLINE,
122                 this,        /** more factory points to {@link #getMoreEntry} */
123                 this);       /** corpus factory points to {@link #getCorpusEntry} */
124 
125         mMoreNotExpanded = new SuggestionData.Builder(mName1)
126                 .title("more unexpanded")
127                 .build();
128 
129         mMoreExpanded = new SuggestionData.Builder(mName1)
130                 .title("more expanded")
131                 .build();
132 
133         mBacker.setNow(NOW);
134     }
135 
136     /** {@inheritDoc} */
getMoreEntry( boolean expanded, List<SourceSuggestionBacker.SourceStat> sourceStats)137     public SuggestionData getMoreEntry(
138             boolean expanded,
139             List<SourceSuggestionBacker.SourceStat> sourceStats) {
140         return expanded ? mMoreExpanded : mMoreNotExpanded;
141     }
142 
143     /** {@inheritDoc} */
getCorpusEntry( String query, SourceSuggestionBacker.SourceStat sourceStat)144     public SuggestionData getCorpusEntry(
145             String query, SourceSuggestionBacker.SourceStat sourceStat) {
146         return makeCorpusEntry(
147                 sourceStat.getLabel(),
148                 sourceStat.getResponseStatus(),
149                 sourceStat.getNumResults());
150     }
151 
152 
testNoResultsReported()153     public void testNoResultsReported() {
154 
155         assertContentsInOrder(
156                 "should only be shortcuts before deadline.",
157                 getSnapshotFromBacker(false),
158                 mShortcut1);
159 
160         assertContentsInOrder(
161                 "should only be shortcuts before deadline.",
162                 getSnapshotFromBacker(true),
163                 mShortcut1);
164 
165         mBacker.setNow(NOW + DEADLINE);
166 
167         assertContentsInOrder(
168                 "after deadline should see 'search the web' and 'more' entry.",
169                 getSnapshotFromBacker(false),
170                 mShortcut1,
171                 mSearchTheWeb,
172                 mMoreNotExpanded);
173 
174         assertContentsInOrder(
175                 "after deadline (expanded) should see 'search the web' and 'more' entries.",
176                 getSnapshotFromBacker(true),
177                 mShortcut1,
178                 mSearchTheWeb,
179                 mMoreExpanded,
180                 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0),
181                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0),
182                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0));
183     }
184 
testSomeResultsReported()185     public void testSomeResultsReported() {
186 
187         // source 1 reports back 4 results
188         mBacker.addSourceResults(
189                 new SuggestionResult(mSource1, Lists.newArrayList(
190                         makeSourceResult(mName1, 0),
191                         makeSourceResult(mName1, 1),
192                         makeSourceResult(mName1, 2),
193                         makeSourceResult(mName1, 3)
194                 )));
195 
196         assertContentsInOrder(
197                 "before deadline, should show shortcuts and chunks of promoted sources.",
198                 getSnapshotFromBacker(false),
199                 mShortcut1,
200                 makeSourceResult(mName1, 0),
201                 makeSourceResult(mName1, 1));
202 
203         mBacker.setNow(NOW + DEADLINE);
204 
205         // source 3 is in progress
206         mBacker.reportSourceStarted(mName3);
207 
208         assertContentsInOrder(
209                 "after deadline(expanded), should show shortcuts, chunks of promoted sources, " +
210                         "rest of promoted slots filled, and more.",
211                 getSnapshotFromBacker(true),
212                 mShortcut1,
213                 makeSourceResult(mName1, 0),
214                 makeSourceResult(mName1, 1),
215                 makeSourceResult(mName1, 2),
216                 makeSourceResult(mName1, 3),
217                 mSearchTheWeb,
218                 mMoreExpanded,
219                 // no "more" result for source 1 since we've displayed all of its entries now
220                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0),
221                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_IN_PROGRESS, 0));
222     }
223 
testPromotedSourceRespondsAfterDeadline()224     public void testPromotedSourceRespondsAfterDeadline() {
225         mBacker.addSourceResults(
226                 new SuggestionResult(mSource1, Lists.newArrayList(
227                         makeSourceResult(mName1, 0),
228                         makeSourceResult(mName1, 1))));
229 
230         mBacker.setNow(NOW + DEADLINE);
231 
232         assertContentsInOrder(
233                 "after deadline pending promoted source should be under 'more'.",
234                 getSnapshotFromBacker(true),
235                 mShortcut1,
236                 makeSourceResult(mName1, 0),
237                 makeSourceResult(mName1, 1),
238                 mSearchTheWeb,
239                 mMoreExpanded,
240                 // source 2 hasn't responded yet
241                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0),
242                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0));
243 
244         mBacker.addSourceResults(
245                 new SuggestionResult(mSource2, Lists.newArrayList(
246                         makeSourceResult(mName2, 0),
247                         makeSourceResult(mName2, 1))));
248 
249         assertContentsInOrder(
250                 "after deadline late reporting promoted source should be under 'more'.",
251                 getSnapshotFromBacker(true),
252                 mShortcut1,
253                 makeSourceResult(mName1, 0),
254                 makeSourceResult(mName1, 1),
255                 mSearchTheWeb,
256                 mMoreExpanded,
257                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 2),
258                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0));
259     }
260 
testZeroReportingSources()261     public void testZeroReportingSources() {
262 
263         assertFalse("reporting zero results before ever being shown should not require updating.",
264                 mBacker.addSourceResults(new SuggestionResult(mSource1)));
265 
266         assertContentsInOrder(
267                 "zero reporting before deadline.",
268                 getSnapshotFromBacker(true),
269                 mShortcut1);
270 
271         mBacker.setNow(NOW + DEADLINE);
272 
273         assertContentsInOrder(
274                 "zero reporting before deadline, viewing after.",
275                 getSnapshotFromBacker(true),
276                 mShortcut1,
277                 mSearchTheWeb,
278                 mMoreExpanded,
279                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0),
280                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0));
281 
282         // source 2 reports after deadline
283         assertTrue("reporting zero results after being shown should require updating.",
284                 mBacker.addSourceResults(new SuggestionResult(mSource2)));
285 
286         assertContentsInOrder(
287                 "zero reporting after deadline.",
288                 getSnapshotFromBacker(true),
289                 mShortcut1,
290                 mSearchTheWeb,
291                 mMoreExpanded,
292                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 0),
293                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0));
294 
295         mBacker.addSourceResults(
296                 new SuggestionResult(mSource3, Lists.newArrayList(
297                         makeSourceResult(mName3, 0),
298                         makeSourceResult(mName3, 1))));
299 
300         assertContentsInOrder(
301                 "last source reported after deadline.",
302                 getSnapshotFromBacker(true),
303                 mShortcut1,
304                 mSearchTheWeb,
305                 mMoreExpanded,
306                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 0),
307                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 2));
308     }
309 
testSourceReportsAfterDeadlineWithResults()310     public void testSourceReportsAfterDeadlineWithResults() {
311         mBacker = new TestBacker(
312                 "query",
313                 Lists.<SuggestionData>newArrayList(),  // no shortcuts
314                 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3),
315                 Sets.newHashSet(mName1, mName2, mName3), // all 3 are promoted sources
316                 mSource1,
317                 Lists.<SuggestionResult>newArrayList(),
318                 null,
319                 mSearchTheWeb,
320                 MAX_PROMOTED_SHOWING,
321                 DEADLINE,
322                 this,
323                 this);
324 
325         mBacker.addSourceResults(
326                 new SuggestionResult(mSource1, Lists.newArrayList(
327                         makeSourceResult(mName1, 0),
328                         makeSourceResult(mName1, 1),
329                         makeSourceResult(mName1, 2)
330                         )));
331 
332         mBacker.addSourceResults(
333                 new SuggestionResult(mSource2, Lists.newArrayList(
334                         makeSourceResult(mName2, 0),
335                         makeSourceResult(mName2, 1),
336                         makeSourceResult(mName2, 2)
337                         )));
338 
339         mBacker.setNow(NOW + DEADLINE);
340 
341         assertContentsInOrder(
342                 "after deadline passes, we mix in the remaining promoted slots among the " +
343                         "promoted  sources that have responded.",
344                 getSnapshotFromBacker(true),
345                 makeSourceResult(mName1, 0),
346                 makeSourceResult(mName1, 1),
347                 makeSourceResult(mName2, 0),
348                 makeSourceResult(mName2, 1),
349                 makeSourceResult(mName1, 2),  // remaining space (now that deadline has passed)
350                 makeSourceResult(mName2, 2),
351                 mSearchTheWeb,
352                 mMoreExpanded,
353                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0)
354         );
355 
356         mBacker.addSourceResults(
357                 new SuggestionResult(mSource3, Lists.newArrayList(
358                         makeSourceResult(mName3, 0),
359                         makeSourceResult(mName3, 1),
360                         makeSourceResult(mName3, 2)
361                         )));
362 
363         assertContentsInOrder(
364                 "promoted source reported after deadline, results should remain stable (even " +
365                         "though this is not the optimal order if we had known the third promoted " +
366                         "source was going to miss the deadline).",
367                 getSnapshotFromBacker(true),
368                 makeSourceResult(mName1, 0),
369                 makeSourceResult(mName1, 1),
370                 makeSourceResult(mName2, 0),
371                 makeSourceResult(mName2, 1),
372                 makeSourceResult(mName1, 2),
373                 makeSourceResult(mName2, 2),
374                 mSearchTheWeb,
375                 mMoreExpanded,
376                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 3)
377         );
378     }
379 
testFillSpaceLargerThanChunkSizeAfterDeadline()380     public void testFillSpaceLargerThanChunkSizeAfterDeadline() {
381         mBacker = new TestBacker(
382                 "query",
383                 Lists.<SuggestionData>newArrayList(),  // no shortcuts
384                 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3),
385                 Sets.newHashSet(mName1, mName2, mName3), // all 3 are promoted sources
386                 mSource1,
387                 Lists.<SuggestionResult>newArrayList(),
388                 null,
389                 mSearchTheWeb,
390                 MAX_PROMOTED_SHOWING,
391                 DEADLINE,
392                 this,
393                 this);
394 
395         mBacker.addSourceResults(
396                 new SuggestionResult(mSource1, Lists.newArrayList(
397                         makeSourceResult(mName1, 0),
398                         makeSourceResult(mName1, 1),
399                         makeSourceResult(mName1, 2),
400                         makeSourceResult(mName1, 3),
401                         makeSourceResult(mName1, 4),
402                         makeSourceResult(mName1, 5)
403                         )));
404 
405         mBacker.addSourceResults(
406                 new SuggestionResult(mSource2, Lists.newArrayList(
407                         makeSourceResult(mName2, 0)
408                         )));
409 
410         mBacker.setNow(NOW + DEADLINE);
411 
412         mBacker.addSourceResults(
413                 new SuggestionResult(mSource3, Lists.newArrayList(
414                         makeSourceResult(mName3, 0),
415                         makeSourceResult(mName3, 1),
416                         makeSourceResult(mName3, 2),
417                         makeSourceResult(mName3, 3),
418                         makeSourceResult(mName3, 4),
419                         makeSourceResult(mName3, 5)
420                         )));
421 
422         assertContentsInOrder(
423                 "after deadline has passed, promoted sources that have reported should fill in " +
424                         "the remaining slots",
425                 getSnapshotFromBacker(true),
426                 // chunk 1
427                 makeSourceResult(mName1, 0),
428                 makeSourceResult(mName1, 1),
429                 // chunk 2
430                 makeSourceResult(mName2, 0),
431                 // remaining space
432                 makeSourceResult(mName1, 2),
433                 makeSourceResult(mName1, 3),
434                 makeSourceResult(mName1, 4),
435                 mSearchTheWeb,
436                 mMoreExpanded,
437                 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_FINISHED, 1),   // 1 remaining result
438                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 6));  // reported after deadline
439     }
440 
testAllResultsReported()441     public void testAllResultsReported() {
442 
443         // each one reports 4 results
444         mBacker.addSourceResults(
445                 new SuggestionResult(mSource1, Lists.newArrayList(
446                         makeSourceResult(mName1, 0),
447                         makeSourceResult(mName1, 1),
448                         makeSourceResult(mName1, 2),
449                         makeSourceResult(mName1, 3)
450                 )));
451         mBacker.addSourceResults(
452                 new SuggestionResult(mSource2, Lists.newArrayList(
453                         makeSourceResult(mName2, 0),
454                         makeSourceResult(mName2, 1),
455                         makeSourceResult(mName2, 2),
456                         makeSourceResult(mName2, 3)
457                 )));
458         mBacker.addSourceResults(
459                 new SuggestionResult(mSource3, Lists.newArrayList(
460                         makeSourceResult(mName3, 0),
461                         makeSourceResult(mName3, 1),
462                         makeSourceResult(mName3, 2),
463                         makeSourceResult(mName3, 3)
464                 )));
465 
466         assertContentsInOrder(
467                 "all responded.",
468                 getSnapshotFromBacker(false),
469                 mShortcut1,
470                 makeSourceResult(mName1, 0),
471                 makeSourceResult(mName1, 1),
472                 makeSourceResult(mName2, 0),
473                 makeSourceResult(mName2, 1),
474                 makeSourceResult(mName1, 2),  // remaining slots (source 3 is not promoted)
475                 mSearchTheWeb,
476                 mMoreNotExpanded);
477 
478         assertContentsInOrder(
479                 "all responded (expanded).",
480                 getSnapshotFromBacker(true),
481                 mShortcut1,
482                 makeSourceResult(mName1, 0),
483                 makeSourceResult(mName1, 1),
484                 makeSourceResult(mName2, 0),
485                 makeSourceResult(mName2, 1),
486                 makeSourceResult(mName1, 2),
487                 mSearchTheWeb,
488                 mMoreExpanded,
489                 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_FINISHED, 1),  // 1 remaining
490                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 2),  // 2 remaining
491                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 4));
492     }
493 
testNoWebSource()494     public void testNoWebSource() {
495         mBacker = new TestBacker(
496                 "query",
497                 Lists.newArrayList(mShortcut1),
498                 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3),
499                 Sets.newHashSet(mName1, mName2), // promoted sources
500                 null,             // NO WEB SOURCE
501                 Lists.<SuggestionResult>newArrayList(),
502                 null,                 // no "go to website" suggestion
503                 mSearchTheWeb,        // the "search the web" suggestion
504                 MAX_PROMOTED_SHOWING,
505                 DEADLINE,
506                 this,        /** more factory points to {@link #getMoreEntry} */
507                 this);       /** corpus factory points to {@link #getCorpusEntry} */
508 
509 
510         mBacker.addSourceResults(
511                 new SuggestionResult(mSource1, Lists.newArrayList(
512                         makeSourceResult(mName1, 0),
513                         makeSourceResult(mName1, 1),
514                         makeSourceResult(mName1, 2),
515                         makeSourceResult(mName1, 3)
516                 )));
517 
518         assertContentsInOrder(
519                 "expecting business as usual even though there is no web source enabled.",
520                 getSnapshotFromBacker(false),
521                 mShortcut1,
522                 makeSourceResult(mName1, 0),
523                 makeSourceResult(mName1, 1));
524     }
525 
testDuplicatesOfShortcut()526     public void testDuplicatesOfShortcut() {
527 
528         // four results from source 1, the first of which is a dupe of the shortcut
529         mBacker.addSourceResults(
530                 new SuggestionResult(mSource1, Lists.newArrayList(
531                         mShortcut1,
532                         makeSourceResult(mName1, 1),
533                         makeSourceResult(mName1, 2),
534                         makeSourceResult(mName1, 3)
535                 )));
536 
537         // before deadline, not all have reported, so just one chunk from source 1
538         assertContentsInOrder(
539                 "expecting duplicate to be removed.",
540                 getSnapshotFromBacker(false),
541                 mShortcut1,
542                 makeSourceResult(mName1, 1),
543                 makeSourceResult(mName1, 2));
544     }
545 
testDuplicatesBetweenSources()546     public void testDuplicatesBetweenSources() {
547         // two different suggestions for facebook with the same intent action and intent data
548         final SuggestionData facebook1 = new SuggestionData.Builder(mName1)
549                 .title("facebook")
550                 .intentAction("action.VIEW")
551                 .intentData("http://www.facebook.com")
552                 .build();
553         final SuggestionData facebook2 = new SuggestionData.Builder(mName2)
554                 .title("facebook home")
555                 .intentAction("action.VIEW")
556                 .intentData("http://www.facebook.com")
557                 .build();
558 
559         mBacker.addSourceResults(
560                 new SuggestionResult(mSource1, Lists.newArrayList(
561                         facebook1
562                 )));
563         mBacker.addSourceResults(
564                 new SuggestionResult(mSource2, Lists.newArrayList(
565                         facebook2
566                 )));
567 
568         assertContentsInOrder(
569                 "expecting duplicate to be removed.",
570                 getSnapshotFromBacker(false),
571                 mShortcut1,
572                 facebook1,
573                 mSearchTheWeb,
574                 mMoreNotExpanded);
575     }
576 
577     /**
578      * When there are duplicates removed, we still want to fill the valuable slots with more
579      * information.
580      */
testDuplicatesRemovedSlotsStillFilled()581     public void testDuplicatesRemovedSlotsStillFilled() {
582         // two different suggestions for facebook with the same intent action and intent data
583         final SuggestionData facebook1 = new SuggestionData.Builder(mName1)
584                 .title("facebook")
585                 .intentAction("action.VIEW")
586                 .intentData("http://www.facebook.com")
587                 .build();
588         final SuggestionData facebook2 = new SuggestionData.Builder(mName2)
589                 .title("facebook home")
590                 .intentAction("action.VIEW")
591                 .intentData("http://www.facebook.com")
592                 .build();
593 
594         mBacker.addSourceResults(
595                 new SuggestionResult(mSource1, Lists.newArrayList(
596                         facebook1,
597                         makeSourceResult(mName1, 1),
598                         makeSourceResult(mName1, 2)
599                 )));
600         mBacker.addSourceResults(
601                 new SuggestionResult(mSource2, Lists.newArrayList(
602                         facebook2,
603                         makeSourceResult(mName2, 1),
604                         makeSourceResult(mName2, 2),
605                         makeSourceResult(mName2, 3)
606                 )));
607 
608         assertContentsInOrder(
609                 "after duplicate is removed, expecting all 6 promoted slots to still be filled.",
610                 getSnapshotFromBacker(false),
611                 mShortcut1,
612                 // chunk 1
613                 facebook1,
614                 makeSourceResult(mName1, 1),
615                 // chunk 2
616                 makeSourceResult(mName2, 1),
617                 makeSourceResult(mName2, 2),
618                 // remainder
619                 makeSourceResult(mName1, 2),
620                 mSearchTheWeb,
621                 mMoreNotExpanded);
622     }
623 
testShortcutsOnly()624     public void testShortcutsOnly() {
625         mBacker = new TestBacker(
626                 "query",
627                 Lists.newArrayList(mShortcut1),
628                 Lists.<SuggestionSource>newArrayList(), // no sources
629                 Sets.<ComponentName>newHashSet(),
630                 mSource1,
631                 Lists.<SuggestionResult>newArrayList(),
632                 null,
633                 mSearchTheWeb,
634                 MAX_PROMOTED_SHOWING,
635                 DEADLINE,
636                 this,
637                 this);
638         mBacker.setNow(NOW);
639 
640         assertContentsInOrder(
641                 "shortcuts only.",
642                 getSnapshotFromBacker(false),
643                 mShortcut1);
644 
645         assertContentsInOrder(
646                 "shortcuts only.",
647                 getSnapshotFromBacker(true),
648                 mShortcut1);
649     }
650 
testAllSourcesPromotedResponded_resultsFitInPromotedSlots()651     public void testAllSourcesPromotedResponded_resultsFitInPromotedSlots() {
652         mBacker = new TestBacker(
653                 "query",
654                 Lists.newArrayList(mShortcut1),
655                 Lists.<SuggestionSource>newArrayList(mSource1, mSource2),
656                 Sets.<ComponentName>newHashSet(mName1, mName2), // every source is promoted
657                 mSource1,
658                 Lists.<SuggestionResult>newArrayList(),
659                 null,
660                 mSearchTheWeb,
661                 MAX_PROMOTED_SHOWING,
662                 DEADLINE,
663                 this,
664                 this);
665         mBacker.setNow(NOW);
666 
667         mBacker.addSourceResults(
668                 new SuggestionResult(mSource1, Lists.newArrayList(
669                         makeSourceResult(mName1, 0),
670                         makeSourceResult(mName1, 1))));
671         mBacker.addSourceResults(
672                 new SuggestionResult(mSource2, Lists.newArrayList(
673                         makeSourceResult(mName2, 0),
674                         makeSourceResult(mName2, 1))));
675 
676         assertContentsInOrder(
677                 "should not show 'more' entries if all results fit, and there are no unpromoted " +
678                         "sources.",
679                 getSnapshotFromBacker(true),
680                 mShortcut1,
681                 makeSourceResult(mName1, 0),
682                 makeSourceResult(mName1, 1),
683                 makeSourceResult(mName2, 0),
684                 makeSourceResult(mName2, 1),
685                 mSearchTheWeb);
686     }
687 
testCachedSourceResults()688     public void testCachedSourceResults() {
689 
690         final ArrayList<SuggestionResult> cached = Lists.newArrayList(
691                 new SuggestionResult(mSource1, Lists.newArrayList(
692                         makeSourceResult(mName1, 0),
693                         makeSourceResult(mName1, 1))),
694                 new SuggestionResult(mSource2, Lists.newArrayList(
695                         makeSourceResult(mName2, 0),
696                         makeSourceResult(mName2, 1))),
697                 new SuggestionResult(mSource3, Lists.newArrayList(
698                         makeSourceResult(mName3, 0),
699                         makeSourceResult(mName3, 1))));
700 
701         mBacker = new TestBacker(
702                 "query",
703                 Lists.newArrayList(mShortcut1),
704                 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3),
705                 Sets.<ComponentName>newHashSet(mName1, mName2), // promoted sources
706                 mSource1,
707                 cached,
708                 null,
709                 mSearchTheWeb,
710                 MAX_PROMOTED_SHOWING,
711                 DEADLINE,
712                 this,
713                 this);
714 
715         assertContentsInOrder(
716                 "three cached sources, 2 promoted, one not.",
717                 getSnapshotFromBacker(true),
718                 mShortcut1,
719                 makeSourceResult(mName1, 0),
720                 makeSourceResult(mName1, 1),
721                 makeSourceResult(mName2, 0),
722                 makeSourceResult(mName2, 1),
723                 mSearchTheWeb,
724                 mMoreExpanded,
725                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 2));
726     }
727 
testGoToWebsiteSuggestion()728     public void testGoToWebsiteSuggestion() {
729         // Recreate the backer, this time with the "go to website" suggestion passed in.
730         // Then check that the backer correctly shows the suggestion even when there are
731         // lots of other results.
732         mBacker = new TestBacker(
733                 "query",
734                 Lists.newArrayList(mShortcut1),
735                 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3),
736                 Sets.newHashSet(mName1, mName2), // promoted sources
737                 mSource1,
738                 Lists.<SuggestionResult>newArrayList(),
739                 mGoToWebsite,
740                 mSearchTheWeb,
741                 MAX_PROMOTED_SHOWING,
742                 DEADLINE,
743                 this,
744                 this);
745 
746         // each one reports 4 results
747         mBacker.addSourceResults(
748                 new SuggestionResult(mSource1, Lists.newArrayList(
749                         makeSourceResult(mName1, 0),
750                         makeSourceResult(mName1, 1),
751                         makeSourceResult(mName1, 2),
752                         makeSourceResult(mName1, 3)
753                 )));
754         mBacker.addSourceResults(
755                 new SuggestionResult(mSource2, Lists.newArrayList(
756                         makeSourceResult(mName2, 0),
757                         makeSourceResult(mName2, 1),
758                         makeSourceResult(mName2, 2),
759                         makeSourceResult(mName2, 3)
760                 )));
761         mBacker.addSourceResults(
762                 new SuggestionResult(mSource3, Lists.newArrayList(
763                         makeSourceResult(mName3, 0),
764                         makeSourceResult(mName3, 1),
765                         makeSourceResult(mName3, 2),
766                         makeSourceResult(mName3, 3)
767                 )));
768 
769         assertContentsInOrder(
770                 "first suggestion should be go to website suggestion",
771                 getSnapshotFromBacker(false),
772                 mGoToWebsite,
773                 mShortcut1,
774                 makeSourceResult(mName1, 0),
775                 makeSourceResult(mName1, 1),
776                 makeSourceResult(mName2, 0),
777                 makeSourceResult(mName2, 1),
778                 makeSourceResult(mName1, 2),  // remaining slots (source 3 is not promoted)
779                 mSearchTheWeb,
780                 mMoreNotExpanded);
781     }
782 
testPinToBottomSuggestion()783     public void testPinToBottomSuggestion() {
784         // each one reports 4 results; source 1 reports a pin-to-bottom suggestion last
785         mBacker.addSourceResults(
786                 new SuggestionResult(mSource1, Lists.newArrayList(
787                         makeSourceResult(mName1, 0),
788                         makeSourceResult(mName1, 1),
789                         makeSourceResult(mName1, 2),
790                         makeSourceResult(mName1, 3),
791                         makePinToBottomSourceResult(mName1, 4)
792                 )));
793         mBacker.addSourceResults(
794                 new SuggestionResult(mSource2, Lists.newArrayList(
795                         makeSourceResult(mName2, 0),
796                         makeSourceResult(mName2, 1),
797                         makeSourceResult(mName2, 2),
798                         makeSourceResult(mName2, 3)
799                 )));
800         mBacker.addSourceResults(
801                 new SuggestionResult(mSource3, Lists.newArrayList(
802                         makeSourceResult(mName3, 0),
803                         makeSourceResult(mName3, 1),
804                         makeSourceResult(mName3, 2),
805                         makeSourceResult(mName3, 3)
806                 )));
807 
808         assertContentsInOrder(
809                 "pin to bottom non-expanded.",
810                 getSnapshotFromBacker(false),
811                 mShortcut1,
812                 makeSourceResult(mName1, 0),
813                 makeSourceResult(mName1, 1),
814                 makeSourceResult(mName2, 0),
815                 makeSourceResult(mName2, 1),
816                 makeSourceResult(mName1, 2),  // remaining slots (source 3 is not promoted)
817                 mSearchTheWeb,
818                 makePinToBottomSourceResult(mName1, 4),
819                 mMoreNotExpanded);
820 
821         assertContentsInOrder(
822                 "pin to bottom expanded.",
823                 getSnapshotFromBacker(true),
824                 mShortcut1,
825                 makeSourceResult(mName1, 0),
826                 makeSourceResult(mName1, 1),
827                 makeSourceResult(mName2, 0),
828                 makeSourceResult(mName2, 1),
829                 makeSourceResult(mName1, 2),
830                 mSearchTheWeb,
831                 makePinToBottomSourceResult(mName1, 4),
832                 mMoreExpanded,
833                 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_FINISHED, 1),  // 1 remaining
834                 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 2),  // 2 remaining
835                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 4));
836     }
837 
testPinToBottomNotShownIfSourceNotInPromotedList()838     public void testPinToBottomNotShownIfSourceNotInPromotedList() {
839         // a couple source return before deadline
840         mBacker.addSourceResults(
841                 new SuggestionResult(mSource2, Lists.newArrayList(
842                         makeSourceResult(mName2, 0),
843                         makeSourceResult(mName2, 1),
844                         makeSourceResult(mName2, 2),
845                         makeSourceResult(mName2, 3)
846                 )));
847         mBacker.addSourceResults(
848                 new SuggestionResult(mSource3, Lists.newArrayList(
849                         makeSourceResult(mName3, 0),
850                         makeSourceResult(mName3, 1),
851                         makeSourceResult(mName3, 2),
852                         makeSourceResult(mName3, 3)
853                 )));
854 
855         // another returns after the deadline with a pin to bottom
856         mBacker.setNow(NOW + DEADLINE);
857         mBacker.addSourceResults(
858                 new SuggestionResult(mSource1, Lists.newArrayList(
859                         makeSourceResult(mName1, 0),
860                         makeSourceResult(mName1, 1),
861                         makeSourceResult(mName1, 2),
862                         makeSourceResult(mName1, 3),
863                         makePinToBottomSourceResult(mName1, 4)
864                 )));
865 
866         assertContentsInOrder(
867                 "pin to bottom shouldn't be shown if its source responded after deadline.",
868                 getSnapshotFromBacker(false),
869                 mShortcut1,
870                 makeSourceResult(mName2, 0),
871                 makeSourceResult(mName2, 1),
872                 makeSourceResult(mName2, 2),
873                 makeSourceResult(mName2, 3),
874                 mSearchTheWeb,
875                 mMoreNotExpanded);
876     }
877 
878     /**
879      * If non promoted sources report zero results before the user has expanded "more", then
880      * we don't bother showing "source X reported 0 results" upon expansion.
881      *
882      * We also remove the "more results" once we know there are no sources under "more results"
883      * to show.
884      */
testNonPromotedSourcesWithZeroResults_reportedBeforeViewed()885     public void testNonPromotedSourcesWithZeroResults_reportedBeforeViewed() {
886         mBacker.addSourceResults(
887                 new SuggestionResult(mSource1, Lists.newArrayList(
888                         makeSourceResult(mName1, 0),
889                         makeSourceResult(mName1, 1)
890                 )));
891         mBacker.addSourceResults(
892                 new SuggestionResult(mSource2, Lists.newArrayList(
893                         makeSourceResult(mName2, 0),
894                         makeSourceResult(mName2, 1)
895                 )));
896 
897         assertContentsInOrder(
898                 "non promoted source with zero results before viewed.",
899                 getSnapshotFromBacker(false),
900                 mShortcut1,
901                 makeSourceResult(mName1, 0),
902                 makeSourceResult(mName1, 1),
903                 makeSourceResult(mName2, 0),
904                 makeSourceResult(mName2, 1),
905                 mSearchTheWeb,
906                 mMoreNotExpanded);
907 
908         // non-promoted source 3
909         mBacker.addSourceResults(new SuggestionResult(mSource3));
910 
911         assertContentsInOrder(
912                 "non promoted source with zero results after expansion.",
913                 getSnapshotFromBacker(true),  // EXPANDED
914                 mShortcut1,
915                 makeSourceResult(mName1, 0),
916                 makeSourceResult(mName1, 1),
917                 makeSourceResult(mName2, 0),
918                 makeSourceResult(mName2, 1),
919                 mSearchTheWeb);  // no "more" results entry at all
920     }
921 
922     /**
923      * Similar to {@link #testNonPromotedSourcesWithZeroResults_reportedBeforeViewed()}, but in
924      * this case, the user has expanded the "more results" and thus viewed the entries; we can't
925      * remove them at this point and will continue to show them, even though they returned
926      * zero results.
927      */
testNonPromotedSourcesWithZeroResults_reportedAfterViewed()928     public void testNonPromotedSourcesWithZeroResults_reportedAfterViewed() {
929         mBacker.addSourceResults(
930                 new SuggestionResult(mSource1, Lists.newArrayList(
931                         makeSourceResult(mName1, 0),
932                         makeSourceResult(mName1, 1)
933                 )));
934         mBacker.addSourceResults(
935                 new SuggestionResult(mSource2, Lists.newArrayList(
936                         makeSourceResult(mName2, 0),
937                         makeSourceResult(mName2, 1)
938                 )));
939 
940         assertContentsInOrder(
941                 "non promoted source with zero results viewed before report.",
942                 getSnapshotFromBacker(true),
943                 mShortcut1,
944                 makeSourceResult(mName1, 0),
945                 makeSourceResult(mName1, 1),
946                 makeSourceResult(mName2, 0),
947                 makeSourceResult(mName2, 1),
948                 mSearchTheWeb,
949                 mMoreExpanded,
950                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0));
951 
952         // non-promoted source 3
953         mBacker.addSourceResults(new SuggestionResult(mSource3));
954 
955         assertContentsInOrder(
956                 "non promoted source with zero results pinned after being viewed.",
957                 getSnapshotFromBacker(true),  // EXPANDED
958                 mShortcut1,
959                 makeSourceResult(mName1, 0),
960                 makeSourceResult(mName1, 1),
961                 makeSourceResult(mName2, 0),
962                 makeSourceResult(mName2, 1),
963                 mSearchTheWeb,
964                 mMoreExpanded,
965                 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 0));
966     }
967 
getSnapshotFromBacker(boolean expandAdditional)968     List<SuggestionData> getSnapshotFromBacker(boolean expandAdditional) {
969         final ArrayList<SuggestionData> list = Lists.newArrayList();
970         mBacker.snapshotSuggestions(list, expandAdditional);
971         return list;
972     }
973 
makeCorpusEntry( String label, int responseStatus, int numResultsUndisplayed)974     private SuggestionData makeCorpusEntry(
975             String label, int responseStatus, int numResultsUndisplayed) {
976         final SuggestionData.Builder builder = new SuggestionData.Builder(mName1);
977         builder.title("more_" + label + " " + getResponseStatusString(responseStatus)
978                 + ", numleft: " + numResultsUndisplayed);
979         return builder
980                 .build();
981     }
982 
getResponseStatusString(int responseStatus)983     private String getResponseStatusString(int responseStatus) {
984         switch (responseStatus) {
985             case SourceStat.RESPONSE_NOT_STARTED:
986                 return "RESPONSE_NOT_STARTED";
987             case SourceStat.RESPONSE_IN_PROGRESS:
988                 return "RESPONSE_IN_PROGRESS";
989             case SourceStat.RESPONSE_FINISHED:
990                 return "RESPONSE_FINISHED";
991             default:
992                 throw new IllegalArgumentException("unknown status " + responseStatus);
993         }
994     }
995 
makeSourceResult(ComponentName name, int index)996     private SuggestionData makeSourceResult(ComponentName name, int index) {
997         return new SuggestionData.Builder(name)
998                 .title(name.getClassName() + " " + index)
999                 .intentAction(name.getClassName())
1000                 .intentData("" + index)
1001                 .build();
1002     }
1003 
makePinToBottomSourceResult(ComponentName name, int index)1004     private SuggestionData makePinToBottomSourceResult(ComponentName name, int index) {
1005         return new SuggestionData.Builder(name)
1006                 .title(name.getClassName() + " manage search history " + index)
1007                 .intentAction(name.getClassName())
1008                 .intentData("" + index)
1009                 .pinToBottom(true)
1010                 .build();
1011     }
1012 
1013     /**
1014      * Allows setting what "now" is for testing
1015      */
1016     private static class TestBacker extends SourceSuggestionBacker {
1017 
1018         long now = 0L;
1019 
TestBacker( String query, List<SuggestionData> shortcuts, List<SuggestionSource> sources, HashSet<ComponentName> promotedSources, SuggestionSource selectedWebSearchSource, List<SuggestionResult> cachedResults, SuggestionData goToWebsite, SuggestionData searchTheWeb, int maxPromotedSlots, long deadline, MoreExpanderFactory moreFactory, CorpusResultFactory corpusFactory)1020         public TestBacker(
1021                 String query,
1022                 List<SuggestionData> shortcuts,
1023                 List<SuggestionSource> sources,
1024                 HashSet<ComponentName> promotedSources,
1025                 SuggestionSource selectedWebSearchSource,
1026                 List<SuggestionResult> cachedResults,
1027                 SuggestionData goToWebsite,
1028                 SuggestionData searchTheWeb,
1029                 int maxPromotedSlots,
1030                 long deadline,
1031                 MoreExpanderFactory moreFactory,
1032                 CorpusResultFactory corpusFactory) {
1033             super(query, shortcuts, sources, promotedSources, selectedWebSearchSource, cachedResults,
1034                     goToWebsite, searchTheWeb, maxPromotedSlots, deadline, moreFactory,
1035                     corpusFactory);
1036         }
1037 
1038 
getNow()1039         public long getNow() {
1040             return now;
1041         }
1042 
setNow(long now)1043         public void setNow(long now) {
1044             this.now = now;
1045         }
1046     }
1047 
assertContentsInOrder(Iterable<?> actual, Object... expected)1048     static void assertContentsInOrder(Iterable<?> actual, Object... expected) {
1049         assertContentsInOrder(null, actual, expected);
1050     }
1051 
1052     /**
1053      * an implementation of {@link MoreAsserts#assertContentsInOrder(String, Iterable, Object[])}
1054      * with some additional information placed in the assert message in the error case to make it
1055      * easier to see where the mismatch is.
1056      */
assertContentsInOrder( String message, Iterable<?> actual, Object... expected)1057     static void assertContentsInOrder(
1058             String message, Iterable<?> actual, Object... expected) {
1059         ArrayList actualList = new ArrayList();
1060         for (Object o : actual) {
1061             actualList.add(o);
1062         }
1063         StringBuilder sb = new StringBuilder();
1064         if (message != null) sb.append(message);
1065         final List<Object> expectedList = Arrays.asList(expected);
1066 
1067         if (expectedList.size() != actualList.size()) {
1068             sb.append("\nsize mismatch (expected: ").append(expectedList.size())
1069                     .append(" actual: ").append(actualList.size()).append('.');
1070         }
1071         for (int i = 0; i < Math.min(expectedList.size(), actualList.size()); i++) {
1072             final Object expectedItem = expectedList.get(i);
1073             final Object actualItem = actualList.get(i);
1074             if (!expectedItem.equals(actualItem)) {
1075                 sb.append("\n").append("at index ").append(i)
1076                         .append(" expected: ").append(expectedItem)
1077                         .append("\n").append("actual: ").append(actualItem);
1078             }
1079         }
1080         Assert.assertEquals(sb.toString(), expectedList, actualList);
1081     }
1082 }
1083