OLD | NEW |
| (Empty) |
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 <map> | |
6 | |
7 #include "base/files/file_path.h" | |
8 #include "base/files/scoped_temp_dir.h" | |
9 #include "base/path_service.h" | |
10 #include "base/strings/utf_string_conversions.h" | |
11 #include "chrome/browser/history/top_sites_database.h" | |
12 #include "chrome/common/chrome_paths.h" | |
13 #include "chrome/tools/profiles/thumbnail-inl.h" | |
14 #include "components/history/core/browser/history_types.h" | |
15 #include "sql/connection.h" | |
16 #include "sql/recovery.h" | |
17 #include "sql/test/scoped_error_ignorer.h" | |
18 #include "sql/test/test_helpers.h" | |
19 #include "testing/gtest/include/gtest/gtest.h" | |
20 #include "third_party/sqlite/sqlite3.h" | |
21 #include "url/gurl.h" | |
22 | |
23 namespace { | |
24 | |
25 // URL with url_rank 0 in golden files. | |
26 const GURL kUrl0 = GURL("http://www.google.com/"); | |
27 | |
28 // URL with url_rank 1 in golden files. | |
29 const GURL kUrl1 = GURL("http://www.google.com/chrome/intl/en/welcome.html"); | |
30 | |
31 // URL with url_rank 2 in golden files. | |
32 const GURL kUrl2 = GURL("https://chrome.google.com/webstore?hl=en"); | |
33 | |
34 // Create the test database at |db_path| from the golden file at | |
35 // |ascii_path| in the "History/" subdir of the test data dir. | |
36 WARN_UNUSED_RESULT bool CreateDatabaseFromSQL(const base::FilePath &db_path, | |
37 const char* ascii_path) { | |
38 base::FilePath sql_path; | |
39 if (!PathService::Get(chrome::DIR_TEST_DATA, &sql_path)) | |
40 return false; | |
41 sql_path = sql_path.AppendASCII("History").AppendASCII(ascii_path); | |
42 return sql::test::CreateDatabaseFromSQL(db_path, sql_path); | |
43 } | |
44 | |
45 // Verify that the up-to-date database has the expected tables and | |
46 // columns. Functional tests only check whether the things which | |
47 // should be there are, but do not check if extraneous items are | |
48 // present. Any extraneous items have the potential to interact | |
49 // negatively with future schema changes. | |
50 void VerifyTablesAndColumns(sql::Connection* db) { | |
51 // [meta] and [thumbnails]. | |
52 EXPECT_EQ(2u, sql::test::CountSQLTables(db)); | |
53 | |
54 // Implicit index on [meta], index on [thumbnails]. | |
55 EXPECT_EQ(2u, sql::test::CountSQLIndices(db)); | |
56 | |
57 // [key] and [value]. | |
58 EXPECT_EQ(2u, sql::test::CountTableColumns(db, "meta")); | |
59 | |
60 // [url], [url_rank], [title], [thumbnail], [redirects], | |
61 // [boring_score], [good_clipping], [at_top], [last_updated], and | |
62 // [load_completed], [last_forced] | |
63 EXPECT_EQ(11u, sql::test::CountTableColumns(db, "thumbnails")); | |
64 } | |
65 | |
66 void VerifyDatabaseEmpty(sql::Connection* db) { | |
67 size_t rows = 0; | |
68 EXPECT_TRUE(sql::test::CountTableRows(db, "thumbnails", &rows)); | |
69 EXPECT_EQ(0u, rows); | |
70 } | |
71 | |
72 } // namespace | |
73 | |
74 namespace history { | |
75 | |
76 class TopSitesDatabaseTest : public testing::Test { | |
77 protected: | |
78 void SetUp() override { | |
79 // Get a temporary directory for the test DB files. | |
80 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); | |
81 file_name_ = temp_dir_.path().AppendASCII("TestTopSites.db"); | |
82 } | |
83 | |
84 base::ScopedTempDir temp_dir_; | |
85 base::FilePath file_name_; | |
86 }; | |
87 | |
88 // Version 1 is deprecated, the resulting schema should be current, | |
89 // with no data. | |
90 TEST_F(TopSitesDatabaseTest, Version1) { | |
91 ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql")); | |
92 | |
93 TopSitesDatabase db; | |
94 ASSERT_TRUE(db.Init(file_name_)); | |
95 VerifyTablesAndColumns(db.db_.get()); | |
96 VerifyDatabaseEmpty(db.db_.get()); | |
97 } | |
98 | |
99 TEST_F(TopSitesDatabaseTest, Version2) { | |
100 ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql")); | |
101 | |
102 TopSitesDatabase db; | |
103 ASSERT_TRUE(db.Init(file_name_)); | |
104 | |
105 VerifyTablesAndColumns(db.db_.get()); | |
106 | |
107 // Basic operational check. | |
108 MostVisitedURLList urls; | |
109 std::map<GURL, Images> thumbnails; | |
110 db.GetPageThumbnails(&urls, &thumbnails); | |
111 ASSERT_EQ(3u, urls.size()); | |
112 ASSERT_EQ(3u, thumbnails.size()); | |
113 EXPECT_EQ(kUrl0, urls[0].url); // [0] because of url_rank. | |
114 // kGoogleThumbnail includes nul terminator. | |
115 ASSERT_EQ(sizeof(kGoogleThumbnail) - 1, | |
116 thumbnails[urls[0].url].thumbnail->size()); | |
117 EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(), | |
118 kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1)); | |
119 | |
120 ASSERT_TRUE(db.RemoveURL(urls[1])); | |
121 db.GetPageThumbnails(&urls, &thumbnails); | |
122 ASSERT_EQ(2u, urls.size()); | |
123 ASSERT_EQ(2u, thumbnails.size()); | |
124 } | |
125 | |
126 TEST_F(TopSitesDatabaseTest, Version3) { | |
127 ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql")); | |
128 | |
129 TopSitesDatabase db; | |
130 ASSERT_TRUE(db.Init(file_name_)); | |
131 | |
132 VerifyTablesAndColumns(db.db_.get()); | |
133 | |
134 // Basic operational check. | |
135 MostVisitedURLList urls; | |
136 std::map<GURL, Images> thumbnails; | |
137 db.GetPageThumbnails(&urls, &thumbnails); | |
138 ASSERT_EQ(3u, urls.size()); | |
139 ASSERT_EQ(3u, thumbnails.size()); | |
140 EXPECT_EQ(kUrl0, urls[0].url); // [0] because of url_rank. | |
141 // kGoogleThumbnail includes nul terminator. | |
142 ASSERT_EQ(sizeof(kGoogleThumbnail) - 1, | |
143 thumbnails[urls[0].url].thumbnail->size()); | |
144 EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(), | |
145 kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1)); | |
146 | |
147 ASSERT_TRUE(db.RemoveURL(urls[1])); | |
148 db.GetPageThumbnails(&urls, &thumbnails); | |
149 ASSERT_EQ(2u, urls.size()); | |
150 ASSERT_EQ(2u, thumbnails.size()); | |
151 } | |
152 | |
153 // Version 1 is deprecated, the resulting schema should be current, | |
154 // with no data. | |
155 TEST_F(TopSitesDatabaseTest, Recovery1) { | |
156 // Recovery module only supports some platforms at this time. | |
157 if (!sql::Recovery::FullRecoverySupported()) | |
158 return; | |
159 | |
160 // Create an example database. | |
161 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql")); | |
162 | |
163 // Corrupt the database by adjusting the header size. | |
164 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); | |
165 | |
166 // Database is unusable at the SQLite level. | |
167 { | |
168 sql::ScopedErrorIgnorer ignore_errors; | |
169 ignore_errors.IgnoreError(SQLITE_CORRUPT); | |
170 sql::Connection raw_db; | |
171 EXPECT_TRUE(raw_db.Open(file_name_)); | |
172 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); | |
173 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
174 } | |
175 | |
176 // Corruption should be detected and recovered during Init(). | |
177 { | |
178 sql::ScopedErrorIgnorer ignore_errors; | |
179 ignore_errors.IgnoreError(SQLITE_CORRUPT); | |
180 | |
181 TopSitesDatabase db; | |
182 ASSERT_TRUE(db.Init(file_name_)); | |
183 VerifyTablesAndColumns(db.db_.get()); | |
184 VerifyDatabaseEmpty(db.db_.get()); | |
185 | |
186 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
187 } | |
188 } | |
189 | |
190 TEST_F(TopSitesDatabaseTest, Recovery2) { | |
191 // Recovery module only supports some platforms at this time. | |
192 if (!sql::Recovery::FullRecoverySupported()) | |
193 return; | |
194 | |
195 // Create an example database. | |
196 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql")); | |
197 | |
198 // Corrupt the database by adjusting the header. | |
199 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); | |
200 | |
201 // Database is unusable at the SQLite level. | |
202 { | |
203 sql::ScopedErrorIgnorer ignore_errors; | |
204 ignore_errors.IgnoreError(SQLITE_CORRUPT); | |
205 sql::Connection raw_db; | |
206 EXPECT_TRUE(raw_db.Open(file_name_)); | |
207 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); | |
208 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
209 } | |
210 | |
211 // Corruption should be detected and recovered during Init(). After recovery, | |
212 // the Version2 checks should work. | |
213 { | |
214 sql::ScopedErrorIgnorer ignore_errors; | |
215 ignore_errors.IgnoreError(SQLITE_CORRUPT); | |
216 | |
217 TopSitesDatabase db; | |
218 ASSERT_TRUE(db.Init(file_name_)); | |
219 | |
220 VerifyTablesAndColumns(db.db_.get()); | |
221 | |
222 // Basic operational check. | |
223 MostVisitedURLList urls; | |
224 std::map<GURL, Images> thumbnails; | |
225 db.GetPageThumbnails(&urls, &thumbnails); | |
226 ASSERT_EQ(3u, urls.size()); | |
227 ASSERT_EQ(3u, thumbnails.size()); | |
228 EXPECT_EQ(kUrl0, urls[0].url); // [0] because of url_rank. | |
229 // kGoogleThumbnail includes nul terminator. | |
230 ASSERT_EQ(sizeof(kGoogleThumbnail) - 1, | |
231 thumbnails[urls[0].url].thumbnail->size()); | |
232 EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(), | |
233 kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1)); | |
234 | |
235 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
236 } | |
237 } | |
238 | |
239 TEST_F(TopSitesDatabaseTest, Recovery3) { | |
240 // Recovery module only supports some platforms at this time. | |
241 if (!sql::Recovery::FullRecoverySupported()) | |
242 return; | |
243 | |
244 // Create an example database. | |
245 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql")); | |
246 | |
247 // Corrupt the database by adjusting the header. | |
248 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); | |
249 | |
250 // Database is unusable at the SQLite level. | |
251 { | |
252 sql::ScopedErrorIgnorer ignore_errors; | |
253 ignore_errors.IgnoreError(SQLITE_CORRUPT); | |
254 sql::Connection raw_db; | |
255 EXPECT_TRUE(raw_db.Open(file_name_)); | |
256 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); | |
257 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
258 } | |
259 | |
260 // Corruption should be detected and recovered during Init(). | |
261 { | |
262 sql::ScopedErrorIgnorer ignore_errors; | |
263 ignore_errors.IgnoreError(SQLITE_CORRUPT); | |
264 | |
265 TopSitesDatabase db; | |
266 ASSERT_TRUE(db.Init(file_name_)); | |
267 | |
268 MostVisitedURLList urls; | |
269 std::map<GURL, Images> thumbnails; | |
270 db.GetPageThumbnails(&urls, &thumbnails); | |
271 ASSERT_EQ(3u, urls.size()); | |
272 ASSERT_EQ(3u, thumbnails.size()); | |
273 EXPECT_EQ(kUrl0, urls[0].url); // [0] because of url_rank. | |
274 // kGoogleThumbnail includes nul terminator. | |
275 ASSERT_EQ(sizeof(kGoogleThumbnail) - 1, | |
276 thumbnails[urls[0].url].thumbnail->size()); | |
277 EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(), | |
278 kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1)); | |
279 | |
280 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
281 } | |
282 | |
283 // Double-check database integrity. | |
284 { | |
285 sql::Connection raw_db; | |
286 EXPECT_TRUE(raw_db.Open(file_name_)); | |
287 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); | |
288 } | |
289 | |
290 // Corrupt the thumnails.url auto-index by deleting an element from the table | |
291 // but leaving it in the index. | |
292 const char kIndexName[] = "sqlite_autoindex_thumbnails_1"; | |
293 // TODO(shess): Refactor CorruptTableOrIndex() to make parameterized | |
294 // statements easy. | |
295 const char kDeleteSql[] = | |
296 "DELETE FROM thumbnails WHERE url = " | |
297 "'http://www.google.com/chrome/intl/en/welcome.html'"; | |
298 EXPECT_TRUE( | |
299 sql::test::CorruptTableOrIndex(file_name_, kIndexName, kDeleteSql)); | |
300 | |
301 // SQLite can operate on the database, but notices the corruption in integrity | |
302 // check. | |
303 { | |
304 sql::Connection raw_db; | |
305 EXPECT_TRUE(raw_db.Open(file_name_)); | |
306 ASSERT_NE("ok", sql::test::IntegrityCheck(&raw_db)); | |
307 } | |
308 | |
309 // Open the database and access the corrupt index. | |
310 { | |
311 TopSitesDatabase db; | |
312 ASSERT_TRUE(db.Init(file_name_)); | |
313 | |
314 { | |
315 sql::ScopedErrorIgnorer ignore_errors; | |
316 ignore_errors.IgnoreError(SQLITE_CORRUPT); | |
317 | |
318 // Data for kUrl1 was deleted, but the index entry remains, this will | |
319 // throw SQLITE_CORRUPT. The corruption handler will recover the database | |
320 // and poison the handle, so the outer call fails. | |
321 EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL, | |
322 db.GetURLRank(MostVisitedURL(kUrl1, base::string16()))); | |
323 | |
324 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
325 } | |
326 } | |
327 | |
328 // Check that the database is recovered at the SQLite level. | |
329 { | |
330 sql::Connection raw_db; | |
331 EXPECT_TRUE(raw_db.Open(file_name_)); | |
332 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); | |
333 } | |
334 | |
335 // After recovery, the database accesses won't throw errors. The top-ranked | |
336 // item is removed, but the ranking was revised in post-processing. | |
337 { | |
338 TopSitesDatabase db; | |
339 ASSERT_TRUE(db.Init(file_name_)); | |
340 VerifyTablesAndColumns(db.db_.get()); | |
341 | |
342 EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL, | |
343 db.GetURLRank(MostVisitedURL(kUrl1, base::string16()))); | |
344 | |
345 MostVisitedURLList urls; | |
346 std::map<GURL, Images> thumbnails; | |
347 db.GetPageThumbnails(&urls, &thumbnails); | |
348 ASSERT_EQ(2u, urls.size()); | |
349 ASSERT_EQ(2u, thumbnails.size()); | |
350 EXPECT_EQ(kUrl0, urls[0].url); // [0] because of url_rank. | |
351 EXPECT_EQ(kUrl2, urls[1].url); // [1] because of url_rank. | |
352 } | |
353 } | |
354 | |
355 TEST_F(TopSitesDatabaseTest, AddRemoveEditThumbnails) { | |
356 ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql")); | |
357 | |
358 TopSitesDatabase db; | |
359 ASSERT_TRUE(db.Init(file_name_)); | |
360 | |
361 // Add a new URL, not forced, rank = 1. | |
362 GURL mapsUrl = GURL("http://maps.google.com/"); | |
363 MostVisitedURL url1(mapsUrl, base::ASCIIToUTF16("Google Maps")); | |
364 db.SetPageThumbnail(url1, 1, Images()); | |
365 | |
366 MostVisitedURLList urls; | |
367 std::map<GURL, Images> thumbnails; | |
368 db.GetPageThumbnails(&urls, &thumbnails); | |
369 ASSERT_EQ(4u, urls.size()); | |
370 ASSERT_EQ(4u, thumbnails.size()); | |
371 EXPECT_EQ(kUrl0, urls[0].url); | |
372 EXPECT_EQ(mapsUrl, urls[1].url); | |
373 | |
374 // Add a new URL, forced. | |
375 GURL driveUrl = GURL("http://drive.google.com/"); | |
376 MostVisitedURL url2(driveUrl, base::ASCIIToUTF16("Google Drive")); | |
377 url2.last_forced_time = base::Time::FromJsTime(789714000000); // 10/1/1995 | |
378 db.SetPageThumbnail(url2, TopSitesDatabase::kRankOfForcedURL, Images()); | |
379 | |
380 db.GetPageThumbnails(&urls, &thumbnails); | |
381 ASSERT_EQ(5u, urls.size()); | |
382 ASSERT_EQ(5u, thumbnails.size()); | |
383 EXPECT_EQ(driveUrl, urls[0].url); // Forced URLs always appear first. | |
384 EXPECT_EQ(kUrl0, urls[1].url); | |
385 EXPECT_EQ(mapsUrl, urls[2].url); | |
386 | |
387 // Add a new URL, forced (earlier). | |
388 GURL plusUrl = GURL("http://plus.google.com/"); | |
389 MostVisitedURL url3(plusUrl, base::ASCIIToUTF16("Google Plus")); | |
390 url3.last_forced_time = base::Time::FromJsTime(787035600000); // 10/12/1994 | |
391 db.SetPageThumbnail(url3, TopSitesDatabase::kRankOfForcedURL, Images()); | |
392 | |
393 db.GetPageThumbnails(&urls, &thumbnails); | |
394 ASSERT_EQ(6u, urls.size()); | |
395 ASSERT_EQ(6u, thumbnails.size()); | |
396 EXPECT_EQ(plusUrl, urls[0].url); // New forced URL should appear first. | |
397 EXPECT_EQ(driveUrl, urls[1].url); | |
398 EXPECT_EQ(kUrl0, urls[2].url); | |
399 EXPECT_EQ(mapsUrl, urls[3].url); | |
400 | |
401 // Change the last_forced_time of a forced URL. | |
402 url3.last_forced_time = base::Time::FromJsTime(792392400000); // 10/2/1995 | |
403 db.SetPageThumbnail(url3, TopSitesDatabase::kRankOfForcedURL, Images()); | |
404 | |
405 db.GetPageThumbnails(&urls, &thumbnails); | |
406 ASSERT_EQ(6u, urls.size()); | |
407 ASSERT_EQ(6u, thumbnails.size()); | |
408 EXPECT_EQ(driveUrl, urls[0].url); | |
409 EXPECT_EQ(plusUrl, urls[1].url); // Forced URL should have moved second. | |
410 EXPECT_EQ(kUrl0, urls[2].url); | |
411 EXPECT_EQ(mapsUrl, urls[3].url); | |
412 | |
413 // Change a non-forced URL to forced using UpdatePageRank. | |
414 url1.last_forced_time = base::Time::FromJsTime(792219600000); // 8/2/1995 | |
415 db.UpdatePageRank(url1, TopSitesDatabase::kRankOfForcedURL); | |
416 | |
417 db.GetPageThumbnails(&urls, &thumbnails); | |
418 ASSERT_EQ(6u, urls.size()); | |
419 ASSERT_EQ(6u, thumbnails.size()); | |
420 EXPECT_EQ(driveUrl, urls[0].url); | |
421 EXPECT_EQ(mapsUrl, urls[1].url); // Maps moves to second forced URL. | |
422 EXPECT_EQ(plusUrl, urls[2].url); | |
423 EXPECT_EQ(kUrl0, urls[3].url); | |
424 | |
425 // Change a forced URL to non-forced using SetPageThumbnail. | |
426 url3.last_forced_time = base::Time(); | |
427 db.SetPageThumbnail(url3, 1, Images()); | |
428 | |
429 db.GetPageThumbnails(&urls, &thumbnails); | |
430 ASSERT_EQ(6u, urls.size()); | |
431 ASSERT_EQ(6u, thumbnails.size()); | |
432 EXPECT_EQ(driveUrl, urls[0].url); | |
433 EXPECT_EQ(mapsUrl, urls[1].url); | |
434 EXPECT_EQ(kUrl0, urls[2].url); | |
435 EXPECT_EQ(plusUrl, urls[3].url); // Plus moves to second non-forced URL. | |
436 | |
437 // Change a non-forced URL to earlier non-forced using UpdatePageRank. | |
438 db.UpdatePageRank(url3, 0); | |
439 | |
440 db.GetPageThumbnails(&urls, &thumbnails); | |
441 ASSERT_EQ(6u, urls.size()); | |
442 ASSERT_EQ(6u, thumbnails.size()); | |
443 EXPECT_EQ(driveUrl, urls[0].url); | |
444 EXPECT_EQ(mapsUrl, urls[1].url); | |
445 EXPECT_EQ(plusUrl, urls[2].url); // Plus moves to first non-forced URL. | |
446 EXPECT_EQ(kUrl0, urls[3].url); | |
447 | |
448 // Change a non-forced URL to later non-forced using SetPageThumbnail. | |
449 db.SetPageThumbnail(url3, 2, Images()); | |
450 | |
451 db.GetPageThumbnails(&urls, &thumbnails); | |
452 ASSERT_EQ(6u, urls.size()); | |
453 ASSERT_EQ(6u, thumbnails.size()); | |
454 EXPECT_EQ(driveUrl, urls[0].url); | |
455 EXPECT_EQ(mapsUrl, urls[1].url); | |
456 EXPECT_EQ(kUrl0, urls[2].url); | |
457 EXPECT_EQ(plusUrl, urls[4].url); // Plus moves to third non-forced URL. | |
458 | |
459 // Remove a non-forced URL. | |
460 db.RemoveURL(url3); | |
461 | |
462 db.GetPageThumbnails(&urls, &thumbnails); | |
463 ASSERT_EQ(5u, urls.size()); | |
464 ASSERT_EQ(5u, thumbnails.size()); | |
465 EXPECT_EQ(driveUrl, urls[0].url); | |
466 EXPECT_EQ(mapsUrl, urls[1].url); | |
467 EXPECT_EQ(kUrl0, urls[2].url); | |
468 | |
469 // Remove a forced URL. | |
470 db.RemoveURL(url2); | |
471 | |
472 db.GetPageThumbnails(&urls, &thumbnails); | |
473 ASSERT_EQ(4u, urls.size()); | |
474 ASSERT_EQ(4u, thumbnails.size()); | |
475 EXPECT_EQ(mapsUrl, urls[0].url); | |
476 EXPECT_EQ(kUrl0, urls[1].url); | |
477 } | |
478 | |
479 } // namespace history | |
OLD | NEW |