OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 // TODO(beaudoin): What is really needed here? | |
6 | |
7 #include <deque> | |
8 #include <string> | |
9 | |
10 #include "base/memory/scoped_ptr.h" | |
11 #include "base/stl_util.h" | |
12 #include "base/strings/string_util.h" | |
13 #include "base/values.h" | |
14 #include "chrome/browser/ui/webui/ntp/suggestions_combiner.h" | |
15 #include "chrome/browser/ui/webui/ntp/suggestions_page_handler.h" | |
16 #include "chrome/browser/ui/webui/ntp/suggestions_source.h" | |
17 #include "chrome/test/base/testing_profile.h" | |
18 #include "content/public/test/test_browser_thread_bundle.h" | |
19 #include "testing/gtest/include/gtest/gtest.h" | |
20 | |
21 namespace { | |
22 | |
23 struct SourceInfo { | |
24 int weight; | |
25 const char* source_name; | |
26 int number_of_suggestions; | |
27 }; | |
28 | |
29 struct TestDescription { | |
30 SourceInfo sources[3]; | |
31 const char* results[8]; | |
32 } test_suite[] = { | |
33 // One source, more than 8 items. | |
34 { | |
35 {{1, "A", 10}}, | |
36 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"} | |
37 }, | |
38 // One source, exactly 8 items. | |
39 { | |
40 {{1, "A", 8}}, | |
41 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"} | |
42 }, | |
43 // One source, not enough items. | |
44 { | |
45 {{1, "A", 3}}, | |
46 {"A 0", "A 1", "A 2"} | |
47 }, | |
48 // One source, no items. | |
49 { | |
50 {{1, "A", 0}}, | |
51 {} | |
52 }, | |
53 // Two sources, equal weight, more than 8 items. | |
54 { | |
55 {{1, "A", 10}, {1, "B", 10}}, | |
56 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2", "B 3"} | |
57 }, | |
58 // Two sources, equal weight, exactly 8 items. | |
59 { | |
60 {{1, "A", 4}, {1, "B", 4}}, | |
61 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2", "B 3"} | |
62 }, | |
63 // Two sources, equal weight, exactly 8 items but source A has more. | |
64 { | |
65 {{1, "A", 5}, {1, "B", 3}}, | |
66 {"A 0", "A 1", "A 2", "A 3", "A 4", "B 0", "B 1", "B 2"} | |
67 }, | |
68 // Two sources, equal weight, exactly 8 items but source B has more. | |
69 { | |
70 {{1, "A", 2}, {1, "B", 6}}, | |
71 {"A 0", "A 1", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5"} | |
72 }, | |
73 // Two sources, equal weight, exactly 8 items but source A has none. | |
74 { | |
75 {{1, "A", 0}, {1, "B", 8}}, | |
76 {"B 0", "B 1", "B 2", "B 3", "B 4", "B 5", "B 6", "B 7"} | |
77 }, | |
78 // Two sources, equal weight, exactly 8 items but source B has none. | |
79 { | |
80 {{1, "A", 8}, {1, "B", 0}}, | |
81 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"} | |
82 }, | |
83 // Two sources, equal weight, less than 8 items. | |
84 { | |
85 {{1, "A", 3}, {1, "B", 3}}, | |
86 {"A 0", "A 1", "A 2", "B 0", "B 1", "B 2"} | |
87 }, | |
88 // Two sources, equal weight, less than 8 items but source A has more. | |
89 { | |
90 {{1, "A", 4}, {1, "B", 3}}, | |
91 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2"} | |
92 }, | |
93 // Two sources, equal weight, less than 8 items but source B has more. | |
94 { | |
95 {{1, "A", 1}, {1, "B", 3}}, | |
96 {"A 0", "B 0", "B 1", "B 2"} | |
97 }, | |
98 // Two sources, weights 3/4 A 1/4 B, more than 8 items. | |
99 { | |
100 {{3, "A", 10}, {1, "B", 10}}, | |
101 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "B 0", "B 1"} | |
102 }, | |
103 // Two sources, weights 1/8 A 7/8 B, more than 8 items. | |
104 { | |
105 {{1, "A", 10}, {7, "B", 10}}, | |
106 {"A 0", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5", "B 6"} | |
107 }, | |
108 // Two sources, weights 1/3 A 2/3 B, more than 8 items. | |
109 { | |
110 {{1, "A", 10}, {2, "B", 10}}, | |
111 {"A 0", "A 1", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5"} | |
112 }, | |
113 // Three sources, weights 1/2 A 1/4 B 1/4 C, more than 8 items. | |
114 { | |
115 {{2, "A", 10}, {1, "B", 10}, {1, "C", 10}}, | |
116 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "C 0", "C 1"} | |
117 }, | |
118 // Three sources, weights 1/3 A 1/3 B 1/3 C, more than 8 items. | |
119 { | |
120 {{1, "A", 10}, {1, "B", 10}, {1, "C", 10}}, | |
121 {"A 0", "A 1", "B 0", "B 1", "B 2", "C 0", "C 1", "C 2"} | |
122 }, | |
123 // Extra items should be grouped together. | |
124 { | |
125 {{1, "A", 3}, {1, "B", 4}, {10, "C", 1}}, | |
126 {"A 0", "A 1", "A 2", "B 0", "B 1", "B 2", "B 3", "C 0"} | |
127 } | |
128 }; | |
129 | |
130 } // namespace | |
131 | |
132 // Stub for a SuggestionsSource that can provide a number of fake suggestions. | |
133 // Fake suggestions are DictionaryValue with a single "title" string field | |
134 // containing the |source_name| followed by the index of the suggestion. | |
135 // Not in the empty namespace since it's a friend of SuggestionsCombiner. | |
136 class SuggestionsSourceStub : public SuggestionsSource { | |
137 public: | |
138 explicit SuggestionsSourceStub(int weight, | |
139 const std::string& source_name, int number_of_suggestions) | |
140 : combiner_(NULL), | |
141 weight_(weight), | |
142 source_name_(source_name), | |
143 number_of_suggestions_(number_of_suggestions), | |
144 debug_(false) { | |
145 } | |
146 ~SuggestionsSourceStub() override { STLDeleteElements(&items_); } | |
147 | |
148 // Call this method to simulate that the SuggestionsSource has received all | |
149 // its suggestions. | |
150 void Done() { | |
151 combiner_->OnItemsReady(); | |
152 } | |
153 | |
154 private: | |
155 // SuggestionsSource Override and implementation. | |
156 void SetDebug(bool enable) override { debug_ = enable; } | |
157 int GetWeight() override { return weight_; } | |
158 int GetItemCount() override { return items_.size(); } | |
159 base::DictionaryValue* PopItem() override { | |
160 if (items_.empty()) | |
161 return NULL; | |
162 base::DictionaryValue* item = items_.front(); | |
163 items_.pop_front(); | |
164 return item; | |
165 } | |
166 | |
167 void FetchItems(Profile* profile) override { | |
168 char num_str[21]; // Enough to hold all numbers up to 64-bits. | |
169 for (int i = 0; i < number_of_suggestions_; ++i) { | |
170 base::snprintf(num_str, sizeof(num_str), "%d", i); | |
171 AddSuggestion(source_name_ + ' ' + num_str); | |
172 } | |
173 } | |
174 | |
175 // Adds a fake suggestion. This suggestion is a DictionaryValue with a single | |
176 // "title" field containing |title|. | |
177 void AddSuggestion(const std::string& title) { | |
178 base::DictionaryValue* item = new base::DictionaryValue(); | |
179 item->SetString("title", title); | |
180 items_.push_back(item); | |
181 } | |
182 | |
183 void SetCombiner(SuggestionsCombiner* combiner) override { | |
184 DCHECK(!combiner_); | |
185 combiner_ = combiner; | |
186 } | |
187 | |
188 // Our combiner. | |
189 SuggestionsCombiner* combiner_; | |
190 | |
191 int weight_; | |
192 std::string source_name_; | |
193 int number_of_suggestions_; | |
194 bool debug_; | |
195 | |
196 // Keep the results of the db query here. | |
197 std::deque<base::DictionaryValue*> items_; | |
198 | |
199 DISALLOW_COPY_AND_ASSIGN(SuggestionsSourceStub); | |
200 }; | |
201 | |
202 class SuggestionsCombinerTest : public testing::Test { | |
203 public: | |
204 SuggestionsCombinerTest() { | |
205 } | |
206 | |
207 protected: | |
208 content::TestBrowserThreadBundle thread_bundle_; | |
209 Profile* profile_; | |
210 SuggestionsHandler* suggestions_handler_; | |
211 SuggestionsCombiner* combiner_; | |
212 | |
213 void Reset() { | |
214 delete combiner_; | |
215 combiner_ = new SuggestionsCombiner(suggestions_handler_, profile_); | |
216 } | |
217 | |
218 private: | |
219 void SetUp() override { | |
220 profile_ = new TestingProfile(); | |
221 suggestions_handler_ = new SuggestionsHandler(); | |
222 combiner_ = new SuggestionsCombiner(suggestions_handler_, profile_); | |
223 } | |
224 | |
225 void TearDown() override { | |
226 delete combiner_; | |
227 delete suggestions_handler_; | |
228 delete profile_; | |
229 } | |
230 | |
231 DISALLOW_COPY_AND_ASSIGN(SuggestionsCombinerTest); | |
232 }; | |
233 | |
234 TEST_F(SuggestionsCombinerTest, NoSource) { | |
235 combiner_->FetchItems(NULL); | |
236 EXPECT_EQ(0UL, combiner_->GetPageValues()->GetSize()); | |
237 } | |
238 | |
239 TEST_F(SuggestionsCombinerTest, SourcesAreNotDoneFetching) { | |
240 combiner_->AddSource(new SuggestionsSourceStub(1, "sourceA", 10)); | |
241 combiner_->AddSource(new SuggestionsSourceStub(1, "sourceB", 10)); | |
242 combiner_->FetchItems(NULL); | |
243 EXPECT_EQ(0UL, combiner_->GetPageValues()->GetSize()); | |
244 } | |
245 | |
246 TEST_F(SuggestionsCombinerTest, TestSuite) { | |
247 size_t test_count = arraysize(test_suite); | |
248 for (size_t i = 0; i < test_count; ++i) { | |
249 const TestDescription& description = test_suite[i]; | |
250 size_t source_count = arraysize(description.sources); | |
251 | |
252 scoped_ptr<SuggestionsSourceStub*[]> sources( | |
253 new SuggestionsSourceStub*[source_count]); | |
254 | |
255 // Setup sources. | |
256 for (size_t j = 0; j < source_count; ++j) { | |
257 const SourceInfo& source_info = description.sources[j]; | |
258 // A NULL |source_name| means we shouldn't add this source. | |
259 if (source_info.source_name) { | |
260 sources[j] = new SuggestionsSourceStub(source_info.weight, | |
261 source_info.source_name, source_info.number_of_suggestions); | |
262 combiner_->AddSource(sources[j]); | |
263 } else { | |
264 sources[j] = NULL; | |
265 } | |
266 } | |
267 | |
268 // Start fetching. | |
269 combiner_->FetchItems(NULL); | |
270 | |
271 // Sources complete. | |
272 for (size_t j = 0; j < source_count; ++j) { | |
273 if (sources[j]) | |
274 sources[j]->Done(); | |
275 } | |
276 | |
277 // Verify expectations. | |
278 base::ListValue* results = combiner_->GetPageValues(); | |
279 size_t result_count = results->GetSize(); | |
280 EXPECT_LE(result_count, 8UL); | |
281 for (size_t j = 0; j < 8; ++j) { | |
282 if (j < result_count) { | |
283 std::string value; | |
284 base::DictionaryValue* dictionary; | |
285 results->GetDictionary(j, &dictionary); | |
286 dictionary->GetString("title", &value); | |
287 EXPECT_STREQ(description.results[j], value.c_str()) << | |
288 " test index:" << i; | |
289 } else { | |
290 EXPECT_EQ(description.results[j], static_cast<const char*>(NULL)) << | |
291 " test index:" << i; | |
292 } | |
293 } | |
294 | |
295 Reset(); | |
296 } | |
297 } | |
298 | |
OLD | NEW |