1 // Copyright (c) 2010 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 #include <string>
6 #include <vector>
7
8 #include "base/message_loop.h"
9 #include "base/string_number_conversions.h"
10 #include "base/string_split.h"
11 #include "base/string_util.h"
12 #include "base/utf_string_conversions.h"
13 #include "chrome/browser/bookmarks/bookmark_index.h"
14 #include "chrome/browser/bookmarks/bookmark_model.h"
15 #include "chrome/browser/bookmarks/bookmark_utils.h"
16 #include "chrome/browser/history/history_database.h"
17 #include "chrome/browser/history/in_memory_database.h"
18 #include "chrome/browser/history/query_parser.h"
19 #include "chrome/test/testing_browser_process_test.h"
20 #include "chrome/test/testing_profile.h"
21 #include "content/browser/browser_thread.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 class BookmarkIndexTest : public TestingBrowserProcessTest {
25 public:
BookmarkIndexTest()26 BookmarkIndexTest() : model_(new BookmarkModel(NULL)) {}
27
AddBookmarksWithTitles(const char ** titles,size_t count)28 void AddBookmarksWithTitles(const char** titles, size_t count) {
29 std::vector<std::string> title_vector;
30 for (size_t i = 0; i < count; ++i)
31 title_vector.push_back(titles[i]);
32 AddBookmarksWithTitles(title_vector);
33 }
34
AddBookmarksWithTitles(const std::vector<std::string> & titles)35 void AddBookmarksWithTitles(const std::vector<std::string>& titles) {
36 GURL url("about:blank");
37 for (size_t i = 0; i < titles.size(); ++i)
38 model_->AddURL(model_->other_node(), static_cast<int>(i),
39 ASCIIToUTF16(titles[i]), url);
40 }
41
ExpectMatches(const std::string & query,const char ** expected_titles,size_t expected_count)42 void ExpectMatches(const std::string& query,
43 const char** expected_titles,
44 size_t expected_count) {
45 std::vector<std::string> title_vector;
46 for (size_t i = 0; i < expected_count; ++i)
47 title_vector.push_back(expected_titles[i]);
48 ExpectMatches(query, title_vector);
49 }
50
ExpectMatches(const std::string & query,const std::vector<std::string> expected_titles)51 void ExpectMatches(const std::string& query,
52 const std::vector<std::string> expected_titles) {
53 std::vector<bookmark_utils::TitleMatch> matches;
54 model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16(query), 1000, &matches);
55 ASSERT_EQ(expected_titles.size(), matches.size());
56 for (size_t i = 0; i < expected_titles.size(); ++i) {
57 bool found = false;
58 for (size_t j = 0; j < matches.size(); ++j) {
59 if (ASCIIToUTF16(expected_titles[i]) == matches[j].node->GetTitle()) {
60 matches.erase(matches.begin() + j);
61 found = true;
62 break;
63 }
64 }
65 ASSERT_TRUE(found);
66 }
67 }
68
ExtractMatchPositions(const std::string & string,Snippet::MatchPositions * matches)69 void ExtractMatchPositions(const std::string& string,
70 Snippet::MatchPositions* matches) {
71 std::vector<std::string> match_strings;
72 base::SplitString(string, ':', &match_strings);
73 for (size_t i = 0; i < match_strings.size(); ++i) {
74 std::vector<std::string> chunks;
75 base::SplitString(match_strings[i], ',', &chunks);
76 ASSERT_EQ(2U, chunks.size());
77 matches->push_back(Snippet::MatchPosition());
78 int chunks0, chunks1;
79 base::StringToInt(chunks[0], &chunks0);
80 base::StringToInt(chunks[1], &chunks1);
81 matches->back().first = chunks0;
82 matches->back().second = chunks1;
83 }
84 }
85
ExpectMatchPositions(const std::string & query,const Snippet::MatchPositions & expected_positions)86 void ExpectMatchPositions(const std::string& query,
87 const Snippet::MatchPositions& expected_positions) {
88 std::vector<bookmark_utils::TitleMatch> matches;
89 model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16(query), 1000, &matches);
90 ASSERT_EQ(1U, matches.size());
91 const bookmark_utils::TitleMatch& match = matches[0];
92 ASSERT_EQ(expected_positions.size(), match.match_positions.size());
93 for (size_t i = 0; i < expected_positions.size(); ++i) {
94 EXPECT_EQ(expected_positions[i].first, match.match_positions[i].first);
95 EXPECT_EQ(expected_positions[i].second, match.match_positions[i].second);
96 }
97 }
98
99 protected:
100 scoped_ptr<BookmarkModel> model_;
101
102 private:
103 DISALLOW_COPY_AND_ASSIGN(BookmarkIndexTest);
104 };
105
106 // Various permutations with differing input, queries and output that exercises
107 // all query paths.
TEST_F(BookmarkIndexTest,Tests)108 TEST_F(BookmarkIndexTest, Tests) {
109 struct TestData {
110 const std::string input;
111 const std::string query;
112 const std::string expected;
113 } data[] = {
114 // Trivial test case of only one term, exact match.
115 { "a;b", "A", "a" },
116
117 // Prefix match, one term.
118 { "abcd;abc;b", "abc", "abcd;abc" },
119
120 // Prefix match, multiple terms.
121 { "abcd cdef;abcd;abcd cdefg", "abc cde", "abcd cdef;abcd cdefg"},
122
123 // Exact and prefix match.
124 { "ab cdef;abcd;abcd cdefg", "ab cdef", "ab cdef"},
125
126 // Exact and prefix match.
127 { "ab cdef ghij;ab;cde;cdef;ghi;cdef ab;ghij ab",
128 "ab cde ghi",
129 "ab cdef ghij"},
130
131 // Title with term multiple times.
132 { "ab ab", "ab", "ab ab"},
133
134 // Make sure quotes don't do a prefix match.
135 { "think", "\"thi\"", ""},
136 };
137 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
138 std::vector<std::string> titles;
139 base::SplitString(data[i].input, ';', &titles);
140 AddBookmarksWithTitles(titles);
141
142 std::vector<std::string> expected;
143 if (!data[i].expected.empty())
144 base::SplitString(data[i].expected, ';', &expected);
145
146 ExpectMatches(data[i].query, expected);
147
148 model_.reset(new BookmarkModel(NULL));
149 }
150 }
151
152 // Makes sure match positions are updated appropriately.
TEST_F(BookmarkIndexTest,MatchPositions)153 TEST_F(BookmarkIndexTest, MatchPositions) {
154 struct TestData {
155 const std::string title;
156 const std::string query;
157 const std::string expected;
158 } data[] = {
159 // Trivial test case of only one term, exact match.
160 { "a", "A", "0,1" },
161 { "foo bar", "bar", "4,7" },
162 { "fooey bark", "bar foo", "0,3:6,9"},
163 };
164 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
165 std::vector<std::string> titles;
166 titles.push_back(data[i].title);
167 AddBookmarksWithTitles(titles);
168
169 Snippet::MatchPositions expected_matches;
170 ExtractMatchPositions(data[i].expected, &expected_matches);
171 ExpectMatchPositions(data[i].query, expected_matches);
172
173 model_.reset(new BookmarkModel(NULL));
174 }
175 }
176
177 // Makes sure index is updated when a node is removed.
TEST_F(BookmarkIndexTest,Remove)178 TEST_F(BookmarkIndexTest, Remove) {
179 const char* input[] = { "a", "b" };
180 AddBookmarksWithTitles(input, ARRAYSIZE_UNSAFE(input));
181
182 // Remove the node and make sure we don't get back any results.
183 model_->Remove(model_->other_node(), 0);
184 ExpectMatches("A", NULL, 0U);
185 }
186
187 // Makes sure index is updated when a node's title is changed.
TEST_F(BookmarkIndexTest,ChangeTitle)188 TEST_F(BookmarkIndexTest, ChangeTitle) {
189 const char* input[] = { "a", "b" };
190 AddBookmarksWithTitles(input, ARRAYSIZE_UNSAFE(input));
191
192 // Remove the node and make sure we don't get back any results.
193 const char* expected[] = { "blah" };
194 model_->SetTitle(model_->other_node()->GetChild(0), ASCIIToUTF16("blah"));
195 ExpectMatches("BlAh", expected, ARRAYSIZE_UNSAFE(expected));
196 }
197
198 // Makes sure no more than max queries is returned.
TEST_F(BookmarkIndexTest,HonorMax)199 TEST_F(BookmarkIndexTest, HonorMax) {
200 const char* input[] = { "abcd", "abcde" };
201 AddBookmarksWithTitles(input, ARRAYSIZE_UNSAFE(input));
202
203 std::vector<bookmark_utils::TitleMatch> matches;
204 model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16("ABc"), 1, &matches);
205 EXPECT_EQ(1U, matches.size());
206 }
207
208 // Makes sure if the lower case string of a bookmark title is more characters
209 // than the upper case string no match positions are returned.
TEST_F(BookmarkIndexTest,EmptyMatchOnMultiwideLowercaseString)210 TEST_F(BookmarkIndexTest, EmptyMatchOnMultiwideLowercaseString) {
211 const BookmarkNode* n1 = model_->AddURL(model_->other_node(), 0,
212 WideToUTF16(L"\u0130 i"),
213 GURL("http://www.google.com"));
214
215 std::vector<bookmark_utils::TitleMatch> matches;
216 model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16("i"), 100, &matches);
217 ASSERT_EQ(1U, matches.size());
218 EXPECT_TRUE(matches[0].node == n1);
219 EXPECT_TRUE(matches[0].match_positions.empty());
220 }
221
TEST_F(BookmarkIndexTest,GetResultsSortedByTypedCount)222 TEST_F(BookmarkIndexTest, GetResultsSortedByTypedCount) {
223 // This ensures MessageLoop::current() will exist, which is needed by
224 // TestingProfile::BlockUntilHistoryProcessesPendingRequests().
225 MessageLoop loop(MessageLoop::TYPE_DEFAULT);
226 BrowserThread ui_thread(BrowserThread::UI, &loop);
227 BrowserThread file_thread(BrowserThread::FILE, &loop);
228
229 TestingProfile profile;
230 profile.CreateHistoryService(true, false);
231 profile.BlockUntilHistoryProcessesPendingRequests();
232 profile.CreateBookmarkModel(true);
233 profile.BlockUntilBookmarkModelLoaded();
234
235 BookmarkModel* model = profile.GetBookmarkModel();
236
237 HistoryService* const history_service =
238 profile.GetHistoryService(Profile::EXPLICIT_ACCESS);
239
240 history::URLDatabase* url_db = history_service->InMemoryDatabase();
241
242 struct TestData {
243 const GURL url;
244 const char* title;
245 const int typed_count;
246 } data[] = {
247 { GURL("http://www.google.com/"), "Google", 100 },
248 { GURL("http://maps.google.com/"), "Google Maps", 40 },
249 { GURL("http://docs.google.com/"), "Google Docs", 50 },
250 { GURL("http://reader.google.com/"), "Google Reader", 80 },
251 };
252
253 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
254 history::URLRow info(data[i].url);
255 info.set_title(UTF8ToUTF16(data[i].title));
256 info.set_typed_count(data[i].typed_count);
257 // Populate the InMemoryDatabase....
258 url_db->AddURL(info);
259 // Populate the BookmarkIndex.
260 model->AddURL(model->other_node(), i, UTF8ToUTF16(data[i].title),
261 data[i].url);
262 }
263
264 // Check that the InMemoryDatabase stored the URLs properly.
265 history::URLRow result1;
266 url_db->GetRowForURL(data[0].url, &result1);
267 EXPECT_EQ(data[0].title, UTF16ToUTF8(result1.title()));
268
269 history::URLRow result2;
270 url_db->GetRowForURL(data[1].url, &result2);
271 EXPECT_EQ(data[1].title, UTF16ToUTF8(result2.title()));
272
273 history::URLRow result3;
274 url_db->GetRowForURL(data[2].url, &result3);
275 EXPECT_EQ(data[2].title, UTF16ToUTF8(result3.title()));
276
277 history::URLRow result4;
278 url_db->GetRowForURL(data[3].url, &result4);
279 EXPECT_EQ(data[3].title, UTF16ToUTF8(result4.title()));
280
281 // Populate match nodes.
282 std::vector<bookmark_utils::TitleMatch> matches;
283 model->GetBookmarksWithTitlesMatching(ASCIIToUTF16("google"), 4, &matches);
284
285 // The resulting order should be:
286 // 1. Google (google.com) 100
287 // 2. Google Reader (google.com/reader) 80
288 // 3. Google Docs (docs.google.com) 50
289 // 4. Google Maps (maps.google.com) 40
290 EXPECT_EQ(4, static_cast<int>(matches.size()));
291 EXPECT_EQ(data[0].url, matches[0].node->GetURL());
292 EXPECT_EQ(data[3].url, matches[1].node->GetURL());
293 EXPECT_EQ(data[2].url, matches[2].node->GetURL());
294 EXPECT_EQ(data[1].url, matches[3].node->GetURL());
295
296 matches.clear();
297 // Select top two matches.
298 model->GetBookmarksWithTitlesMatching(ASCIIToUTF16("google"), 2, &matches);
299
300 EXPECT_EQ(2, static_cast<int>(matches.size()));
301 EXPECT_EQ(data[0].url, matches[0].node->GetURL());
302 EXPECT_EQ(data[3].url, matches[1].node->GetURL());
303 }
304
305