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/history/history_service.h"
16#include "chrome/browser/history/history_service_factory.h"
17#include "chrome/browser/prerender/prerender_field_trial.h"
18#include "chrome/common/chrome_switches.h"
19#include "chrome/test/base/testing_profile.h"
20#include "components/history/core/browser/in_memory_database.h"
21#include "components/history/core/browser/url_database.h"
22#include "components/omnibox/autocomplete_match.h"
23#include "content/public/test/test_browser_thread.h"
24#include "testing/gtest/include/gtest/gtest.h"
25
26using base::ASCIIToUTF16;
27using content::BrowserThread;
28using predictors::AutocompleteActionPredictor;
29
30namespace {
31
32struct TestUrlInfo {
33  GURL url;
34  base::string16 title;
35  int days_from_now;
36  base::string16 user_text;
37  int number_of_hits;
38  int number_of_misses;
39  AutocompleteActionPredictor::Action expected_action;
40} test_url_db[] = {
41  { GURL("http://www.testsite.com/a.html"),
42    ASCIIToUTF16("Test - site - just a test"), 1,
43    ASCIIToUTF16("j"), 5, 0,
44    AutocompleteActionPredictor::ACTION_PRERENDER },
45  { GURL("http://www.testsite.com/b.html"),
46    ASCIIToUTF16("Test - site - just a test"), 1,
47    ASCIIToUTF16("ju"), 3, 0,
48    AutocompleteActionPredictor::ACTION_PRERENDER },
49  { GURL("http://www.testsite.com/c.html"),
50    ASCIIToUTF16("Test - site - just a test"), 5,
51    ASCIIToUTF16("just"), 3, 1,
52    AutocompleteActionPredictor::ACTION_PRECONNECT },
53  { GURL("http://www.testsite.com/d.html"),
54    ASCIIToUTF16("Test - site - just a test"), 5,
55    ASCIIToUTF16("just"), 3, 0,
56    AutocompleteActionPredictor::ACTION_PRERENDER },
57  { GURL("http://www.testsite.com/e.html"),
58    ASCIIToUTF16("Test - site - just a test"), 8,
59    ASCIIToUTF16("just"), 3, 1,
60    AutocompleteActionPredictor::ACTION_PRECONNECT },
61  { GURL("http://www.testsite.com/f.html"),
62    ASCIIToUTF16("Test - site - just a test"), 8,
63    ASCIIToUTF16("just"), 3, 0,
64    AutocompleteActionPredictor::ACTION_PRERENDER },
65  { GURL("http://www.testsite.com/g.html"),
66    ASCIIToUTF16("Test - site - just a test"), 12,
67    base::string16(), 5, 0,
68    AutocompleteActionPredictor::ACTION_NONE },
69  { GURL("http://www.testsite.com/h.html"),
70    ASCIIToUTF16("Test - site - just a test"), 21,
71    ASCIIToUTF16("just a test"), 2, 0,
72    AutocompleteActionPredictor::ACTION_NONE },
73  { GURL("http://www.testsite.com/i.html"),
74    ASCIIToUTF16("Test - site - just a test"), 28,
75    ASCIIToUTF16("just a test"), 2, 0,
76    AutocompleteActionPredictor::ACTION_NONE }
77};
78
79}  // end namespace
80
81namespace predictors {
82
83class AutocompleteActionPredictorTest : public testing::Test {
84 public:
85  AutocompleteActionPredictorTest()
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
226TEST_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
245TEST_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
281TEST_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
293TEST_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
317TEST_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
351TEST_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
365TEST_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