Home | History | Annotate | Download | only in predictors
      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/predictors/autocomplete_action_predictor.h"
      6 
      7 #include "base/auto_reset.h"
      8 #include "base/command_line.h"
      9 #include "base/guid.h"
     10 #include "base/memory/ref_counted.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/time/time.h"
     15 #include "chrome/browser/autocomplete/autocomplete_match.h"
     16 #include "chrome/browser/history/history_service.h"
     17 #include "chrome/browser/history/history_service_factory.h"
     18 #include "chrome/browser/history/in_memory_database.h"
     19 #include "chrome/browser/history/url_database.h"
     20 #include "chrome/browser/prerender/prerender_field_trial.h"
     21 #include "chrome/common/chrome_switches.h"
     22 #include "chrome/test/base/testing_profile.h"
     23 #include "content/public/test/test_browser_thread.h"
     24 #include "testing/gtest/include/gtest/gtest.h"
     25 
     26 using content::BrowserThread;
     27 using predictors::AutocompleteActionPredictor;
     28 
     29 namespace {
     30 
     31 struct TestUrlInfo {
     32   GURL url;
     33   string16 title;
     34   int days_from_now;
     35   string16 user_text;
     36   int number_of_hits;
     37   int number_of_misses;
     38   AutocompleteActionPredictor::Action expected_action;
     39 } test_url_db[] = {
     40   { GURL("http://www.testsite.com/a.html"),
     41     ASCIIToUTF16("Test - site - just a test"), 1,
     42     ASCIIToUTF16("j"), 5, 0,
     43     AutocompleteActionPredictor::ACTION_PRERENDER },
     44   { GURL("http://www.testsite.com/b.html"),
     45     ASCIIToUTF16("Test - site - just a test"), 1,
     46     ASCIIToUTF16("ju"), 3, 0,
     47     AutocompleteActionPredictor::ACTION_PRERENDER },
     48   { GURL("http://www.testsite.com/c.html"),
     49     ASCIIToUTF16("Test - site - just a test"), 5,
     50     ASCIIToUTF16("just"), 3, 1,
     51     AutocompleteActionPredictor::ACTION_PRECONNECT },
     52   { GURL("http://www.testsite.com/d.html"),
     53     ASCIIToUTF16("Test - site - just a test"), 5,
     54     ASCIIToUTF16("just"), 3, 0,
     55     AutocompleteActionPredictor::ACTION_PRERENDER },
     56   { GURL("http://www.testsite.com/e.html"),
     57     ASCIIToUTF16("Test - site - just a test"), 8,
     58     ASCIIToUTF16("just"), 3, 1,
     59     AutocompleteActionPredictor::ACTION_PRECONNECT },
     60   { GURL("http://www.testsite.com/f.html"),
     61     ASCIIToUTF16("Test - site - just a test"), 8,
     62     ASCIIToUTF16("just"), 3, 0,
     63     AutocompleteActionPredictor::ACTION_PRERENDER },
     64   { GURL("http://www.testsite.com/g.html"),
     65     ASCIIToUTF16("Test - site - just a test"), 12,
     66     string16(), 5, 0,
     67     AutocompleteActionPredictor::ACTION_NONE },
     68   { GURL("http://www.testsite.com/h.html"),
     69     ASCIIToUTF16("Test - site - just a test"), 21,
     70     ASCIIToUTF16("just a test"), 2, 0,
     71     AutocompleteActionPredictor::ACTION_NONE },
     72   { GURL("http://www.testsite.com/i.html"),
     73     ASCIIToUTF16("Test - site - just a test"), 28,
     74     ASCIIToUTF16("just a test"), 2, 0,
     75     AutocompleteActionPredictor::ACTION_NONE }
     76 };
     77 
     78 }  // end namespace
     79 
     80 namespace predictors {
     81 
     82 class AutocompleteActionPredictorTest : public testing::Test {
     83  public:
     84   AutocompleteActionPredictorTest()
     85       : loop_(base::MessageLoop::TYPE_DEFAULT),
     86         ui_thread_(BrowserThread::UI, &loop_),
     87         db_thread_(BrowserThread::DB, &loop_),
     88         file_thread_(BrowserThread::FILE, &loop_),
     89         profile_(new TestingProfile()),
     90         predictor_(new AutocompleteActionPredictor(profile_.get())) {
     91   }
     92 
     93   virtual ~AutocompleteActionPredictorTest() {
     94     predictor_.reset(NULL);
     95     profile_.reset(NULL);
     96     loop_.RunUntilIdle();
     97   }
     98 
     99   virtual void SetUp() {
    100     CommandLine::ForCurrentProcess()->AppendSwitchASCII(
    101         switches::kPrerenderFromOmnibox,
    102         switches::kPrerenderFromOmniboxSwitchValueEnabled);
    103 
    104     predictor_->CreateLocalCachesFromDatabase();
    105     ASSERT_TRUE(profile_->CreateHistoryService(true, false));
    106     profile_->BlockUntilHistoryProcessesPendingRequests();
    107 
    108     ASSERT_TRUE(predictor_->initialized_);
    109     ASSERT_TRUE(db_cache()->empty());
    110     ASSERT_TRUE(db_id_cache()->empty());
    111   }
    112 
    113   virtual void TearDown() {
    114     profile_->DestroyHistoryService();
    115     predictor_->Shutdown();
    116   }
    117 
    118  protected:
    119   typedef AutocompleteActionPredictor::DBCacheKey DBCacheKey;
    120   typedef AutocompleteActionPredictor::DBCacheValue DBCacheValue;
    121   typedef AutocompleteActionPredictor::DBCacheMap DBCacheMap;
    122   typedef AutocompleteActionPredictor::DBIdCacheMap DBIdCacheMap;
    123 
    124   void AddAllRowsToHistory() {
    125     for (size_t i = 0; i < arraysize(test_url_db); ++i)
    126       ASSERT_TRUE(AddRowToHistory(test_url_db[i]));
    127   }
    128 
    129   history::URLID AddRowToHistory(const TestUrlInfo& test_row) {
    130     HistoryService* history =
    131         HistoryServiceFactory::GetForProfile(profile_.get(),
    132                                              Profile::EXPLICIT_ACCESS);
    133     CHECK(history);
    134     history::URLDatabase* url_db = history->InMemoryDatabase();
    135     CHECK(url_db);
    136 
    137     const base::Time visit_time =
    138         base::Time::Now() - base::TimeDelta::FromDays(
    139             test_row.days_from_now);
    140 
    141     history::URLRow row(test_row.url);
    142     row.set_title(test_row.title);
    143     row.set_last_visit(visit_time);
    144 
    145     return url_db->AddURL(row);
    146   }
    147 
    148   AutocompleteActionPredictorTable::Row CreateRowFromTestUrlInfo(
    149       const TestUrlInfo& test_row) const {
    150     AutocompleteActionPredictorTable::Row row;
    151     row.id = base::GenerateGUID();
    152     row.user_text = test_row.user_text;
    153     row.url = test_row.url;
    154     row.number_of_hits = test_row.number_of_hits;
    155     row.number_of_misses = test_row.number_of_misses;
    156     return row;
    157   }
    158 
    159   void AddAllRows() {
    160     for (size_t i = 0; i < arraysize(test_url_db); ++i)
    161       AddRow(test_url_db[i]);
    162   }
    163 
    164   std::string AddRow(const TestUrlInfo& test_row) {
    165     AutocompleteActionPredictorTable::Row row =
    166         CreateRowFromTestUrlInfo(test_row);
    167     predictor_->AddAndUpdateRows(
    168         AutocompleteActionPredictorTable::Rows(1, row),
    169         AutocompleteActionPredictorTable::Rows());
    170 
    171     return row.id;
    172   }
    173 
    174   void UpdateRow(const AutocompleteActionPredictorTable::Row& row) {
    175     AutocompleteActionPredictor::DBCacheKey key = { row.user_text, row.url };
    176     ASSERT_TRUE(db_cache()->find(key) != db_cache()->end());
    177     predictor_->AddAndUpdateRows(
    178         AutocompleteActionPredictorTable::Rows(),
    179         AutocompleteActionPredictorTable::Rows(1, row));
    180   }
    181 
    182   void DeleteAllRows() {
    183     predictor_->DeleteAllRows();
    184   }
    185 
    186   void DeleteRowsWithURLs(const history::URLRows& rows) {
    187     predictor_->DeleteRowsWithURLs(rows);
    188   }
    189 
    190   void DeleteOldIdsFromCaches(
    191       std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
    192     HistoryService* history_service =
    193         HistoryServiceFactory::GetForProfile(profile_.get(),
    194                                              Profile::EXPLICIT_ACCESS);
    195     ASSERT_TRUE(history_service);
    196 
    197     history::URLDatabase* url_db = history_service->InMemoryDatabase();
    198     ASSERT_TRUE(url_db);
    199 
    200     // Reset the predictor's |initialized_| flag for the life of this call,
    201     // since outside of testing this function is only supposed to be reached
    202     // before initialization is completed.
    203     base::AutoReset<bool> initialized_reset(&predictor_->initialized_, false);
    204     predictor_->DeleteOldIdsFromCaches(url_db, id_list);
    205   }
    206 
    207   AutocompleteActionPredictor* predictor() { return predictor_.get(); }
    208 
    209   DBCacheMap* db_cache() { return &predictor_->db_cache_; }
    210   DBIdCacheMap* db_id_cache() { return &predictor_->db_id_cache_; }
    211 
    212   static int maximum_days_to_keep_entry() {
    213     return AutocompleteActionPredictor::kMaximumDaysToKeepEntry;
    214   }
    215 
    216  private:
    217   base::MessageLoop loop_;
    218   content::TestBrowserThread ui_thread_;
    219   content::TestBrowserThread db_thread_;
    220   content::TestBrowserThread file_thread_;
    221   scoped_ptr<TestingProfile> profile_;
    222   scoped_ptr<AutocompleteActionPredictor> predictor_;
    223 };
    224 
    225 
    226 TEST_F(AutocompleteActionPredictorTest, AddRow) {
    227   // Add a test entry to the predictor.
    228   std::string guid = AddRow(test_url_db[0]);
    229 
    230   // Get the data back out of the cache.
    231   const DBCacheKey key = { test_url_db[0].user_text, test_url_db[0].url };
    232   DBCacheMap::const_iterator it = db_cache()->find(key);
    233   EXPECT_TRUE(it != db_cache()->end());
    234 
    235   const DBCacheValue value = { test_url_db[0].number_of_hits,
    236                                test_url_db[0].number_of_misses };
    237   EXPECT_EQ(value.number_of_hits, it->second.number_of_hits);
    238   EXPECT_EQ(value.number_of_misses, it->second.number_of_misses);
    239 
    240   DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key);
    241   EXPECT_TRUE(id_it != db_id_cache()->end());
    242   EXPECT_EQ(guid, id_it->second);
    243 }
    244 
    245 TEST_F(AutocompleteActionPredictorTest, UpdateRow) {
    246   ASSERT_NO_FATAL_FAILURE(AddAllRows());
    247 
    248   EXPECT_EQ(arraysize(test_url_db), db_cache()->size());
    249   EXPECT_EQ(arraysize(test_url_db), db_id_cache()->size());
    250 
    251   // Get the data back out of the cache.
    252   const DBCacheKey key = { test_url_db[0].user_text, test_url_db[0].url };
    253   DBCacheMap::const_iterator it = db_cache()->find(key);
    254   EXPECT_TRUE(it != db_cache()->end());
    255 
    256   DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key);
    257   EXPECT_TRUE(id_it != db_id_cache()->end());
    258 
    259   AutocompleteActionPredictorTable::Row update_row;
    260   update_row.id = id_it->second;
    261   update_row.user_text = key.user_text;
    262   update_row.url = key.url;
    263   update_row.number_of_hits = it->second.number_of_hits + 1;
    264   update_row.number_of_misses = it->second.number_of_misses + 2;
    265 
    266   UpdateRow(update_row);
    267 
    268   // Get the updated version.
    269   DBCacheMap::const_iterator update_it = db_cache()->find(key);
    270   EXPECT_TRUE(update_it != db_cache()->end());
    271 
    272   EXPECT_EQ(update_row.number_of_hits, update_it->second.number_of_hits);
    273   EXPECT_EQ(update_row.number_of_misses, update_it->second.number_of_misses);
    274 
    275   DBIdCacheMap::const_iterator update_id_it = db_id_cache()->find(key);
    276   EXPECT_TRUE(update_id_it != db_id_cache()->end());
    277 
    278   EXPECT_EQ(id_it->second, update_id_it->second);
    279 }
    280 
    281 TEST_F(AutocompleteActionPredictorTest, DeleteAllRows) {
    282   ASSERT_NO_FATAL_FAILURE(AddAllRows());
    283 
    284   EXPECT_EQ(arraysize(test_url_db), db_cache()->size());
    285   EXPECT_EQ(arraysize(test_url_db), db_id_cache()->size());
    286 
    287   DeleteAllRows();
    288 
    289   EXPECT_TRUE(db_cache()->empty());
    290   EXPECT_TRUE(db_id_cache()->empty());
    291 }
    292 
    293 TEST_F(AutocompleteActionPredictorTest, DeleteRowsWithURLs) {
    294   ASSERT_NO_FATAL_FAILURE(AddAllRows());
    295 
    296   EXPECT_EQ(arraysize(test_url_db), db_cache()->size());
    297   EXPECT_EQ(arraysize(test_url_db), db_id_cache()->size());
    298 
    299   history::URLRows rows;
    300   for (size_t i = 0; i < 2; ++i)
    301     rows.push_back(history::URLRow(test_url_db[i].url));
    302 
    303   DeleteRowsWithURLs(rows);
    304 
    305   EXPECT_EQ(arraysize(test_url_db) - 2, db_cache()->size());
    306   EXPECT_EQ(arraysize(test_url_db) - 2, db_id_cache()->size());
    307 
    308   for (size_t i = 0; i < arraysize(test_url_db); ++i) {
    309     DBCacheKey key = { test_url_db[i].user_text, test_url_db[i].url };
    310 
    311     bool deleted = (i < 2);
    312     EXPECT_EQ(deleted, db_cache()->find(key) == db_cache()->end());
    313     EXPECT_EQ(deleted, db_id_cache()->find(key) == db_id_cache()->end());
    314   }
    315 }
    316 
    317 TEST_F(AutocompleteActionPredictorTest, DeleteOldIdsFromCaches) {
    318   std::vector<AutocompleteActionPredictorTable::Row::Id> expected;
    319   std::vector<AutocompleteActionPredictorTable::Row::Id> all_ids;
    320 
    321   for (size_t i = 0; i < arraysize(test_url_db); ++i) {
    322     std::string row_id = AddRow(test_url_db[i]);
    323     all_ids.push_back(row_id);
    324 
    325     bool exclude_url = StartsWithASCII(test_url_db[i].url.path(), "/d", true) ||
    326         (test_url_db[i].days_from_now > maximum_days_to_keep_entry());
    327 
    328     if (exclude_url)
    329       expected.push_back(row_id);
    330     else
    331       ASSERT_TRUE(AddRowToHistory(test_url_db[i]));
    332   }
    333 
    334   std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
    335   DeleteOldIdsFromCaches(&id_list);
    336   EXPECT_EQ(expected.size(), id_list.size());
    337   EXPECT_EQ(all_ids.size() - expected.size(), db_cache()->size());
    338   EXPECT_EQ(all_ids.size() - expected.size(), db_id_cache()->size());
    339 
    340   for (std::vector<AutocompleteActionPredictorTable::Row::Id>::iterator it =
    341        all_ids.begin();
    342        it != all_ids.end(); ++it) {
    343     bool in_expected =
    344         (std::find(expected.begin(), expected.end(), *it) != expected.end());
    345     bool in_list =
    346         (std::find(id_list.begin(), id_list.end(), *it) != id_list.end());
    347     EXPECT_EQ(in_expected, in_list);
    348   }
    349 }
    350 
    351 TEST_F(AutocompleteActionPredictorTest, RecommendActionURL) {
    352   ASSERT_NO_FATAL_FAILURE(AddAllRows());
    353 
    354   AutocompleteMatch match;
    355   match.type = AutocompleteMatchType::HISTORY_URL;
    356 
    357   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_url_db); ++i) {
    358     match.destination_url = GURL(test_url_db[i].url);
    359     EXPECT_EQ(test_url_db[i].expected_action,
    360               predictor()->RecommendAction(test_url_db[i].user_text, match))
    361         << "Unexpected action for " << match.destination_url;
    362   }
    363 }
    364 
    365 TEST_F(AutocompleteActionPredictorTest, RecommendActionSearch) {
    366   ASSERT_NO_FATAL_FAILURE(AddAllRows());
    367 
    368   AutocompleteMatch match;
    369   match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;
    370 
    371   for (size_t i = 0; i < arraysize(test_url_db); ++i) {
    372     match.destination_url = GURL(test_url_db[i].url);
    373     AutocompleteActionPredictor::Action expected_action =
    374         (test_url_db[i].expected_action ==
    375          AutocompleteActionPredictor::ACTION_PRERENDER) ?
    376         AutocompleteActionPredictor::ACTION_PRECONNECT :
    377         test_url_db[i].expected_action;
    378     EXPECT_EQ(expected_action,
    379               predictor()->RecommendAction(test_url_db[i].user_text, match))
    380         << "Unexpected action for " << match.destination_url;
    381   }
    382 }
    383 
    384 }  // namespace predictors
    385