Home | History | Annotate | Download | only in bookmarks
      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:
     26   BookmarkIndexTest() : model_(new BookmarkModel(NULL)) {}
     27 
     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 
     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 
     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 
     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 
     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 
     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.
    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.
    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.
    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.
    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.
    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.
    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 
    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