Home | History | Annotate | Download | only in autocomplete
      1 // Copyright (c) 2012 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 "chrome/browser/autocomplete/shortcuts_provider.h"
      6 
      7 #include <math.h>
      8 
      9 #include <algorithm>
     10 #include <functional>
     11 #include <set>
     12 #include <string>
     13 #include <vector>
     14 
     15 #include "base/memory/ref_counted.h"
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/prefs/pref_service.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "base/strings/utf_string_conversions.h"
     20 #include "chrome/browser/autocomplete/autocomplete_input.h"
     21 #include "chrome/browser/autocomplete/autocomplete_match.h"
     22 #include "chrome/browser/autocomplete/autocomplete_provider.h"
     23 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
     24 #include "chrome/browser/autocomplete/autocomplete_result.h"
     25 #include "chrome/browser/history/history_service.h"
     26 #include "chrome/browser/history/in_memory_url_index.h"
     27 #include "chrome/browser/history/shortcuts_backend.h"
     28 #include "chrome/browser/history/shortcuts_backend_factory.h"
     29 #include "chrome/browser/history/url_database.h"
     30 #include "chrome/common/pref_names.h"
     31 #include "chrome/test/base/testing_profile.h"
     32 #include "content/public/test/test_browser_thread.h"
     33 #include "testing/gtest/include/gtest/gtest.h"
     34 
     35 using content::BrowserThread;
     36 using history::ShortcutsBackend;
     37 
     38 namespace {
     39 
     40 struct TestShortcutInfo {
     41   std::string guid;
     42   std::string url;
     43   std::string title;  // The text that orginally was searched for.
     44   std::string contents;
     45   std::string contents_class;
     46   std::string description;
     47   std::string description_class;
     48   int typed_count;
     49   int days_from_now;
     50 } shortcut_test_db[] = {
     51   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E0",
     52     "http://www.google.com/", "goog",
     53     "Google", "0,1,4,0", "Google", "0,3,4,1", 100, 1 },
     54   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E1",
     55     "http://slashdot.org/", "slash",
     56     "slashdot.org", "0,3,5,1",
     57     "Slashdot - News for nerds, stuff that matters", "0,2,5,0", 100, 0},
     58   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E2",
     59     "http://slashdot.org/", "news",
     60     "slashdot.org", "0,1",
     61     "Slashdot - News for nerds, stuff that matters", "0,0,11,2,15,0", 5, 0},
     62   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E3",
     63     "http://sports.yahoo.com/", "news",
     64     "sports.yahoo.com", "0,1",
     65     "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
     66     "0,0,23,2,27,0", 5, 2},
     67   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E4",
     68     "http://www.cnn.com/index.html", "news weather",
     69     "www.cnn.com/index.html", "0,1",
     70     "CNN.com - Breaking News, U.S., World, Weather, Entertainment & Video",
     71     "0,0,19,2,23,0,38,2,45,0", 10, 1},
     72   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E5",
     73     "http://sports.yahoo.com/", "nhl scores",
     74     "sports.yahoo.com", "0,1",
     75     "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
     76     "0,0,29,2,35,0", 10, 1},
     77   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E6",
     78     "http://www.nhl.com/scores/index.html", "nhl scores",
     79     "www.nhl.com/scores/index.html", "0,1,4,3,7,1",
     80     "January 13, 2010 - NHL.com - Scores", "0,0,19,2,22,0,29,2,35,0", 1, 5},
     81   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E7",
     82     "http://www.testsite.com/a.html", "just",
     83     "www.testsite.com/a.html", "0,1",
     84     "Test - site - just a test", "0,0,14,2,18,0", 1, 5},
     85   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E8",
     86     "http://www.testsite.com/b.html", "just",
     87     "www.testsite.com/b.html", "0,1",
     88     "Test - site - just a test", "0,0,14,2,18,0", 2, 5},
     89   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E9",
     90     "http://www.testsite.com/c.html", "just",
     91     "www.testsite.com/c.html", "0,1",
     92     "Test - site - just a test", "0,0,14,2,18,0", 1, 8},
     93   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EA",
     94     "http://www.testsite.com/d.html", "just a",
     95     "www.testsite.com/d.html", "0,1",
     96     "Test - site - just a test", "0,0,14,2,18,0", 1, 12},
     97   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EB",
     98     "http://www.testsite.com/e.html", "just a t",
     99     "www.testsite.com/e.html", "0,1",
    100     "Test - site - just a test", "0,0,14,2,18,0", 1, 12},
    101   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EC",
    102     "http://www.testsite.com/f.html", "just a te",
    103     "www.testsite.com/f.html", "0,1",
    104     "Test - site - just a test", "0,0,14,2,18,0", 1, 12},
    105   { "BD85DBA2-8C29-49F9-84AE-48E1E90880ED",
    106     "http://www.daysagotest.com/a.html", "ago",
    107     "www.daysagotest.com/a.html", "0,1,8,3,11,1",
    108     "Test - site", "0,0", 1, 1},
    109   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EE",
    110     "http://www.daysagotest.com/b.html", "ago",
    111     "www.daysagotest.com/b.html", "0,1,8,3,11,1",
    112     "Test - site", "0,0", 1, 2},
    113   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EF",
    114     "http://www.daysagotest.com/c.html", "ago",
    115     "www.daysagotest.com/c.html", "0,1,8,3,11,1",
    116     "Test - site", "0,0", 1, 3},
    117   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F0",
    118     "http://www.daysagotest.com/d.html", "ago",
    119     "www.daysagotest.com/d.html", "0,1,8,3,11,1",
    120     "Test - site", "0,0", 1, 4},
    121 };
    122 
    123 }  // namespace
    124 
    125 class ShortcutsProviderTest : public testing::Test,
    126                               public AutocompleteProviderListener {
    127  public:
    128   ShortcutsProviderTest();
    129 
    130   // AutocompleteProviderListener:
    131   virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
    132 
    133  protected:
    134   class SetShouldContain
    135       : public std::unary_function<const std::string&, std::set<std::string> > {
    136    public:
    137     explicit SetShouldContain(const ACMatches& matched_urls);
    138 
    139     void operator()(const std::string& expected);
    140     std::set<std::string> Leftovers() const { return matches_; }
    141 
    142    private:
    143     std::set<std::string> matches_;
    144   };
    145 
    146   virtual void SetUp();
    147   virtual void TearDown();
    148 
    149   // Fills test data into the provider.
    150   void FillData(TestShortcutInfo* db, size_t db_size);
    151 
    152   // Runs an autocomplete query on |text| and checks to see that the returned
    153   // results' destination URLs match those provided. |expected_urls| does not
    154   // need to be in sorted order.
    155   void RunTest(const string16 text,
    156                std::vector<std::string> expected_urls,
    157                std::string expected_top_result);
    158 
    159   base::MessageLoopForUI message_loop_;
    160   content::TestBrowserThread ui_thread_;
    161   content::TestBrowserThread file_thread_;
    162 
    163   TestingProfile profile_;
    164 
    165   ACMatches ac_matches_;  // The resulting matches after running RunTest.
    166 
    167   scoped_refptr<ShortcutsBackend> backend_;
    168   scoped_refptr<ShortcutsProvider> provider_;
    169 };
    170 
    171 ShortcutsProviderTest::ShortcutsProviderTest()
    172     : ui_thread_(BrowserThread::UI, &message_loop_),
    173       file_thread_(BrowserThread::FILE, &message_loop_) {
    174 }
    175 
    176 void ShortcutsProviderTest::OnProviderUpdate(bool updated_matches) {}
    177 
    178 void ShortcutsProviderTest::SetUp() {
    179   ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
    180       &profile_, &ShortcutsBackendFactory::BuildProfileNoDatabaseForTesting);
    181   backend_ = ShortcutsBackendFactory::GetForProfile(&profile_);
    182   ASSERT_TRUE(backend_.get());
    183   ASSERT_TRUE(profile_.CreateHistoryService(true, false));
    184   provider_ = new ShortcutsProvider(this, &profile_);
    185   FillData(shortcut_test_db, arraysize(shortcut_test_db));
    186 }
    187 
    188 void ShortcutsProviderTest::TearDown() {
    189   // Run all pending tasks or else some threads hold on to the message loop
    190   // and prevent it from being deleted.
    191   message_loop_.RunUntilIdle();
    192   provider_ = NULL;
    193 }
    194 
    195 void ShortcutsProviderTest::FillData(TestShortcutInfo* db, size_t db_size) {
    196   DCHECK(provider_.get());
    197   size_t expected_size = backend_->shortcuts_map().size() + db_size;
    198   for (size_t i = 0; i < db_size; ++i) {
    199     const TestShortcutInfo& cur = db[i];
    200     ShortcutsBackend::Shortcut shortcut(cur.guid,
    201         ASCIIToUTF16(cur.title), GURL(cur.url), ASCIIToUTF16(cur.contents),
    202         AutocompleteMatch::ClassificationsFromString(cur.contents_class),
    203         ASCIIToUTF16(cur.description),
    204         AutocompleteMatch::ClassificationsFromString(cur.description_class),
    205         base::Time::Now() - base::TimeDelta::FromDays(cur.days_from_now),
    206         cur.typed_count);
    207     backend_->AddShortcut(shortcut);
    208   }
    209   EXPECT_EQ(expected_size, backend_->shortcuts_map().size());
    210 }
    211 
    212 ShortcutsProviderTest::SetShouldContain::SetShouldContain(
    213     const ACMatches& matched_urls) {
    214   for (ACMatches::const_iterator iter = matched_urls.begin();
    215        iter != matched_urls.end(); ++iter)
    216     matches_.insert(iter->destination_url.spec());
    217 }
    218 
    219 void ShortcutsProviderTest::SetShouldContain::operator()(
    220     const std::string& expected) {
    221   EXPECT_EQ(1U, matches_.erase(expected));
    222 }
    223 
    224 void ShortcutsProviderTest::RunTest(const string16 text,
    225                                    std::vector<std::string> expected_urls,
    226                                    std::string expected_top_result) {
    227   std::sort(expected_urls.begin(), expected_urls.end());
    228 
    229   base::MessageLoop::current()->RunUntilIdle();
    230   AutocompleteInput input(text, string16::npos, string16(), GURL(),
    231                           AutocompleteInput::INVALID_SPEC, false, false, true,
    232                           AutocompleteInput::ALL_MATCHES);
    233   provider_->Start(input, false);
    234   EXPECT_TRUE(provider_->done());
    235 
    236   ac_matches_ = provider_->matches();
    237 
    238   // We should have gotten back at most AutocompleteProvider::kMaxMatches.
    239   EXPECT_LE(ac_matches_.size(), AutocompleteProvider::kMaxMatches);
    240 
    241   // If the number of expected and actual matches aren't equal then we need
    242   // test no further, but let's do anyway so that we know which URLs failed.
    243   EXPECT_EQ(expected_urls.size(), ac_matches_.size());
    244 
    245   // Verify that all expected URLs were found and that all found URLs
    246   // were expected.
    247   std::set<std::string> Leftovers =
    248       for_each(expected_urls.begin(), expected_urls.end(),
    249                SetShouldContain(ac_matches_)).Leftovers();
    250   EXPECT_EQ(0U, Leftovers.size());
    251 
    252   // See if we got the expected top scorer.
    253   if (!ac_matches_.empty()) {
    254     std::partial_sort(ac_matches_.begin(), ac_matches_.begin() + 1,
    255                       ac_matches_.end(), AutocompleteMatch::MoreRelevant);
    256     EXPECT_EQ(expected_top_result, ac_matches_[0].destination_url.spec());
    257   }
    258 
    259   // No shortcuts matches are allowed to be inlined no matter how highly
    260   // they score.
    261   for (ACMatches::const_iterator it = ac_matches_.begin();
    262        it != ac_matches_.end(); ++it)
    263     EXPECT_FALSE(it->allowed_to_be_default_match);
    264 }
    265 
    266 TEST_F(ShortcutsProviderTest, SimpleSingleMatch) {
    267   string16 text(ASCIIToUTF16("go"));
    268   std::string expected_url("http://www.google.com/");
    269   std::vector<std::string> expected_urls;
    270   expected_urls.push_back(expected_url);
    271   RunTest(text, expected_urls, expected_url);
    272 }
    273 
    274 TEST_F(ShortcutsProviderTest, MultiMatch) {
    275   string16 text(ASCIIToUTF16("NEWS"));
    276   std::vector<std::string> expected_urls;
    277   // Scores high because of completion length.
    278   expected_urls.push_back("http://slashdot.org/");
    279   // Scores high because of visit count.
    280   expected_urls.push_back("http://sports.yahoo.com/");
    281   // Scores high because of visit count but less match span,
    282   // which is more important.
    283   expected_urls.push_back("http://www.cnn.com/index.html");
    284   RunTest(text, expected_urls, "http://slashdot.org/");
    285 }
    286 
    287 TEST_F(ShortcutsProviderTest, TypedCountMatches) {
    288   string16 text(ASCIIToUTF16("just"));
    289   std::vector<std::string> expected_urls;
    290   expected_urls.push_back("http://www.testsite.com/b.html");
    291   expected_urls.push_back("http://www.testsite.com/a.html");
    292   expected_urls.push_back("http://www.testsite.com/c.html");
    293   RunTest(text, expected_urls, "http://www.testsite.com/b.html");
    294 }
    295 
    296 TEST_F(ShortcutsProviderTest, FragmentLengthMatches) {
    297   string16 text(ASCIIToUTF16("just a"));
    298   std::vector<std::string> expected_urls;
    299   expected_urls.push_back("http://www.testsite.com/d.html");
    300   expected_urls.push_back("http://www.testsite.com/e.html");
    301   expected_urls.push_back("http://www.testsite.com/f.html");
    302   RunTest(text, expected_urls, "http://www.testsite.com/d.html");
    303 }
    304 
    305 TEST_F(ShortcutsProviderTest, DaysAgoMatches) {
    306   string16 text(ASCIIToUTF16("ago"));
    307   std::vector<std::string> expected_urls;
    308   expected_urls.push_back("http://www.daysagotest.com/a.html");
    309   expected_urls.push_back("http://www.daysagotest.com/b.html");
    310   expected_urls.push_back("http://www.daysagotest.com/c.html");
    311   RunTest(text, expected_urls, "http://www.daysagotest.com/a.html");
    312 }
    313 
    314 // Helper class to make running tests of ClassifyAllMatchesInString() more
    315 // convenient.
    316 class ClassifyTest {
    317  public:
    318   ClassifyTest(const string16& text, ACMatchClassifications matches);
    319   ~ClassifyTest();
    320 
    321   ACMatchClassifications RunTest(const string16& find_text);
    322 
    323  private:
    324   const string16 text_;
    325   const ACMatchClassifications matches_;
    326 };
    327 
    328 ClassifyTest::ClassifyTest(const string16& text, ACMatchClassifications matches)
    329     : text_(text),
    330       matches_(matches) {
    331 }
    332 
    333 ClassifyTest::~ClassifyTest() {
    334 }
    335 
    336 ACMatchClassifications ClassifyTest::RunTest(const string16& find_text) {
    337   return ShortcutsProvider::ClassifyAllMatchesInString(find_text,
    338       ShortcutsProvider::CreateWordMapForString(find_text), text_, matches_);
    339 }
    340 
    341 TEST_F(ShortcutsProviderTest, ClassifyAllMatchesInString) {
    342   ACMatchClassifications matches;
    343   matches.push_back(ACMatchClassification(0, ACMatchClassification::NONE));
    344   ClassifyTest classify_test(ASCIIToUTF16("A man, a plan, a canal Panama"),
    345                              matches);
    346 
    347   ACMatchClassifications spans_a = classify_test.RunTest(ASCIIToUTF16("man"));
    348   // ACMatch spans should be: '--MMM------------------------'
    349   ASSERT_EQ(3U, spans_a.size());
    350   EXPECT_EQ(0U, spans_a[0].offset);
    351   EXPECT_EQ(ACMatchClassification::NONE, spans_a[0].style);
    352   EXPECT_EQ(2U, spans_a[1].offset);
    353   EXPECT_EQ(ACMatchClassification::MATCH, spans_a[1].style);
    354   EXPECT_EQ(5U, spans_a[2].offset);
    355   EXPECT_EQ(ACMatchClassification::NONE, spans_a[2].style);
    356 
    357   ACMatchClassifications spans_b = classify_test.RunTest(ASCIIToUTF16("man p"));
    358   // ACMatch spans should be: '--MMM----M-------------M-----'
    359   ASSERT_EQ(7U, spans_b.size());
    360   EXPECT_EQ(0U, spans_b[0].offset);
    361   EXPECT_EQ(ACMatchClassification::NONE, spans_b[0].style);
    362   EXPECT_EQ(2U, spans_b[1].offset);
    363   EXPECT_EQ(ACMatchClassification::MATCH, spans_b[1].style);
    364   EXPECT_EQ(5U, spans_b[2].offset);
    365   EXPECT_EQ(ACMatchClassification::NONE, spans_b[2].style);
    366   EXPECT_EQ(9U, spans_b[3].offset);
    367   EXPECT_EQ(ACMatchClassification::MATCH, spans_b[3].style);
    368   EXPECT_EQ(10U, spans_b[4].offset);
    369   EXPECT_EQ(ACMatchClassification::NONE, spans_b[4].style);
    370   EXPECT_EQ(23U, spans_b[5].offset);
    371   EXPECT_EQ(ACMatchClassification::MATCH, spans_b[5].style);
    372   EXPECT_EQ(24U, spans_b[6].offset);
    373   EXPECT_EQ(ACMatchClassification::NONE, spans_b[6].style);
    374 
    375   ACMatchClassifications spans_c =
    376       classify_test.RunTest(ASCIIToUTF16("man plan panama"));
    377   // ACMatch spans should be:'--MMM----MMMM----------MMMMMM'
    378   ASSERT_EQ(6U, spans_c.size());
    379   EXPECT_EQ(0U, spans_c[0].offset);
    380   EXPECT_EQ(ACMatchClassification::NONE, spans_c[0].style);
    381   EXPECT_EQ(2U, spans_c[1].offset);
    382   EXPECT_EQ(ACMatchClassification::MATCH, spans_c[1].style);
    383   EXPECT_EQ(5U, spans_c[2].offset);
    384   EXPECT_EQ(ACMatchClassification::NONE, spans_c[2].style);
    385   EXPECT_EQ(9U, spans_c[3].offset);
    386   EXPECT_EQ(ACMatchClassification::MATCH, spans_c[3].style);
    387   EXPECT_EQ(13U, spans_c[4].offset);
    388   EXPECT_EQ(ACMatchClassification::NONE, spans_c[4].style);
    389   EXPECT_EQ(23U, spans_c[5].offset);
    390   EXPECT_EQ(ACMatchClassification::MATCH, spans_c[5].style);
    391 
    392   ClassifyTest classify_test2(ASCIIToUTF16("Yahoo! Sports - Sports News, "
    393       "Scores, Rumors, Fantasy Games, and more"), matches);
    394 
    395   ACMatchClassifications spans_d = classify_test2.RunTest(ASCIIToUTF16("ne"));
    396   // ACMatch spans should match first two letters of the "news".
    397   ASSERT_EQ(3U, spans_d.size());
    398   EXPECT_EQ(0U, spans_d[0].offset);
    399   EXPECT_EQ(ACMatchClassification::NONE, spans_d[0].style);
    400   EXPECT_EQ(23U, spans_d[1].offset);
    401   EXPECT_EQ(ACMatchClassification::MATCH, spans_d[1].style);
    402   EXPECT_EQ(25U, spans_d[2].offset);
    403   EXPECT_EQ(ACMatchClassification::NONE, spans_d[2].style);
    404 
    405   ACMatchClassifications spans_e =
    406       classify_test2.RunTest(ASCIIToUTF16("news r"));
    407   // ACMatch spans should be the same as original matches.
    408   ASSERT_EQ(15U, spans_e.size());
    409   EXPECT_EQ(0U, spans_e[0].offset);
    410   EXPECT_EQ(ACMatchClassification::NONE, spans_e[0].style);
    411   // "r" in "Sports".
    412   EXPECT_EQ(10U, spans_e[1].offset);
    413   EXPECT_EQ(ACMatchClassification::MATCH, spans_e[1].style);
    414   EXPECT_EQ(11U, spans_e[2].offset);
    415   EXPECT_EQ(ACMatchClassification::NONE, spans_e[2].style);
    416   // "r" in second "Sports".
    417   EXPECT_EQ(19U, spans_e[3].offset);
    418   EXPECT_EQ(ACMatchClassification::MATCH, spans_e[3].style);
    419   EXPECT_EQ(20U, spans_e[4].offset);
    420   EXPECT_EQ(ACMatchClassification::NONE, spans_e[4].style);
    421   // "News".
    422   EXPECT_EQ(23U, spans_e[5].offset);
    423   EXPECT_EQ(ACMatchClassification::MATCH, spans_e[5].style);
    424   EXPECT_EQ(27U, spans_e[6].offset);
    425   EXPECT_EQ(ACMatchClassification::NONE, spans_e[6].style);
    426   // "r" in "Scores".
    427   EXPECT_EQ(32U, spans_e[7].offset);
    428   EXPECT_EQ(ACMatchClassification::MATCH, spans_e[7].style);
    429   EXPECT_EQ(33U, spans_e[8].offset);
    430   EXPECT_EQ(ACMatchClassification::NONE, spans_e[8].style);
    431   // First "r" in "Rumors".
    432   EXPECT_EQ(37U, spans_e[9].offset);
    433   EXPECT_EQ(ACMatchClassification::MATCH, spans_e[9].style);
    434   EXPECT_EQ(38U, spans_e[10].offset);
    435   EXPECT_EQ(ACMatchClassification::NONE, spans_e[10].style);
    436   // Second "r" in "Rumors".
    437   EXPECT_EQ(41U, spans_e[11].offset);
    438   EXPECT_EQ(ACMatchClassification::MATCH, spans_e[11].style);
    439   EXPECT_EQ(42U, spans_e[12].offset);
    440   EXPECT_EQ(ACMatchClassification::NONE, spans_e[12].style);
    441   // "r" in "more".
    442   EXPECT_EQ(66U, spans_e[13].offset);
    443   EXPECT_EQ(ACMatchClassification::MATCH, spans_e[13].style);
    444   EXPECT_EQ(67U, spans_e[14].offset);
    445   EXPECT_EQ(ACMatchClassification::NONE, spans_e[14].style);
    446 
    447   matches.clear();
    448   matches.push_back(ACMatchClassification(0, ACMatchClassification::URL));
    449   ClassifyTest classify_test3(ASCIIToUTF16("livescore.goal.com"), matches);
    450 
    451   ACMatchClassifications spans_f = classify_test3.RunTest(ASCIIToUTF16("go"));
    452   // ACMatch spans should match first two letters of the "goal".
    453   ASSERT_EQ(3U, spans_f.size());
    454   EXPECT_EQ(0U, spans_f[0].offset);
    455   EXPECT_EQ(ACMatchClassification::URL, spans_f[0].style);
    456   EXPECT_EQ(10U, spans_f[1].offset);
    457   EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
    458             spans_f[1].style);
    459   EXPECT_EQ(12U, spans_f[2].offset);
    460   EXPECT_EQ(ACMatchClassification::URL, spans_f[2].style);
    461 
    462   matches.clear();
    463   matches.push_back(ACMatchClassification(0, ACMatchClassification::NONE));
    464   matches.push_back(ACMatchClassification(13, ACMatchClassification::URL));
    465   ClassifyTest classify_test4(ASCIIToUTF16("Email login: mail.somecorp.com"),
    466                               matches);
    467 
    468   ACMatchClassifications spans_g = classify_test4.RunTest(ASCIIToUTF16("ail"));
    469   ASSERT_EQ(6U, spans_g.size());
    470   EXPECT_EQ(0U, spans_g[0].offset);
    471   EXPECT_EQ(ACMatchClassification::NONE, spans_g[0].style);
    472   EXPECT_EQ(2U, spans_g[1].offset);
    473   EXPECT_EQ(ACMatchClassification::MATCH, spans_g[1].style);
    474   EXPECT_EQ(5U, spans_g[2].offset);
    475   EXPECT_EQ(ACMatchClassification::NONE, spans_g[2].style);
    476   EXPECT_EQ(13U, spans_g[3].offset);
    477   EXPECT_EQ(ACMatchClassification::URL, spans_g[3].style);
    478   EXPECT_EQ(14U, spans_g[4].offset);
    479   EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
    480             spans_g[4].style);
    481   EXPECT_EQ(17U, spans_g[5].offset);
    482   EXPECT_EQ(ACMatchClassification::URL, spans_g[5].style);
    483 
    484   ACMatchClassifications spans_h =
    485       classify_test4.RunTest(ASCIIToUTF16("lo log"));
    486   ASSERT_EQ(4U, spans_h.size());
    487   EXPECT_EQ(0U, spans_h[0].offset);
    488   EXPECT_EQ(ACMatchClassification::NONE, spans_h[0].style);
    489   EXPECT_EQ(6U, spans_h[1].offset);
    490   EXPECT_EQ(ACMatchClassification::MATCH, spans_h[1].style);
    491   EXPECT_EQ(9U, spans_h[2].offset);
    492   EXPECT_EQ(ACMatchClassification::NONE, spans_h[2].style);
    493   EXPECT_EQ(13U, spans_h[3].offset);
    494   EXPECT_EQ(ACMatchClassification::URL, spans_h[3].style);
    495 
    496   ACMatchClassifications spans_i =
    497       classify_test4.RunTest(ASCIIToUTF16("ail em"));
    498   // 'Email' and 'ail' should be matched.
    499   ASSERT_EQ(5U, spans_i.size());
    500   EXPECT_EQ(0U, spans_i[0].offset);
    501   EXPECT_EQ(ACMatchClassification::MATCH, spans_i[0].style);
    502   EXPECT_EQ(5U, spans_i[1].offset);
    503   EXPECT_EQ(ACMatchClassification::NONE, spans_i[1].style);
    504   EXPECT_EQ(13U, spans_i[2].offset);
    505   EXPECT_EQ(ACMatchClassification::URL, spans_i[2].style);
    506   EXPECT_EQ(14U, spans_i[3].offset);
    507   EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
    508             spans_i[3].style);
    509   EXPECT_EQ(17U, spans_i[4].offset);
    510   EXPECT_EQ(ACMatchClassification::URL, spans_i[4].style);
    511 
    512   // Some web sites do not have a description.  If the string being searched is
    513   // empty, the classifications must also be empty: http://crbug.com/148647
    514   // Extra parens in the next line hack around C++03's "most vexing parse".
    515   class ClassifyTest classify_test5((string16()), ACMatchClassifications());
    516   ACMatchClassifications spans_j = classify_test5.RunTest(ASCIIToUTF16("man"));
    517   ASSERT_EQ(0U, spans_j.size());
    518 
    519   // Matches which end at beginning of classification merge properly.
    520   matches.clear();
    521   matches.push_back(ACMatchClassification(0, ACMatchClassification::DIM));
    522   matches.push_back(ACMatchClassification(9, ACMatchClassification::NONE));
    523   ClassifyTest classify_test6(ASCIIToUTF16("html password example"), matches);
    524 
    525   // Extra space in the next string avoids having the string be a prefix of the
    526   // text above, which would allow for two different valid classification sets,
    527   // one of which uses two spans (the first of which would mark all of "html
    528   // pass" as a match) and one which uses four (which marks the individual words
    529   // as matches but not the space between them).  This way only the latter is
    530   // valid.
    531   ACMatchClassifications spans_k =
    532       classify_test6.RunTest(ASCIIToUTF16("html  pass"));
    533   ASSERT_EQ(4U, spans_k.size());
    534   EXPECT_EQ(0U, spans_k[0].offset);
    535   EXPECT_EQ(ACMatchClassification::DIM | ACMatchClassification::MATCH,
    536             spans_k[0].style);
    537   EXPECT_EQ(4U, spans_k[1].offset);
    538   EXPECT_EQ(ACMatchClassification::DIM, spans_k[1].style);
    539   EXPECT_EQ(5U, spans_k[2].offset);
    540   EXPECT_EQ(ACMatchClassification::DIM | ACMatchClassification::MATCH,
    541             spans_k[2].style);
    542   EXPECT_EQ(9U, spans_k[3].offset);
    543   EXPECT_EQ(ACMatchClassification::NONE, spans_k[3].style);
    544 
    545   // Multiple matches with both beginning and end at beginning of
    546   // classifications merge properly.
    547   matches.clear();
    548   matches.push_back(ACMatchClassification(0, ACMatchClassification::URL));
    549   matches.push_back(ACMatchClassification(11, ACMatchClassification::NONE));
    550   ClassifyTest classify_test7(ASCIIToUTF16("http://a.co is great"), matches);
    551 
    552   ACMatchClassifications spans_l =
    553       classify_test7.RunTest(ASCIIToUTF16("ht co"));
    554   ASSERT_EQ(4U, spans_l.size());
    555   EXPECT_EQ(0U, spans_l[0].offset);
    556   EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
    557             spans_l[0].style);
    558   EXPECT_EQ(2U, spans_l[1].offset);
    559   EXPECT_EQ(ACMatchClassification::URL, spans_l[1].style);
    560   EXPECT_EQ(9U, spans_l[2].offset);
    561   EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
    562             spans_l[2].style);
    563   EXPECT_EQ(11U, spans_l[3].offset);
    564   EXPECT_EQ(ACMatchClassification::NONE, spans_l[3].style);
    565 }
    566 
    567 TEST_F(ShortcutsProviderTest, CalculateScore) {
    568   ACMatchClassifications spans_content;
    569   spans_content.push_back(ACMatchClassification(0, ACMatchClassification::URL));
    570   spans_content.push_back(ACMatchClassification(
    571       4, ACMatchClassification::MATCH | ACMatchClassification::URL));
    572   spans_content.push_back(ACMatchClassification(8, ACMatchClassification::URL));
    573   ACMatchClassifications spans_description;
    574   spans_description.push_back(
    575       ACMatchClassification(0, ACMatchClassification::NONE));
    576   spans_description.push_back(
    577       ACMatchClassification(2, ACMatchClassification::MATCH));
    578   ShortcutsBackend::Shortcut shortcut(std::string(),
    579       ASCIIToUTF16("test"), GURL("http://www.test.com"),
    580       ASCIIToUTF16("www.test.com"), spans_content, ASCIIToUTF16("A test"),
    581       spans_description, base::Time::Now(), 1);
    582 
    583   // Maximal score.
    584   const int max_relevance = AutocompleteResult::kLowestDefaultScore - 1;
    585   const int kMaxScore = provider_->CalculateScore(
    586       ASCIIToUTF16("test"), shortcut, max_relevance);
    587 
    588   // Score decreases as percent of the match is decreased.
    589   int score_three_quarters =
    590       provider_->CalculateScore(ASCIIToUTF16("tes"), shortcut, max_relevance);
    591   EXPECT_LT(score_three_quarters, kMaxScore);
    592   int score_one_half =
    593       provider_->CalculateScore(ASCIIToUTF16("te"), shortcut, max_relevance);
    594   EXPECT_LT(score_one_half, score_three_quarters);
    595   int score_one_quarter =
    596       provider_->CalculateScore(ASCIIToUTF16("t"), shortcut, max_relevance);
    597   EXPECT_LT(score_one_quarter, score_one_half);
    598 
    599   // Should decay with time - one week.
    600   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(7);
    601   int score_week_old =
    602       provider_->CalculateScore(ASCIIToUTF16("test"), shortcut, max_relevance);
    603   EXPECT_LT(score_week_old, kMaxScore);
    604 
    605   // Should decay more in two weeks.
    606   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
    607   int score_two_weeks_old =
    608       provider_->CalculateScore(ASCIIToUTF16("test"), shortcut, max_relevance);
    609   EXPECT_LT(score_two_weeks_old, score_week_old);
    610 
    611   // But not if it was activly clicked on. 2 hits slow decaying power.
    612   shortcut.number_of_hits = 2;
    613   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
    614   int score_popular_two_weeks_old =
    615       provider_->CalculateScore(ASCIIToUTF16("test"), shortcut, max_relevance);
    616   EXPECT_LT(score_two_weeks_old, score_popular_two_weeks_old);
    617   // But still decayed.
    618   EXPECT_LT(score_popular_two_weeks_old, kMaxScore);
    619 
    620   // 3 hits slow decaying power even more.
    621   shortcut.number_of_hits = 3;
    622   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
    623   int score_more_popular_two_weeks_old =
    624       provider_->CalculateScore(ASCIIToUTF16("test"), shortcut, max_relevance);
    625   EXPECT_LT(score_two_weeks_old, score_more_popular_two_weeks_old);
    626   EXPECT_LT(score_popular_two_weeks_old, score_more_popular_two_weeks_old);
    627   // But still decayed.
    628   EXPECT_LT(score_more_popular_two_weeks_old, kMaxScore);
    629 }
    630 
    631 TEST_F(ShortcutsProviderTest, DeleteMatch) {
    632   TestShortcutInfo shortcuts_to_test_delete[3] = {
    633     { "BD85DBA2-8C29-49F9-84AE-48E1E90880F1",
    634       "http://www.deletetest.com/1.html", "delete",
    635       "http://www.deletetest.com/1.html", "0,2",
    636       "Erase this shortcut!", "0,0", 1, 1},
    637     { "BD85DBA2-8C29-49F9-84AE-48E1E90880F2",
    638       "http://www.deletetest.com/1.html", "erase",
    639       "http://www.deletetest.com/1.html", "0,2",
    640       "Erase this shortcut!", "0,0", 1, 1},
    641     { "BD85DBA2-8C29-49F9-84AE-48E1E90880F3",
    642       "http://www.deletetest.com/2.html", "delete",
    643       "http://www.deletetest.com/2.html", "0,2",
    644       "Erase this shortcut!", "0,0", 1, 1},
    645   };
    646 
    647   size_t original_shortcuts_count = backend_->shortcuts_map().size();
    648 
    649   FillData(shortcuts_to_test_delete, arraysize(shortcuts_to_test_delete));
    650 
    651   EXPECT_EQ(original_shortcuts_count + 3, backend_->shortcuts_map().size());
    652   EXPECT_FALSE(backend_->shortcuts_map().end() ==
    653                backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
    654   EXPECT_FALSE(backend_->shortcuts_map().end() ==
    655                backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
    656 
    657   AutocompleteMatch match(
    658       provider_.get(), 1200, true, AutocompleteMatchType::HISTORY_TITLE);
    659 
    660   match.destination_url = GURL(shortcuts_to_test_delete[0].url);
    661   match.contents = ASCIIToUTF16(shortcuts_to_test_delete[0].contents);
    662   match.description = ASCIIToUTF16(shortcuts_to_test_delete[0].description);
    663 
    664   provider_->DeleteMatch(match);
    665 
    666   // |shortcuts_to_test_delete[0]| and |shortcuts_to_test_delete[1]| should be
    667   // deleted, but not |shortcuts_to_test_delete[2]| as it has different url.
    668   EXPECT_EQ(original_shortcuts_count + 1, backend_->shortcuts_map().size());
    669   EXPECT_FALSE(backend_->shortcuts_map().end() ==
    670                backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
    671   EXPECT_TRUE(backend_->shortcuts_map().end() ==
    672               backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
    673 
    674   match.destination_url = GURL(shortcuts_to_test_delete[2].url);
    675   match.contents = ASCIIToUTF16(shortcuts_to_test_delete[2].contents);
    676   match.description = ASCIIToUTF16(shortcuts_to_test_delete[2].description);
    677 
    678   provider_->DeleteMatch(match);
    679   EXPECT_EQ(original_shortcuts_count, backend_->shortcuts_map().size());
    680   EXPECT_TRUE(backend_->shortcuts_map().end() ==
    681               backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
    682 }
    683