• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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