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