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