普通文本  |  321行  |  11.49 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string>

#include "base/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_temp_dir.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/history/text_database.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

using base::Time;

namespace history {

namespace {

// Note that all pages have "COUNTTAG" which allows us to count the number of
// pages in the database withoujt adding any extra functions to the DB object.
const char kURL1[] = "http://www.google.com/";
const int kTime1 = 1000;
const char kTitle1[] = "Google";
const char kBody1[] =
    "COUNTTAG Web Images Maps News Shopping Gmail more My Account | "
    "Sign out Advanced Search Preferences Language Tools Advertising Programs "
    "- Business Solutions - About Google, 2008 Google";

const char kURL2[] = "http://images.google.com/";
const int kTime2 = 2000;
const char kTitle2[] = "Google Image Search";
const char kBody2[] =
    "COUNTTAG Web Images Maps News Shopping Gmail more My Account | "
    "Sign out Advanced Image Search Preferences The most comprehensive image "
    "search on the web. Want to help improve Google Image Search? Try Google "
    "Image Labeler. Advertising Programs - Business Solutions - About Google "
    "2008 Google";

const char kURL3[] = "http://slashdot.org/";
const int kTime3 = 3000;
const char kTitle3[] = "Slashdot: News for nerds, stuff that matters";
const char kBody3[] =
    "COUNTTAG Slashdot Log In Create Account Subscribe Firehose Why "
    "Log In? Why Subscribe? Nickname Password Public Terminal Sections "
    "Main Apple AskSlashdot Backslash Books Developers Games Hardware "
    "Interviews IT Linux Mobile Politics Science YRO";

// Returns the number of rows currently in the database.
int RowCount(TextDatabase* db) {
  QueryOptions options;
  options.begin_time = Time::FromInternalValue(0);
  // Leave end_time at now.

  std::vector<TextDatabase::Match> results;
  Time first_time_searched;
  TextDatabase::URLSet unique_urls;
  db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
                     &first_time_searched);
  return static_cast<int>(results.size());
}

// Adds each of the test pages to the database.
void AddAllTestData(TextDatabase* db) {
  EXPECT_TRUE(db->AddPageData(
      Time::FromInternalValue(kTime1), kURL1, kTitle1, kBody1));
  EXPECT_TRUE(db->AddPageData(
      Time::FromInternalValue(kTime2), kURL2, kTitle2, kBody2));
  EXPECT_TRUE(db->AddPageData(
      Time::FromInternalValue(kTime3), kURL3, kTitle3, kBody3));
  EXPECT_EQ(3, RowCount(db));
}

bool ResultsHaveURL(const std::vector<TextDatabase::Match>& results,
                    const char* url) {
  GURL gurl(url);
  for (size_t i = 0; i < results.size(); i++) {
    if (results[i].url == gurl)
      return true;
  }
  return false;
}

}  // namespace

class TextDatabaseTest : public PlatformTest {
 public:
  TextDatabaseTest() {}

 protected:
  void SetUp() {
    PlatformTest::SetUp();
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  }

  // Create databases with this function, which will ensure that the files are
  // deleted on shutdown. Only open one database for each file. Returns NULL on
  // failure.
  //
  // Set |delete_file| to delete any existing file. If we are trying to create
  // the file for the first time, we don't want a previous test left in a
  // weird state to have left a file that would affect us.
  TextDatabase* CreateDB(TextDatabase::DBIdent id,
                         bool allow_create,
                         bool delete_file) {
    TextDatabase* db = new TextDatabase(temp_dir_.path(), id, allow_create);

    if (delete_file)
      file_util::Delete(db->file_name(), false);

    if (!db->Init()) {
      delete db;
      return NULL;
    }
    return db;
  }

  // Directory containing the databases.
  ScopedTempDir temp_dir_;

  // Name of the main database file.
  FilePath file_name_;
};

TEST_F(TextDatabaseTest, AttachDetach) {
  // First database with one page.
  const int kIdee1 = 200801;
  scoped_ptr<TextDatabase> db1(CreateDB(kIdee1, true, true));
  ASSERT_TRUE(!!db1.get());
  EXPECT_TRUE(db1->AddPageData(
      Time::FromInternalValue(kTime1), kURL1, kTitle1, kBody1));

  // Second database with one page.
  const int kIdee2 = 200802;
  scoped_ptr<TextDatabase> db2(CreateDB(kIdee2, true, true));
  ASSERT_TRUE(!!db2.get());
  EXPECT_TRUE(db2->AddPageData(
      Time::FromInternalValue(kTime2), kURL2, kTitle2, kBody2));

  // Detach, then reattach database one. The file should exist, so we force
  // opening an existing file.
  db1.reset();
  db1.reset(CreateDB(kIdee1, false, false));
  ASSERT_TRUE(!!db1.get());

  // We should not be able to attach this random database for which no file
  // exists.
  const int kIdeeNoExisto = 999999999;
  scoped_ptr<TextDatabase> db3(CreateDB(kIdeeNoExisto, false, true));
  EXPECT_FALSE(!!db3.get());
}

TEST_F(TextDatabaseTest, AddRemove) {
  // Create a database and add some pages to it.
  const int kIdee1 = 200801;
  scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
  ASSERT_TRUE(!!db.get());
  URLID id1 = db->AddPageData(
      Time::FromInternalValue(kTime1), kURL1, kTitle1, kBody1);
  EXPECT_NE(0, id1);
  URLID id2 = db->AddPageData(
      Time::FromInternalValue(kTime2), kURL2, kTitle2, kBody2);
  EXPECT_NE(0, id2);
  URLID id3 = db->AddPageData(
      Time::FromInternalValue(kTime3), kURL3, kTitle3, kBody3);
  EXPECT_NE(0, id3);
  EXPECT_EQ(3, RowCount(db.get()));

  // Make sure we can delete some of the data.
  db->DeletePageData(Time::FromInternalValue(kTime1), kURL1);
  EXPECT_EQ(2, RowCount(db.get()));

  // Close and reopen.
  db.reset(new TextDatabase(temp_dir_.path(), kIdee1, false));
  EXPECT_TRUE(db->Init());

  // Verify that the deleted ID is gone and try to delete another one.
  EXPECT_EQ(2, RowCount(db.get()));
  db->DeletePageData(Time::FromInternalValue(kTime2), kURL2);
  EXPECT_EQ(1, RowCount(db.get()));
}

TEST_F(TextDatabaseTest, Query) {
  // Make a database with some pages.
  const int kIdee1 = 200801;
  scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
  EXPECT_TRUE(!!db.get());
  AddAllTestData(db.get());

  // Get all the results.
  QueryOptions options;
  options.begin_time = Time::FromInternalValue(0);

  std::vector<TextDatabase::Match> results;
  Time first_time_searched;
  TextDatabase::URLSet unique_urls;
  db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
                     &first_time_searched);
  EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";

  // All 3 sites should be returned in order.
  ASSERT_EQ(3U, results.size());
  EXPECT_EQ(GURL(kURL1), results[2].url);
  EXPECT_EQ(GURL(kURL2), results[1].url);
  EXPECT_EQ(GURL(kURL3), results[0].url);

  // Verify the info on those results.
  EXPECT_TRUE(Time::FromInternalValue(kTime1) == results[2].time);
  EXPECT_TRUE(Time::FromInternalValue(kTime2) == results[1].time);
  EXPECT_TRUE(Time::FromInternalValue(kTime3) == results[0].time);

  EXPECT_EQ(std::string(kTitle1), UTF16ToUTF8(results[2].title));
  EXPECT_EQ(std::string(kTitle2), UTF16ToUTF8(results[1].title));
  EXPECT_EQ(std::string(kTitle3), UTF16ToUTF8(results[0].title));

  // Should have no matches in the title.
  EXPECT_EQ(0U, results[0].title_match_positions.size());
  EXPECT_EQ(0U, results[1].title_match_positions.size());
  EXPECT_EQ(0U, results[2].title_match_positions.size());

  // We don't want to be dependent on the exact snippet algorithm, but we know
  // since we searched for "COUNTTAG" which occurs at the beginning of each
  // document, that each snippet should start with that.
  EXPECT_TRUE(StartsWithASCII(UTF16ToUTF8(results[0].snippet.text()),
                              "COUNTTAG", false));
  EXPECT_TRUE(StartsWithASCII(UTF16ToUTF8(results[1].snippet.text()),
                              "COUNTTAG", false));
  EXPECT_TRUE(StartsWithASCII(UTF16ToUTF8(results[2].snippet.text()),
                              "COUNTTAG", false));
}

TEST_F(TextDatabaseTest, TimeRange) {
  // Make a database with some pages.
  const int kIdee1 = 200801;
  scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
  ASSERT_TRUE(!!db.get());
  AddAllTestData(db.get());

  // Beginning should be inclusive, and the ending exclusive.
  // Get all the results.
  QueryOptions options;
  options.begin_time = Time::FromInternalValue(kTime1);
  options.end_time = Time::FromInternalValue(kTime3);

  std::vector<TextDatabase::Match> results;
  Time first_time_searched;
  TextDatabase::URLSet unique_urls;
  db->GetTextMatches("COUNTTAG", options, &results,  &unique_urls,
                     &first_time_searched);
  EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";

  // The first and second should have been returned.
  EXPECT_EQ(2U, results.size());
  EXPECT_TRUE(ResultsHaveURL(results, kURL1));
  EXPECT_TRUE(ResultsHaveURL(results, kURL2));
  EXPECT_FALSE(ResultsHaveURL(results, kURL3));
  EXPECT_EQ(kTime1, first_time_searched.ToInternalValue());

  // ---------------------------------------------------------------------------
  // Do a query where there isn't a result on the begin boundary, so we can
  // test that the first time searched is set to the minimum time considered
  // instead of the min value.
  options.begin_time = Time::FromInternalValue((kTime2 - kTime1) / 2 + kTime1);
  options.end_time = Time::FromInternalValue(kTime3 + 1);
  results.clear();  // GetTextMatches does *not* clear the results.
  db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
                     &first_time_searched);
  EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";
  EXPECT_EQ(options.begin_time.ToInternalValue(),
            first_time_searched.ToInternalValue());

  // Should have two results, the second and third.
  EXPECT_EQ(2U, results.size());
  EXPECT_FALSE(ResultsHaveURL(results, kURL1));
  EXPECT_TRUE(ResultsHaveURL(results, kURL2));
  EXPECT_TRUE(ResultsHaveURL(results, kURL3));

  // No results should also set the first_time_searched.
  options.begin_time = Time::FromInternalValue(kTime3 + 1);
  options.end_time = Time::FromInternalValue(kTime3 * 100);
  results.clear();
  db->GetTextMatches("COUNTTAG", options, &results, &unique_urls,
                     &first_time_searched);
  EXPECT_EQ(options.begin_time.ToInternalValue(),
            first_time_searched.ToInternalValue());
}

// Make sure that max_count works.
TEST_F(TextDatabaseTest, MaxCount) {
  // Make a database with some pages.
  const int kIdee1 = 200801;
  scoped_ptr<TextDatabase> db(CreateDB(kIdee1, true, true));
  ASSERT_TRUE(!!db.get());
  AddAllTestData(db.get());

  // Set up the query to return all the results with "Google" (should be 2), but
  // with a maximum of 1.
  QueryOptions options;
  options.begin_time = Time::FromInternalValue(kTime1);
  options.end_time = Time::FromInternalValue(kTime3 + 1);
  options.max_count = 1;

  std::vector<TextDatabase::Match> results;
  Time first_time_searched;
  TextDatabase::URLSet unique_urls;
  db->GetTextMatches("google", options, &results, &unique_urls,
                     &first_time_searched);
  EXPECT_TRUE(unique_urls.empty()) << "Didn't ask for unique URLs";

  // There should be one result, the most recent one.
  EXPECT_EQ(1U, results.size());
  EXPECT_TRUE(ResultsHaveURL(results, kURL2));

  // The max time considered should be the date of the returned item.
  EXPECT_EQ(kTime2, first_time_searched.ToInternalValue());
}

}  // namespace history