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/history_url_provider.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/path_service.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "base/time/time.h"
     14 #include "chrome/browser/autocomplete/autocomplete_match.h"
     15 #include "chrome/browser/autocomplete/autocomplete_provider.h"
     16 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
     17 #include "chrome/browser/autocomplete/history_quick_provider.h"
     18 #include "chrome/browser/history/history_service.h"
     19 #include "chrome/browser/history/history_service_factory.h"
     20 #include "chrome/browser/search_engines/template_url.h"
     21 #include "chrome/browser/search_engines/template_url_service.h"
     22 #include "chrome/browser/search_engines/template_url_service_factory.h"
     23 #include "chrome/common/net/url_fixer_upper.h"
     24 #include "chrome/test/base/testing_browser_process.h"
     25 #include "chrome/test/base/testing_profile.h"
     26 #include "content/public/test/test_browser_thread_bundle.h"
     27 #include "testing/gtest/include/gtest/gtest.h"
     28 
     29 using base::Time;
     30 using base::TimeDelta;
     31 
     32 using content::TestBrowserThreadBundle;
     33 
     34 struct TestURLInfo {
     35   const char* url;
     36   const char* title;
     37   int visit_count;
     38   int typed_count;
     39 } test_db[] = {
     40   {"http://www.google.com/", "Google", 3, 3},
     41 
     42   // High-quality pages should get a host synthesized as a lower-quality match.
     43   {"http://slashdot.org/favorite_page.html", "Favorite page", 200, 100},
     44 
     45   // Less popular pages should have hosts synthesized as higher-quality
     46   // matches.
     47   {"http://kerneltrap.org/not_very_popular.html", "Less popular", 4, 0},
     48 
     49   // Unpopular pages should not appear in the results at all.
     50   {"http://freshmeat.net/unpopular.html", "Unpopular", 1, 0},
     51 
     52   // If a host has a match, we should pick it up during host synthesis.
     53   {"http://news.google.com/?ned=us&topic=n", "Google News - U.S.", 2, 2},
     54   {"http://news.google.com/", "Google News", 1, 1},
     55 
     56   // Matches that are normally not inline-autocompletable should be
     57   // autocompleted if they are shorter substitutes for longer matches that would
     58   // have been inline autocompleted.
     59   {"http://synthesisatest.com/foo/", "Test A", 1, 1},
     60   {"http://synthesisbtest.com/foo/", "Test B", 1, 1},
     61   {"http://synthesisbtest.com/foo/bar.html", "Test B Bar", 2, 2},
     62 
     63   // Suggested short URLs must be "good enough" and must match user input.
     64   {"http://foo.com/", "Dir", 5, 5},
     65   {"http://foo.com/dir/", "Dir", 2, 2},
     66   {"http://foo.com/dir/another/", "Dir", 5, 1},
     67   {"http://foo.com/dir/another/again/", "Dir", 10, 0},
     68   {"http://foo.com/dir/another/again/myfile.html", "File", 10, 2},
     69 
     70   // We throw in a lot of extra URLs here to make sure we're testing the
     71   // history database's query, not just the autocomplete provider.
     72   {"http://startest.com/y/a", "A", 2, 2},
     73   {"http://startest.com/y/b", "B", 5, 2},
     74   {"http://startest.com/x/c", "C", 5, 2},
     75   {"http://startest.com/x/d", "D", 5, 5},
     76   {"http://startest.com/y/e", "E", 4, 2},
     77   {"http://startest.com/y/f", "F", 3, 2},
     78   {"http://startest.com/y/g", "G", 3, 2},
     79   {"http://startest.com/y/h", "H", 3, 2},
     80   {"http://startest.com/y/i", "I", 3, 2},
     81   {"http://startest.com/y/j", "J", 3, 2},
     82   {"http://startest.com/y/k", "K", 3, 2},
     83   {"http://startest.com/y/l", "L", 3, 2},
     84   {"http://startest.com/y/m", "M", 3, 2},
     85 
     86   // A file: URL is useful for testing that fixup does the right thing w.r.t.
     87   // the number of trailing slashes on the user's input.
     88   {"file:///C:/foo.txt", "", 2, 2},
     89 
     90   // Results with absurdly high typed_counts so that very generic queries like
     91   // "http" will give consistent results even if more data is added above.
     92   {"http://bogussite.com/a", "Bogus A", 10002, 10000},
     93   {"http://bogussite.com/b", "Bogus B", 10001, 10000},
     94   {"http://bogussite.com/c", "Bogus C", 10000, 10000},
     95 
     96   // Domain name with number.
     97   {"http://www.17173.com/", "Domain with number", 3, 3},
     98 
     99   // URLs to test exact-matching behavior.
    100   {"http://go/", "Intranet URL", 1, 1},
    101   {"http://gooey/", "Intranet URL 2", 5, 5},
    102 
    103   // URLs for testing offset adjustment.
    104   {"http://www.\xEA\xB5\x90\xEC\x9C\xA1.kr/", "Korean", 2, 2},
    105   {"http://spaces.com/path%20with%20spaces/foo.html", "Spaces", 2, 2},
    106   {"http://ms/c++%20style%20guide", "Style guide", 2, 2},
    107 
    108   // URLs for testing ctrl-enter behavior.
    109   {"http://binky/", "Intranet binky", 2, 2},
    110   {"http://winky/", "Intranet winky", 2, 2},
    111   {"http://www.winky.com/", "Internet winky", 5, 0},
    112 
    113   // URLs used by EmptyVisits.
    114   {"http://pandora.com/", "Pandora", 2, 2},
    115   // This entry is explicitly added more recently than
    116   // history::kLowQualityMatchAgeLimitInDays.
    117   // {"http://p/", "p", 0, 0},
    118 
    119   // For intranet based tests.
    120   {"http://intra/one", "Intranet", 2, 2},
    121   {"http://intra/two", "Intranet two", 1, 1},
    122   {"http://intra/three", "Intranet three", 2, 2},
    123   {"http://moo/bar", "Intranet moo", 1, 1},
    124   {"http://typedhost/typedpath", "Intranet typed", 1, 1},
    125   {"http://typedhost/untypedpath", "Intranet untyped", 1, 0},
    126 
    127   {"http://x.com/one", "Internet", 2, 2},
    128   {"http://x.com/two", "Internet two", 1, 1},
    129   {"http://x.com/three", "Internet three", 2, 2},
    130 };
    131 
    132 class HistoryURLProviderTest : public testing::Test,
    133                                public AutocompleteProviderListener {
    134  public:
    135   struct UrlAndLegalDefault {
    136     std::string url;
    137     bool allowed_to_be_default_match;
    138   };
    139 
    140   HistoryURLProviderTest()
    141       : sort_matches_(false) {
    142     HistoryQuickProvider::set_disabled(true);
    143   }
    144 
    145   virtual ~HistoryURLProviderTest() {
    146     HistoryQuickProvider::set_disabled(false);
    147   }
    148 
    149   // AutocompleteProviderListener:
    150   virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
    151 
    152  protected:
    153   static BrowserContextKeyedService* CreateTemplateURLService(
    154       content::BrowserContext* profile) {
    155     return new TemplateURLService(static_cast<Profile*>(profile));
    156   }
    157 
    158   // testing::Test
    159   virtual void SetUp() {
    160     ASSERT_TRUE(SetUpImpl(false));
    161   }
    162   virtual void TearDown();
    163 
    164   // Does the real setup.
    165   bool SetUpImpl(bool no_db) WARN_UNUSED_RESULT;
    166 
    167   // Fills test data into the history system.
    168   void FillData();
    169 
    170   // Runs an autocomplete query on |text| and checks to see that the returned
    171   // results' destination URLs match those provided.  Also allows checking
    172   // that the input type was identified correctly.
    173   void RunTest(const string16 text,
    174                const string16& desired_tld,
    175                bool prevent_inline_autocomplete,
    176                const UrlAndLegalDefault* expected_urls,
    177                size_t num_results,
    178                AutocompleteInput::Type* identified_input_type);
    179 
    180   // A version of the above without the final |type| output parameter.
    181   void RunTest(const string16 text,
    182                const string16& desired_tld,
    183                bool prevent_inline_autocomplete,
    184                const UrlAndLegalDefault* expected_urls,
    185                size_t num_results) {
    186     AutocompleteInput::Type type;
    187     return RunTest(text, desired_tld, prevent_inline_autocomplete,
    188                    expected_urls, num_results, &type);
    189   }
    190 
    191   content::TestBrowserThreadBundle thread_bundle_;
    192   ACMatches matches_;
    193   scoped_ptr<TestingProfile> profile_;
    194   HistoryService* history_service_;
    195   scoped_refptr<HistoryURLProvider> autocomplete_;
    196   // Should the matches be sorted and duplicates removed?
    197   bool sort_matches_;
    198 };
    199 
    200 class HistoryURLProviderTestNoDB : public HistoryURLProviderTest {
    201  protected:
    202   virtual void SetUp() {
    203     ASSERT_TRUE(SetUpImpl(true));
    204   }
    205 };
    206 
    207 void HistoryURLProviderTest::OnProviderUpdate(bool updated_matches) {
    208   if (autocomplete_->done())
    209     base::MessageLoop::current()->Quit();
    210 }
    211 
    212 bool HistoryURLProviderTest::SetUpImpl(bool no_db) {
    213   profile_.reset(new TestingProfile());
    214   if (!(profile_->CreateHistoryService(true, no_db)))
    215     return false;
    216   if (!no_db) {
    217     profile_->BlockUntilHistoryProcessesPendingRequests();
    218     profile_->BlockUntilHistoryIndexIsRefreshed();
    219   }
    220   history_service_ =
    221       HistoryServiceFactory::GetForProfile(profile_.get(),
    222                                            Profile::EXPLICIT_ACCESS);
    223 
    224   autocomplete_ = new HistoryURLProvider(this, profile_.get(), "en-US,en,ko");
    225   TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
    226       profile_.get(), &HistoryURLProviderTest::CreateTemplateURLService);
    227   FillData();
    228   return true;
    229 }
    230 
    231 void HistoryURLProviderTest::TearDown() {
    232   autocomplete_ = NULL;
    233 }
    234 
    235 void HistoryURLProviderTest::FillData() {
    236   // All visits are a long time ago (some tests require this since we do some
    237   // special logic for things visited very recently). Note that this time must
    238   // be more recent than the "archived history" threshold for the data to go
    239   // into the main database.
    240   //
    241   // TODO(brettw) It would be nice if we could test this behavior, in which
    242   // case the time would be specifed in the test_db structure.
    243   Time visit_time = Time::Now() - TimeDelta::FromDays(80);
    244 
    245   for (size_t i = 0; i < arraysize(test_db); ++i) {
    246     const TestURLInfo& cur = test_db[i];
    247     const GURL current_url(cur.url);
    248     history_service_->AddPageWithDetails(current_url, UTF8ToUTF16(cur.title),
    249                                          cur.visit_count, cur.typed_count,
    250                                          visit_time, false,
    251                                          history::SOURCE_BROWSED);
    252   }
    253 
    254   history_service_->AddPageWithDetails(
    255       GURL("http://p/"), UTF8ToUTF16("p"), 0, 0,
    256       Time::Now() -
    257       TimeDelta::FromDays(history::kLowQualityMatchAgeLimitInDays - 1),
    258       false, history::SOURCE_BROWSED);
    259 }
    260 
    261 void HistoryURLProviderTest::RunTest(
    262     const string16 text,
    263     const string16& desired_tld,
    264     bool prevent_inline_autocomplete,
    265     const UrlAndLegalDefault* expected_urls,
    266     size_t num_results,
    267     AutocompleteInput::Type* identified_input_type) {
    268   AutocompleteInput input(text, string16::npos, desired_tld, GURL(),
    269                           AutocompleteInput::INVALID_SPEC,
    270                           prevent_inline_autocomplete, false, true,
    271                           AutocompleteInput::ALL_MATCHES);
    272   *identified_input_type = input.type();
    273   autocomplete_->Start(input, false);
    274   if (!autocomplete_->done())
    275     base::MessageLoop::current()->Run();
    276 
    277   matches_ = autocomplete_->matches();
    278   if (sort_matches_) {
    279     for (ACMatches::iterator i = matches_.begin(); i != matches_.end(); ++i)
    280       i->ComputeStrippedDestinationURL(profile_.get());
    281     std::sort(matches_.begin(), matches_.end(),
    282               &AutocompleteMatch::DestinationSortFunc);
    283     matches_.erase(std::unique(matches_.begin(), matches_.end(),
    284                                &AutocompleteMatch::DestinationsEqual),
    285                    matches_.end());
    286     std::sort(matches_.begin(), matches_.end(),
    287               &AutocompleteMatch::MoreRelevant);
    288   }
    289   ASSERT_EQ(num_results, matches_.size()) << "Input text: " << text
    290                                           << "\nTLD: \"" << desired_tld << "\"";
    291   for (size_t i = 0; i < num_results; ++i) {
    292     EXPECT_EQ(expected_urls[i].url, matches_[i].destination_url.spec());
    293     EXPECT_EQ(expected_urls[i].allowed_to_be_default_match,
    294               matches_[i].allowed_to_be_default_match);
    295   }
    296 }
    297 
    298 TEST_F(HistoryURLProviderTest, PromoteShorterURLs) {
    299   // Test that hosts get synthesized below popular pages.
    300   const UrlAndLegalDefault expected_nonsynth[] = {
    301     { "http://slashdot.org/favorite_page.html", false },
    302     { "http://slashdot.org/", false }
    303   };
    304   RunTest(ASCIIToUTF16("slash"), string16(), true, expected_nonsynth,
    305           arraysize(expected_nonsynth));
    306 
    307   // Test that hosts get synthesized above less popular pages.
    308   const UrlAndLegalDefault expected_synth[] = {
    309     { "http://kerneltrap.org/", false },
    310     { "http://kerneltrap.org/not_very_popular.html", false }
    311   };
    312   RunTest(ASCIIToUTF16("kernel"), string16(), true, expected_synth,
    313           arraysize(expected_synth));
    314 
    315   // Test that unpopular pages are ignored completely.
    316   RunTest(ASCIIToUTF16("fresh"), string16(), true, NULL, 0);
    317 
    318   // Test that if we create or promote shorter suggestions that would not
    319   // normally be inline autocompletable, we make them inline autocompletable if
    320   // the original suggestion (that we replaced as "top") was inline
    321   // autocompletable.
    322   const UrlAndLegalDefault expected_synthesisa[] = {
    323     { "http://synthesisatest.com/", true },
    324     { "http://synthesisatest.com/foo/", true }
    325   };
    326   RunTest(ASCIIToUTF16("synthesisa"), string16(), false, expected_synthesisa,
    327           arraysize(expected_synthesisa));
    328   EXPECT_LT(matches_.front().relevance, 1200);
    329   const UrlAndLegalDefault expected_synthesisb[] = {
    330     { "http://synthesisbtest.com/foo/", true },
    331     { "http://synthesisbtest.com/foo/bar.html", true }
    332   };
    333   RunTest(ASCIIToUTF16("synthesisb"), string16(), false, expected_synthesisb,
    334           arraysize(expected_synthesisb));
    335   EXPECT_GE(matches_.front().relevance, 1410);
    336 
    337   // Test that if we have a synthesized host that matches a suggestion, they
    338   // get combined into one.
    339   const UrlAndLegalDefault expected_combine[] = {
    340     { "http://news.google.com/", false },
    341     { "http://news.google.com/?ned=us&topic=n", false },
    342   };
    343   ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("news"), string16(), true,
    344       expected_combine, arraysize(expected_combine)));
    345   // The title should also have gotten set properly on the host for the
    346   // synthesized one, since it was also in the results.
    347   EXPECT_EQ(ASCIIToUTF16("Google News"), matches_.front().description);
    348 
    349   // Test that short URL matching works correctly as the user types more
    350   // (several tests):
    351   // The entry for foo.com is the best of all five foo.com* entries.
    352   const UrlAndLegalDefault short_1[] = {
    353     { "http://foo.com/", false },
    354     { "http://foo.com/dir/another/again/myfile.html", false },
    355     { "http://foo.com/dir/", false }
    356   };
    357   RunTest(ASCIIToUTF16("foo"), string16(), true, short_1, arraysize(short_1));
    358 
    359   // When the user types the whole host, make sure we don't get two results for
    360   // it.
    361   const UrlAndLegalDefault short_2[] = {
    362     { "http://foo.com/", true },
    363     { "http://foo.com/dir/another/again/myfile.html", false },
    364     { "http://foo.com/dir/", false },
    365     { "http://foo.com/dir/another/", false }
    366   };
    367   RunTest(ASCIIToUTF16("foo.com"), string16(), true, short_2,
    368           arraysize(short_2));
    369   RunTest(ASCIIToUTF16("foo.com/"), string16(), true, short_2,
    370           arraysize(short_2));
    371 
    372   // The filename is the second best of the foo.com* entries, but there is a
    373   // shorter URL that's "good enough".  The host doesn't match the user input
    374   // and so should not appear.
    375   const UrlAndLegalDefault short_3[] = {
    376     { "http://foo.com/d", true },
    377     { "http://foo.com/dir/another/", false },
    378     { "http://foo.com/dir/another/again/myfile.html", false },
    379     { "http://foo.com/dir/", false }
    380   };
    381   RunTest(ASCIIToUTF16("foo.com/d"), string16(), true, short_3,
    382           arraysize(short_3));
    383 
    384   // We shouldn't promote shorter URLs than the best if they're not good
    385   // enough.
    386   const UrlAndLegalDefault short_4[] = {
    387     { "http://foo.com/dir/another/a", true },
    388     { "http://foo.com/dir/another/again/myfile.html", false },
    389     { "http://foo.com/dir/another/again/", false }
    390   };
    391   RunTest(ASCIIToUTF16("foo.com/dir/another/a"), string16(), true, short_4,
    392           arraysize(short_4));
    393 
    394   // Exact matches should always be best no matter how much more another match
    395   // has been typed.
    396   const UrlAndLegalDefault short_5a[] = {
    397     { "http://gooey/", true },
    398     { "http://www.google.com/", true },
    399     { "http://go/", true }
    400   };
    401   const UrlAndLegalDefault short_5b[] = {
    402     { "http://go/", true },
    403     { "http://gooey/", true },
    404     { "http://www.google.com/", true }
    405   };
    406   RunTest(ASCIIToUTF16("g"), string16(), false, short_5a, arraysize(short_5a));
    407   RunTest(ASCIIToUTF16("go"), string16(), false, short_5b, arraysize(short_5b));
    408 }
    409 
    410 TEST_F(HistoryURLProviderTest, CullRedirects) {
    411   // URLs we will be using, plus the visit counts they will initially get
    412   // (the redirect set below will also increment the visit counts). We want
    413   // the results to be in A,B,C order. Note also that our visit counts are
    414   // all high enough so that domain synthesizing won't get triggered.
    415   struct TestCase {
    416     const char* url;
    417     int count;
    418   } test_cases[] = {
    419     {"http://redirects/A", 30},
    420     {"http://redirects/B", 20},
    421     {"http://redirects/C", 10}
    422   };
    423   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    424     history_service_->AddPageWithDetails(GURL(test_cases[i].url),
    425         UTF8ToUTF16("Title"), test_cases[i].count, test_cases[i].count,
    426         Time::Now(), false, history::SOURCE_BROWSED);
    427   }
    428 
    429   // Create a B->C->A redirect chain, but set the visit counts such that they
    430   // will appear in A,B,C order in the results. The autocomplete query will
    431   // search for the most recent visit when looking for redirects, so this will
    432   // be found even though the previous visits had no redirects.
    433   history::RedirectList redirects_to_a;
    434   redirects_to_a.push_back(GURL(test_cases[1].url));
    435   redirects_to_a.push_back(GURL(test_cases[2].url));
    436   redirects_to_a.push_back(GURL(test_cases[0].url));
    437   history_service_->AddPage(GURL(test_cases[0].url), base::Time::Now(),
    438       NULL, 0, GURL(), redirects_to_a, content::PAGE_TRANSITION_TYPED,
    439       history::SOURCE_BROWSED, true);
    440 
    441   // Because all the results are part of a redirect chain with other results,
    442   // all but the first one (A) should be culled. We should get the default
    443   // "what you typed" result, plus this one.
    444   const string16 typing(ASCIIToUTF16("http://redirects/"));
    445   const UrlAndLegalDefault expected_results[] = {
    446     { UTF16ToUTF8(typing), true },
    447     { test_cases[0].url, false }
    448   };
    449   RunTest(typing, string16(), true, expected_results,
    450           arraysize(expected_results));
    451 }
    452 
    453 TEST_F(HistoryURLProviderTest, WhatYouTyped) {
    454   // Make sure we suggest a What You Typed match at the right times.
    455   RunTest(ASCIIToUTF16("wytmatch"), string16(), false, NULL, 0);
    456   RunTest(ASCIIToUTF16("wytmatch foo bar"), string16(), false, NULL, 0);
    457   RunTest(ASCIIToUTF16("wytmatch+foo+bar"), string16(), false, NULL, 0);
    458   RunTest(ASCIIToUTF16("wytmatch+foo+bar.com"), string16(), false, NULL, 0);
    459 
    460   const UrlAndLegalDefault results_1[] = {
    461     { "http://www.wytmatch.com/", true }
    462   };
    463   RunTest(ASCIIToUTF16("wytmatch"), ASCIIToUTF16("com"), false, results_1,
    464           arraysize(results_1));
    465 
    466   const UrlAndLegalDefault results_2[] = {
    467     { "http://wytmatch%20foo%20bar/", true }
    468   };
    469   RunTest(ASCIIToUTF16("http://wytmatch foo bar"), string16(), false, results_2,
    470           arraysize(results_2));
    471 
    472   const UrlAndLegalDefault results_3[] = {
    473     { "https://wytmatch%20foo%20bar/", true }
    474   };
    475   RunTest(ASCIIToUTF16("https://wytmatch foo bar"), string16(), false,
    476           results_3, arraysize(results_3));
    477 }
    478 
    479 TEST_F(HistoryURLProviderTest, Fixup) {
    480   // Test for various past crashes we've had.
    481   RunTest(ASCIIToUTF16("\\"), string16(), false, NULL, 0);
    482   RunTest(ASCIIToUTF16("#"), string16(), false, NULL, 0);
    483   RunTest(ASCIIToUTF16("%20"), string16(), false, NULL, 0);
    484   const UrlAndLegalDefault fixup_crash[] = {
    485     { "http://%EF%BD%A5@s/", true }
    486   };
    487   RunTest(WideToUTF16(L"\uff65@s"), string16(), false, fixup_crash,
    488           arraysize(fixup_crash));
    489   RunTest(WideToUTF16(L"\u2015\u2015@ \uff7c"), string16(), false, NULL, 0);
    490 
    491   // Fixing up "file:" should result in an inline autocomplete offset of just
    492   // after "file:", not just after "file://".
    493   const string16 input_1(ASCIIToUTF16("file:"));
    494   const UrlAndLegalDefault fixup_1[] = {
    495     { "file:///C:/foo.txt", true }
    496   };
    497   ASSERT_NO_FATAL_FAILURE(RunTest(input_1, string16(), false, fixup_1,
    498                                   arraysize(fixup_1)));
    499   EXPECT_EQ(ASCIIToUTF16("///C:/foo.txt"),
    500             matches_.front().inline_autocompletion);
    501 
    502   // Fixing up "http:/" should result in an inline autocomplete offset of just
    503   // after "http:/", not just after "http:".
    504   const string16 input_2(ASCIIToUTF16("http:/"));
    505   const UrlAndLegalDefault fixup_2[] = {
    506     { "http://bogussite.com/a", true },
    507     { "http://bogussite.com/b", true },
    508     { "http://bogussite.com/c", true }
    509   };
    510   ASSERT_NO_FATAL_FAILURE(RunTest(input_2, string16(), false, fixup_2,
    511                                   arraysize(fixup_2)));
    512   EXPECT_EQ(ASCIIToUTF16("/bogussite.com/a"),
    513             matches_.front().inline_autocompletion);
    514 
    515   // Adding a TLD to a small number like "56" should result in "www.56.com"
    516   // rather than "0.0.0.56.com".
    517   const UrlAndLegalDefault fixup_3[] = {
    518     { "http://www.56.com/", true }
    519   };
    520   RunTest(ASCIIToUTF16("56"), ASCIIToUTF16("com"), true, fixup_3,
    521           arraysize(fixup_3));
    522 
    523   // An input looks like a IP address like "127.0.0.1" should result in
    524   // "http://127.0.0.1/".
    525   const UrlAndLegalDefault fixup_4[] = {
    526     { "http://127.0.0.1/", true }
    527   };
    528   RunTest(ASCIIToUTF16("127.0.0.1"), string16(), false, fixup_4,
    529           arraysize(fixup_4));
    530 
    531   // An number "17173" should result in "http://www.17173.com/" in db.
    532   const UrlAndLegalDefault fixup_5[] = {
    533     { "http://www.17173.com/", true }
    534   };
    535   RunTest(ASCIIToUTF16("17173"), string16(), false, fixup_5,
    536           arraysize(fixup_5));
    537 }
    538 
    539 // Make sure the results for the input 'p' don't change between the first and
    540 // second passes.
    541 TEST_F(HistoryURLProviderTest, EmptyVisits) {
    542   // Wait for history to create the in memory DB.
    543   profile_->BlockUntilHistoryProcessesPendingRequests();
    544 
    545   AutocompleteInput input(ASCIIToUTF16("p"), string16::npos, string16(), GURL(),
    546                           AutocompleteInput::INVALID_SPEC, false, false, true,
    547                           AutocompleteInput::ALL_MATCHES);
    548   autocomplete_->Start(input, false);
    549   // HistoryURLProvider shouldn't be done (waiting on async results).
    550   EXPECT_FALSE(autocomplete_->done());
    551 
    552   // We should get back an entry for pandora.
    553   matches_ = autocomplete_->matches();
    554   ASSERT_GT(matches_.size(), 0u);
    555   EXPECT_EQ(GURL("http://pandora.com/"), matches_[0].destination_url);
    556   int pandora_relevance = matches_[0].relevance;
    557 
    558   // Run the message loop. When |autocomplete_| finishes the loop is quit.
    559   base::MessageLoop::current()->Run();
    560   EXPECT_TRUE(autocomplete_->done());
    561   matches_ = autocomplete_->matches();
    562   ASSERT_GT(matches_.size(), 0u);
    563   EXPECT_EQ(GURL("http://pandora.com/"), matches_[0].destination_url);
    564   EXPECT_EQ(pandora_relevance, matches_[0].relevance);
    565 }
    566 
    567 TEST_F(HistoryURLProviderTestNoDB, NavigateWithoutDB) {
    568   // Ensure that we will still produce matches for navigation when there is no
    569   // database.
    570   UrlAndLegalDefault navigation_1[] = {
    571     { "http://test.com/", true }
    572   };
    573   RunTest(ASCIIToUTF16("test.com"), string16(), false, navigation_1,
    574           arraysize(navigation_1));
    575 
    576   UrlAndLegalDefault navigation_2[] = {
    577     { "http://slash/", true }
    578   };
    579   RunTest(ASCIIToUTF16("slash"), string16(), false, navigation_2,
    580           arraysize(navigation_2));
    581 
    582   RunTest(ASCIIToUTF16("this is a query"), string16(), false, NULL, 0);
    583 }
    584 
    585 TEST_F(HistoryURLProviderTest, DontAutocompleteOnTrailingWhitespace) {
    586   AutocompleteInput input(ASCIIToUTF16("slash "), string16::npos, string16(),
    587                           GURL(), AutocompleteInput::INVALID_SPEC, false, false,
    588                           true, AutocompleteInput::ALL_MATCHES);
    589   autocomplete_->Start(input, false);
    590   if (!autocomplete_->done())
    591     base::MessageLoop::current()->Run();
    592 
    593   // None of the matches should attempt to autocomplete.
    594   matches_ = autocomplete_->matches();
    595   for (size_t i = 0; i < matches_.size(); ++i) {
    596     EXPECT_TRUE(matches_[i].inline_autocompletion.empty());
    597     EXPECT_FALSE(matches_[i].allowed_to_be_default_match);
    598   }
    599 }
    600 
    601 TEST_F(HistoryURLProviderTest, TreatEmailsAsSearches) {
    602   // Visiting foo.com should not make this string be treated as a navigation.
    603   // That means the result should be scored around 1200 ("what you typed")
    604   // and not 1400+.
    605   const UrlAndLegalDefault expected[] = {
    606     { "http://user@foo.com/", true }
    607   };
    608   ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("user (at) foo.com"), string16(),
    609                                   false, expected, arraysize(expected)));
    610   EXPECT_LE(1200, matches_[0].relevance);
    611   EXPECT_LT(matches_[0].relevance, 1210);
    612 }
    613 
    614 TEST_F(HistoryURLProviderTest, IntranetURLsWithPaths) {
    615   struct TestCase {
    616     const char* input;
    617     int relevance;
    618   } test_cases[] = {
    619     { "fooey", 0 },
    620     { "fooey/", 1200 },     // 1200 for URL would still navigate by default.
    621     { "fooey/a", 1200 },    // 1200 for UNKNOWN would not.
    622     { "fooey/a b", 1200 },  // Also UNKNOWN.
    623     { "gooey", 1410 },
    624     { "gooey/", 1410 },
    625     { "gooey/a", 1400 },
    626     { "gooey/a b", 1400 },
    627   };
    628   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    629     SCOPED_TRACE(test_cases[i].input);
    630     if (test_cases[i].relevance == 0) {
    631       RunTest(ASCIIToUTF16(test_cases[i].input), string16(), false, NULL, 0);
    632     } else {
    633       const UrlAndLegalDefault output[] = {
    634         { URLFixerUpper::FixupURL(test_cases[i].input, std::string()).spec(),
    635           true }
    636       };
    637       ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16(test_cases[i].input),
    638                               string16(), false, output, arraysize(output)));
    639       // Actual relevance should be at least what test_cases expects and
    640       // and no more than 10 more.
    641       EXPECT_LE(test_cases[i].relevance, matches_[0].relevance);
    642       EXPECT_LT(matches_[0].relevance, test_cases[i].relevance + 10);
    643     }
    644   }
    645 }
    646 
    647 TEST_F(HistoryURLProviderTest, IntranetURLsWithRefs) {
    648   struct TestCase {
    649     const char* input;
    650     int relevance;
    651     AutocompleteInput::Type type;
    652   } test_cases[] = {
    653     { "gooey", 1410, AutocompleteInput::UNKNOWN },
    654     { "gooey/", 1410, AutocompleteInput::URL },
    655     { "gooey#", 1200, AutocompleteInput::UNKNOWN },
    656     { "gooey/#", 1200, AutocompleteInput::URL },
    657     { "gooey#foo", 1200, AutocompleteInput::UNKNOWN },
    658     { "gooey/#foo", 1200, AutocompleteInput::URL },
    659     { "gooey# foo", 1200, AutocompleteInput::UNKNOWN },
    660     { "gooey/# foo", 1200, AutocompleteInput::URL },
    661   };
    662   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    663     SCOPED_TRACE(test_cases[i].input);
    664     const UrlAndLegalDefault output[] = {
    665       { URLFixerUpper::FixupURL(test_cases[i].input, std::string()).spec(),
    666         true }
    667     };
    668     AutocompleteInput::Type type;
    669     ASSERT_NO_FATAL_FAILURE(
    670         RunTest(ASCIIToUTF16(test_cases[i].input),
    671                 string16(), false, output, arraysize(output), &type));
    672     // Actual relevance should be at least what test_cases expects and
    673     // and no more than 10 more.
    674     EXPECT_LE(test_cases[i].relevance, matches_[0].relevance);
    675     EXPECT_LT(matches_[0].relevance, test_cases[i].relevance + 10);
    676     // Input type should be what we expect.  This is important because
    677     // this provider counts on SearchProvider to give queries a relevance
    678     // score >1200 for UNKNOWN inputs and <1200 for URL inputs.  (That's
    679     // already tested in search_provider_unittest.cc.)  For this test
    680     // here to test that the user sees the correct behavior, it needs
    681     // to check that the input type was identified correctly.
    682     EXPECT_EQ(test_cases[i].type, type);
    683   }
    684 }
    685 
    686 // Makes sure autocompletion happens for intranet sites that have been
    687 // previoulsy visited.
    688 TEST_F(HistoryURLProviderTest, IntranetURLCompletion) {
    689   sort_matches_ = true;
    690 
    691   const UrlAndLegalDefault expected1[] = {
    692     { "http://intra/three", true },
    693     { "http://intra/two", true }
    694   };
    695   ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("intra/t"), string16(), false,
    696                                   expected1, arraysize(expected1)));
    697   EXPECT_LE(1410, matches_[0].relevance);
    698   EXPECT_LT(matches_[0].relevance, 1420);
    699   EXPECT_EQ(matches_[0].relevance - 1, matches_[1].relevance);
    700 
    701   const UrlAndLegalDefault expected2[] = {
    702     { "http://moo/b", true },
    703     { "http://moo/bar", true }
    704   };
    705   ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("moo/b"), string16(), false,
    706                                   expected2, arraysize(expected2)));
    707   // The url what you typed match should be around 1400, otherwise the
    708   // search what you typed match is going to be first.
    709   EXPECT_LE(1400, matches_[0].relevance);
    710   EXPECT_LT(matches_[0].relevance, 1410);
    711 
    712   const UrlAndLegalDefault expected3[] = {
    713     { "http://intra/one", true },
    714     { "http://intra/three", true },
    715     { "http://intra/two", true }
    716   };
    717   RunTest(ASCIIToUTF16("intra"), string16(), false, expected3,
    718           arraysize(expected3));
    719 
    720   const UrlAndLegalDefault expected4[] = {
    721     { "http://intra/one", true },
    722     { "http://intra/three", true },
    723     { "http://intra/two", true }
    724   };
    725   RunTest(ASCIIToUTF16("intra/"), string16(), false, expected4,
    726           arraysize(expected4));
    727 
    728   const UrlAndLegalDefault expected5[] = {
    729     { "http://intra/one", true }
    730   };
    731   ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("intra/o"), string16(), false,
    732                                   expected5, arraysize(expected5)));
    733   EXPECT_LE(1410, matches_[0].relevance);
    734   EXPECT_LT(matches_[0].relevance, 1420);
    735 
    736   const UrlAndLegalDefault expected6[] = {
    737     { "http://intra/x", true }
    738   };
    739   ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("intra/x"), string16(), false,
    740                                   expected6, arraysize(expected6)));
    741   EXPECT_LE(1400, matches_[0].relevance);
    742   EXPECT_LT(matches_[0].relevance, 1410);
    743 
    744   const UrlAndLegalDefault expected7[] = {
    745     { "http://typedhost/untypedpath", true }
    746   };
    747   ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("typedhost/untypedpath"),
    748       string16(), false, expected7, arraysize(expected7)));
    749   EXPECT_LE(1400, matches_[0].relevance);
    750   EXPECT_LT(matches_[0].relevance, 1410);
    751 }
    752 
    753 TEST_F(HistoryURLProviderTest, CrashDueToFixup) {
    754   // This test passes if we don't crash.  The results don't matter.
    755   const char* const test_cases[] = {
    756     "//c",
    757     "\\@st"
    758   };
    759   for (size_t i = 0; i < arraysize(test_cases); ++i) {
    760     AutocompleteInput input(ASCIIToUTF16(test_cases[i]), string16::npos,
    761                             string16(), GURL(), AutocompleteInput::INVALID_SPEC,
    762                             false, false, true, AutocompleteInput::ALL_MATCHES);
    763     autocomplete_->Start(input, false);
    764     if (!autocomplete_->done())
    765       base::MessageLoop::current()->Run();
    766   }
    767 }
    768 
    769 TEST_F(HistoryURLProviderTest, CullSearchResults) {
    770   // Set up a default search engine.
    771   TemplateURLData data;
    772   data.SetKeyword(ASCIIToUTF16("TestEngine"));
    773   data.SetURL("http://testsearch.com/?q={searchTerms}");
    774   TemplateURLService* template_url_service =
    775       TemplateURLServiceFactory::GetForProfile(profile_.get());
    776   TemplateURL* template_url = new TemplateURL(profile_.get(), data);
    777   template_url_service->Add(template_url);
    778   template_url_service->SetDefaultSearchProvider(template_url);
    779   template_url_service->Load();
    780 
    781   // URLs we will be using, plus the visit counts they will initially get
    782   // (the redirect set below will also increment the visit counts). We want
    783   // the results to be in A,B,C order. Note also that our visit counts are
    784   // all high enough so that domain synthesizing won't get triggered.
    785   struct TestCase {
    786     const char* url;
    787     int count;
    788   } test_cases[] = {
    789     {"https://testsearch.com/", 30},
    790     {"https://testsearch.com/?q=foobar", 20},
    791     {"http://foobar.com/", 10}
    792   };
    793   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    794     history_service_->AddPageWithDetails(GURL(test_cases[i].url),
    795         UTF8ToUTF16("Title"), test_cases[i].count, test_cases[i].count,
    796         Time::Now(), false, history::SOURCE_BROWSED);
    797   }
    798 
    799   // We should not see search URLs when typing a previously used query.
    800   const UrlAndLegalDefault expected_when_searching_query[] = {
    801     { test_cases[2].url, false }
    802   };
    803   RunTest(ASCIIToUTF16("foobar"), string16(), true,
    804       expected_when_searching_query, arraysize(expected_when_searching_query));
    805 
    806   // We should not see search URLs when typing the search engine name.
    807   const UrlAndLegalDefault expected_when_searching_site[] = {
    808     { test_cases[0].url, false }
    809   };
    810   RunTest(ASCIIToUTF16("testsearch"), string16(), true,
    811       expected_when_searching_site, arraysize(expected_when_searching_site));
    812 }
    813 
    814 TEST_F(HistoryURLProviderTest, SuggestExactInput) {
    815   const size_t npos = std::string::npos;
    816   struct TestCase {
    817     // Inputs:
    818     const char* input;
    819     bool trim_http;
    820     // Expected Outputs:
    821     const char* contents;
    822     // Offsets of the ACMatchClassifications, terminated by npos.
    823     size_t offsets[3];
    824     // The index of the ACMatchClassification that should have the MATCH bit
    825     // set, npos if no ACMatchClassification should have the MATCH bit set.
    826     size_t match_classification_index;
    827   } test_cases[] = {
    828     { "http://www.somesite.com", false,
    829       "http://www.somesite.com", {0, npos, npos}, 0 },
    830     { "www.somesite.com", true,
    831       "www.somesite.com", {0, npos, npos}, 0 },
    832     { "www.somesite.com", false,
    833       "http://www.somesite.com", {0, 7, npos}, 1 },
    834     { "somesite.com", true,
    835       "somesite.com", {0, npos, npos}, 0 },
    836     { "somesite.com", false,
    837       "http://somesite.com", {0, 7, npos}, 1 },
    838     { "w", true,
    839       "w", {0, npos, npos}, 0 },
    840     { "w", false,
    841       "http://w", {0, 7, npos}, 1 },
    842     { "w.com", true,
    843       "w.com", {0, npos, npos}, 0 },
    844     { "w.com", false,
    845       "http://w.com", {0, 7, npos}, 1 },
    846     { "www.w.com", true,
    847       "www.w.com", {0, npos, npos}, 0 },
    848     { "www.w.com", false,
    849       "http://www.w.com", {0, 7, npos}, 1 },
    850     { "view-source:www.w.com/", true,
    851       "view-source:www.w.com", {0, npos, npos}, npos },
    852     { "view-source:www.w.com/", false,
    853       "view-source:http://www.w.com", {0, npos, npos}, npos },
    854     { "view-source:http://www.w.com/", false,
    855       "view-source:http://www.w.com", {0, npos, npos}, 0 },
    856     { "   view-source:", true,
    857       "view-source:", {0, npos, npos}, 0 },
    858     { "http:////////w.com", false,
    859       "http://w.com", {0, npos, npos}, npos },
    860     { "    http:////////www.w.com", false,
    861       "http://www.w.com", {0, npos, npos}, npos },
    862     { "http:a///www.w.com", false,
    863       "http://a///www.w.com", {0, npos, npos}, npos },
    864     { "mailto://a (at) b.com", true,
    865       "mailto://a (at) b.com", {0, npos, npos}, 0 },
    866     { "mailto://a (at) b.com", false,
    867       "mailto://a (at) b.com", {0, npos, npos}, 0 },
    868   };
    869   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    870     SCOPED_TRACE(testing::Message() << "Index " << i << " input: "
    871                                     << test_cases[i].input << ", trim_http: "
    872                                     << test_cases[i].trim_http);
    873 
    874     AutocompleteInput input(ASCIIToUTF16(test_cases[i].input), string16::npos,
    875                             string16(), GURL("about:blank"),
    876                             AutocompleteInput::INVALID_SPEC, false, false, true,
    877                             AutocompleteInput::ALL_MATCHES);
    878     AutocompleteMatch match = HistoryURLProvider::SuggestExactInput(
    879         autocomplete_.get(), input, test_cases[i].trim_http);
    880     EXPECT_EQ(ASCIIToUTF16(test_cases[i].contents), match.contents);
    881     for (size_t match_index = 0; match_index < match.contents_class.size();
    882          ++match_index) {
    883       EXPECT_EQ(test_cases[i].offsets[match_index],
    884                 match.contents_class[match_index].offset);
    885       EXPECT_EQ(ACMatchClassification::URL |
    886                 (match_index == test_cases[i].match_classification_index ?
    887                  ACMatchClassification::MATCH : 0),
    888                 match.contents_class[match_index].style);
    889     }
    890     EXPECT_EQ(npos, test_cases[i].offsets[match.contents_class.size()]);
    891   }
    892 }
    893