• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
RowCount(TextDatabase * db)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.
AddAllTestData(TextDatabase * db)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 
ResultsHaveURL(const std::vector<TextDatabase::Match> & results,const char * url)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:
TextDatabaseTest()90   TextDatabaseTest() {}
91 
92  protected:
SetUp()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.
CreateDB(TextDatabase::DBIdent id,bool allow_create,bool delete_file)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 
TEST_F(TextDatabaseTest,AttachDetach)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 
TEST_F(TextDatabaseTest,AddRemove)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 
TEST_F(TextDatabaseTest,Query)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 
TEST_F(TextDatabaseTest,TimeRange)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.
TEST_F(TextDatabaseTest,MaxCount)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