Home | History | Annotate | Download | only in history
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <string>
      6 
      7 #include "base/file_util.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "base/memory/scoped_temp_dir.h"
     10 #include "base/string_util.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "chrome/browser/history/text_database.h"
     13 #include "testing/gtest/include/gtest/gtest.h"
     14 #include "testing/platform_test.h"
     15 
     16 using base::Time;
     17 
     18 namespace history {
     19 
     20 namespace {
     21 
     22 // Note that all pages have "COUNTTAG" which allows us to count the number of
     23 // pages in the database withoujt adding any extra functions to the DB object.
     24 const char kURL1[] = "http://www.google.com/";
     25 const int kTime1 = 1000;
     26 const char kTitle1[] = "Google";
     27 const char kBody1[] =
     28     "COUNTTAG Web Images Maps News Shopping Gmail more My Account | "
     29     "Sign out Advanced Search Preferences Language Tools Advertising Programs "
     30     "- Business Solutions - About Google, 2008 Google";
     31 
     32 const char kURL2[] = "http://images.google.com/";
     33 const int kTime2 = 2000;
     34 const char kTitle2[] = "Google Image Search";
     35 const char kBody2[] =
     36     "COUNTTAG Web Images Maps News Shopping Gmail more My Account | "
     37     "Sign out Advanced Image Search Preferences The most comprehensive image "
     38     "search on the web. Want to help improve Google Image Search? Try Google "
     39     "Image Labeler. Advertising Programs - Business Solutions - About Google "
     40     "2008 Google";
     41 
     42 const char kURL3[] = "http://slashdot.org/";
     43 const int kTime3 = 3000;
     44 const char kTitle3[] = "Slashdot: News for nerds, stuff that matters";
     45 const char kBody3[] =
     46     "COUNTTAG Slashdot Log In Create Account Subscribe Firehose Why "
     47     "Log In? Why Subscribe? Nickname Password Public Terminal Sections "
     48     "Main Apple AskSlashdot Backslash Books Developers Games Hardware "
     49     "Interviews IT Linux Mobile Politics Science YRO";
     50 
     51 // Returns the number of rows currently in the database.
     52 int RowCount(TextDatabase* db) {
     53   QueryOptions options;
     54   options.begin_time = Time::FromInternalValue(0);
     55   // Leave end_time at now.
     56 
     57   std::vector<TextDatabase::Match> results;
     58   Time first_time_searched;
     59   TextDatabase::URLSet unique_urls;
     60   db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
     61                      &first_time_searched);
     62   return static_cast<int>(results.size());
     63 }
     64 
     65 // Adds each of the test pages to the database.
     66 void AddAllTestData(TextDatabase* db) {
     67   EXPECT_TRUE(db->AddPageData(
     68       Time::FromInternalValue(kTime1), kURL1, kTitle1, kBody1));
     69   EXPECT_TRUE(db->AddPageData(
     70       Time::FromInternalValue(kTime2), kURL2, kTitle2, kBody2));
     71   EXPECT_TRUE(db->AddPageData(
     72       Time::FromInternalValue(kTime3), kURL3, kTitle3, kBody3));
     73   EXPECT_EQ(3, RowCount(db));
     74 }
     75 
     76 bool ResultsHaveURL(const std::vector<TextDatabase::Match>& results,
     77                     const char* url) {
     78   GURL gurl(url);
     79   for (size_t i = 0; i < results.size(); i++) {
     80     if (results[i].url == gurl)
     81       return true;
     82   }
     83   return false;
     84 }
     85 
     86 }  // namespace
     87 
     88 class TextDatabaseTest : public PlatformTest {
     89  public:
     90   TextDatabaseTest() {}
     91 
     92  protected:
     93   void SetUp() {
     94     PlatformTest::SetUp();
     95     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     96   }
     97 
     98   // Create databases with this function, which will ensure that the files are
     99   // deleted on shutdown. Only open one database for each file. Returns NULL on
    100   // failure.
    101   //
    102   // Set |delete_file| to delete any existing file. If we are trying to create
    103   // the file for the first time, we don't want a previous test left in a
    104   // weird state to have left a file that would affect us.
    105   TextDatabase* CreateDB(TextDatabase::DBIdent id,
    106                          bool allow_create,
    107                          bool delete_file) {
    108     TextDatabase* db = new TextDatabase(temp_dir_.path(), id, allow_create);
    109 
    110     if (delete_file)
    111       file_util::Delete(db->file_name(), false);
    112 
    113     if (!db->Init()) {
    114       delete db;
    115       return NULL;
    116     }
    117     return db;
    118   }
    119 
    120   // Directory containing the databases.
    121   ScopedTempDir temp_dir_;
    122 
    123   // Name of the main database file.
    124   FilePath file_name_;
    125 };
    126 
    127 TEST_F(TextDatabaseTest, AttachDetach) {
    128   // First database with one page.
    129   const int kIdee1 = 200801;
    130   scoped_ptr<TextDatabase> db1(CreateDB(kIdee1, true, true));
    131   ASSERT_TRUE(!!db1.get());
    132   EXPECT_TRUE(db1->AddPageData(
    133       Time::FromInternalValue(kTime1), kURL1, kTitle1, kBody1));
    134 
    135   // Second database with one page.
    136   const int kIdee2 = 200802;
    137   scoped_ptr<TextDatabase> db2(CreateDB(kIdee2, true, true));
    138   ASSERT_TRUE(!!db2.get());
    139   EXPECT_TRUE(db2->AddPageData(
    140       Time::FromInternalValue(kTime2), kURL2, kTitle2, kBody2));
    141 
    142   // Detach, then reattach database one. The file should exist, so we force
    143   // opening an existing file.
    144   db1.reset();
    145   db1.reset(CreateDB(kIdee1, false, false));
    146   ASSERT_TRUE(!!db1.get());
    147 
    148   // We should not be able to attach this random database for which no file
    149   // exists.
    150   const int kIdeeNoExisto = 999999999;
    151   scoped_ptr<TextDatabase> db3(CreateDB(kIdeeNoExisto, false, true));
    152   EXPECT_FALSE(!!db3.get());
    153 }
    154 
    155 TEST_F(TextDatabaseTest, AddRemove) {
    156   // Create a database and add some pages to it.
    157   const int kIdee1 = 200801;
    158   scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
    159   ASSERT_TRUE(!!db.get());
    160   URLID id1 = db->AddPageData(
    161       Time::FromInternalValue(kTime1), kURL1, kTitle1, kBody1);
    162   EXPECT_NE(0, id1);
    163   URLID id2 = db->AddPageData(
    164       Time::FromInternalValue(kTime2), kURL2, kTitle2, kBody2);
    165   EXPECT_NE(0, id2);
    166   URLID id3 = db->AddPageData(
    167       Time::FromInternalValue(kTime3), kURL3, kTitle3, kBody3);
    168   EXPECT_NE(0, id3);
    169   EXPECT_EQ(3, RowCount(db.get()));
    170 
    171   // Make sure we can delete some of the data.
    172   db->DeletePageData(Time::FromInternalValue(kTime1), kURL1);
    173   EXPECT_EQ(2, RowCount(db.get()));
    174 
    175   // Close and reopen.
    176   db.reset(new TextDatabase(temp_dir_.path(), kIdee1, false));
    177   EXPECT_TRUE(db->Init());
    178 
    179   // Verify that the deleted ID is gone and try to delete another one.
    180   EXPECT_EQ(2, RowCount(db.get()));
    181   db->DeletePageData(Time::FromInternalValue(kTime2), kURL2);
    182   EXPECT_EQ(1, RowCount(db.get()));
    183 }
    184 
    185 TEST_F(TextDatabaseTest, Query) {
    186   // Make a database with some pages.
    187   const int kIdee1 = 200801;
    188   scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
    189   EXPECT_TRUE(!!db.get());
    190   AddAllTestData(db.get());
    191 
    192   // Get all the results.
    193   QueryOptions options;
    194   options.begin_time = Time::FromInternalValue(0);
    195 
    196   std::vector<TextDatabase::Match> results;
    197   Time first_time_searched;
    198   TextDatabase::URLSet unique_urls;
    199   db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
    200                      &first_time_searched);
    201   EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";
    202 
    203   // All 3 sites should be returned in order.
    204   ASSERT_EQ(3U, results.size());
    205   EXPECT_EQ(GURL(kURL1), results[2].url);
    206   EXPECT_EQ(GURL(kURL2), results[1].url);
    207   EXPECT_EQ(GURL(kURL3), results[0].url);
    208 
    209   // Verify the info on those results.
    210   EXPECT_TRUE(Time::FromInternalValue(kTime1) == results[2].time);
    211   EXPECT_TRUE(Time::FromInternalValue(kTime2) == results[1].time);
    212   EXPECT_TRUE(Time::FromInternalValue(kTime3) == results[0].time);
    213 
    214   EXPECT_EQ(std::string(kTitle1), UTF16ToUTF8(results[2].title));
    215   EXPECT_EQ(std::string(kTitle2), UTF16ToUTF8(results[1].title));
    216   EXPECT_EQ(std::string(kTitle3), UTF16ToUTF8(results[0].title));
    217 
    218   // Should have no matches in the title.
    219   EXPECT_EQ(0U, results[0].title_match_positions.size());
    220   EXPECT_EQ(0U, results[1].title_match_positions.size());
    221   EXPECT_EQ(0U, results[2].title_match_positions.size());
    222 
    223   // We don't want to be dependent on the exact snippet algorithm, but we know
    224   // since we searched for "COUNTTAG" which occurs at the beginning of each
    225   // document, that each snippet should start with that.
    226   EXPECT_TRUE(StartsWithASCII(UTF16ToUTF8(results[0].snippet.text()),
    227                               "COUNTTAG", false));
    228   EXPECT_TRUE(StartsWithASCII(UTF16ToUTF8(results[1].snippet.text()),
    229                               "COUNTTAG", false));
    230   EXPECT_TRUE(StartsWithASCII(UTF16ToUTF8(results[2].snippet.text()),
    231                               "COUNTTAG", false));
    232 }
    233 
    234 TEST_F(TextDatabaseTest, TimeRange) {
    235   // Make a database with some pages.
    236   const int kIdee1 = 200801;
    237   scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
    238   ASSERT_TRUE(!!db.get());
    239   AddAllTestData(db.get());
    240 
    241   // Beginning should be inclusive, and the ending exclusive.
    242   // Get all the results.
    243   QueryOptions options;
    244   options.begin_time = Time::FromInternalValue(kTime1);
    245   options.end_time = Time::FromInternalValue(kTime3);
    246 
    247   std::vector<TextDatabase::Match> results;
    248   Time first_time_searched;
    249   TextDatabase::URLSet unique_urls;
    250   db->GetTextMatches("COUNTTAG", options, &results,  &unique_urls,
    251                      &first_time_searched);
    252   EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";
    253 
    254   // The first and second should have been returned.
    255   EXPECT_EQ(2U, results.size());
    256   EXPECT_TRUE(ResultsHaveURL(results, kURL1));
    257   EXPECT_TRUE(ResultsHaveURL(results, kURL2));
    258   EXPECT_FALSE(ResultsHaveURL(results, kURL3));
    259   EXPECT_EQ(kTime1, first_time_searched.ToInternalValue());
    260 
    261   // ---------------------------------------------------------------------------
    262   // Do a query where there isn't a result on the begin boundary, so we can
    263   // test that the first time searched is set to the minimum time considered
    264   // instead of the min value.
    265   options.begin_time = Time::FromInternalValue((kTime2 - kTime1) / 2 + kTime1);
    266   options.end_time = Time::FromInternalValue(kTime3 + 1);
    267   results.clear();  // GetTextMatches does *not* clear the results.
    268   db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
    269                      &first_time_searched);
    270   EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";
    271   EXPECT_EQ(options.begin_time.ToInternalValue(),
    272             first_time_searched.ToInternalValue());
    273 
    274   // Should have two results, the second and third.
    275   EXPECT_EQ(2U, results.size());
    276   EXPECT_FALSE(ResultsHaveURL(results, kURL1));
    277   EXPECT_TRUE(ResultsHaveURL(results, kURL2));
    278   EXPECT_TRUE(ResultsHaveURL(results, kURL3));
    279 
    280   // No results should also set the first_time_searched.
    281   options.begin_time = Time::FromInternalValue(kTime3 + 1);
    282   options.end_time = Time::FromInternalValue(kTime3 * 100);
    283   results.clear();
    284   db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
    285                      &first_time_searched);
    286   EXPECT_EQ(options.begin_time.ToInternalValue(),
    287             first_time_searched.ToInternalValue());
    288 }
    289 
    290 // Make sure that max_count works.
    291 TEST_F(TextDatabaseTest, MaxCount) {
    292   // Make a database with some pages.
    293   const int kIdee1 = 200801;
    294   scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
    295   ASSERT_TRUE(!!db.get());
    296   AddAllTestData(db.get());
    297 
    298   // Set up the query to return all the results with "Google" (should be 2), but
    299   // with a maximum of 1.
    300   QueryOptions options;
    301   options.begin_time = Time::FromInternalValue(kTime1);
    302   options.end_time = Time::FromInternalValue(kTime3 + 1);
    303   options.max_count = 1;
    304 
    305   std::vector<TextDatabase::Match> results;
    306   Time first_time_searched;
    307   TextDatabase::URLSet unique_urls;
    308   db->GetTextMatches("google", options, &results, &unique_urls,
    309                      &first_time_searched);
    310   EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";
    311 
    312   // There should be one result, the most recent one.
    313   EXPECT_EQ(1U, results.size());
    314   EXPECT_TRUE(ResultsHaveURL(results, kURL2));
    315 
    316   // The max time considered should be the date of the returned item.
    317   EXPECT_EQ(kTime2, first_time_searched.ToInternalValue());
    318 }
    319 
    320 }  // namespace history
    321