diff options
author | sdefresne <sdefresne@chromium.org> | 2015-05-05 11:14:24 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-05-05 18:15:50 +0000 |
commit | f13c9fddb23eb7ee9acfe6a5c5907c389c3c6da3 (patch) | |
tree | 58d2bbd4742f300b7a56a098b893245821b614a1 | |
parent | 84809a6eb565c1bc54abb25c261f94ed2b4b3c83 (diff) | |
download | chromium_src-f13c9fddb23eb7ee9acfe6a5c5907c389c3c6da3.zip chromium_src-f13c9fddb23eb7ee9acfe6a5c5907c389c3c6da3.tar.gz chromium_src-f13c9fddb23eb7ee9acfe6a5c5907c389c3c6da3.tar.bz2 |
[History] Componentize history unittests
Remove dependencies on //content & //chrome that are no longer needed
and move unit tests into //components/history/core/browser.
Rename incorrectly named HistoryBackendDBTest and cie. to avoid name
collision with HistoryBackendTest.
Cleanup ZeroSuggestProvider to not use TestingProfile::DestroyTopSites
as it is not needed since it is registering a fake TopSites that does
not have the limitation of TopSitesImpl of only one instance per path.
Remove methods CreateTopSites/DestroyTopSites/BlockUntilTopSitesLoaded
from TestingProfile as they are no longer used.
Introduce //components/history/core/test/thumbnail{.cc,_ios.mm} to
abstract dependency on JPEG loading and run the tests on iOS.
Move tests data to //components/test/data/history.
BUG=370850, 227969
Review URL: https://codereview.chromium.org/1121473002
Cr-Commit-Position: refs/heads/master@{#328358}
54 files changed, 4496 insertions, 4477 deletions
diff --git a/chrome/browser/autocomplete/zero_suggest_provider_unittest.cc b/chrome/browser/autocomplete/zero_suggest_provider_unittest.cc index e15c471..5699e93 100644 --- a/chrome/browser/autocomplete/zero_suggest_provider_unittest.cc +++ b/chrome/browser/autocomplete/zero_suggest_provider_unittest.cc @@ -181,9 +181,8 @@ void ZeroSuggestProviderTest::SetUp() { turl_model->Add(default_t_url_); turl_model->SetUserSelectedDefaultSearchProvider(default_t_url_); - profile_.DestroyTopSites(); - TopSitesFactory::GetInstance()->SetTestingFactory(&profile_, - BuildFakeEmptyTopSites); + TopSitesFactory* top_sites_factory = TopSitesFactory::GetInstance(); + top_sites_factory->SetTestingFactory(&profile_, BuildFakeEmptyTopSites); provider_ = ZeroSuggestProvider::Create(this, turl_model, &profile_); } diff --git a/chrome/browser/history/history_backend_unittest.cc b/chrome/browser/history/history_backend_unittest.cc deleted file mode 100644 index 308b9fd..0000000 --- a/chrome/browser/history/history_backend_unittest.cc +++ /dev/null @@ -1,3337 +0,0 @@ -// Copyright (c) 2012 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 "components/history/core/browser/history_backend.h" - -#include <algorithm> -#include <set> -#include <vector> - -#include "base/basictypes.h" -#include "base/bind.h" -#include "base/callback.h" -#include "base/command_line.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/path_service.h" -#include "base/prefs/pref_service.h" -#include "base/run_loop.h" -#include "base/strings/string16.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/history/history_service_factory.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/common/chrome_paths.h" -#include "chrome/common/pref_names.h" -#include "chrome/test/base/testing_profile.h" -#include "components/favicon_base/favicon_usage_data.h" -#include "components/history/content/browser/content_visit_delegate.h" -#include "components/history/core/browser/history_constants.h" -#include "components/history/core/browser/history_database_params.h" -#include "components/history/core/browser/history_service.h" -#include "components/history/core/browser/history_service_observer.h" -#include "components/history/core/browser/in_memory_database.h" -#include "components/history/core/browser/in_memory_history_backend.h" -#include "components/history/core/browser/keyword_search_term.h" -#include "components/history/core/browser/visit_filter.h" -#include "components/history/core/test/history_client_fake_bookmarks.h" -#include "components/history/core/test/test_history_database.h" -#include "content/public/test/test_browser_thread.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "ui/gfx/codec/png_codec.h" -#include "url/gurl.h" - -using base::Time; - -// This file only tests functionality where it is most convenient to call the -// backend directly. Most of the history backend functions are tested by the -// history unit test. Because of the elaborate callbacks involved, this is no -// harder than calling it directly for many things. - -namespace { - -const int kTinyEdgeSize = 10; -const int kSmallEdgeSize = 16; -const int kLargeEdgeSize = 32; - -const gfx::Size kTinySize = gfx::Size(kTinyEdgeSize, kTinyEdgeSize); -const gfx::Size kSmallSize = gfx::Size(kSmallEdgeSize, kSmallEdgeSize); -const gfx::Size kLargeSize = gfx::Size(kLargeEdgeSize, kLargeEdgeSize); - -typedef base::Callback<void(const history::URLRow*, - const history::URLRow*, - const history::URLRow*)> - SimulateNotificationCallback; - -class HistoryClientMock : public history::HistoryClientFakeBookmarks { - public: - MOCK_METHOD0(BlockUntilBookmarksLoaded, void()); -}; - -void SimulateNotificationURLVisited(history::HistoryServiceObserver* observer, - const history::URLRow* row1, - const history::URLRow* row2, - const history::URLRow* row3) { - history::URLRows rows; - rows.push_back(*row1); - if (row2) - rows.push_back(*row2); - if (row3) - rows.push_back(*row3); - - base::Time visit_time; - history::RedirectList redirects; - for (const auto& row : rows) { - observer->OnURLVisited( - nullptr, ui::PAGE_TRANSITION_LINK, row, redirects, visit_time); - } -} - -void SimulateNotificationURLsModified(history::HistoryServiceObserver* observer, - const history::URLRow* row1, - const history::URLRow* row2, - const history::URLRow* row3) { - history::URLRows rows; - rows.push_back(*row1); - if (row2) - rows.push_back(*row2); - if (row3) - rows.push_back(*row3); - - observer->OnURLsModified(nullptr, rows); -} - -} // namespace - -namespace history { - -class HistoryBackendTestBase; - -// This must be a separate object since HistoryBackend manages its lifetime. -// This just forwards the messages we're interested in to the test object. -class HistoryBackendTestDelegate : public HistoryBackend::Delegate { - public: - explicit HistoryBackendTestDelegate(HistoryBackendTestBase* test) - : test_(test) {} - - void NotifyProfileError(sql::InitStatus init_status) override {} - void SetInMemoryBackend(scoped_ptr<InMemoryHistoryBackend> backend) override; - void NotifyFaviconChanged(const std::set<GURL>& urls) override; - void NotifyURLVisited(ui::PageTransition transition, - const URLRow& row, - const RedirectList& redirects, - base::Time visit_time) override; - void NotifyURLsModified(const URLRows& changed_urls) override; - void NotifyURLsDeleted(bool all_history, - bool expired, - const URLRows& deleted_rows, - const std::set<GURL>& favicon_urls) override; - void NotifyKeywordSearchTermUpdated(const URLRow& row, - KeywordID keyword_id, - const base::string16& term) override; - void NotifyKeywordSearchTermDeleted(URLID url_id) override; - void DBLoaded() override; - - private: - // Not owned by us. - HistoryBackendTestBase* test_; - - DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestDelegate); -}; - -class HistoryBackendTestBase : public testing::Test { - public: - typedef std::vector<std::pair<ui::PageTransition, URLRow>> URLVisitedList; - typedef std::vector<URLRows> URLsModifiedList; - typedef std::vector<std::pair<bool, bool>> URLsDeletedList; - - HistoryBackendTestBase() - : loaded_(false), - favicon_changed_notifications_(0), - ui_thread_(content::BrowserThread::UI, &message_loop_) {} - - ~HistoryBackendTestBase() override { - } - - protected: - int favicon_changed_notifications() const { - return favicon_changed_notifications_; - } - - void ClearFaviconChangedNotificationCounter() { - favicon_changed_notifications_ = 0; - } - - int num_url_visited_notifications() const { - return url_visited_notifications_.size(); - } - - const URLVisitedList& url_visited_notifications() const { - return url_visited_notifications_; - } - - int num_urls_modified_notifications() const { - return urls_modified_notifications_.size(); - } - - const URLsModifiedList& urls_modified_notifications() const { - return urls_modified_notifications_; - } - - const URLsDeletedList& urls_deleted_notifications() const { - return urls_deleted_notifications_; - } - - void ClearBroadcastedNotifications() { - url_visited_notifications_.clear(); - urls_modified_notifications_.clear(); - urls_deleted_notifications_.clear(); - } - - base::FilePath test_dir() { - return test_dir_; - } - - void NotifyFaviconChanged(const std::set<GURL>& changed_favicons) { - ++favicon_changed_notifications_; - } - - void NotifyURLVisited(ui::PageTransition transition, - const URLRow& row, - const RedirectList& redirects, - base::Time visit_time) { - // Send the notifications directly to the in-memory database. - mem_backend_->OnURLVisited(nullptr, transition, row, redirects, visit_time); - url_visited_notifications_.push_back(std::make_pair(transition, row)); - } - - void NotifyURLsModified(const URLRows& changed_urls) { - // Send the notifications directly to the in-memory database. - mem_backend_->OnURLsModified(nullptr, changed_urls); - urls_modified_notifications_.push_back(changed_urls); - } - - void NotifyURLsDeleted(bool all_history, - bool expired, - const URLRows& deleted_rows, - const std::set<GURL>& favicon_urls) { - mem_backend_->OnURLsDeleted(nullptr, all_history, expired, deleted_rows, - favicon_urls); - urls_deleted_notifications_.push_back(std::make_pair(all_history, expired)); - } - - void NotifyKeywordSearchTermUpdated(const URLRow& row, - KeywordID keyword_id, - const base::string16& term) { - mem_backend_->OnKeywordSearchTermUpdated(nullptr, row, keyword_id, term); - } - - void NotifyKeywordSearchTermDeleted(URLID url_id) { - mem_backend_->OnKeywordSearchTermDeleted(nullptr, url_id); - } - - history::HistoryClientFakeBookmarks history_client_; - scoped_refptr<HistoryBackend> backend_; // Will be NULL on init failure. - scoped_ptr<InMemoryHistoryBackend> mem_backend_; - bool loaded_; - - private: - friend class HistoryBackendTestDelegate; - - // testing::Test - void SetUp() override { - ClearFaviconChangedNotificationCounter(); - if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("BackendTest"), - &test_dir_)) - return; - backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this), - &history_client_); - backend_->Init(std::string(), false, - TestHistoryDatabaseParamsForPath(test_dir_)); - } - - void TearDown() override { - if (backend_.get()) - backend_->Closing(); - backend_ = NULL; - mem_backend_.reset(); - base::DeleteFile(test_dir_, true); - base::RunLoop().RunUntilIdle(); - history_client_.ClearAllBookmarks(); - } - - void SetInMemoryBackend(scoped_ptr<InMemoryHistoryBackend> backend) { - mem_backend_.swap(backend); - } - - // The types and details of notifications which were broadcasted. - int favicon_changed_notifications_; - URLVisitedList url_visited_notifications_; - URLsModifiedList urls_modified_notifications_; - URLsDeletedList urls_deleted_notifications_; - - base::MessageLoop message_loop_; - base::FilePath test_dir_; - content::TestBrowserThread ui_thread_; - - DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestBase); -}; - -void HistoryBackendTestDelegate::SetInMemoryBackend( - scoped_ptr<InMemoryHistoryBackend> backend) { - test_->SetInMemoryBackend(backend.Pass()); -} - -void HistoryBackendTestDelegate::NotifyFaviconChanged( - const std::set<GURL>& changed_favicons) { - test_->NotifyFaviconChanged(changed_favicons); -} - -void HistoryBackendTestDelegate::NotifyURLVisited(ui::PageTransition transition, - const URLRow& row, - const RedirectList& redirects, - base::Time visit_time) { - test_->NotifyURLVisited(transition, row, redirects, visit_time); -} - -void HistoryBackendTestDelegate::NotifyURLsModified( - const URLRows& changed_urls) { - test_->NotifyURLsModified(changed_urls); -} - -void HistoryBackendTestDelegate::NotifyURLsDeleted( - bool all_history, - bool expired, - const URLRows& deleted_rows, - const std::set<GURL>& favicon_urls) { - test_->NotifyURLsDeleted(all_history, expired, deleted_rows, favicon_urls); -} - -void HistoryBackendTestDelegate::NotifyKeywordSearchTermUpdated( - const URLRow& row, - KeywordID keyword_id, - const base::string16& term) { - test_->NotifyKeywordSearchTermUpdated(row, keyword_id, term); -} - -void HistoryBackendTestDelegate::NotifyKeywordSearchTermDeleted(URLID url_id) { - test_->NotifyKeywordSearchTermDeleted(url_id); -} - -void HistoryBackendTestDelegate::DBLoaded() { - test_->loaded_ = true; -} - -class HistoryBackendTest : public HistoryBackendTestBase { - public: - HistoryBackendTest() {} - ~HistoryBackendTest() override {} - - protected: - void AddRedirectChain(const char* sequence[], int nav_entry_id) { - AddRedirectChainWithTransitionAndTime(sequence, nav_entry_id, - ui::PAGE_TRANSITION_LINK, - Time::Now()); - } - - void AddRedirectChainWithTransitionAndTime( - const char* sequence[], - int nav_entry_id, - ui::PageTransition transition, - base::Time time) { - history::RedirectList redirects; - for (int i = 0; sequence[i] != NULL; ++i) - redirects.push_back(GURL(sequence[i])); - - ContextID context_id = reinterpret_cast<ContextID>(1); - history::HistoryAddPageArgs request( - redirects.back(), time, context_id, nav_entry_id, GURL(), - redirects, transition, history::SOURCE_BROWSED, - true); - backend_->AddPage(request); - } - - // Adds CLIENT_REDIRECT page transition. - // |url1| is the source URL and |url2| is the destination. - // |did_replace| is true if the transition is non-user initiated and the - // navigation entry for |url2| has replaced that for |url1|. The possibly - // updated transition code of the visit records for |url1| and |url2| is - // returned by filling in |*transition1| and |*transition2|, respectively. - // |time| is a time of the redirect. - void AddClientRedirect(const GURL& url1, const GURL& url2, bool did_replace, - base::Time time, - int* transition1, int* transition2) { - ContextID dummy_context_id = reinterpret_cast<ContextID>(0x87654321); - history::RedirectList redirects; - if (url1.is_valid()) - redirects.push_back(url1); - if (url2.is_valid()) - redirects.push_back(url2); - HistoryAddPageArgs request( - url2, time, dummy_context_id, 0, url1, - redirects, ui::PAGE_TRANSITION_CLIENT_REDIRECT, - history::SOURCE_BROWSED, did_replace); - backend_->AddPage(request); - - *transition1 = GetTransition(url1); - *transition2 = GetTransition(url2); - } - - int GetTransition(const GURL& url) { - if (!url.is_valid()) - return 0; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url, &row); - VisitVector visits; - EXPECT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - return visits[0].transition; - } - - // Returns a vector with the small and large edge sizes. - const std::vector<int> GetEdgeSizesSmallAndLarge() { - std::vector<int> sizes_small_and_large; - sizes_small_and_large.push_back(kSmallEdgeSize); - sizes_small_and_large.push_back(kLargeEdgeSize); - return sizes_small_and_large; - } - - // Returns the number of icon mappings of |icon_type| to |page_url|. - size_t NumIconMappingsForPageURL(const GURL& page_url, - favicon_base::IconType icon_type) { - std::vector<IconMapping> icon_mappings; - backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_type, - &icon_mappings); - return icon_mappings.size(); - } - - // Returns the icon mappings for |page_url| sorted alphabetically by icon - // URL in ascending order. Returns true if there is at least one icon - // mapping. - bool GetSortedIconMappingsForPageURL( - const GURL& page_url, - std::vector<IconMapping>* icon_mappings) { - if (!backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - icon_mappings)) { - return false; - } - std::sort(icon_mappings->begin(), icon_mappings->end(), - [](const history::IconMapping& a, const history::IconMapping& b) { - return a.icon_url < b.icon_url; - }); - return true; - } - - // Returns the favicon bitmaps for |icon_id| sorted by pixel size in - // ascending order. Returns true if there is at least one favicon bitmap. - bool GetSortedFaviconBitmaps(favicon_base::FaviconID icon_id, - std::vector<FaviconBitmap>* favicon_bitmaps) { - if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, favicon_bitmaps)) - return false; - std::sort( - favicon_bitmaps->begin(), favicon_bitmaps->end(), - [](const history::FaviconBitmap& a, const history::FaviconBitmap& b) { - return a.pixel_size.GetArea() < b.pixel_size.GetArea(); - }); - return true; - } - - // Returns true if there is exactly one favicon bitmap associated to - // |favicon_id|. If true, returns favicon bitmap in output parameter. - bool GetOnlyFaviconBitmap(const favicon_base::FaviconID icon_id, - FaviconBitmap* favicon_bitmap) { - std::vector<FaviconBitmap> favicon_bitmaps; - if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, &favicon_bitmaps)) - return false; - if (favicon_bitmaps.size() != 1) - return false; - *favicon_bitmap = favicon_bitmaps[0]; - return true; - } - - // Creates an |edge_size|x|edge_size| bitmap of |color|. - SkBitmap CreateBitmap(SkColor color, int edge_size) { - SkBitmap bitmap; - bitmap.allocN32Pixels(edge_size, edge_size); - bitmap.eraseColor(color); - return bitmap; - } - - // Returns true if |bitmap_data| is equal to |expected_data|. - bool BitmapDataEqual(char expected_data, - scoped_refptr<base::RefCountedMemory> bitmap_data) { - return bitmap_data.get() && - bitmap_data->size() == 1u && - *bitmap_data->front() == expected_data; - } - - // Returns true if |bitmap_data| is of |color|. - bool BitmapColorEqual(SkColor expected_color, - scoped_refptr<base::RefCountedMemory> bitmap_data) { - SkBitmap bitmap; - if (!gfx::PNGCodec::Decode( - bitmap_data->front(), bitmap_data->size(), &bitmap)) - return false; - SkAutoLockPixels bitmap_lock(bitmap); - return expected_color == bitmap.getColor(0, 0); - } - - private: - DISALLOW_COPY_AND_ASSIGN(HistoryBackendTest); -}; - -class InMemoryHistoryBackendTest : public HistoryBackendTestBase { - public: - InMemoryHistoryBackendTest() {} - ~InMemoryHistoryBackendTest() override {} - - protected: - void SimulateNotificationURLsDeleted(const URLRow* row1, - const URLRow* row2 = NULL, - const URLRow* row3 = NULL) { - URLRows rows; - rows.push_back(*row1); - if (row2) rows.push_back(*row2); - if (row3) rows.push_back(*row3); - - NotifyURLsDeleted(false, false, rows, std::set<GURL>()); - } - - size_t GetNumberOfMatchingSearchTerms(const int keyword_id, - const base::string16& prefix) { - std::vector<KeywordSearchTermVisit> matching_terms; - mem_backend_->db()->GetMostRecentKeywordSearchTerms( - keyword_id, prefix, 1, &matching_terms); - return matching_terms.size(); - } - - static URLRow CreateTestTypedURL() { - URLRow url_row(GURL("https://www.google.com/")); - url_row.set_id(10); - url_row.set_title(base::UTF8ToUTF16("Google Search")); - url_row.set_typed_count(1); - url_row.set_visit_count(1); - url_row.set_last_visit(Time::Now() - base::TimeDelta::FromHours(1)); - return url_row; - } - - static URLRow CreateAnotherTestTypedURL() { - URLRow url_row(GURL("https://maps.google.com/")); - url_row.set_id(20); - url_row.set_title(base::UTF8ToUTF16("Google Maps")); - url_row.set_typed_count(2); - url_row.set_visit_count(3); - url_row.set_last_visit(Time::Now() - base::TimeDelta::FromHours(2)); - return url_row; - } - - static URLRow CreateTestNonTypedURL() { - URLRow url_row(GURL("https://news.google.com/")); - url_row.set_id(30); - url_row.set_title(base::UTF8ToUTF16("Google News")); - url_row.set_visit_count(5); - url_row.set_last_visit(Time::Now() - base::TimeDelta::FromHours(3)); - return url_row; - } - - void PopulateTestURLsAndSearchTerms(URLRow* row1, - URLRow* row2, - const base::string16& term1, - const base::string16& term2); - - void TestAddingAndChangingURLRows( - const SimulateNotificationCallback& callback); - - static const KeywordID kTestKeywordId; - static const char kTestSearchTerm1[]; - static const char kTestSearchTerm2[]; - - private: - DISALLOW_COPY_AND_ASSIGN(InMemoryHistoryBackendTest); -}; - -const KeywordID InMemoryHistoryBackendTest::kTestKeywordId = 42; -const char InMemoryHistoryBackendTest::kTestSearchTerm1[] = "banana"; -const char InMemoryHistoryBackendTest::kTestSearchTerm2[] = "orange"; - -// http://crbug.com/114287 -#if defined(OS_WIN) -#define MAYBE_Loaded DISABLED_Loaded -#else -#define MAYBE_Loaded Loaded -#endif // defined(OS_WIN) -TEST_F(HistoryBackendTest, MAYBE_Loaded) { - ASSERT_TRUE(backend_.get()); - ASSERT_TRUE(loaded_); -} - -TEST_F(HistoryBackendTest, DeleteAll) { - ASSERT_TRUE(backend_.get()); - - // Add two favicons, each with two bitmaps. Note that we add favicon2 before - // adding favicon1. This is so that favicon1 one gets ID 2 autoassigned to - // the database, which will change when the other one is deleted. This way - // we can test that updating works properly. - GURL favicon_url1("http://www.google.com/favicon.ico"); - GURL favicon_url2("http://news.google.com/favicon.ico"); - favicon_base::FaviconID favicon2 = - backend_->thumbnail_db_->AddFavicon(favicon_url2, favicon_base::FAVICON); - favicon_base::FaviconID favicon1 = - backend_->thumbnail_db_->AddFavicon(favicon_url1, favicon_base::FAVICON); - - std::vector<unsigned char> data; - data.push_back('a'); - EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon1, - new base::RefCountedBytes(data), Time::Now(), kSmallSize)); - data[0] = 'b'; - EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon1, - new base::RefCountedBytes(data), Time::Now(), kLargeSize)); - - data[0] = 'c'; - EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2, - new base::RefCountedBytes(data), Time::Now(), kSmallSize)); - data[0] = 'd'; - EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2, - new base::RefCountedBytes(data), Time::Now(), kLargeSize)); - - // First visit two URLs. - URLRow row1(GURL("http://www.google.com/")); - row1.set_visit_count(2); - row1.set_typed_count(1); - row1.set_last_visit(Time::Now()); - backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1); - - URLRow row2(GURL("http://news.google.com/")); - row2.set_visit_count(1); - row2.set_last_visit(Time::Now()); - backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2); - - URLRows rows; - rows.push_back(row2); // Reversed order for the same reason as favicons. - rows.push_back(row1); - backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); - - URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL); - URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL); - - // Get the two visits for the URLs we just added. - VisitVector visits; - backend_->db_->GetVisitsForURL(row1_id, &visits); - ASSERT_EQ(1U, visits.size()); - - visits.clear(); - backend_->db_->GetVisitsForURL(row2_id, &visits); - ASSERT_EQ(1U, visits.size()); - - // The in-memory backend should have been set and it should have gotten the - // typed URL. - ASSERT_TRUE(mem_backend_.get()); - URLRow outrow1; - EXPECT_TRUE(mem_backend_->db_->GetRowForURL(row1.url(), NULL)); - - // Star row1. - history_client_.AddBookmark(row1.url()); - - // Now finally clear all history. - ClearBroadcastedNotifications(); - backend_->DeleteAllHistory(); - - // The first URL should be preserved but the time should be cleared. - EXPECT_TRUE(backend_->db_->GetRowForURL(row1.url(), &outrow1)); - EXPECT_EQ(row1.url(), outrow1.url()); - EXPECT_EQ(0, outrow1.visit_count()); - EXPECT_EQ(0, outrow1.typed_count()); - EXPECT_TRUE(Time() == outrow1.last_visit()); - - // The second row should be deleted. - URLRow outrow2; - EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &outrow2)); - - // All visits should be deleted for both URLs. - VisitVector all_visits; - backend_->db_->GetAllVisitsInRange(Time(), Time(), 0, &all_visits); - ASSERT_EQ(0U, all_visits.size()); - - // We should have a favicon and favicon bitmaps for the first URL only. We - // look them up by favicon URL since the IDs may have changed. - favicon_base::FaviconID out_favicon1 = - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - favicon_url1, favicon_base::FAVICON, NULL); - EXPECT_TRUE(out_favicon1); - - std::vector<FaviconBitmap> favicon_bitmaps; - EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps( - out_favicon1, &favicon_bitmaps)); - ASSERT_EQ(2u, favicon_bitmaps.size()); - - FaviconBitmap favicon_bitmap1 = favicon_bitmaps[0]; - FaviconBitmap favicon_bitmap2 = favicon_bitmaps[1]; - - // Favicon bitmaps do not need to be in particular order. - if (favicon_bitmap1.pixel_size == kLargeSize) { - FaviconBitmap tmp_favicon_bitmap = favicon_bitmap1; - favicon_bitmap1 = favicon_bitmap2; - favicon_bitmap2 = tmp_favicon_bitmap; - } - - EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap1.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap1.pixel_size); - - EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap2.bitmap_data)); - EXPECT_EQ(kLargeSize, favicon_bitmap2.pixel_size); - - favicon_base::FaviconID out_favicon2 = - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - favicon_url2, favicon_base::FAVICON, NULL); - EXPECT_FALSE(out_favicon2) << "Favicon not deleted"; - - // The remaining URL should still reference the same favicon, even if its - // ID has changed. - std::vector<IconMapping> mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - outrow1.url(), favicon_base::FAVICON, &mappings)); - EXPECT_EQ(1u, mappings.size()); - EXPECT_EQ(out_favicon1, mappings[0].icon_id); - - // The first URL should still be bookmarked. - EXPECT_TRUE(history_client_.IsBookmarked(row1.url())); - - // Check that we fire the notification about all history having been deleted. - ASSERT_EQ(1u, urls_deleted_notifications().size()); - EXPECT_TRUE(urls_deleted_notifications()[0].first); - EXPECT_FALSE(urls_deleted_notifications()[0].second); -} - -// Checks that adding a visit, then calling DeleteAll, and then trying to add -// data for the visited page works. This can happen when clearing the history -// immediately after visiting a page. -TEST_F(HistoryBackendTest, DeleteAllThenAddData) { - ASSERT_TRUE(backend_.get()); - - Time visit_time = Time::Now(); - GURL url("http://www.google.com/"); - HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(), - history::RedirectList(), - ui::PAGE_TRANSITION_KEYWORD_GENERATED, - history::SOURCE_BROWSED, false); - backend_->AddPage(request); - - // Check that a row was added. - URLRow outrow; - EXPECT_TRUE(backend_->db_->GetRowForURL(url, &outrow)); - - // Check that the visit was added. - VisitVector all_visits; - backend_->db_->GetAllVisitsInRange(Time(), Time(), 0, &all_visits); - ASSERT_EQ(1U, all_visits.size()); - - // Clear all history. - backend_->DeleteAllHistory(); - - // The row should be deleted. - EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow)); - - // The visit should be deleted. - backend_->db_->GetAllVisitsInRange(Time(), Time(), 0, &all_visits); - ASSERT_EQ(0U, all_visits.size()); - - // Try and set the title. - backend_->SetPageTitle(url, base::UTF8ToUTF16("Title")); - - // The row should still be deleted. - EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow)); - - // The visit should still be deleted. - backend_->db_->GetAllVisitsInRange(Time(), Time(), 0, &all_visits); - ASSERT_EQ(0U, all_visits.size()); -} - -TEST_F(HistoryBackendTest, URLsNoLongerBookmarked) { - GURL favicon_url1("http://www.google.com/favicon.ico"); - GURL favicon_url2("http://news.google.com/favicon.ico"); - - std::vector<unsigned char> data; - data.push_back('1'); - favicon_base::FaviconID favicon1 = - backend_->thumbnail_db_->AddFavicon(favicon_url1, - favicon_base::FAVICON, - new base::RefCountedBytes(data), - Time::Now(), - gfx::Size()); - - data[0] = '2'; - favicon_base::FaviconID favicon2 = - backend_->thumbnail_db_->AddFavicon(favicon_url2, - favicon_base::FAVICON, - new base::RefCountedBytes(data), - Time::Now(), - gfx::Size()); - - // First visit two URLs. - URLRow row1(GURL("http://www.google.com/")); - row1.set_visit_count(2); - row1.set_typed_count(1); - row1.set_last_visit(Time::Now()); - EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1)); - - URLRow row2(GURL("http://news.google.com/")); - row2.set_visit_count(1); - row2.set_last_visit(Time::Now()); - EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2)); - - URLRows rows; - rows.push_back(row2); // Reversed order for the same reason as favicons. - rows.push_back(row1); - backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); - - URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL); - URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL); - - // Star the two URLs. - history_client_.AddBookmark(row1.url()); - history_client_.AddBookmark(row2.url()); - - // Delete url 2. - backend_->expirer_.DeleteURL(row2.url()); - EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL)); - VisitVector visits; - backend_->db_->GetVisitsForURL(row2_id, &visits); - EXPECT_EQ(0U, visits.size()); - // The favicon should still be valid. - EXPECT_EQ(favicon2, - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - favicon_url2, favicon_base::FAVICON, NULL)); - - // Unstar row2. - history_client_.DelBookmark(row2.url()); - - // Tell the backend it was unstarred. We have to explicitly do this as - // BookmarkModel isn't wired up to the backend during testing. - std::set<GURL> unstarred_urls; - unstarred_urls.insert(row2.url()); - backend_->URLsNoLongerBookmarked(unstarred_urls); - - // The URL should still not exist. - EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL)); - // And the favicon should be deleted. - EXPECT_EQ(0, - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - favicon_url2, favicon_base::FAVICON, NULL)); - - // Unstar row 1. - history_client_.DelBookmark(row1.url()); - - // Tell the backend it was unstarred. We have to explicitly do this as - // BookmarkModel isn't wired up to the backend during testing. - unstarred_urls.clear(); - unstarred_urls.insert(row1.url()); - backend_->URLsNoLongerBookmarked(unstarred_urls); - - // The URL should still exist (because there were visits). - EXPECT_EQ(row1_id, backend_->db_->GetRowForURL(row1.url(), NULL)); - - // There should still be visits. - visits.clear(); - backend_->db_->GetVisitsForURL(row1_id, &visits); - EXPECT_EQ(1U, visits.size()); - - // The favicon should still be valid. - EXPECT_EQ(favicon1, - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - favicon_url1, favicon_base::FAVICON, NULL)); -} - -// Tests a handful of assertions for a navigation with a type of -// KEYWORD_GENERATED. -TEST_F(HistoryBackendTest, KeywordGenerated) { - ASSERT_TRUE(backend_.get()); - - GURL url("http://google.com"); - - Time visit_time = Time::Now() - base::TimeDelta::FromDays(1); - HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(), - history::RedirectList(), - ui::PAGE_TRANSITION_KEYWORD_GENERATED, - history::SOURCE_BROWSED, false); - backend_->AddPage(request); - - // A row should have been added for the url. - URLRow row; - URLID url_id = backend_->db()->GetRowForURL(url, &row); - ASSERT_NE(0, url_id); - - // The typed count should be 1. - ASSERT_EQ(1, row.typed_count()); - - // KEYWORD_GENERATED urls should not be added to the segment db. - std::string segment_name = VisitSegmentDatabase::ComputeSegmentName(url); - EXPECT_EQ(0, backend_->db()->GetSegmentNamed(segment_name)); - - // One visit should be added. - VisitVector visits; - EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits)); - EXPECT_EQ(1U, visits.size()); - - // But no visible visits. - visits.clear(); - QueryOptions query_options; - query_options.max_count = 1; - backend_->db()->GetVisibleVisitsInRange(query_options, &visits); - EXPECT_TRUE(visits.empty()); - - // Going back to the same entry should not increment the typed count. - ui::PageTransition back_transition = ui::PageTransitionFromInt( - ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK); - HistoryAddPageArgs back_request(url, visit_time, NULL, 0, GURL(), - history::RedirectList(), back_transition, - history::SOURCE_BROWSED, false); - backend_->AddPage(back_request); - url_id = backend_->db()->GetRowForURL(url, &row); - ASSERT_NE(0, url_id); - ASSERT_EQ(1, row.typed_count()); - - // Expire the visits. - std::set<GURL> restrict_urls; - backend_->expire_backend()->ExpireHistoryBetween(restrict_urls, - visit_time, Time::Now()); - - // The visit should have been nuked. - visits.clear(); - EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits)); - EXPECT_TRUE(visits.empty()); - - // As well as the url. - ASSERT_EQ(0, backend_->db()->GetRowForURL(url, &row)); -} - -TEST_F(HistoryBackendTest, ClientRedirect) { - ASSERT_TRUE(backend_.get()); - - int transition1; - int transition2; - - // Initial transition to page A. - GURL url_a("http://google.com/a"); - AddClientRedirect(GURL(), url_a, false, base::Time(), - &transition1, &transition2); - EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END); - - // User initiated redirect to page B. - GURL url_b("http://google.com/b"); - AddClientRedirect(url_a, url_b, false, base::Time(), - &transition1, &transition2); - EXPECT_TRUE(transition1 & ui::PAGE_TRANSITION_CHAIN_END); - EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END); - - // Non-user initiated redirect to page C. - GURL url_c("http://google.com/c"); - AddClientRedirect(url_b, url_c, true, base::Time(), - &transition1, &transition2); - EXPECT_FALSE(transition1 & ui::PAGE_TRANSITION_CHAIN_END); - EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END); -} - -TEST_F(HistoryBackendTest, AddPagesWithDetails) { - ASSERT_TRUE(backend_.get()); - - // Import one non-typed URL, and two recent and one expired typed URLs. - URLRow row1(GURL("https://news.google.com/")); - row1.set_visit_count(1); - row1.set_last_visit(Time::Now()); - URLRow row2(GURL("https://www.google.com/")); - row2.set_typed_count(1); - row2.set_last_visit(Time::Now()); - URLRow row3(GURL("https://mail.google.com/")); - row3.set_visit_count(1); - row3.set_typed_count(1); - row3.set_last_visit(Time::Now() - base::TimeDelta::FromDays(7 - 1)); - URLRow row4(GURL("https://maps.google.com/")); - row4.set_visit_count(1); - row4.set_typed_count(1); - row4.set_last_visit(Time::Now() - base::TimeDelta::FromDays(365 + 2)); - - URLRows rows; - rows.push_back(row1); - rows.push_back(row2); - rows.push_back(row3); - rows.push_back(row4); - backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); - - // Verify that recent URLs have ended up in the main |db_|, while the already - // expired URL has been ignored. - URLRow stored_row1, stored_row2, stored_row3, stored_row4; - EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1)); - EXPECT_NE(0, backend_->db_->GetRowForURL(row2.url(), &stored_row2)); - EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3)); - EXPECT_EQ(0, backend_->db_->GetRowForURL(row4.url(), &stored_row4)); - - // Ensure that a notification was fired for both typed and non-typed URLs. - // Further verify that the IDs in the notification are set to those that are - // in effect in the main database. The InMemoryHistoryBackend relies on this - // for caching. - ASSERT_EQ(1, num_urls_modified_notifications()); - - const URLRows& changed_urls = urls_modified_notifications()[0]; - EXPECT_EQ(3u, changed_urls.size()); - - URLRows::const_iterator it_row1 = - std::find_if(changed_urls.begin(), - changed_urls.end(), - history::URLRow::URLRowHasURL(row1.url())); - ASSERT_NE(changed_urls.end(), it_row1); - EXPECT_EQ(stored_row1.id(), it_row1->id()); - - URLRows::const_iterator it_row2 = - std::find_if(changed_urls.begin(), - changed_urls.end(), - history::URLRow::URLRowHasURL(row2.url())); - ASSERT_NE(changed_urls.end(), it_row2); - EXPECT_EQ(stored_row2.id(), it_row2->id()); - - URLRows::const_iterator it_row3 = - std::find_if(changed_urls.begin(), - changed_urls.end(), - history::URLRow::URLRowHasURL(row3.url())); - ASSERT_NE(changed_urls.end(), it_row3); - EXPECT_EQ(stored_row3.id(), it_row3->id()); -} - -TEST_F(HistoryBackendTest, UpdateURLs) { - ASSERT_TRUE(backend_.get()); - - // Add three pages directly to the database. - URLRow row1(GURL("https://news.google.com/")); - row1.set_visit_count(1); - row1.set_last_visit(Time::Now()); - URLRow row2(GURL("https://maps.google.com/")); - row2.set_visit_count(2); - row2.set_last_visit(Time::Now()); - URLRow row3(GURL("https://www.google.com/")); - row3.set_visit_count(3); - row3.set_last_visit(Time::Now()); - - backend_->db_->AddURL(row1); - backend_->db_->AddURL(row2); - backend_->db_->AddURL(row3); - - // Now create changed versions of all URLRows by incrementing their visit - // counts, and in the meantime, also delete the second row from the database. - URLRow altered_row1, altered_row2, altered_row3; - backend_->db_->GetRowForURL(row1.url(), &altered_row1); - altered_row1.set_visit_count(42); - backend_->db_->GetRowForURL(row2.url(), &altered_row2); - altered_row2.set_visit_count(43); - backend_->db_->GetRowForURL(row3.url(), &altered_row3); - altered_row3.set_visit_count(44); - - backend_->db_->DeleteURLRow(altered_row2.id()); - - // Now try to update all three rows at once. The change to the second URLRow - // should be ignored, as it is no longer present in the DB. - URLRows rows; - rows.push_back(altered_row1); - rows.push_back(altered_row2); - rows.push_back(altered_row3); - EXPECT_EQ(2u, backend_->UpdateURLs(rows)); - - URLRow stored_row1, stored_row3; - EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1)); - EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3)); - EXPECT_EQ(altered_row1.visit_count(), stored_row1.visit_count()); - EXPECT_EQ(altered_row3.visit_count(), stored_row3.visit_count()); - - // Ensure that a notification was fired, and further verify that the IDs in - // the notification are set to those that are in effect in the main database. - // The InMemoryHistoryBackend relies on this for caching. - ASSERT_EQ(1, num_urls_modified_notifications()); - - const URLRows& changed_urls = urls_modified_notifications()[0]; - EXPECT_EQ(2u, changed_urls.size()); - - URLRows::const_iterator it_row1 = - std::find_if(changed_urls.begin(), - changed_urls.end(), - history::URLRow::URLRowHasURL(row1.url())); - ASSERT_NE(changed_urls.end(), it_row1); - EXPECT_EQ(altered_row1.id(), it_row1->id()); - EXPECT_EQ(altered_row1.visit_count(), it_row1->visit_count()); - - URLRows::const_iterator it_row3 = - std::find_if(changed_urls.begin(), - changed_urls.end(), - history::URLRow::URLRowHasURL(row3.url())); - ASSERT_NE(changed_urls.end(), it_row3); - EXPECT_EQ(altered_row3.id(), it_row3->id()); - EXPECT_EQ(altered_row3.visit_count(), it_row3->visit_count()); -} - -// This verifies that a notification is fired. In-depth testing of logic should -// be done in HistoryTest.SetTitle. -TEST_F(HistoryBackendTest, SetPageTitleFiresNotificationWithCorrectDetails) { - const char kTestUrlTitle[] = "Google Search"; - - ASSERT_TRUE(backend_.get()); - - // Add two pages, then change the title of the second one. - URLRow row1(GURL("https://news.google.com/")); - row1.set_typed_count(1); - row1.set_last_visit(Time::Now()); - URLRow row2(GURL("https://www.google.com/")); - row2.set_visit_count(2); - row2.set_last_visit(Time::Now()); - - URLRows rows; - rows.push_back(row1); - rows.push_back(row2); - backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); - - ClearBroadcastedNotifications(); - backend_->SetPageTitle(row2.url(), base::UTF8ToUTF16(kTestUrlTitle)); - - // Ensure that a notification was fired, and further verify that the IDs in - // the notification are set to those that are in effect in the main database. - // The InMemoryHistoryBackend relies on this for caching. - URLRow stored_row2; - EXPECT_TRUE(backend_->GetURL(row2.url(), &stored_row2)); - ASSERT_EQ(1, num_urls_modified_notifications()); - - const URLRows& changed_urls = urls_modified_notifications()[0]; - ASSERT_EQ(1u, changed_urls.size()); - EXPECT_EQ(base::UTF8ToUTF16(kTestUrlTitle), changed_urls[0].title()); - EXPECT_EQ(stored_row2.id(), changed_urls[0].id()); -} - -// There's no importer on Android. -#if !defined(OS_ANDROID) -TEST_F(HistoryBackendTest, ImportedFaviconsTest) { - // Setup test data - two Urls in the history, one with favicon assigned and - // one without. - GURL favicon_url1("http://www.google.com/favicon.ico"); - std::vector<unsigned char> data; - data.push_back('1'); - favicon_base::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon( - favicon_url1, - favicon_base::FAVICON, - base::RefCountedBytes::TakeVector(&data), - Time::Now(), - gfx::Size()); - URLRow row1(GURL("http://www.google.com/")); - row1.set_visit_count(1); - row1.set_last_visit(Time::Now()); - EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1)); - - URLRow row2(GURL("http://news.google.com/")); - row2.set_visit_count(1); - row2.set_last_visit(Time::Now()); - URLRows rows; - rows.push_back(row1); - rows.push_back(row2); - backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); - URLRow url_row1, url_row2; - EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0); - EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0); - EXPECT_EQ(1u, NumIconMappingsForPageURL(row1.url(), favicon_base::FAVICON)); - EXPECT_EQ(0u, NumIconMappingsForPageURL(row2.url(), favicon_base::FAVICON)); - - // Now provide one imported favicon for both URLs already in the registry. - // The new favicon should only be used with the URL that doesn't already have - // a favicon. - favicon_base::FaviconUsageDataList favicons; - favicon_base::FaviconUsageData favicon; - favicon.favicon_url = GURL("http://news.google.com/favicon.ico"); - favicon.png_data.push_back('2'); - favicon.urls.insert(row1.url()); - favicon.urls.insert(row2.url()); - favicons.push_back(favicon); - backend_->SetImportedFavicons(favicons); - EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0); - EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0); - - std::vector<IconMapping> mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - row1.url(), favicon_base::FAVICON, &mappings)); - EXPECT_EQ(1u, mappings.size()); - EXPECT_EQ(favicon1, mappings[0].icon_id); - EXPECT_EQ(favicon_url1, mappings[0].icon_url); - - mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - row2.url(), favicon_base::FAVICON, &mappings)); - EXPECT_EQ(1u, mappings.size()); - EXPECT_EQ(favicon.favicon_url, mappings[0].icon_url); - - // A URL should not be added to history (to store favicon), if - // the URL is not bookmarked. - GURL url3("http://mail.google.com"); - favicons.clear(); - favicon.favicon_url = GURL("http://mail.google.com/favicon.ico"); - favicon.png_data.push_back('3'); - favicon.urls.insert(url3); - favicons.push_back(favicon); - backend_->SetImportedFavicons(favicons); - URLRow url_row3; - EXPECT_TRUE(backend_->db_->GetRowForURL(url3, &url_row3) == 0); - - // If the URL is bookmarked, it should get added to history with 0 visits. - history_client_.AddBookmark(url3); - backend_->SetImportedFavicons(favicons); - EXPECT_FALSE(backend_->db_->GetRowForURL(url3, &url_row3) == 0); - EXPECT_TRUE(url_row3.visit_count() == 0); -} -#endif // !defined(OS_ANDROID) - -TEST_F(HistoryBackendTest, StripUsernamePasswordTest) { - ASSERT_TRUE(backend_.get()); - - GURL url("http://anyuser:anypass@www.google.com"); - GURL stripped_url("http://www.google.com"); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Visit the url with username, password. - backend_->AddPageVisit(url, base::Time::Now(), 0, - ui::PageTransitionFromInt( - ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)), - history::SOURCE_BROWSED); - - // Fetch the row information about stripped url from history db. - VisitVector visits; - URLID row_id = backend_->db_->GetRowForURL(stripped_url, NULL); - backend_->db_->GetVisitsForURL(row_id, &visits); - - // Check if stripped url is stored in database. - ASSERT_EQ(1U, visits.size()); -} - -TEST_F(HistoryBackendTest, AddPageVisitBackForward) { - ASSERT_TRUE(backend_.get()); - - GURL url("http://www.google.com"); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Visit the url after typing it. - backend_->AddPageVisit(url, base::Time::Now(), 0, - ui::PAGE_TRANSITION_TYPED, - history::SOURCE_BROWSED); - - // Ensure both the typed count and visit count are 1. - VisitVector visits; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - EXPECT_EQ(1, row.typed_count()); - EXPECT_EQ(1, row.visit_count()); - - // Visit the url again via back/forward. - backend_->AddPageVisit(url, base::Time::Now(), 0, - ui::PageTransitionFromInt( - ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK), - history::SOURCE_BROWSED); - - // Ensure the typed count is still 1 but the visit count is 2. - id = backend_->db()->GetRowForURL(url, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - EXPECT_EQ(1, row.typed_count()); - EXPECT_EQ(2, row.visit_count()); -} - -TEST_F(HistoryBackendTest, AddPageVisitRedirectBackForward) { - ASSERT_TRUE(backend_.get()); - - GURL url1("http://www.google.com"); - GURL url2("http://www.chromium.org"); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Visit a typed URL with a redirect. - backend_->AddPageVisit(url1, base::Time::Now(), 0, - ui::PAGE_TRANSITION_TYPED, - history::SOURCE_BROWSED); - backend_->AddPageVisit(url2, base::Time::Now(), 0, - ui::PageTransitionFromInt( - ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_CLIENT_REDIRECT), - history::SOURCE_BROWSED); - - // Ensure the redirected URL does not count as typed. - VisitVector visits; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url2, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - EXPECT_EQ(0, row.typed_count()); - EXPECT_EQ(1, row.visit_count()); - - // Visit the redirected url again via back/forward. - backend_->AddPageVisit(url2, base::Time::Now(), 0, - ui::PageTransitionFromInt( - ui::PAGE_TRANSITION_TYPED | - ui::PAGE_TRANSITION_FORWARD_BACK | - ui::PAGE_TRANSITION_CLIENT_REDIRECT), - history::SOURCE_BROWSED); - - // Ensure the typed count is still 1 but the visit count is 2. - id = backend_->db()->GetRowForURL(url2, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - EXPECT_EQ(0, row.typed_count()); - EXPECT_EQ(2, row.visit_count()); -} - -TEST_F(HistoryBackendTest, AddPageVisitSource) { - ASSERT_TRUE(backend_.get()); - - GURL url("http://www.google.com"); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Assume visiting the url from an externsion. - backend_->AddPageVisit( - url, base::Time::Now(), 0, ui::PAGE_TRANSITION_TYPED, - history::SOURCE_EXTENSION); - // Assume the url is imported from Firefox. - backend_->AddPageVisit(url, base::Time::Now(), 0, - ui::PAGE_TRANSITION_TYPED, - history::SOURCE_FIREFOX_IMPORTED); - // Assume this url is also synced. - backend_->AddPageVisit(url, base::Time::Now(), 0, - ui::PAGE_TRANSITION_TYPED, - history::SOURCE_SYNCED); - - // Fetch the row information about the url from history db. - VisitVector visits; - URLID row_id = backend_->db_->GetRowForURL(url, NULL); - backend_->db_->GetVisitsForURL(row_id, &visits); - - // Check if all the visits to the url are stored in database. - ASSERT_EQ(3U, visits.size()); - VisitSourceMap visit_sources; - ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); - ASSERT_EQ(3U, visit_sources.size()); - int sources = 0; - for (int i = 0; i < 3; i++) { - switch (visit_sources[visits[i].visit_id]) { - case history::SOURCE_EXTENSION: - sources |= 0x1; - break; - case history::SOURCE_FIREFOX_IMPORTED: - sources |= 0x2; - break; - case history::SOURCE_SYNCED: - sources |= 0x4; - default: - break; - } - } - EXPECT_EQ(0x7, sources); -} - -TEST_F(HistoryBackendTest, AddPageVisitNotLastVisit) { - ASSERT_TRUE(backend_.get()); - - GURL url("http://www.google.com"); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Create visit times - base::Time recent_time = base::Time::Now(); - base::TimeDelta visit_age = base::TimeDelta::FromDays(3); - base::Time older_time = recent_time - visit_age; - - // Visit the url with recent time. - backend_->AddPageVisit(url, recent_time, 0, - ui::PageTransitionFromInt( - ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)), - history::SOURCE_BROWSED); - - // Add to the url a visit with older time (could be syncing from another - // client, etc.). - backend_->AddPageVisit(url, older_time, 0, - ui::PageTransitionFromInt( - ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)), - history::SOURCE_SYNCED); - - // Fetch the row information about url from history db. - VisitVector visits; - URLRow row; - URLID row_id = backend_->db_->GetRowForURL(url, &row); - backend_->db_->GetVisitsForURL(row_id, &visits); - - // Last visit time should be the most recent time, not the most recently added - // visit. - ASSERT_EQ(2U, visits.size()); - ASSERT_EQ(recent_time, row.last_visit()); -} - -TEST_F(HistoryBackendTest, AddPageVisitFiresNotificationWithCorrectDetails) { - ASSERT_TRUE(backend_.get()); - - GURL url1("http://www.google.com"); - GURL url2("http://maps.google.com"); - - // Clear all history. - backend_->DeleteAllHistory(); - ClearBroadcastedNotifications(); - - // Visit two distinct URLs, the second one twice. - backend_->AddPageVisit(url1, base::Time::Now(), 0, - ui::PAGE_TRANSITION_LINK, - history::SOURCE_BROWSED); - for (int i = 0; i < 2; ++i) { - backend_->AddPageVisit(url2, base::Time::Now(), 0, - ui::PAGE_TRANSITION_TYPED, - history::SOURCE_BROWSED); - } - - URLRow stored_row1, stored_row2; - EXPECT_NE(0, backend_->db_->GetRowForURL(url1, &stored_row1)); - EXPECT_NE(0, backend_->db_->GetRowForURL(url2, &stored_row2)); - - // Expect that HistoryServiceObserver::OnURLVisited has been called 3 times, - // and that each time the URLRows have the correct URLs and IDs set. - ASSERT_EQ(3, num_url_visited_notifications()); - EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[0].first, - ui::PAGE_TRANSITION_LINK)); - EXPECT_EQ(stored_row1.id(), url_visited_notifications()[0].second.id()); - EXPECT_EQ(stored_row1.url(), url_visited_notifications()[0].second.url()); - - EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[1].first, - ui::PAGE_TRANSITION_TYPED)); - EXPECT_EQ(stored_row2.id(), url_visited_notifications()[1].second.id()); - EXPECT_EQ(stored_row2.url(), url_visited_notifications()[1].second.url()); - - EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[2].first, - ui::PAGE_TRANSITION_TYPED)); - EXPECT_EQ(stored_row2.id(), url_visited_notifications()[2].second.id()); - EXPECT_EQ(stored_row2.url(), url_visited_notifications()[2].second.url()); -} - -TEST_F(HistoryBackendTest, AddPageArgsSource) { - ASSERT_TRUE(backend_.get()); - - GURL url("http://testpageargs.com"); - - // Assume this page is browsed by user. - HistoryAddPageArgs request1(url, base::Time::Now(), NULL, 0, GURL(), - history::RedirectList(), - ui::PAGE_TRANSITION_KEYWORD_GENERATED, - history::SOURCE_BROWSED, false); - backend_->AddPage(request1); - // Assume this page is synced. - HistoryAddPageArgs request2(url, base::Time::Now(), NULL, 0, GURL(), - history::RedirectList(), - ui::PAGE_TRANSITION_LINK, - history::SOURCE_SYNCED, false); - backend_->AddPage(request2); - // Assume this page is browsed again. - HistoryAddPageArgs request3(url, base::Time::Now(), NULL, 0, GURL(), - history::RedirectList(), - ui::PAGE_TRANSITION_TYPED, - history::SOURCE_BROWSED, false); - backend_->AddPage(request3); - - // Three visits should be added with proper sources. - VisitVector visits; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(3U, visits.size()); - VisitSourceMap visit_sources; - ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); - ASSERT_EQ(1U, visit_sources.size()); - EXPECT_EQ(history::SOURCE_SYNCED, visit_sources.begin()->second); -} - -TEST_F(HistoryBackendTest, AddVisitsSource) { - ASSERT_TRUE(backend_.get()); - - GURL url1("http://www.cnn.com"); - std::vector<VisitInfo> visits1, visits2; - visits1.push_back(VisitInfo( - Time::Now() - base::TimeDelta::FromDays(5), - ui::PAGE_TRANSITION_LINK)); - visits1.push_back(VisitInfo( - Time::Now() - base::TimeDelta::FromDays(1), - ui::PAGE_TRANSITION_LINK)); - visits1.push_back(VisitInfo( - Time::Now(), ui::PAGE_TRANSITION_LINK)); - - GURL url2("http://www.example.com"); - visits2.push_back(VisitInfo( - Time::Now() - base::TimeDelta::FromDays(10), - ui::PAGE_TRANSITION_LINK)); - visits2.push_back(VisitInfo(Time::Now(), ui::PAGE_TRANSITION_LINK)); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Add the visits. - backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); - backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED); - - // Verify the visits were added with their sources. - VisitVector visits; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url1, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(3U, visits.size()); - VisitSourceMap visit_sources; - ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); - ASSERT_EQ(3U, visit_sources.size()); - for (int i = 0; i < 3; i++) - EXPECT_EQ(history::SOURCE_IE_IMPORTED, visit_sources[visits[i].visit_id]); - id = backend_->db()->GetRowForURL(url2, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(2U, visits.size()); - ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); - ASSERT_EQ(2U, visit_sources.size()); - for (int i = 0; i < 2; i++) - EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]); -} - -TEST_F(HistoryBackendTest, GetMostRecentVisits) { - ASSERT_TRUE(backend_.get()); - - GURL url1("http://www.cnn.com"); - std::vector<VisitInfo> visits1; - visits1.push_back(VisitInfo( - Time::Now() - base::TimeDelta::FromDays(5), - ui::PAGE_TRANSITION_LINK)); - visits1.push_back(VisitInfo( - Time::Now() - base::TimeDelta::FromDays(1), - ui::PAGE_TRANSITION_LINK)); - visits1.push_back(VisitInfo( - Time::Now(), ui::PAGE_TRANSITION_LINK)); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Add the visits. - backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); - - // Verify the visits were added with their sources. - VisitVector visits; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url1, &row); - ASSERT_TRUE(backend_->db()->GetMostRecentVisitsForURL(id, 1, &visits)); - ASSERT_EQ(1U, visits.size()); - EXPECT_EQ(visits1[2].first, visits[0].visit_time); -} - -TEST_F(HistoryBackendTest, RemoveVisitsTransitions) { - ASSERT_TRUE(backend_.get()); - - // Clear all history. - backend_->DeleteAllHistory(); - - GURL url1("http://www.cnn.com"); - VisitInfo typed_visit( - Time::Now() - base::TimeDelta::FromDays(6), - ui::PAGE_TRANSITION_TYPED); - VisitInfo reload_visit( - Time::Now() - base::TimeDelta::FromDays(5), - ui::PAGE_TRANSITION_RELOAD); - VisitInfo link_visit( - Time::Now() - base::TimeDelta::FromDays(4), - ui::PAGE_TRANSITION_LINK); - std::vector<VisitInfo> visits_to_add; - visits_to_add.push_back(typed_visit); - visits_to_add.push_back(reload_visit); - visits_to_add.push_back(link_visit); - - // Add the visits. - backend_->AddVisits(url1, visits_to_add, history::SOURCE_SYNCED); - - // Verify that the various counts are what we expect. - VisitVector visits; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url1, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(3U, visits.size()); - ASSERT_EQ(1, row.typed_count()); - ASSERT_EQ(2, row.visit_count()); - - // Now, delete the typed visit and verify that typed_count is updated. - ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0]))); - id = backend_->db()->GetRowForURL(url1, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(2U, visits.size()); - ASSERT_EQ(0, row.typed_count()); - ASSERT_EQ(1, row.visit_count()); - - // Delete the reload visit now and verify that none of the counts have - // changed. - ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0]))); - id = backend_->db()->GetRowForURL(url1, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(1U, visits.size()); - ASSERT_EQ(0, row.typed_count()); - ASSERT_EQ(1, row.visit_count()); - - // Delete the last visit and verify that we delete the URL. - ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0]))); - ASSERT_EQ(0, backend_->db()->GetRowForURL(url1, &row)); -} - -TEST_F(HistoryBackendTest, RemoveVisitsSource) { - ASSERT_TRUE(backend_.get()); - - GURL url1("http://www.cnn.com"); - std::vector<VisitInfo> visits1, visits2; - visits1.push_back(VisitInfo( - Time::Now() - base::TimeDelta::FromDays(5), - ui::PAGE_TRANSITION_LINK)); - visits1.push_back(VisitInfo(Time::Now(), - ui::PAGE_TRANSITION_LINK)); - - GURL url2("http://www.example.com"); - visits2.push_back(VisitInfo( - Time::Now() - base::TimeDelta::FromDays(10), - ui::PAGE_TRANSITION_LINK)); - visits2.push_back(VisitInfo(Time::Now(), ui::PAGE_TRANSITION_LINK)); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Add the visits. - backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); - backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED); - - // Verify the visits of url1 were added. - VisitVector visits; - URLRow row; - URLID id = backend_->db()->GetRowForURL(url1, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(2U, visits.size()); - // Remove these visits. - ASSERT_TRUE(backend_->RemoveVisits(visits)); - - // Now check only url2's source in visit_source table. - VisitSourceMap visit_sources; - ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); - ASSERT_EQ(0U, visit_sources.size()); - id = backend_->db()->GetRowForURL(url2, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); - ASSERT_EQ(2U, visits.size()); - ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); - ASSERT_EQ(2U, visit_sources.size()); - for (int i = 0; i < 2; i++) - EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]); -} - -// Test for migration of adding visit_source table. -TEST_F(HistoryBackendTest, MigrationVisitSource) { - ASSERT_TRUE(backend_.get()); - backend_->Closing(); - backend_ = NULL; - - base::FilePath old_history_path; - ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &old_history_path)); - old_history_path = old_history_path.AppendASCII("History"); - old_history_path = old_history_path.AppendASCII("HistoryNoSource"); - - // Copy history database file to current directory so that it will be deleted - // in Teardown. - base::FilePath new_history_path(test_dir()); - base::DeleteFile(new_history_path, true); - base::CreateDirectory(new_history_path); - base::FilePath new_history_file = new_history_path.Append(kHistoryFilename); - ASSERT_TRUE(base::CopyFile(old_history_path, new_history_file)); - - backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this), - &history_client_); - backend_->Init(std::string(), false, - TestHistoryDatabaseParamsForPath(new_history_path)); - backend_->Closing(); - backend_ = NULL; - - // Now the database should already be migrated. - // Check version first. - int cur_version = HistoryDatabase::GetCurrentVersion(); - sql::Connection db; - ASSERT_TRUE(db.Open(new_history_file)); - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - ASSERT_TRUE(s.Step()); - int file_version = s.ColumnInt(0); - EXPECT_EQ(cur_version, file_version); - - // Check visit_source table is created and empty. - s.Assign(db.GetUniqueStatement( - "SELECT name FROM sqlite_master WHERE name=\"visit_source\"")); - ASSERT_TRUE(s.Step()); - s.Assign(db.GetUniqueStatement("SELECT * FROM visit_source LIMIT 10")); - EXPECT_FALSE(s.Step()); -} - -// Test that SetFaviconMappingsForPageAndRedirects correctly updates icon -// mappings based on redirects, icon URLs and icon types. -TEST_F(HistoryBackendTest, SetFaviconMappingsForPageAndRedirects) { - // Init recent_redirects_ - const GURL url1("http://www.google.com"); - const GURL url2("http://www.google.com/m"); - URLRow url_info1(url1); - url_info1.set_visit_count(0); - url_info1.set_typed_count(0); - url_info1.set_last_visit(base::Time()); - url_info1.set_hidden(false); - backend_->db_->AddURL(url_info1); - - URLRow url_info2(url2); - url_info2.set_visit_count(0); - url_info2.set_typed_count(0); - url_info2.set_last_visit(base::Time()); - url_info2.set_hidden(false); - backend_->db_->AddURL(url_info2); - - history::RedirectList redirects; - redirects.push_back(url2); - redirects.push_back(url1); - backend_->recent_redirects_.Put(url1, redirects); - - const GURL icon_url1("http://www.google.com/icon"); - const GURL icon_url2("http://www.google.com/icon2"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); - - // Add a favicon. - backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON)); - - // Add one touch_icon - backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::TOUCH_ICON)); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); - - // Add one TOUCH_PRECOMPOSED_ICON - backend_->SetFavicons( - url1, favicon_base::TOUCH_PRECOMPOSED_ICON, icon_url1, bitmaps); - // The touch_icon was replaced. - EXPECT_EQ(0u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); - EXPECT_EQ( - 1u, - NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON)); - EXPECT_EQ( - 1u, - NumIconMappingsForPageURL(url2, favicon_base::TOUCH_PRECOMPOSED_ICON)); - - // Add a touch_icon. - backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); - // The TOUCH_PRECOMPOSED_ICON was replaced. - EXPECT_EQ( - 0u, - NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON)); - - // Add a different favicon. - backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url2, bitmaps); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); - EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON)); -} - -// Test that there is no churn in icon mappings from calling -// SetFavicons() twice with the same |bitmaps| parameter. -TEST_F(HistoryBackendTest, SetFaviconMappingsForPageDuplicates) { - const GURL url("http://www.google.com/"); - const GURL icon_url("http://www.google.com/icon"); - - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); - - backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps); - - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - url, favicon_base::FAVICON, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - IconMappingID mapping_id = icon_mappings[0].mapping_id; - - backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps); - - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - url, favicon_base::FAVICON, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - - // The same row in the icon_mapping table should be used for the mapping as - // before. - EXPECT_EQ(mapping_id, icon_mappings[0].mapping_id); -} - -// Test that calling SetFavicons() with FaviconBitmapData of different pixel -// sizes than the initially passed in FaviconBitmapData deletes the no longer -// used favicon bitmaps. -TEST_F(HistoryBackendTest, SetFaviconsDeleteBitmaps) { - const GURL page_url("http://www.google.com/"); - const GURL icon_url("http://www.google.com/icon"); - - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - // Test initial state. - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url, icon_mappings[0].icon_url); - EXPECT_EQ(favicon_base::FAVICON, icon_mappings[0].icon_type); - favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id; - - std::vector<FaviconBitmap> favicon_bitmaps; - EXPECT_TRUE(GetSortedFaviconBitmaps(favicon_id, &favicon_bitmaps)); - EXPECT_EQ(2u, favicon_bitmaps.size()); - FaviconBitmapID small_bitmap_id = favicon_bitmaps[0].bitmap_id; - EXPECT_NE(0, small_bitmap_id); - EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmaps[0].bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmaps[0].pixel_size); - FaviconBitmapID large_bitmap_id = favicon_bitmaps[1].bitmap_id; - EXPECT_NE(0, large_bitmap_id); - EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, favicon_bitmaps[1].bitmap_data)); - EXPECT_EQ(kLargeSize, favicon_bitmaps[1].pixel_size); - - // Call SetFavicons() with bitmap data for only the large bitmap. Check that - // the small bitmap is in fact deleted. - bitmaps.clear(); - bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kLargeEdgeSize)); - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - scoped_refptr<base::RefCountedMemory> bitmap_data_out; - gfx::Size pixel_size_out; - EXPECT_FALSE(backend_->thumbnail_db_->GetFaviconBitmap(small_bitmap_id, - NULL, NULL, &bitmap_data_out, &pixel_size_out)); - EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmap(large_bitmap_id, - NULL, NULL, &bitmap_data_out, &pixel_size_out)); - EXPECT_TRUE(BitmapColorEqual(SK_ColorWHITE, bitmap_data_out)); - EXPECT_EQ(kLargeSize, pixel_size_out); - - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); - - // Notifications should have been broadcast for each call to SetFavicons(). - EXPECT_EQ(2, favicon_changed_notifications()); -} - -// Test updating a single favicon bitmap's data via SetFavicons. -TEST_F(HistoryBackendTest, SetFaviconsReplaceBitmapData) { - const GURL page_url("http://www.google.com/"); - const GURL icon_url("http://www.google.com/icon"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - - // Add bitmap to the database. - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - favicon_base::FaviconID original_favicon_id = - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - icon_url, favicon_base::FAVICON, NULL); - EXPECT_NE(0, original_favicon_id); - FaviconBitmap original_favicon_bitmap; - EXPECT_TRUE( - GetOnlyFaviconBitmap(original_favicon_id, &original_favicon_bitmap)); - EXPECT_TRUE( - BitmapColorEqual(SK_ColorBLUE, original_favicon_bitmap.bitmap_data)); - - EXPECT_EQ(1, favicon_changed_notifications()); - - // Call SetFavicons() with completely identical data. - bitmaps[0] = CreateBitmap(SK_ColorBLUE, kSmallEdgeSize); - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - favicon_base::FaviconID updated_favicon_id = - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - icon_url, favicon_base::FAVICON, NULL); - EXPECT_NE(0, updated_favicon_id); - FaviconBitmap updated_favicon_bitmap; - EXPECT_TRUE( - GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap)); - EXPECT_TRUE( - BitmapColorEqual(SK_ColorBLUE, updated_favicon_bitmap.bitmap_data)); - - // Because the bitmap data is byte equivalent, no notifications should have - // been broadcasted. - EXPECT_EQ(1, favicon_changed_notifications()); - - // Call SetFavicons() with a different bitmap of the same size. - bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize); - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - updated_favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - icon_url, favicon_base::FAVICON, NULL); - EXPECT_NE(0, updated_favicon_id); - EXPECT_TRUE( - GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap)); - EXPECT_TRUE( - BitmapColorEqual(SK_ColorWHITE, updated_favicon_bitmap.bitmap_data)); - - // There should be no churn in FaviconIDs or FaviconBitmapIds even though - // the bitmap data changed. - EXPECT_EQ(original_favicon_bitmap.icon_id, updated_favicon_bitmap.icon_id); - EXPECT_EQ(original_favicon_bitmap.bitmap_id, - updated_favicon_bitmap.bitmap_id); - - // A notification should have been broadcasted as the favicon bitmap data has - // changed. - EXPECT_EQ(2, favicon_changed_notifications()); -} - -// Test that if two pages share the same FaviconID, changing the favicon for -// one page does not affect the other. -TEST_F(HistoryBackendTest, SetFaviconsSameFaviconURLForTwoPages) { - GURL icon_url("http://www.google.com/favicon.ico"); - GURL icon_url_new("http://www.google.com/favicon2.ico"); - GURL page_url1("http://www.google.com"); - GURL page_url2("http://www.google.ca"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); - - backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps); - - std::vector<GURL> icon_urls; - icon_urls.push_back(icon_url); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; - backend_->UpdateFaviconMappingsAndFetch(page_url2, - icon_urls, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results); - - // Check that the same FaviconID is mapped to both page URLs. - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - page_url1, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id; - EXPECT_NE(0, favicon_id); - - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - page_url2, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); - - // Change the icon URL that |page_url1| is mapped to. - bitmaps.clear(); - bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kSmallEdgeSize)); - backend_->SetFavicons( - page_url1, favicon_base::FAVICON, icon_url_new, bitmaps); - - // |page_url1| should map to a new FaviconID and have valid bitmap data. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - page_url1, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url_new, icon_mappings[0].icon_url); - EXPECT_NE(favicon_id, icon_mappings[0].icon_id); - - std::vector<FaviconBitmap> favicon_bitmaps; - EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps( - icon_mappings[0].icon_id, &favicon_bitmaps)); - EXPECT_EQ(1u, favicon_bitmaps.size()); - - // |page_url2| should still map to the same FaviconID and have valid bitmap - // data. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( - page_url2, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); - - favicon_bitmaps.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(favicon_id, - &favicon_bitmaps)); - EXPECT_EQ(2u, favicon_bitmaps.size()); - - // A notification should have been broadcast for each call to SetFavicons() - // and each call to UpdateFaviconMappingsAndFetch(). - EXPECT_EQ(3, favicon_changed_notifications()); -} - -// Test that no notifications are broadcast as a result of calling -// UpdateFaviconMappingsAndFetch() for an icon URL which is already -// mapped to the passed in page URL. -TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchNoChange) { - GURL page_url("http://www.google.com"); - GURL icon_url("http://www.google.com/favicon.ico"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - favicon_base::FaviconID icon_id = - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - icon_url, favicon_base::FAVICON, NULL); - EXPECT_NE(0, icon_id); - EXPECT_EQ(1, favicon_changed_notifications()); - - std::vector<GURL> icon_urls; - icon_urls.push_back(icon_url); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; - backend_->UpdateFaviconMappingsAndFetch(page_url, - icon_urls, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results); - - EXPECT_EQ(icon_id, - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - icon_url, favicon_base::FAVICON, NULL)); - - // No notification should have been broadcast as no icon mapping, favicon, - // or favicon bitmap was updated, added or removed. - EXPECT_EQ(1, favicon_changed_notifications()); -} - -// Test repeatedly calling MergeFavicon(). |page_url| is initially not known -// to the database. -TEST_F(HistoryBackendTest, MergeFaviconPageURLNotInDB) { - GURL page_url("http://www.google.com"); - GURL icon_url("http:/www.google.com/favicon.ico"); - - std::vector<unsigned char> data; - data.push_back('a'); - scoped_refptr<base::RefCountedBytes> bitmap_data( - new base::RefCountedBytes(data)); - - backend_->MergeFavicon( - page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); - - // |page_url| should now be mapped to |icon_url| and the favicon bitmap should - // not be expired. - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url, icon_mappings[0].icon_url); - - FaviconBitmap favicon_bitmap; - EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); - EXPECT_NE(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); - - data[0] = 'b'; - bitmap_data = new base::RefCountedBytes(data); - backend_->MergeFavicon( - page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); - - // |page_url| should still have a single favicon bitmap. The bitmap data - // should be updated. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url, icon_mappings[0].icon_url); - - EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); - EXPECT_EQ(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); -} - -// Test calling MergeFavicon() when |page_url| is known to the database. -TEST_F(HistoryBackendTest, MergeFaviconPageURLInDB) { - GURL page_url("http://www.google.com"); - GURL icon_url1("http:/www.google.com/favicon.ico"); - GURL icon_url2("http://www.google.com/favicon2.ico"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps); - - // Test initial state. - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); - - FaviconBitmap favicon_bitmap; - EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); - EXPECT_NE(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); - - EXPECT_EQ(1, favicon_changed_notifications()); - - // 1) Merge identical favicon bitmap. - std::vector<unsigned char> data; - gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data); - scoped_refptr<base::RefCountedBytes> bitmap_data( - new base::RefCountedBytes(data)); - backend_->MergeFavicon( - page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize); - - // All the data should stay the same and no notifications should have been - // sent. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); - - EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); - EXPECT_NE(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); - - EXPECT_EQ(1, favicon_changed_notifications()); - - // 2) Merge favicon bitmap of the same size. - data.clear(); - data.push_back('b'); - bitmap_data = new base::RefCountedBytes(data); - backend_->MergeFavicon( - page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize); - - // The small favicon bitmap at |icon_url1| should be overwritten. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); - - EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); - EXPECT_EQ(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); - - // 3) Merge favicon for the same icon URL, but a pixel size for which there is - // no favicon bitmap. - data[0] = 'c'; - bitmap_data = new base::RefCountedBytes(data); - backend_->MergeFavicon( - page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kTinySize); - - // A new favicon bitmap should be created and the preexisting favicon bitmap - // ('b') should be expired. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); - - std::vector<FaviconBitmap> favicon_bitmaps; - EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id, - &favicon_bitmaps)); - EXPECT_NE(base::Time(), favicon_bitmaps[0].last_updated); - EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data)); - EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size); - EXPECT_EQ(base::Time(), favicon_bitmaps[1].last_updated); - EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmaps[1].bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size); - - // 4) Merge favicon for an icon URL different from the icon URLs already - // mapped to page URL. - data[0] = 'd'; - bitmap_data = new base::RefCountedBytes(data); - backend_->MergeFavicon( - page_url, icon_url2, favicon_base::FAVICON, bitmap_data, kSmallSize); - - // The existing favicon bitmaps should be copied over to the newly created - // favicon at |icon_url2|. |page_url| should solely be mapped to |icon_url2|. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url2, icon_mappings[0].icon_url); - - favicon_bitmaps.clear(); - EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id, - &favicon_bitmaps)); - EXPECT_EQ(base::Time(), favicon_bitmaps[0].last_updated); - EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data)); - EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size); - // The favicon being merged should take precedence over the preexisting - // favicon bitmaps. - EXPECT_NE(base::Time(), favicon_bitmaps[1].last_updated); - EXPECT_TRUE(BitmapDataEqual('d', favicon_bitmaps[1].bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size); - - // A notification should have been broadcast for each call to SetFavicons() - // and MergeFavicon(). - EXPECT_EQ(4, favicon_changed_notifications()); -} - -// Test calling MergeFavicon() when |icon_url| is known to the database but not -// mapped to |page_url|. -TEST_F(HistoryBackendTest, MergeFaviconIconURLMappedToDifferentPageURL) { - GURL page_url1("http://www.google.com"); - GURL page_url2("http://news.google.com"); - GURL page_url3("http://maps.google.com"); - GURL icon_url("http:/www.google.com/favicon.ico"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - - backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps); - - // Test initial state. - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_url, icon_mappings[0].icon_url); - - FaviconBitmap favicon_bitmap; - EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); - EXPECT_NE(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); - - // 1) Merge in an identical favicon bitmap data but for a different page URL. - std::vector<unsigned char> data; - gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data); - scoped_refptr<base::RefCountedBytes> bitmap_data( - new base::RefCountedBytes(data)); - - backend_->MergeFavicon( - page_url2, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); - - favicon_base::FaviconID favicon_id = - backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - icon_url, favicon_base::FAVICON, NULL); - EXPECT_NE(0, favicon_id); - - EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap)); - EXPECT_NE(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); - - // 2) Merging a favicon bitmap with different bitmap data for the same icon - // URL should overwrite the small favicon bitmap at |icon_url|. - data.clear(); - data.push_back('b'); - bitmap_data = new base::RefCountedBytes(data); - backend_->MergeFavicon( - page_url3, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); - - favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL( - icon_url, favicon_base::FAVICON, NULL); - EXPECT_NE(0, favicon_id); - - EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap)); - EXPECT_EQ(base::Time(), favicon_bitmap.last_updated); - EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data)); - EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); - - // |icon_url| should be mapped to all three page URLs. - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); - - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url2, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); - - icon_mappings.clear(); - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url3, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); - - // A notification should have been broadcast for each call to SetFavicons() - // and MergeFavicon(). - EXPECT_EQ(3, favicon_changed_notifications()); -} - -// Test that MergeFavicon() does not add more than -// |kMaxFaviconBitmapsPerIconURL| to a favicon. -TEST_F(HistoryBackendTest, MergeFaviconMaxFaviconBitmapsPerIconURL) { - GURL page_url("http://www.google.com"); - std::string icon_url_string("http://www.google.com/favicon.ico"); - size_t replace_index = icon_url_string.size() - 1; - - std::vector<unsigned char> data; - data.push_back('a'); - scoped_refptr<base::RefCountedMemory> bitmap_data = - base::RefCountedBytes::TakeVector(&data); - - int pixel_size = 1; - for (size_t i = 0; i < kMaxFaviconBitmapsPerIconURL + 1; ++i) { - icon_url_string[replace_index] = '0' + i; - GURL icon_url(icon_url_string); - - backend_->MergeFavicon(page_url, - icon_url, - favicon_base::FAVICON, - bitmap_data, - gfx::Size(pixel_size, pixel_size)); - ++pixel_size; - } - - // There should be a single favicon mapped to |page_url| with exactly - // kMaxFaviconBitmapsPerIconURL favicon bitmaps. - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - std::vector<FaviconBitmap> favicon_bitmaps; - EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps( - icon_mappings[0].icon_id, &favicon_bitmaps)); - EXPECT_EQ(kMaxFaviconBitmapsPerIconURL, favicon_bitmaps.size()); -} - -// Tests that the favicon set by MergeFavicon() shows up in the result of -// GetFaviconsForURL(). -TEST_F(HistoryBackendTest, MergeFaviconShowsUpInGetFaviconsForURLResult) { - GURL page_url("http://www.google.com"); - GURL icon_url("http://www.google.com/favicon.ico"); - GURL merged_icon_url("http://wwww.google.com/favicon2.ico"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); - - // Set some preexisting favicons for |page_url|. - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - // Merge small favicon. - std::vector<unsigned char> data; - data.push_back('c'); - scoped_refptr<base::RefCountedBytes> bitmap_data( - new base::RefCountedBytes(data)); - backend_->MergeFavicon(page_url, - merged_icon_url, - favicon_base::FAVICON, - bitmap_data, - kSmallSize); - - // Request favicon bitmaps for both 1x and 2x to simulate request done by - // BookmarkModel::GetFavicon(). - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; - backend_->GetFaviconsForURL(page_url, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results); - - EXPECT_EQ(2u, bitmap_results.size()); - const favicon_base::FaviconRawBitmapResult& first_result = bitmap_results[0]; - const favicon_base::FaviconRawBitmapResult& result = - (first_result.pixel_size == kSmallSize) ? first_result - : bitmap_results[1]; - EXPECT_TRUE(BitmapDataEqual('c', result.bitmap_data)); -} - -// Tests GetFaviconsForURL with icon_types priority, -TEST_F(HistoryBackendTest, TestGetFaviconsForURLWithIconTypesPriority) { - GURL page_url("http://www.google.com"); - GURL icon_url("http://www.google.com/favicon.ico"); - GURL touch_icon_url("http://wwww.google.com/touch_icon.ico"); - - std::vector<SkBitmap> favicon_bitmaps; - favicon_bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16)); - favicon_bitmaps.push_back(CreateBitmap(SK_ColorRED, 32)); - - std::vector<SkBitmap> touch_bitmaps; - touch_bitmaps.push_back(CreateBitmap(SK_ColorWHITE, 64)); - - // Set some preexisting favicons for |page_url|. - backend_->SetFavicons( - page_url, favicon_base::FAVICON, icon_url, favicon_bitmaps); - backend_->SetFavicons( - page_url, favicon_base::TOUCH_ICON, touch_icon_url, touch_bitmaps); - - favicon_base::FaviconRawBitmapResult result; - std::vector<int> icon_types; - icon_types.push_back(favicon_base::FAVICON); - icon_types.push_back(favicon_base::TOUCH_ICON); - - backend_->GetLargestFaviconForURL(page_url, icon_types, 16, &result); - - // Verify the result icon is 32x32 favicon. - EXPECT_EQ(gfx::Size(32, 32), result.pixel_size); - EXPECT_EQ(favicon_base::FAVICON, result.icon_type); - - // Change Minimal size to 32x32 and verify the 64x64 touch icon returned. - backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result); - EXPECT_EQ(gfx::Size(64, 64), result.pixel_size); - EXPECT_EQ(favicon_base::TOUCH_ICON, result.icon_type); -} - -// Test the the first types of icon is returned if its size equal to the -// second types icon. -TEST_F(HistoryBackendTest, TestGetFaviconsForURLReturnFavicon) { - GURL page_url("http://www.google.com"); - GURL icon_url("http://www.google.com/favicon.ico"); - GURL touch_icon_url("http://wwww.google.com/touch_icon.ico"); - - std::vector<SkBitmap> favicon_bitmaps; - favicon_bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16)); - favicon_bitmaps.push_back(CreateBitmap(SK_ColorRED, 32)); - - std::vector<SkBitmap> touch_bitmaps; - touch_bitmaps.push_back(CreateBitmap(SK_ColorWHITE, 32)); - - // Set some preexisting favicons for |page_url|. - backend_->SetFavicons( - page_url, favicon_base::FAVICON, icon_url, favicon_bitmaps); - backend_->SetFavicons( - page_url, favicon_base::TOUCH_ICON, touch_icon_url, touch_bitmaps); - - favicon_base::FaviconRawBitmapResult result; - std::vector<int> icon_types; - icon_types.push_back(favicon_base::FAVICON); - icon_types.push_back(favicon_base::TOUCH_ICON); - - backend_->GetLargestFaviconForURL(page_url, icon_types, 16, &result); - - // Verify the result icon is 32x32 favicon. - EXPECT_EQ(gfx::Size(32, 32), result.pixel_size); - EXPECT_EQ(favicon_base::FAVICON, result.icon_type); - - // Change minimal size to 32x32 and verify the 32x32 favicon returned. - favicon_base::FaviconRawBitmapResult result1; - backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result1); - EXPECT_EQ(gfx::Size(32, 32), result1.pixel_size); - EXPECT_EQ(favicon_base::FAVICON, result1.icon_type); -} - -// Test the favicon is returned if its size is smaller than minimal size, -// because it is only one available. -TEST_F(HistoryBackendTest, TestGetFaviconsForURLReturnFaviconEvenItSmaller) { - GURL page_url("http://www.google.com"); - GURL icon_url("http://www.google.com/favicon.ico"); - - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16)); - - // Set preexisting favicons for |page_url|. - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - favicon_base::FaviconRawBitmapResult result; - std::vector<int> icon_types; - icon_types.push_back(favicon_base::FAVICON); - icon_types.push_back(favicon_base::TOUCH_ICON); - - backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result); - - // Verify 16x16 icon is returned, even it small than minimal_size. - EXPECT_EQ(gfx::Size(16, 16), result.pixel_size); - EXPECT_EQ(favicon_base::FAVICON, result.icon_type); -} - -// Test UpdateFaviconMapingsAndFetch() when multiple icon types are passed in. -TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchMultipleIconTypes) { - GURL page_url1("http://www.google.com"); - GURL page_url2("http://news.google.com"); - GURL page_url3("http://mail.google.com"); - GURL icon_urla("http://www.google.com/favicon1.ico"); - GURL icon_urlb("http://www.google.com/favicon2.ico"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - - // |page_url1| is mapped to |icon_urla| which if of type TOUCH_ICON. - backend_->SetFavicons( - page_url1, favicon_base::TOUCH_ICON, icon_urla, bitmaps); - - // |page_url2| is mapped to |icon_urlb| which is of type - // TOUCH_PRECOMPOSED_ICON. - backend_->SetFavicons( - page_url2, favicon_base::TOUCH_PRECOMPOSED_ICON, icon_urlb, bitmaps); - - std::vector<GURL> icon_urls; - icon_urls.push_back(icon_urla); - icon_urls.push_back(icon_urlb); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; - backend_->UpdateFaviconMappingsAndFetch( - page_url3, - icon_urls, - (favicon_base::TOUCH_ICON | favicon_base::TOUCH_PRECOMPOSED_ICON), - GetEdgeSizesSmallAndLarge(), - &bitmap_results); - - // |page_url1| and |page_url2| should still be mapped to the same icon URLs. - std::vector<IconMapping> icon_mappings; - EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1, - &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_urla, icon_mappings[0].icon_url); - EXPECT_EQ(favicon_base::TOUCH_ICON, icon_mappings[0].icon_type); - - icon_mappings.clear(); - EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url2, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_urlb, icon_mappings[0].icon_url); - EXPECT_EQ(favicon_base::TOUCH_PRECOMPOSED_ICON, icon_mappings[0].icon_type); - - // |page_url3| should be mapped only to |icon_urlb| as TOUCH_PRECOMPOSED_ICON - // is the largest IconType. - icon_mappings.clear(); - EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url3, &icon_mappings)); - EXPECT_EQ(1u, icon_mappings.size()); - EXPECT_EQ(icon_urlb, icon_mappings[0].icon_url); - EXPECT_EQ(favicon_base::TOUCH_PRECOMPOSED_ICON, icon_mappings[0].icon_type); -} - -// Test the results of GetFaviconsFromDB() when there are no found favicons. -TEST_F(HistoryBackendTest, GetFaviconsFromDBEmpty) { - const GURL page_url("http://www.google.com/"); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; - EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results)); - EXPECT_TRUE(bitmap_results.empty()); -} - -// Test the results of GetFaviconsFromDB() when there are matching favicons -// but there are no associated favicon bitmaps. -TEST_F(HistoryBackendTest, GetFaviconsFromDBNoFaviconBitmaps) { - const GURL page_url("http://www.google.com/"); - const GURL icon_url("http://www.google.com/icon1"); - - favicon_base::FaviconID icon_id = - backend_->thumbnail_db_->AddFavicon(icon_url, favicon_base::FAVICON); - EXPECT_NE(0, icon_id); - EXPECT_NE(0, backend_->thumbnail_db_->AddIconMapping(page_url, icon_id)); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; - EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results_out)); - EXPECT_TRUE(bitmap_results_out.empty()); -} - -// Test that GetFaviconsFromDB() returns results for the bitmaps which most -// closely match the passed in the desired pixel sizes. -TEST_F(HistoryBackendTest, GetFaviconsFromDBSelectClosestMatch) { - const GURL page_url("http://www.google.com/"); - const GURL icon_url("http://www.google.com/icon1"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kTinyEdgeSize)); - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); - - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; - EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results_out)); - - // The bitmap data for the small and large bitmaps should be returned as their - // sizes match exactly. - EXPECT_EQ(2u, bitmap_results_out.size()); - // No required order for results. - if (bitmap_results_out[0].pixel_size == kLargeSize) { - favicon_base::FaviconRawBitmapResult tmp_result = bitmap_results_out[0]; - bitmap_results_out[0] = bitmap_results_out[1]; - bitmap_results_out[1] = tmp_result; - } - - EXPECT_FALSE(bitmap_results_out[0].expired); - EXPECT_TRUE( - BitmapColorEqual(SK_ColorBLUE, bitmap_results_out[0].bitmap_data)); - EXPECT_EQ(kSmallSize, bitmap_results_out[0].pixel_size); - EXPECT_EQ(icon_url, bitmap_results_out[0].icon_url); - EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[0].icon_type); - - EXPECT_FALSE(bitmap_results_out[1].expired); - EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, bitmap_results_out[1].bitmap_data)); - EXPECT_EQ(kLargeSize, bitmap_results_out[1].pixel_size); - EXPECT_EQ(icon_url, bitmap_results_out[1].icon_url); - EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[1].icon_type); -} - -// Test the results of GetFaviconsFromDB() when called with different -// |icon_types|. -TEST_F(HistoryBackendTest, GetFaviconsFromDBIconType) { - const GURL page_url("http://www.google.com/"); - const GURL icon_url1("http://www.google.com/icon1.png"); - const GURL icon_url2("http://www.google.com/icon2.png"); - std::vector<SkBitmap> bitmaps; - bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); - - std::vector<favicon_base::FaviconRawBitmapData> favicon_bitmap_data; - backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps); - backend_->SetFavicons(page_url, favicon_base::TOUCH_ICON, icon_url2, bitmaps); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; - EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results_out)); - - EXPECT_EQ(1u, bitmap_results_out.size()); - EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[0].icon_type); - EXPECT_EQ(icon_url1, bitmap_results_out[0].icon_url); - - bitmap_results_out.clear(); - EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, - favicon_base::TOUCH_ICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results_out)); - - EXPECT_EQ(1u, bitmap_results_out.size()); - EXPECT_EQ(favicon_base::TOUCH_ICON, bitmap_results_out[0].icon_type); - EXPECT_EQ(icon_url2, bitmap_results_out[0].icon_url); -} - -// Test that GetFaviconsFromDB() correctly sets the expired flag for bitmap -// reults. -TEST_F(HistoryBackendTest, GetFaviconsFromDBExpired) { - const GURL page_url("http://www.google.com/"); - const GURL icon_url("http://www.google.com/icon.png"); - - std::vector<unsigned char> data; - data.push_back('a'); - scoped_refptr<base::RefCountedBytes> bitmap_data( - base::RefCountedBytes::TakeVector(&data)); - base::Time last_updated = base::Time::FromTimeT(0); - favicon_base::FaviconID icon_id = backend_->thumbnail_db_->AddFavicon( - icon_url, favicon_base::FAVICON, bitmap_data, last_updated, kSmallSize); - EXPECT_NE(0, icon_id); - EXPECT_NE(0, backend_->thumbnail_db_->AddIconMapping(page_url, icon_id)); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; - EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results_out)); - - EXPECT_EQ(1u, bitmap_results_out.size()); - EXPECT_TRUE(bitmap_results_out[0].expired); -} - -// Check that UpdateFaviconMappingsAndFetch() call back to the UI when there is -// no valid thumbnail database. -TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchNoDB) { - // Make the thumbnail database invalid. - backend_->thumbnail_db_.reset(); - - std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; - - backend_->UpdateFaviconMappingsAndFetch(GURL(), - std::vector<GURL>(), - favicon_base::FAVICON, - GetEdgeSizesSmallAndLarge(), - &bitmap_results); - - EXPECT_TRUE(bitmap_results.empty()); -} - -TEST_F(HistoryBackendTest, QueryFilteredURLs) { - const char* google = "http://www.google.com/"; - const char* yahoo = "http://www.yahoo.com/"; - const char* yahoo_sports = "http://sports.yahoo.com/"; - const char* yahoo_sports_with_article1 = - "http://sports.yahoo.com/article1.htm"; - const char* yahoo_sports_with_article2 = - "http://sports.yahoo.com/article2.htm"; - const char* yahoo_sports_soccer = "http://sports.yahoo.com/soccer"; - const char* apple = "http://www.apple.com/"; - - // Clear all history. - backend_->DeleteAllHistory(); - - Time tested_time = Time::Now().LocalMidnight() + - base::TimeDelta::FromHours(4); - base::TimeDelta half_an_hour = base::TimeDelta::FromMinutes(30); - base::TimeDelta one_hour = base::TimeDelta::FromHours(1); - base::TimeDelta one_day = base::TimeDelta::FromDays(1); - - const ui::PageTransition kTypedTransition = - ui::PAGE_TRANSITION_TYPED; - const ui::PageTransition kKeywordGeneratedTransition = - ui::PAGE_TRANSITION_KEYWORD_GENERATED; - - const char* redirect_sequence[2]; - redirect_sequence[1] = NULL; - - redirect_sequence[0] = google; - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, kTypedTransition, - tested_time - one_day - half_an_hour * 2); - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, - kTypedTransition, tested_time - one_day); - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, - kTypedTransition, tested_time - half_an_hour / 2); - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, - kTypedTransition, tested_time); - - // Add a visit with a transition that will make sure that no segment gets - // created for this page (so the subsequent entries will have different URLIDs - // and SegmentIDs). - redirect_sequence[0] = apple; - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, kKeywordGeneratedTransition, - tested_time - one_day + one_hour * 6); - - redirect_sequence[0] = yahoo; - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, kTypedTransition, - tested_time - one_day + half_an_hour); - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, kTypedTransition, - tested_time - one_day + half_an_hour * 2); - - redirect_sequence[0] = yahoo_sports; - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, kTypedTransition, - tested_time - one_day - half_an_hour * 2); - AddRedirectChainWithTransitionAndTime( - redirect_sequence, 0, kTypedTransition, - tested_time - one_day); - int transition1, transition2; - AddClientRedirect(GURL(yahoo_sports), GURL(yahoo_sports_with_article1), false, - tested_time - one_day + half_an_hour, - &transition1, &transition2); - AddClientRedirect(GURL(yahoo_sports_with_article1), - GURL(yahoo_sports_with_article2), - false, - tested_time - one_day + half_an_hour * 2, - &transition1, &transition2); - - redirect_sequence[0] = yahoo_sports_soccer; - AddRedirectChainWithTransitionAndTime(redirect_sequence, 0, - kTypedTransition, - tested_time - half_an_hour); - backend_->Commit(); - - VisitFilter filter; - FilteredURLList filtered_list; - // Time limit is |tested_time| +/- 45 min. - base::TimeDelta three_quarters_of_an_hour = base::TimeDelta::FromMinutes(45); - filter.SetFilterTime(tested_time); - filter.SetFilterWidth(three_quarters_of_an_hour); - backend_->QueryFilteredURLs(100, filter, false, &filtered_list); - - ASSERT_EQ(4U, filtered_list.size()); - EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); - EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); - EXPECT_EQ(std::string(yahoo), filtered_list[2].url.spec()); - EXPECT_EQ(std::string(yahoo_sports), filtered_list[3].url.spec()); - - // Time limit is between |tested_time| and |tested_time| + 2 hours. - filter.SetFilterTime(tested_time + one_hour); - filter.SetFilterWidth(one_hour); - backend_->QueryFilteredURLs(100, filter, false, &filtered_list); - - ASSERT_EQ(3U, filtered_list.size()); - EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); - EXPECT_EQ(std::string(yahoo), filtered_list[1].url.spec()); - EXPECT_EQ(std::string(yahoo_sports), filtered_list[2].url.spec()); - - // Time limit is between |tested_time| - 2 hours and |tested_time|. - filter.SetFilterTime(tested_time - one_hour); - filter.SetFilterWidth(one_hour); - backend_->QueryFilteredURLs(100, filter, false, &filtered_list); - - ASSERT_EQ(3U, filtered_list.size()); - EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); - EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); - EXPECT_EQ(std::string(yahoo_sports), filtered_list[2].url.spec()); - - filter.ClearFilters(); - base::Time::Exploded exploded_time; - tested_time.LocalExplode(&exploded_time); - - // Today. - filter.SetFilterTime(tested_time); - filter.SetDayOfTheWeekFilter(static_cast<int>(exploded_time.day_of_week)); - backend_->QueryFilteredURLs(100, filter, false, &filtered_list); - - ASSERT_EQ(2U, filtered_list.size()); - EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); - EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); - - // Today + time limit - only yahoo_sports_soccer should fit. - filter.SetFilterTime(tested_time - base::TimeDelta::FromMinutes(40)); - filter.SetFilterWidth(base::TimeDelta::FromMinutes(20)); - backend_->QueryFilteredURLs(100, filter, false, &filtered_list); - - ASSERT_EQ(1U, filtered_list.size()); - EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[0].url.spec()); - - // Make sure we get debug data if we request it. - filter.SetFilterTime(tested_time); - filter.SetFilterWidth(one_hour * 2); - backend_->QueryFilteredURLs(100, filter, true, &filtered_list); - - // If the SegmentID is used by QueryFilteredURLs when generating the debug - // data instead of the URLID, the |total_visits| for the |yahoo_sports_soccer| - // entry will be zero instead of 1. - ASSERT_GE(filtered_list.size(), 2U); - EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); - EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); - EXPECT_EQ(4U, filtered_list[0].extended_info.total_visits); - EXPECT_EQ(1U, filtered_list[1].extended_info.total_visits); -} - -TEST_F(HistoryBackendTest, UpdateVisitDuration) { - // This unit test will test adding and deleting visit details information. - ASSERT_TRUE(backend_.get()); - - GURL url1("http://www.cnn.com"); - std::vector<VisitInfo> visit_info1, visit_info2; - Time start_ts = Time::Now() - base::TimeDelta::FromDays(5); - Time end_ts = start_ts + base::TimeDelta::FromDays(2); - visit_info1.push_back(VisitInfo(start_ts, ui::PAGE_TRANSITION_LINK)); - - GURL url2("http://www.example.com"); - visit_info2.push_back(VisitInfo(Time::Now() - base::TimeDelta::FromDays(10), - ui::PAGE_TRANSITION_LINK)); - - // Clear all history. - backend_->DeleteAllHistory(); - - // Add the visits. - backend_->AddVisits(url1, visit_info1, history::SOURCE_BROWSED); - backend_->AddVisits(url2, visit_info2, history::SOURCE_BROWSED); - - // Verify the entries for both visits were added in visit_details. - VisitVector visits1, visits2; - URLRow row; - URLID url_id1 = backend_->db()->GetRowForURL(url1, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id1, &visits1)); - ASSERT_EQ(1U, visits1.size()); - EXPECT_EQ(0, visits1[0].visit_duration.ToInternalValue()); - - URLID url_id2 = backend_->db()->GetRowForURL(url2, &row); - ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id2, &visits2)); - ASSERT_EQ(1U, visits2.size()); - EXPECT_EQ(0, visits2[0].visit_duration.ToInternalValue()); - - // Update the visit to cnn.com. - backend_->UpdateVisitDuration(visits1[0].visit_id, end_ts); - - // Check the duration for visiting cnn.com was correctly updated. - ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id1, &visits1)); - ASSERT_EQ(1U, visits1.size()); - base::TimeDelta expected_duration = end_ts - start_ts; - EXPECT_EQ(expected_duration.ToInternalValue(), - visits1[0].visit_duration.ToInternalValue()); - - // Remove the visit to cnn.com. - ASSERT_TRUE(backend_->RemoveVisits(visits1)); -} - -// Test for migration of adding visit_duration column. -TEST_F(HistoryBackendTest, MigrationVisitDuration) { - ASSERT_TRUE(backend_.get()); - backend_->Closing(); - backend_ = NULL; - - base::FilePath old_history_path, old_history; - ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &old_history_path)); - old_history_path = old_history_path.AppendASCII("History"); - old_history = old_history_path.AppendASCII("HistoryNoDuration"); - - // Copy history database file to current directory so that it will be deleted - // in Teardown. - base::FilePath new_history_path(test_dir()); - base::DeleteFile(new_history_path, true); - base::CreateDirectory(new_history_path); - base::FilePath new_history_file = new_history_path.Append(kHistoryFilename); - ASSERT_TRUE(base::CopyFile(old_history, new_history_file)); - - backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this), - &history_client_); - backend_->Init(std::string(), false, - TestHistoryDatabaseParamsForPath(new_history_path)); - backend_->Closing(); - backend_ = NULL; - - // Now the history database should already be migrated. - - // Check version in history database first. - int cur_version = HistoryDatabase::GetCurrentVersion(); - sql::Connection db; - ASSERT_TRUE(db.Open(new_history_file)); - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - ASSERT_TRUE(s.Step()); - int file_version = s.ColumnInt(0); - EXPECT_EQ(cur_version, file_version); - - // Check visit_duration column in visits table is created and set to 0. - s.Assign(db.GetUniqueStatement( - "SELECT visit_duration FROM visits LIMIT 1")); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(0, s.ColumnInt(0)); -} - -TEST_F(HistoryBackendTest, AddPageNoVisitForBookmark) { - ASSERT_TRUE(backend_.get()); - - GURL url("http://www.google.com"); - base::string16 title(base::UTF8ToUTF16("Bookmark title")); - backend_->AddPageNoVisitForBookmark(url, title); - - URLRow row; - backend_->GetURL(url, &row); - EXPECT_EQ(url, row.url()); - EXPECT_EQ(title, row.title()); - EXPECT_EQ(0, row.visit_count()); - - backend_->DeleteURL(url); - backend_->AddPageNoVisitForBookmark(url, base::string16()); - backend_->GetURL(url, &row); - EXPECT_EQ(url, row.url()); - EXPECT_EQ(base::UTF8ToUTF16(url.spec()), row.title()); - EXPECT_EQ(0, row.visit_count()); -} - -TEST_F(HistoryBackendTest, ExpireHistoryForTimes) { - ASSERT_TRUE(backend_.get()); - - HistoryAddPageArgs args[10]; - for (size_t i = 0; i < arraysize(args); ++i) { - args[i].url = GURL("http://example" + - std::string((i % 2 == 0 ? ".com" : ".net"))); - args[i].time = base::Time::FromInternalValue(i); - backend_->AddPage(args[i]); - } - EXPECT_EQ(base::Time(), backend_->GetFirstRecordedTimeForTest()); - - URLRow row; - for (size_t i = 0; i < arraysize(args); ++i) { - EXPECT_TRUE(backend_->GetURL(args[i].url, &row)); - } - - std::set<base::Time> times; - times.insert(args[5].time); - backend_->ExpireHistoryForTimes(times, - base::Time::FromInternalValue(2), - base::Time::FromInternalValue(8)); - - EXPECT_EQ(base::Time::FromInternalValue(0), - backend_->GetFirstRecordedTimeForTest()); - - // Visits to http://example.com are untouched. - VisitVector visit_vector; - EXPECT_TRUE(backend_->GetVisitsForURL( - backend_->db_->GetRowForURL(GURL("http://example.com"), NULL), - &visit_vector)); - ASSERT_EQ(5u, visit_vector.size()); - EXPECT_EQ(base::Time::FromInternalValue(0), visit_vector[0].visit_time); - EXPECT_EQ(base::Time::FromInternalValue(2), visit_vector[1].visit_time); - EXPECT_EQ(base::Time::FromInternalValue(4), visit_vector[2].visit_time); - EXPECT_EQ(base::Time::FromInternalValue(6), visit_vector[3].visit_time); - EXPECT_EQ(base::Time::FromInternalValue(8), visit_vector[4].visit_time); - - // Visits to http://example.net between [2,8] are removed. - visit_vector.clear(); - EXPECT_TRUE(backend_->GetVisitsForURL( - backend_->db_->GetRowForURL(GURL("http://example.net"), NULL), - &visit_vector)); - ASSERT_EQ(2u, visit_vector.size()); - EXPECT_EQ(base::Time::FromInternalValue(1), visit_vector[0].visit_time); - EXPECT_EQ(base::Time::FromInternalValue(9), visit_vector[1].visit_time); - - EXPECT_EQ(base::Time::FromInternalValue(0), - backend_->GetFirstRecordedTimeForTest()); -} - -TEST_F(HistoryBackendTest, ExpireHistory) { - ASSERT_TRUE(backend_.get()); - // Since history operations are dependent on the local timezone, make all - // entries relative to a fixed, local reference time. - base::Time reference_time = base::Time::UnixEpoch().LocalMidnight() + - base::TimeDelta::FromHours(12); - - // Insert 4 entries into the database. - HistoryAddPageArgs args[4]; - for (size_t i = 0; i < arraysize(args); ++i) { - args[i].url = GURL("http://example" + base::IntToString(i) + ".com"); - args[i].time = reference_time + base::TimeDelta::FromDays(i); - backend_->AddPage(args[i]); - } - - URLRow url_rows[4]; - for (unsigned int i = 0; i < arraysize(args); ++i) - ASSERT_TRUE(backend_->GetURL(args[i].url, &url_rows[i])); - - std::vector<ExpireHistoryArgs> expire_list; - VisitVector visits; - - // Passing an empty map should be a no-op. - backend_->ExpireHistory(expire_list); - backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); - EXPECT_EQ(4U, visits.size()); - - // Trying to delete an unknown URL with the time of the first visit should - // also be a no-op. - expire_list.resize(expire_list.size() + 1); - expire_list[0].SetTimeRangeForOneDay(args[0].time); - expire_list[0].urls.insert(GURL("http://google.does-not-exist")); - backend_->ExpireHistory(expire_list); - backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); - EXPECT_EQ(4U, visits.size()); - - // Now add the first URL with the same time -- it should get deleted. - expire_list.back().urls.insert(url_rows[0].url()); - backend_->ExpireHistory(expire_list); - - backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); - ASSERT_EQ(3U, visits.size()); - EXPECT_EQ(visits[0].url_id, url_rows[1].id()); - EXPECT_EQ(visits[1].url_id, url_rows[2].id()); - EXPECT_EQ(visits[2].url_id, url_rows[3].id()); - - // The first recorded time should also get updated. - EXPECT_EQ(backend_->GetFirstRecordedTimeForTest(), args[1].time); - - // Now delete the rest of the visits in one call. - for (unsigned int i = 1; i < arraysize(args); ++i) { - expire_list.resize(expire_list.size() + 1); - expire_list[i].SetTimeRangeForOneDay(args[i].time); - expire_list[i].urls.insert(args[i].url); - } - backend_->ExpireHistory(expire_list); - - backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); - ASSERT_EQ(0U, visits.size()); -} - -TEST_F(HistoryBackendTest, DeleteMatchingUrlsForKeyword) { - // Set up urls and keyword_search_terms - GURL url1("https://www.bing.com/?q=bar"); - URLRow url_info1(url1); - url_info1.set_visit_count(0); - url_info1.set_typed_count(0); - url_info1.set_last_visit(Time()); - url_info1.set_hidden(false); - const URLID url1_id = backend_->db()->AddURL(url_info1); - EXPECT_NE(0, url1_id); - - KeywordID keyword_id = 1; - base::string16 keyword = base::UTF8ToUTF16("bar"); - ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL( - url1_id, keyword_id, keyword)); - - GURL url2("https://www.google.com/?q=bar"); - URLRow url_info2(url2); - url_info2.set_visit_count(0); - url_info2.set_typed_count(0); - url_info2.set_last_visit(Time()); - url_info2.set_hidden(false); - const URLID url2_id = backend_->db()->AddURL(url_info2); - EXPECT_NE(0, url2_id); - - KeywordID keyword_id2 = 2; - ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL( - url2_id, keyword_id2, keyword)); - - // Add another visit to the same URL - URLRow url_info3(url2); - url_info3.set_visit_count(0); - url_info3.set_typed_count(0); - url_info3.set_last_visit(Time()); - url_info3.set_hidden(false); - const URLID url3_id = backend_->db()->AddURL(url_info3); - EXPECT_NE(0, url3_id); - ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL( - url3_id, keyword_id2, keyword)); - - // Test that deletion works correctly - backend_->DeleteMatchingURLsForKeyword(keyword_id2, keyword); - - // Test that rows 2 and 3 are deleted, while 1 is intact - URLRow row; - EXPECT_TRUE(backend_->db()->GetURLRow(url1_id, &row)); - EXPECT_EQ(url1.spec(), row.url().spec()); - EXPECT_FALSE(backend_->db()->GetURLRow(url2_id, &row)); - EXPECT_FALSE(backend_->db()->GetURLRow(url3_id, &row)); - - // Test that corresponding keyword search terms are deleted for rows 2 & 3, - // but not for row 1 - EXPECT_TRUE(backend_->db()->GetKeywordSearchTermRow(url1_id, NULL)); - EXPECT_FALSE(backend_->db()->GetKeywordSearchTermRow(url2_id, NULL)); - EXPECT_FALSE(backend_->db()->GetKeywordSearchTermRow(url3_id, NULL)); -} - -// Simple test that removes a bookmark. This test exercises the code paths in -// History that block till bookmark bar model is loaded. -TEST_F(HistoryBackendTest, RemoveNotification) { - scoped_ptr<TestingProfile> profile(new TestingProfile()); - - // Add a URL. - GURL url("http://www.google.com"); - HistoryClientMock history_client; - history_client.AddBookmark(url); - scoped_ptr<HistoryService> service(new HistoryService( - &history_client, scoped_ptr<history::VisitDelegate>())); - EXPECT_TRUE( - service->Init(profile->GetPrefs()->GetString(prefs::kAcceptLanguages), - TestHistoryDatabaseParamsForPath(profile->GetPath()))); - - service->AddPage( - url, base::Time::Now(), NULL, 1, GURL(), RedirectList(), - ui::PAGE_TRANSITION_TYPED, SOURCE_BROWSED, false); - - // This won't actually delete the URL, rather it'll empty out the visits. - // This triggers blocking on the BookmarkModel. - EXPECT_CALL(history_client, BlockUntilBookmarksLoaded()); - service->DeleteURL(url); -} - -// Test DeleteFTSIndexDatabases deletes expected files. -TEST_F(HistoryBackendTest, DeleteFTSIndexDatabases) { - ASSERT_TRUE(backend_.get()); - - base::FilePath history_path(test_dir()); - base::FilePath db1(history_path.AppendASCII("History Index 2013-05")); - base::FilePath db1_journal(db1.InsertBeforeExtensionASCII("-journal")); - base::FilePath db1_wal(db1.InsertBeforeExtensionASCII("-wal")); - base::FilePath db2_symlink(history_path.AppendASCII("History Index 2013-06")); - base::FilePath db2_actual(history_path.AppendASCII("Underlying DB")); - - // Setup dummy index database files. - const char* data = "Dummy"; - const size_t data_len = 5; - ASSERT_TRUE(base::WriteFile(db1, data, data_len)); - ASSERT_TRUE(base::WriteFile(db1_journal, data, data_len)); - ASSERT_TRUE(base::WriteFile(db1_wal, data, data_len)); - ASSERT_TRUE(base::WriteFile(db2_actual, data, data_len)); -#if defined(OS_POSIX) - EXPECT_TRUE(base::CreateSymbolicLink(db2_actual, db2_symlink)); -#endif - - // Delete all DTS index databases. - backend_->DeleteFTSIndexDatabases(); - EXPECT_FALSE(base::PathExists(db1)); - EXPECT_FALSE(base::PathExists(db1_wal)); - EXPECT_FALSE(base::PathExists(db1_journal)); - EXPECT_FALSE(base::PathExists(db2_symlink)); - EXPECT_TRUE(base::PathExists(db2_actual)); // Symlinks shouldn't be followed. -} - -// Common implementation for the two tests below, given that the only difference -// between them is the type of the notification sent out. -void InMemoryHistoryBackendTest::TestAddingAndChangingURLRows( - const SimulateNotificationCallback& callback) { - const char kTestTypedURLAlternativeTitle[] = "Google Search Again"; - const char kTestNonTypedURLAlternativeTitle[] = "Google News Again"; - - // Notify the in-memory database that a typed and non-typed URLRow (which were - // never before seen by the cache) have been modified. - URLRow row1(CreateTestTypedURL()); - URLRow row2(CreateTestNonTypedURL()); - callback.Run(&row1, &row2, nullptr); - - // The in-memory database should only pick up the typed URL, and should ignore - // the non-typed one. The typed URL should retain the ID that was present in - // the notification. - URLRow cached_row1, cached_row2; - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); - EXPECT_EQ(row1.id(), cached_row1.id()); - - // Try changing attributes (other than typed_count) for existing URLRows. - row1.set_title(base::UTF8ToUTF16(kTestTypedURLAlternativeTitle)); - row2.set_title(base::UTF8ToUTF16(kTestNonTypedURLAlternativeTitle)); - callback.Run(&row1, &row2, nullptr); - - // URLRows that are cached by the in-memory database should be updated. - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); - EXPECT_EQ(base::UTF8ToUTF16(kTestTypedURLAlternativeTitle), - cached_row1.title()); - - // Now decrease the typed count for the typed URLRow, and increase it for the - // previously non-typed URLRow. - row1.set_typed_count(0); - row2.set_typed_count(2); - callback.Run(&row1, &row2, nullptr); - - // The in-memory database should stop caching the first URLRow, and start - // caching the second URLRow. - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); - EXPECT_EQ(row2.id(), cached_row2.id()); - EXPECT_EQ(base::UTF8ToUTF16(kTestNonTypedURLAlternativeTitle), - cached_row2.title()); -} - -TEST_F(InMemoryHistoryBackendTest, OnURLsModified) { - TestAddingAndChangingURLRows(base::Bind( - &SimulateNotificationURLsModified, base::Unretained(mem_backend_.get()))); -} - -TEST_F(InMemoryHistoryBackendTest, OnURLsVisisted) { - TestAddingAndChangingURLRows(base::Bind( - &SimulateNotificationURLVisited, base::Unretained(mem_backend_.get()))); -} - -TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedPiecewise) { - // Add two typed and one non-typed URLRow to the in-memory database. - URLRow row1(CreateTestTypedURL()); - URLRow row2(CreateAnotherTestTypedURL()); - URLRow row3(CreateTestNonTypedURL()); - SimulateNotificationURLsModified(mem_backend_.get(), &row1, &row2, &row3); - - // Notify the in-memory database that the second typed URL and the non-typed - // URL has been deleted. - SimulateNotificationURLsDeleted(&row2, &row3); - - // Expect that the first typed URL remains intact, the second typed URL is - // correctly removed, and the non-typed URL does not magically appear. - URLRow cached_row1; - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), NULL)); - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row3.url(), NULL)); - EXPECT_EQ(row1.id(), cached_row1.id()); -} - -TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedEnMasse) { - // Add two typed and one non-typed URLRow to the in-memory database. - URLRow row1(CreateTestTypedURL()); - URLRow row2(CreateAnotherTestTypedURL()); - URLRow row3(CreateTestNonTypedURL()); - SimulateNotificationURLsModified(mem_backend_.get(), &row1, &row2, &row3); - - // Now notify the in-memory database that all history has been deleted. - mem_backend_->OnURLsDeleted(nullptr, true, false, URLRows(), - std::set<GURL>()); - - // Expect that everything goes away. - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row1.url(), NULL)); - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), NULL)); - EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row3.url(), NULL)); -} - -void InMemoryHistoryBackendTest::PopulateTestURLsAndSearchTerms( - URLRow* row1, - URLRow* row2, - const base::string16& term1, - const base::string16& term2) { - // Add a typed and a non-typed URLRow to the in-memory database. This time, - // though, do it through the history backend... - URLRows rows; - rows.push_back(*row1); - rows.push_back(*row2); - backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); - backend_->db()->GetRowForURL(row1->url(), row1); // Get effective IDs from - backend_->db()->GetRowForURL(row2->url(), row2); // the database. - - // ... so that we can also use that for adding the search terms. This way, we - // not only test that the notifications involved are handled correctly, but - // also that they are fired correctly (in the history backend). - backend_->SetKeywordSearchTermsForURL(row1->url(), kTestKeywordId, term1); - backend_->SetKeywordSearchTermsForURL(row2->url(), kTestKeywordId, term2); -} - -TEST_F(InMemoryHistoryBackendTest, SetKeywordSearchTerms) { - URLRow row1(CreateTestTypedURL()); - URLRow row2(CreateTestNonTypedURL()); - base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); - base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); - PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); - - // Both URLs now have associated search terms, so the in-memory database - // should cache both of them, regardless whether they have been typed or not. - URLRow cached_row1, cached_row2; - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); - EXPECT_EQ(row1.id(), cached_row1.id()); - EXPECT_EQ(row2.id(), cached_row2.id()); - - // Verify that lookups will actually return both search terms; and also check - // at the low level that the rows are there. - EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); - EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); - EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); - EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); -} - -TEST_F(InMemoryHistoryBackendTest, DeleteKeywordSearchTerms) { - URLRow row1(CreateTestTypedURL()); - URLRow row2(CreateTestNonTypedURL()); - base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); - base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); - PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); - - // Delete both search terms. This should be reflected in the in-memory DB. - backend_->DeleteKeywordSearchTermForURL(row1.url()); - backend_->DeleteKeywordSearchTermForURL(row2.url()); - - // The typed URL should remain intact. - // Note: we do not need to guarantee anything about the non-typed URL. - URLRow cached_row1; - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); - EXPECT_EQ(row1.id(), cached_row1.id()); - - // Verify that the search terms are no longer returned as results, and also - // check at the low level that they are gone for good. - EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); - EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); - EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); - EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); -} - -TEST_F(InMemoryHistoryBackendTest, DeleteAllSearchTermsForKeyword) { - URLRow row1(CreateTestTypedURL()); - URLRow row2(CreateTestNonTypedURL()); - base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); - base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); - PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); - - // Delete all corresponding search terms from the in-memory database. - KeywordID id = kTestKeywordId; - mem_backend_->DeleteAllSearchTermsForKeyword(id); - - // The typed URL should remain intact. - // Note: we do not need to guarantee anything about the non-typed URL. - URLRow cached_row1; - EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); - EXPECT_EQ(row1.id(), cached_row1.id()); - - // Verify that the search terms are no longer returned as results, and also - // check at the low level that they are gone for good. - EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); - EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); - EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); - EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); -} - -TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedWithSearchTerms) { - URLRow row1(CreateTestTypedURL()); - URLRow row2(CreateTestNonTypedURL()); - base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); - base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); - PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); - - // Notify the in-memory database that the second typed URL has been deleted. - SimulateNotificationURLsDeleted(&row2); - - // Verify that the second term is no longer returned as result, and also check - // at the low level that it is gone for good. The term corresponding to the - // first URLRow should not be affected. - EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); - EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); - EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); - EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); -} - -} // namespace history diff --git a/chrome/browser/history/top_sites_factory.cc b/chrome/browser/history/top_sites_factory.cc index a102fcc..0c56d8c 100644 --- a/chrome/browser/history/top_sites_factory.cc +++ b/chrome/browser/history/top_sites_factory.cc @@ -10,11 +10,11 @@ #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/history/history_utils.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/common/chrome_constants.h" #include "chrome/common/pref_names.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" #include "chrome/grit/locale_settings.h" +#include "components/history/core/browser/history_constants.h" #include "components/history/core/browser/top_sites_impl.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/browser_thread.h" @@ -78,13 +78,6 @@ scoped_refptr<history::TopSites> TopSitesFactory::GetForProfile( } // static -scoped_refptr<history::TopSites> TopSitesFactory::GetForProfileIfExists( - Profile* profile) { - return static_cast<history::TopSites*>( - GetInstance()->GetServiceForBrowserContext(profile, false).get()); -} - -// static TopSitesFactory* TopSitesFactory::GetInstance() { return Singleton<TopSitesFactory>::get(); } @@ -99,7 +92,7 @@ scoped_refptr<history::TopSites> TopSitesFactory::BuildTopSites( profile, ServiceAccessType::EXPLICIT_ACCESS), prefs::kNtpMostVisitedURLsBlacklist, prepopulated_page_list, base::Bind(CanAddURLToHistory))); - top_sites->Init(context->GetPath().Append(chrome::kTopSitesFilename), + top_sites->Init(context->GetPath().Append(history::kTopSitesFilename), content::BrowserThread::GetMessageLoopProxyForThread( content::BrowserThread::DB)); return top_sites; diff --git a/chrome/browser/history/top_sites_factory.h b/chrome/browser/history/top_sites_factory.h index 3978dbe..0f1e811 100644 --- a/chrome/browser/history/top_sites_factory.h +++ b/chrome/browser/history/top_sites_factory.h @@ -29,11 +29,6 @@ class TopSitesFactory : public RefcountedBrowserContextKeyedServiceFactory { // Get the TopSites service for |profile|, creating one if needed. static scoped_refptr<history::TopSites> GetForProfile(Profile* profile); - // Get the TopSites service for |profile|, but do not create it if it doesn't - // exist. - static scoped_refptr<history::TopSites> GetForProfileIfExists( - Profile* profile); - // Get the singleton instance of the factory. static TopSitesFactory* GetInstance(); diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index c1404ce..424d27d 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -110,13 +110,6 @@ 'browser/history/android/sqlite_cursor_unittest.cc', 'browser/history/android/urls_sql_handler_unittest.cc', 'browser/history/android/visit_sql_handler_unittest.cc', - 'browser/history/expire_history_backend_unittest.cc', - 'browser/history/history_backend_unittest.cc', - 'browser/history/history_database_unittest.cc', - 'browser/history/history_querying_unittest.cc', - 'browser/history/thumbnail_database_unittest.cc', - 'browser/history/top_sites_impl_unittest.cc', - 'browser/history/typed_url_syncable_service_unittest.cc', 'browser/history/web_history_service_unittest.cc', 'browser/image_holder_unittest.cc', 'browser/install_verification/win/imported_module_verification_unittest.cc', diff --git a/chrome/common/chrome_constants.cc b/chrome/common/chrome_constants.cc index 7a92eee..db0d5cb 100644 --- a/chrome/common/chrome_constants.cc +++ b/chrome/common/chrome_constants.cc @@ -183,7 +183,6 @@ const base::FilePath::CharType kSupervisedUserSettingsFilename[] = const base::FilePath::CharType kSyncCredentialsFilename[] = FPL("Sync Credentials"); const base::FilePath::CharType kThemePackFilename[] = FPL("Cached Theme.pak"); -const base::FilePath::CharType kTopSitesFilename[] = FPL("Top Sites"); const base::FilePath::CharType kWebAppDirname[] = FPL("Web Applications"); // File name of the Pepper Flash plugin on different platforms. diff --git a/chrome/common/chrome_constants.h b/chrome/common/chrome_constants.h index 5bfbd71..85f09e7 100644 --- a/chrome/common/chrome_constants.h +++ b/chrome/common/chrome_constants.h @@ -94,8 +94,6 @@ extern const base::FilePath::CharType kSingletonSocketFilename[]; extern const base::FilePath::CharType kSupervisedUserSettingsFilename[]; extern const base::FilePath::CharType kSyncCredentialsFilename[]; extern const base::FilePath::CharType kThemePackFilename[]; -extern const base::FilePath::CharType kThumbnailsFilename[]; -extern const base::FilePath::CharType kTopSitesFilename[]; extern const base::FilePath::CharType kWebAppDirname[]; // File name of the Pepper Flash plugin on different platforms. diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc index aee4123..f544479a 100644 --- a/chrome/test/base/testing_profile.cc +++ b/chrome/test/base/testing_profile.cc @@ -27,7 +27,6 @@ #include "chrome/browser/history/chrome_history_client.h" #include "chrome/browser/history/chrome_history_client_factory.h" #include "chrome/browser/history/history_service_factory.h" -#include "chrome/browser/history/top_sites_factory.h" #include "chrome/browser/history/web_history_service_factory.h" #include "chrome/browser/net/pref_proxy_config_tracker.h" #include "chrome/browser/net/proxy_service_factory.h" @@ -64,9 +63,6 @@ #include "components/history/core/browser/history_database_params.h" #include "components/history/core/browser/history_db_task.h" #include "components/history/core/browser/history_service.h" -#include "components/history/core/browser/top_sites.h" -#include "components/history/core/browser/top_sites_impl.h" -#include "components/history/core/browser/top_sites_observer.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/keyed_service/core/refcounted_keyed_service.h" #include "components/policy/core/common/policy_service.h" @@ -126,35 +122,6 @@ using testing::Return; namespace { -// TopSitesImpl::Shutdown schedules some tasks (from TopSitesBackend) that -// need to be run to properly shutdown. Run all pending tasks now. This is -// normally handled by browser_process shutdown. - -void CleanupAfterTopSitesDestroyed() { - if (base::MessageLoop::current()) - base::MessageLoop::current()->RunUntilIdle(); -} - -// Returns true if a TopSites service has been registered for |profile|. -bool HasTopSites(Profile* profile) { - return !!TopSitesFactory::GetInstance()->GetForProfileIfExists(profile); -} - -// Used to make sure TopSites has finished loading -class WaitTopSitesLoadedObserver : public history::TopSitesObserver { - public: - explicit WaitTopSitesLoadedObserver(content::MessageLoopRunner* runner) - : runner_(runner) {} - void TopSitesLoaded(history::TopSites* top_sites) override { - runner_->Quit(); - } - void TopSitesChanged(history::TopSites* top_sites) override {} - - private: - // weak - content::MessageLoopRunner* runner_; -}; - // Task used to make sure history has finished processing a request. Intended // for use with BlockUntilHistoryProcessesPendingRequests. @@ -286,12 +253,6 @@ KeyedService* BuildWebDataService(content::BrowserContext* context) { &TestProfileErrorCallback); } -scoped_refptr<RefcountedKeyedService> BuildTopSites( - content::BrowserContext* context) { - return TopSitesFactory::BuildTopSites(context, - history::PrepopulatedPageList()); -} - } // namespace // static @@ -545,20 +506,11 @@ TestingProfile::~TestingProfile() { MaybeSendDestroyedNotification(); - // Remember whether a TopSites has been created for the current profile, - // so that we can run cleanup after destroying all services. - bool had_top_sites = HasTopSites(this); - browser_context_dependency_manager_->DestroyBrowserContextServices(this); if (host_content_settings_map_.get()) host_content_settings_map_->ShutdownOnUIThread(); - // Wait until TopSites shutdown tasks have completed if a TopSites has - // been created for the current profile. - if (had_top_sites) - CleanupAfterTopSitesDestroyed(); - if (pref_proxy_config_tracker_.get()) pref_proxy_config_tracker_->DetachFromPrefService(); // Failing a post == leaks == heapcheck failure. Make that an immediate test @@ -629,22 +581,6 @@ void TestingProfile::DestroyHistoryService() { base::MessageLoop::current()->Run(); } -void TestingProfile::CreateTopSites() { - DestroyTopSites(); - TopSitesFactory::GetInstance()->SetTestingFactoryAndUse(this, BuildTopSites); -} - -void TestingProfile::DestroyTopSites() { - TopSitesFactory* top_sites_factory = TopSitesFactory::GetInstance(); - if (top_sites_factory->GetForProfileIfExists(this)) { - // BrowserContextKeyedServiceFactory will destroy the previous service when - // registering a new testing factory so use this to ensure that destroy the - // old service. - top_sites_factory->SetTestingFactory(this, nullptr); - CleanupAfterTopSitesDestroyed(); - } -} - void TestingProfile::CreateBookmarkModel(bool delete_file) { if (delete_file) { base::FilePath path = GetPath().Append(bookmarks::kBookmarksFileName); @@ -682,18 +618,6 @@ void TestingProfile::BlockUntilHistoryIndexIsRefreshed() { DCHECK(index->restored()); } -// TODO(phajdan.jr): Doesn't this hang if Top Sites are already loaded? -void TestingProfile::BlockUntilTopSitesLoaded() { - scoped_refptr<content::MessageLoopRunner> runner = - new content::MessageLoopRunner; - WaitTopSitesLoadedObserver observer(runner.get()); - scoped_refptr<history::TopSites> top_sites = - TopSitesFactory::GetForProfile(this); - top_sites->AddObserver(&observer); - runner->Run(); - top_sites->RemoveObserver(&observer); -} - void TestingProfile::SetGuestSession(bool guest) { guest_session_ = guest; } diff --git a/chrome/test/base/testing_profile.h b/chrome/test/base/testing_profile.h index 322a7b0..2f367bb 100644 --- a/chrome/test/base/testing_profile.h +++ b/chrome/test/base/testing_profile.h @@ -22,10 +22,6 @@ class SSLHostStateDelegate; class ZoomLevelDelegate; } -namespace history { -class TopSites; -} - namespace net { class CookieMonster; class URLRequestContextGetter; @@ -180,13 +176,6 @@ class TestingProfile : public Profile { // Shuts down and nulls out the reference to HistoryService. void DestroyHistoryService(); - // Creates TopSites. This returns immediately, and top sites may not be - // loaded. Use BlockUntilTopSitesLoaded to ensure TopSites has finished - // loading. - void CreateTopSites(); - - void DestroyTopSites(); - // Creates the BookmarkBarModel. If not invoked the bookmark bar model is // NULL. If |delete_file| is true, the bookmarks file is deleted first, then // the model is created. As TestingProfile deletes the directory containing @@ -204,9 +193,6 @@ class TestingProfile : public Profile { // This is NOT invoked from CreateHistoryService. void BlockUntilHistoryIndexIsRefreshed(); - // Blocks until TopSites finishes loading. - void BlockUntilTopSitesLoaded(); - // Allow setting a profile as Guest after-the-fact to simplify some tests. void SetGuestSession(bool guest); diff --git a/chrome/test/data/History/ArchivedNoDuration b/chrome/test/data/History/ArchivedNoDuration Binary files differdeleted file mode 100644 index 5b51922..0000000 --- a/chrome/test/data/History/ArchivedNoDuration +++ /dev/null diff --git a/chrome/test/data/History/Favicons.v3.history.sql b/chrome/test/data/History/Favicons.v3.history.sql deleted file mode 100644 index 1c072d5..0000000 --- a/chrome/test/data/History/Favicons.v3.history.sql +++ /dev/null @@ -1,15 +0,0 @@ --- unit_tests --gtest_filter=ThumbnailDatabaseTest.Version3 --- --- .dump that portion of the History database needed to migrate a --- Favicons version 3 database. See also Favicons.v3.sql. -BEGIN TRANSACTION; -CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,value LONGVARCHAR); -INSERT INTO "meta" VALUES('version','20'); -INSERT INTO "meta" VALUES('last_compatible_version','16'); -CREATE TABLE urls(id INTEGER PRIMARY KEY,url LONGVARCHAR,title LONGVARCHAR,visit_count INTEGER DEFAULT 0 NOT NULL,typed_count INTEGER DEFAULT 0 NOT NULL,last_visit_time INTEGER NOT NULL,hidden INTEGER DEFAULT 0 NOT NULL,favicon_id INTEGER DEFAULT 0 NOT NULL); -INSERT INTO "urls" VALUES(1,'http://google.com/','Google',1,1,0,0,1); -INSERT INTO "urls" VALUES(2,'http://www.google.com/','Google',1,0,0,0,1); -INSERT INTO "urls" VALUES(3,'http://www.google.com/blank.html','',1,0,0,1,0); -INSERT INTO "urls" VALUES(4,'http://yahoo.com/','Yahoo!',1,1,0,0,2); -INSERT INTO "urls" VALUES(5,'http://www.yahoo.com/','Yahoo!',1,0,0,0,2); -COMMIT; diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 2ee73c9..2330cf3 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -221,13 +221,20 @@ 'guest_view/browser/guest_view_manager_unittest.cc' ], 'history_unittest_sources': [ - 'history/content/browser/content_history_backend_unittest.cc', + 'history/content/browser/content_history_backend_db_unittest.cc', 'history/core/browser/android/android_history_types_unittest.cc', + 'history/core/browser/expire_history_backend_unittest.cc', + 'history/core/browser/history_backend_db_unittest.cc', 'history/core/browser/history_backend_unittest.cc', + 'history/core/browser/history_database_unittest.cc', + 'history/core/browser/history_querying_unittest.cc', 'history/core/browser/history_service_unittest.cc', 'history/core/browser/history_types_unittest.cc', + 'history/core/browser/thumbnail_database_unittest.cc', 'history/core/browser/top_sites_cache_unittest.cc', 'history/core/browser/top_sites_database_unittest.cc', + 'history/core/browser/top_sites_impl_unittest.cc', + 'history/core/browser/typed_url_syncable_service_unittest.cc', 'history/core/browser/url_database_unittest.cc', 'history/core/browser/url_utils_unittest.cc', 'history/core/browser/visit_database_unittest.cc', diff --git a/components/history.gypi b/components/history.gypi index bd7a09f..9ecec9f 100644 --- a/components/history.gypi +++ b/components/history.gypi @@ -156,10 +156,12 @@ ], 'dependencies': [ '../base/base.gyp:base', + '../skia/skia.gyp:skia', '../sql/sql.gyp:sql', '../sql/sql.gyp:test_support_sql', '../sync/sync.gyp:sync', '../testing/gtest.gyp:gtest', + '../ui/gfx/gfx.gyp:gfx', '../url/url.gyp:url_lib', 'history_core_browser', ], @@ -167,14 +169,27 @@ # Note: sources list duplicated in GN build. 'history/core/test/database_test_utils.cc', 'history/core/test/database_test_utils.h', - 'history/core/test/history_backend_base_test.cc', - 'history/core/test/history_backend_base_test.h', + 'history/core/test/history_backend_db_base_test.cc', + 'history/core/test/history_backend_db_base_test.h', 'history/core/test/history_client_fake_bookmarks.cc', 'history/core/test/history_client_fake_bookmarks.h', 'history/core/test/history_unittest_base.cc', 'history/core/test/history_unittest_base.h', 'history/core/test/test_history_database.cc', 'history/core/test/test_history_database.h', + 'history/core/test/thumbnail-inl.h', + 'history/core/test/thumbnail.cc', + 'history/core/test/thumbnail.h', + 'history/core/test/thumbnail_ios.mm', + 'history/core/test/wait_top_sites_loaded_observer.cc', + 'history/core/test/wait_top_sites_loaded_observer.h', + ], + 'conditions': [ + ['OS=="ios"', { + 'sources!': [ + 'history/core/test/thumbnail.cc', + ], + }], ], }, ], diff --git a/components/history/content/browser/content_history_backend_unittest.cc b/components/history/content/browser/content_history_backend_db_unittest.cc index 8e3a2aa..306c289 100644 --- a/components/history/content/browser/content_history_backend_unittest.cc +++ b/components/history/content/browser/content_history_backend_db_unittest.cc @@ -19,14 +19,14 @@ #include "components/history/core/browser/history_backend.h" -#include "components/history/core/test/history_backend_base_test.h" +#include "components/history/core/test/history_backend_db_base_test.h" namespace history { namespace { // This must be outside the anonymous namespace for the friend statement in // HistoryBackend to work. -class ContentHistoryBackendDBTest : public HistoryBackendBaseTest { +class ContentHistoryBackendDBTest : public HistoryBackendDBBaseTest { public: ContentHistoryBackendDBTest() {} ~ContentHistoryBackendDBTest() override {} diff --git a/chrome/browser/history/expire_history_backend_unittest.cc b/components/history/core/browser/expire_history_backend_unittest.cc index 32a26d2..d976373 100644 --- a/chrome/browser/history/expire_history_backend_unittest.cc +++ b/components/history/core/browser/expire_history_backend_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "components/history/core/browser/expire_history_backend.h" + #include <algorithm> #include <string> #include <utility> @@ -11,54 +13,53 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/testing_pref_service.h" +#include "base/scoped_observer.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" -#include "chrome/browser/history/top_sites_factory.h" -#include "chrome/test/base/testing_profile.h" -#include "components/history/core/browser/expire_history_backend.h" #include "components/history/core/browser/history_backend_notifier.h" +#include "components/history/core/browser/history_constants.h" #include "components/history/core/browser/history_database.h" #include "components/history/core/browser/thumbnail_database.h" #include "components/history/core/browser/top_sites.h" +#include "components/history/core/browser/top_sites_impl.h" +#include "components/history/core/browser/top_sites_observer.h" #include "components/history/core/common/thumbnail_score.h" #include "components/history/core/test/history_client_fake_bookmarks.h" #include "components/history/core/test/test_history_database.h" -#include "components/history/core/test/thumbnail-inl.h" -#include "content/public/test/test_browser_thread.h" +#include "components/history/core/test/thumbnail.h" +#include "components/history/core/test/wait_top_sites_loaded_observer.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "ui/gfx/codec/jpeg_codec.h" - -using base::Time; -using base::TimeDelta; -using base::TimeTicks; -using content::BrowserThread; - -// Filename constants. -static const base::FilePath::CharType kHistoryFile[] = - FILE_PATH_LITERAL("History"); -static const base::FilePath::CharType kThumbnailFile[] = - FILE_PATH_LITERAL("Thumbnails"); // The test must be in the history namespace for the gtest forward declarations // to work. It also eliminates a bunch of ugly "history::". namespace history { +namespace { +// Key for URL blacklist. +const char kBlacklistURLKey[] = "test.blacklist.url"; + +// Returns whether |url| can be added to history. +bool MockCanAddURLToHistory(const GURL& url) { + return url.is_valid(); +} + +} // namespace + // ExpireHistoryTest ----------------------------------------------------------- -class ExpireHistoryTest : public testing::Test, - public HistoryBackendNotifier { +class ExpireHistoryTest : public testing::Test, public HistoryBackendNotifier { public: ExpireHistoryTest() - : ui_thread_(BrowserThread::UI, &message_loop_), - db_thread_(BrowserThread::DB, &message_loop_), - expirer_(this, &history_client_), - now_(Time::Now()) {} + : expirer_(this, &history_client_), now_(base::Time::Now()) {} protected: // Called by individual tests when they want data populated. - void AddExampleData(URLID url_ids[3], Time visit_times[4]); + void AddExampleData(URLID url_ids[3], base::Time visit_times[4]); // Add visits with source information. void AddExampleSourceData(const GURL& url, URLID* id); @@ -97,17 +98,16 @@ class ExpireHistoryTest : public testing::Test, HistoryClientFakeBookmarks history_client_; base::MessageLoopForUI message_loop_; - content::TestBrowserThread ui_thread_; - content::TestBrowserThread db_thread_; ExpireHistoryBackend expirer_; + scoped_ptr<TestingPrefServiceSimple> pref_service_; scoped_ptr<HistoryDatabase> main_db_; scoped_ptr<ThumbnailDatabase> thumb_db_; - TestingProfile profile_; + scoped_refptr<TopSitesImpl> top_sites_; - // Time at the beginning of the test, so everybody agrees what "now" is. - const Time now_; + // base::Time at the beginning of the test, so everybody agrees what "now" is. + const base::Time now_; typedef std::vector<URLRows> URLsModifiedNotificationList; URLsModifiedNotificationList urls_modified_notifications_; @@ -119,29 +119,45 @@ class ExpireHistoryTest : public testing::Test, void SetUp() override { ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); - base::FilePath history_name = path().Append(kHistoryFile); + base::FilePath history_name = path().Append(kHistoryFilename); main_db_.reset(new TestHistoryDatabase); if (main_db_->Init(history_name) != sql::INIT_OK) main_db_.reset(); - base::FilePath thumb_name = path().Append(kThumbnailFile); - thumb_db_.reset(new ThumbnailDatabase(NULL)); + base::FilePath thumb_name = path().Append(kThumbnailsFilename); + thumb_db_.reset(new ThumbnailDatabase(nullptr)); if (thumb_db_->Init(thumb_name) != sql::INIT_OK) thumb_db_.reset(); + pref_service_.reset(new TestingPrefServiceSimple); + pref_service_->registry()->RegisterDictionaryPref(kBlacklistURLKey); + expirer_.SetDatabases(main_db_.get(), thumb_db_.get()); - profile_.CreateTopSites(); - profile_.BlockUntilTopSitesLoaded(); + + top_sites_ = new TopSitesImpl(pref_service_.get(), nullptr, + kBlacklistURLKey, PrepopulatedPageList(), + base::Bind(MockCanAddURLToHistory)); + WaitTopSitesLoadedObserver wait_top_sites_observer(top_sites_); + top_sites_->Init(path().Append(kTopSitesFilename), + message_loop_.task_runner()); + wait_top_sites_observer.Run(); } void TearDown() override { - ClearLastNotifications(); - expirer_.SetDatabases(NULL, NULL); + expirer_.SetDatabases(nullptr, nullptr); main_db_.reset(); thumb_db_.reset(); + + top_sites_->ShutdownOnUIThread(); + top_sites_ = nullptr; + + if (base::MessageLoop::current()) + base::MessageLoop::current()->RunUntilIdle(); + + pref_service_.reset(); } // HistoryBackendNotifier: @@ -168,20 +184,21 @@ class ExpireHistoryTest : public testing::Test, // (with the one in the middle) when it picks the proper threshold time. // // Each visit has indexed data, each URL has thumbnail. The first two URLs will -// share the same avicon, while the last one will have a unique favicon. The +// share the same favicon, while the last one will have a unique favicon. The // second visit for the middle URL is typed. // // The IDs of the added URLs, and the times of the four added visits will be // added to the given arrays. -void ExpireHistoryTest::AddExampleData(URLID url_ids[3], Time visit_times[4]) { +void ExpireHistoryTest::AddExampleData(URLID url_ids[3], + base::Time visit_times[4]) { if (!main_db_.get()) return; // Four times for each visit. - visit_times[3] = Time::Now(); - visit_times[2] = visit_times[3] - TimeDelta::FromDays(1); - visit_times[1] = visit_times[3] - TimeDelta::FromDays(2); - visit_times[0] = visit_times[3] - TimeDelta::FromDays(3); + visit_times[3] = base::Time::Now(); + visit_times[2] = visit_times[3] - base::TimeDelta::FromDays(1); + visit_times[1] = visit_times[3] - base::TimeDelta::FromDays(2); + visit_times[0] = visit_times[3] - base::TimeDelta::FromDays(3); // Two favicons. The first two URLs will share the same one, while the last // one will have a unique favicon. @@ -210,19 +227,15 @@ void ExpireHistoryTest::AddExampleData(URLID url_ids[3], Time visit_times[4]) { url_ids[2] = main_db_->AddURL(url_row3); thumb_db_->AddIconMapping(url_row3.url(), favicon2); - // Thumbnails for each URL. |thumbnail| takes ownership of decoded SkBitmap. - scoped_ptr<SkBitmap> thumbnail_bitmap( - gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail))); - gfx::Image thumbnail = gfx::Image::CreateFrom1xBitmap(*thumbnail_bitmap); - ThumbnailScore score(0.25, true, true, Time::Now()); + // Thumbnails for each URL. + gfx::Image thumbnail = CreateGoogleThumbnailForTest(); + ThumbnailScore score(0.25, true, true, base::Time::Now()); - Time time; + base::Time time; GURL gurl; - scoped_refptr<history::TopSites> top_sites = - TopSitesFactory::GetForProfile(&profile_); - top_sites->SetPageThumbnail(url_row1.url(), thumbnail, score); - top_sites->SetPageThumbnail(url_row2.url(), thumbnail, score); - top_sites->SetPageThumbnail(url_row3.url(), thumbnail, score); + top_sites_->SetPageThumbnail(url_row1.url(), thumbnail, score); + top_sites_->SetPageThumbnail(url_row2.url(), thumbnail, score); + top_sites_->SetPageThumbnail(url_row3.url(), thumbnail, score); // Four visits. VisitRow visit_row1; @@ -251,7 +264,7 @@ void ExpireHistoryTest::AddExampleSourceData(const GURL& url, URLID* id) { if (!main_db_) return; - Time last_visit_time = Time::Now(); + base::Time last_visit_time = base::Time::Now(); // Add one URL. URLRow url_row1(url); url_row1.set_last_visit(last_visit_time); @@ -260,27 +273,26 @@ void ExpireHistoryTest::AddExampleSourceData(const GURL& url, URLID* id) { *id = url_id; // Four times for each visit. - VisitRow visit_row1(url_id, last_visit_time - TimeDelta::FromDays(4), 0, + VisitRow visit_row1(url_id, last_visit_time - base::TimeDelta::FromDays(4), 0, ui::PAGE_TRANSITION_TYPED, 0); main_db_->AddVisit(&visit_row1, SOURCE_SYNCED); - VisitRow visit_row2(url_id, last_visit_time - TimeDelta::FromDays(3), 0, + VisitRow visit_row2(url_id, last_visit_time - base::TimeDelta::FromDays(3), 0, ui::PAGE_TRANSITION_TYPED, 0); main_db_->AddVisit(&visit_row2, SOURCE_BROWSED); - VisitRow visit_row3(url_id, last_visit_time - TimeDelta::FromDays(2), 0, + VisitRow visit_row3(url_id, last_visit_time - base::TimeDelta::FromDays(2), 0, ui::PAGE_TRANSITION_TYPED, 0); main_db_->AddVisit(&visit_row3, SOURCE_EXTENSION); - VisitRow visit_row4( - url_id, last_visit_time, 0, ui::PAGE_TRANSITION_TYPED, 0); + VisitRow visit_row4(url_id, last_visit_time, 0, ui::PAGE_TRANSITION_TYPED, 0); main_db_->AddVisit(&visit_row4, SOURCE_FIREFOX_IMPORTED); } bool ExpireHistoryTest::HasFavicon(favicon_base::FaviconID favicon_id) { if (!thumb_db_.get() || favicon_id == 0) return false; - return thumb_db_->GetFaviconHeader(favicon_id, NULL, NULL); + return thumb_db_->GetFaviconHeader(favicon_id, nullptr, nullptr); } favicon_base::FaviconID ExpireHistoryTest::GetFavicon( @@ -302,9 +314,7 @@ bool ExpireHistoryTest::HasThumbnail(URLID url_id) { return false; GURL url = info.url(); scoped_refptr<base::RefCountedMemory> data; - scoped_refptr<history::TopSites> top_sites = - TopSitesFactory::GetForProfile(&profile_); - return top_sites->GetPageThumbnail(url, false, &data); + return top_sites_->GetPageThumbnail(url, false, &data); } void ExpireHistoryTest::EnsureURLInfoGone(const URLRow& row, bool expired) { @@ -340,8 +350,7 @@ void ExpireHistoryTest::EnsureURLInfoGone(const URLRow& row, bool expired) { } } for (const auto& rows : urls_modified_notifications_) { - EXPECT_TRUE(std::find_if(rows.begin(), - rows.end(), + EXPECT_TRUE(std::find_if(rows.begin(), rows.end(), history::URLRow::URLRowHasURL(row.url())) == rows.end()); } @@ -350,8 +359,7 @@ void ExpireHistoryTest::EnsureURLInfoGone(const URLRow& row, bool expired) { bool ExpireHistoryTest::ModifiedNotificationSent(const GURL& url) { for (const auto& rows : urls_modified_notifications_) { - if (std::find_if(rows.begin(), - rows.end(), + if (std::find_if(rows.begin(), rows.end(), history::URLRow::URLRowHasURL(url)) != rows.end()) return true; } @@ -410,7 +418,7 @@ bool ExpireHistoryTest::IsStringInFile(const base::FilePath& filename, // Fails near end of month. http://crbug.com/43586 TEST_F(ExpireHistoryTest, DISABLED_DeleteURLAndFavicon) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); // Verify things are the way we expect with a URL row, favicon, thumbnail. @@ -439,7 +447,7 @@ TEST_F(ExpireHistoryTest, DISABLED_DeleteURLAndFavicon) { // should not get deleted. This also tests deleting more than one visit. TEST_F(ExpireHistoryTest, DeleteURLWithoutFavicon) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); // Verify things are the way we expect with a URL row, favicon, thumbnail. @@ -467,7 +475,7 @@ TEST_F(ExpireHistoryTest, DeleteURLWithoutFavicon) { // remain starred and its favicon should remain too. TEST_F(ExpireHistoryTest, DeleteStarredVisitedURL) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); URLRow url_row; @@ -485,8 +493,7 @@ TEST_F(ExpireHistoryTest, DeleteStarredVisitedURL) { EnsureURLInfoGone(url_row, false); // Yet the favicon should exist. - favicon_base::FaviconID favicon_id = - GetFavicon(url, favicon_base::FAVICON); + favicon_base::FaviconID favicon_id = GetFavicon(url, favicon_base::FAVICON); EXPECT_TRUE(HasFavicon(favicon_id)); // Should still have the thumbnail. @@ -523,7 +530,7 @@ TEST_F(ExpireHistoryTest, DeleteStarredUnvisitedURL) { // not the first two should be deleted. TEST_F(ExpireHistoryTest, DeleteURLs) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); // Verify things are the way we expect with URL rows, favicons, @@ -560,7 +567,7 @@ TEST_F(ExpireHistoryTest, DeleteURLs) { // the two visits) and one is deleted. TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); URLRow url_row1, url_row2; @@ -573,7 +580,7 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) { // This should delete the last two visits. std::set<GURL> restrict_urls; - expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); + expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], base::Time()); // Verify that the middle URL had its last visit deleted only. visits.clear(); @@ -608,7 +615,7 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) { // Expires all URLs with times in a given set. TEST_F(ExpireHistoryTest, FlushURLsForTimes) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); URLRow url_row1, url_row2; @@ -660,7 +667,7 @@ TEST_F(ExpireHistoryTest, FlushURLsForTimes) { // one of the two visits). TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); URLRow url_row1, url_row2; @@ -674,7 +681,7 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted) { // This should delete the last two visits. std::set<GURL> restrict_urls; restrict_urls.insert(url_row1.url()); - expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); + expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], base::Time()); // Verify that the middle URL had its last visit deleted only. visits.clear(); @@ -709,7 +716,7 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted) { // Expire a starred URL, it shouldn't get deleted TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); URLRow url_row1, url_row2; @@ -722,7 +729,7 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) { // This should delete the last two visits. std::set<GURL> restrict_urls; - expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); + expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], base::Time()); // The URL rows should still exist. URLRow new_url_row1, new_url_row2; @@ -758,7 +765,7 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) { TEST_F(ExpireHistoryTest, ExpireHistoryBeforeUnstarred) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); URLRow url_row0, url_row1, url_row2; @@ -794,7 +801,7 @@ TEST_F(ExpireHistoryTest, ExpireHistoryBeforeUnstarred) { TEST_F(ExpireHistoryTest, ExpireHistoryBeforeStarred) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); URLRow url_row0, url_row1; @@ -834,13 +841,13 @@ TEST_F(ExpireHistoryTest, ExpireHistoryBeforeStarred) { // tests which use this function internally. TEST_F(ExpireHistoryTest, ExpireSomeOldHistory) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); const ExpiringVisitsReader* reader = expirer_.GetAllVisitsReader(); // Deleting a time range with no URLs should return false (nothing found). EXPECT_FALSE(expirer_.ExpireSomeOldHistory( - visit_times[0] - TimeDelta::FromDays(100), reader, 1)); + visit_times[0] - base::TimeDelta::FromDays(100), reader, 1)); // Deleting a time range with not up the the max results should also return // false (there will only be one visit deleted in this range). @@ -853,7 +860,7 @@ TEST_F(ExpireHistoryTest, ExpireSomeOldHistory) { TEST_F(ExpireHistoryTest, ExpiringVisitsReader) { URLID url_ids[3]; - Time visit_times[4]; + base::Time visit_times[4]; AddExampleData(url_ids, visit_times); const ExpiringVisitsReader* all = expirer_.GetAllVisitsReader(); @@ -861,12 +868,12 @@ TEST_F(ExpireHistoryTest, ExpiringVisitsReader) { expirer_.GetAutoSubframeVisitsReader(); VisitVector visits; - Time now = Time::Now(); + base::Time now = base::Time::Now(); // Verify that the early expiration threshold, stored in the meta table is // initialized. EXPECT_TRUE(main_db_->GetEarlyExpirationThreshold() == - Time::FromInternalValue(1L)); + base::Time::FromInternalValue(1L)); // First, attempt reading AUTO_SUBFRAME visits. We should get none. EXPECT_FALSE(auto_subframes->Read(now, main_db_.get(), &visits, 1)); diff --git a/components/history/core/browser/history_backend.h b/components/history/core/browser/history_backend.h index 6332e30..476008d 100644 --- a/components/history/core/browser/history_backend.h +++ b/components/history/core/browser/history_backend.h @@ -42,8 +42,9 @@ class SingleThreadTaskRunner; namespace history { class CommitLaterTask; struct DownloadRow; -class HistoryBackendBaseTest; +class HistoryBackendDBBaseTest; class HistoryBackendObserver; +class HistoryBackendTest; class HistoryClient; class HistoryDatabase; struct HistoryDatabaseParams; @@ -451,8 +452,8 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, friend class base::RefCountedThreadSafe<HistoryBackend>; friend class CommitLaterTask; // The commit task needs to call Commit(). friend class HistoryBackendTest; - friend class HistoryBackendBaseTest; // So the unit tests can poke our - // innards. + friend class HistoryBackendDBBaseTest; // So the unit tests can poke our + // innards. FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, DeleteAll); FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, DeleteAllThenAddData); FRIEND_TEST_ALL_PREFIXES(HistoryBackendTest, AddPagesWithDetails); diff --git a/components/history/core/browser/history_backend_db_unittest.cc b/components/history/core/browser/history_backend_db_unittest.cc new file mode 100644 index 0000000..1f7c5ae --- /dev/null +++ b/components/history/core/browser/history_backend_db_unittest.cc @@ -0,0 +1,760 @@ +// Copyright (c) 2012 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. + +// History unit tests come in two flavors: +// +// 1. The more complicated style is that the unit test creates a full history +// service. This spawns a background thread for the history backend, and +// all communication is asynchronous. This is useful for testing more +// complicated things or end-to-end behavior. +// +// 2. The simpler style is to create a history backend on this thread and +// access it directly without a HistoryService object. This is much simpler +// because communication is synchronous. Generally, sets should go through +// the history backend (since there is a lot of logic) but gets can come +// directly from the HistoryDatabase. This is because the backend generally +// has no logic in the getter except threading stuff, which we don't want +// to run. + +#include "components/history/core/browser/history_backend.h" + +#include "base/strings/utf_string_conversions.h" +#include "components/history/core/browser/download_constants.h" +#include "components/history/core/browser/download_row.h" +#include "components/history/core/browser/history_constants.h" +#include "components/history/core/browser/history_database.h" +#include "components/history/core/browser/page_usage_data.h" +#include "components/history/core/test/history_backend_db_base_test.h" +#include "components/history/core/test/test_history_database.h" + +namespace history { +namespace { + +// This must be outside the anonymous namespace for the friend statement in +// HistoryBackend to work. +class HistoryBackendDBTest : public HistoryBackendDBBaseTest { + public: + HistoryBackendDBTest() {} + ~HistoryBackendDBTest() override {} +}; + +TEST_F(HistoryBackendDBTest, ClearBrowsingData_Downloads) { + CreateBackendAndDatabase(); + + // Initially there should be nothing in the downloads database. + std::vector<DownloadRow> downloads; + db_->QueryDownloads(&downloads); + EXPECT_EQ(0U, downloads.size()); + + // Add a download, test that it was added correctly, remove it, test that it + // was removed. + base::Time now = base::Time(); + uint32 id = 1; + EXPECT_TRUE(AddDownload(id, DownloadState::COMPLETE, base::Time())); + db_->QueryDownloads(&downloads); + EXPECT_EQ(1U, downloads.size()); + + EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("current-path")), + downloads[0].current_path); + EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("target-path")), + downloads[0].target_path); + EXPECT_EQ(1UL, downloads[0].url_chain.size()); + EXPECT_EQ(GURL("foo-url"), downloads[0].url_chain[0]); + EXPECT_EQ(std::string("http://referrer.com/"), + std::string(downloads[0].referrer_url.spec())); + EXPECT_EQ(now, downloads[0].start_time); + EXPECT_EQ(now, downloads[0].end_time); + EXPECT_EQ(0, downloads[0].received_bytes); + EXPECT_EQ(512, downloads[0].total_bytes); + EXPECT_EQ(DownloadState::COMPLETE, downloads[0].state); + EXPECT_EQ(DownloadDangerType::NOT_DANGEROUS, downloads[0].danger_type); + EXPECT_EQ(kTestDownloadInterruptReasonNone, downloads[0].interrupt_reason); + EXPECT_FALSE(downloads[0].opened); + EXPECT_EQ("by_ext_id", downloads[0].by_ext_id); + EXPECT_EQ("by_ext_name", downloads[0].by_ext_name); + EXPECT_EQ("application/vnd.oasis.opendocument.text", downloads[0].mime_type); + EXPECT_EQ("application/octet-stream", downloads[0].original_mime_type); + + db_->QueryDownloads(&downloads); + EXPECT_EQ(1U, downloads.size()); + db_->RemoveDownload(id); + db_->QueryDownloads(&downloads); + EXPECT_EQ(0U, downloads.size()); +} + +TEST_F(HistoryBackendDBTest, MigrateDownloadsState) { + // Create the db we want. + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); + { + // Open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + + // Manually insert corrupted rows; there's infrastructure in place now to + // make this impossible, at least according to the test above. + for (int state = 0; state < 5; ++state) { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads (id, full_path, url, start_time, " + "received_bytes, total_bytes, state, end_time, opened) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?)")); + s.BindInt64(0, 1 + state); + s.BindString(1, "path"); + s.BindString(2, "url"); + s.BindInt64(3, base::Time::Now().ToTimeT()); + s.BindInt64(4, 100); + s.BindInt64(5, 100); + s.BindInt(6, state); + s.BindInt64(7, base::Time::Now().ToTimeT()); + s.BindInt(8, state % 2); + ASSERT_TRUE(s.Run()); + } + } + + // Re-open the db using the HistoryDatabase, which should migrate from version + // 22 to the current version, fixing just the row whose state was 3. + // Then close the db so that we can re-open it directly. + CreateBackendAndDatabase(); + DeleteBackend(); + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + { + // The version should have been updated. + int cur_version = HistoryDatabase::GetCurrentVersion(); + ASSERT_LT(22, cur_version); + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(cur_version, s.ColumnInt(0)); + } + { + sql::Statement statement(db.GetUniqueStatement( + "SELECT id, state, opened " + "FROM downloads " + "ORDER BY id")); + int counter = 0; + while (statement.Step()) { + EXPECT_EQ(1 + counter, statement.ColumnInt64(0)); + // The only thing that migration should have changed was state from 3 to + // 4. + EXPECT_EQ(((counter == 3) ? 4 : counter), statement.ColumnInt(1)); + EXPECT_EQ(counter % 2, statement.ColumnInt(2)); + ++counter; + } + EXPECT_EQ(5, counter); + } + } +} + +TEST_F(HistoryBackendDBTest, MigrateDownloadsReasonPathsAndDangerType) { + base::Time now(base::Time::Now()); + + // Create the db we want. The schema didn't change from 22->23, so just + // re-use the v22 file. + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + + // Manually insert some rows. + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads (id, full_path, url, start_time, " + "received_bytes, total_bytes, state, end_time, opened) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?)")); + + int64 id = 0; + // Null path. + s.BindInt64(0, ++id); + s.BindString(1, std::string()); + s.BindString(2, "http://whatever.com/index.html"); + s.BindInt64(3, now.ToTimeT()); + s.BindInt64(4, 100); + s.BindInt64(5, 100); + s.BindInt(6, 1); + s.BindInt64(7, now.ToTimeT()); + s.BindInt(8, 1); + ASSERT_TRUE(s.Run()); + s.Reset(true); + + // Non-null path. + s.BindInt64(0, ++id); + s.BindString(1, "/path/to/some/file"); + s.BindString(2, "http://whatever.com/index1.html"); + s.BindInt64(3, now.ToTimeT()); + s.BindInt64(4, 100); + s.BindInt64(5, 100); + s.BindInt(6, 1); + s.BindInt64(7, now.ToTimeT()); + s.BindInt(8, 1); + ASSERT_TRUE(s.Run()); + } + + // Re-open the db using the HistoryDatabase, which should migrate from version + // 23 to 24, creating the new tables and creating the new path, reason, + // and danger columns. + CreateBackendAndDatabase(); + DeleteBackend(); + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + { + // The version should have been updated. + int cur_version = HistoryDatabase::GetCurrentVersion(); + ASSERT_LT(23, cur_version); + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(cur_version, s.ColumnInt(0)); + } + { + base::Time nowish(base::Time::FromTimeT(now.ToTimeT())); + + // Confirm downloads table is valid. + sql::Statement statement(db.GetUniqueStatement( + "SELECT id, interrupt_reason, current_path, target_path, " + " danger_type, start_time, end_time " + "FROM downloads ORDER BY id")); + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(1, statement.ColumnInt64(0)); + EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone), + statement.ColumnInt(1)); + EXPECT_EQ("", statement.ColumnString(2)); + EXPECT_EQ("", statement.ColumnString(3)); + // Implicit dependence on value of kDangerTypeNotDangerous from + // download_database.cc. + EXPECT_EQ(0, statement.ColumnInt(4)); + EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5)); + EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6)); + + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(2, statement.ColumnInt64(0)); + EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone), + statement.ColumnInt(1)); + EXPECT_EQ("/path/to/some/file", statement.ColumnString(2)); + EXPECT_EQ("/path/to/some/file", statement.ColumnString(3)); + EXPECT_EQ(0, statement.ColumnInt(4)); + EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5)); + EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6)); + + EXPECT_FALSE(statement.Step()); + } + { + // Confirm downloads_url_chains table is valid. + sql::Statement statement(db.GetUniqueStatement( + "SELECT id, chain_index, url FROM downloads_url_chains " + " ORDER BY id, chain_index")); + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(1, statement.ColumnInt64(0)); + EXPECT_EQ(0, statement.ColumnInt(1)); + EXPECT_EQ("http://whatever.com/index.html", statement.ColumnString(2)); + + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(2, statement.ColumnInt64(0)); + EXPECT_EQ(0, statement.ColumnInt(1)); + EXPECT_EQ("http://whatever.com/index1.html", statement.ColumnString(2)); + + EXPECT_FALSE(statement.Step()); + } + } +} + +TEST_F(HistoryBackendDBTest, MigrateReferrer) { + base::Time now(base::Time::Now()); + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads (id, full_path, url, start_time, " + "received_bytes, total_bytes, state, end_time, opened) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?)")); + int64 db_handle = 0; + s.BindInt64(0, ++db_handle); + s.BindString(1, "full_path"); + s.BindString(2, "http://whatever.com/index.html"); + s.BindInt64(3, now.ToTimeT()); + s.BindInt64(4, 100); + s.BindInt64(5, 100); + s.BindInt(6, 1); + s.BindInt64(7, now.ToTimeT()); + s.BindInt(8, 1); + ASSERT_TRUE(s.Run()); + } + // Re-open the db using the HistoryDatabase, which should migrate to version + // 26, creating the referrer column. + CreateBackendAndDatabase(); + DeleteBackend(); + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + // The version should have been updated. + int cur_version = HistoryDatabase::GetCurrentVersion(); + ASSERT_LE(26, cur_version); + { + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(cur_version, s.ColumnInt(0)); + } + { + sql::Statement s(db.GetUniqueStatement( + "SELECT referrer from downloads")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(std::string(), s.ColumnString(0)); + } + } +} + +TEST_F(HistoryBackendDBTest, MigrateDownloadedByExtension) { + base::Time now(base::Time::Now()); + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(26)); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads (id, current_path, target_path, start_time, " + "received_bytes, total_bytes, state, danger_type, interrupt_reason, " + "end_time, opened, referrer) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); + s.BindInt64(0, 1); + s.BindString(1, "current_path"); + s.BindString(2, "target_path"); + s.BindInt64(3, now.ToTimeT()); + s.BindInt64(4, 100); + s.BindInt64(5, 100); + s.BindInt(6, 1); + s.BindInt(7, 0); + s.BindInt(8, 0); + s.BindInt64(9, now.ToTimeT()); + s.BindInt(10, 1); + s.BindString(11, "referrer"); + ASSERT_TRUE(s.Run()); + } + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads_url_chains (id, chain_index, url) VALUES " + "(?, ?, ?)")); + s.BindInt64(0, 4); + s.BindInt64(1, 0); + s.BindString(2, "url"); + ASSERT_TRUE(s.Run()); + } + } + // Re-open the db using the HistoryDatabase, which should migrate to version + // 27, creating the by_ext_id and by_ext_name columns. + CreateBackendAndDatabase(); + DeleteBackend(); + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + // The version should have been updated. + int cur_version = HistoryDatabase::GetCurrentVersion(); + ASSERT_LE(27, cur_version); + { + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(cur_version, s.ColumnInt(0)); + } + { + sql::Statement s(db.GetUniqueStatement( + "SELECT by_ext_id, by_ext_name from downloads")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(std::string(), s.ColumnString(0)); + EXPECT_EQ(std::string(), s.ColumnString(1)); + } + } +} + +TEST_F(HistoryBackendDBTest, MigrateDownloadValidators) { + base::Time now(base::Time::Now()); + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(27)); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads (id, current_path, target_path, start_time, " + "received_bytes, total_bytes, state, danger_type, interrupt_reason, " + "end_time, opened, referrer, by_ext_id, by_ext_name) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); + s.BindInt64(0, 1); + s.BindString(1, "current_path"); + s.BindString(2, "target_path"); + s.BindInt64(3, now.ToTimeT()); + s.BindInt64(4, 100); + s.BindInt64(5, 100); + s.BindInt(6, 1); + s.BindInt(7, 0); + s.BindInt(8, 0); + s.BindInt64(9, now.ToTimeT()); + s.BindInt(10, 1); + s.BindString(11, "referrer"); + s.BindString(12, "by extension ID"); + s.BindString(13, "by extension name"); + ASSERT_TRUE(s.Run()); + } + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads_url_chains (id, chain_index, url) VALUES " + "(?, ?, ?)")); + s.BindInt64(0, 4); + s.BindInt64(1, 0); + s.BindString(2, "url"); + ASSERT_TRUE(s.Run()); + } + } + // Re-open the db using the HistoryDatabase, which should migrate to the + // current version, creating the etag and last_modified columns. + CreateBackendAndDatabase(); + DeleteBackend(); + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + // The version should have been updated. + int cur_version = HistoryDatabase::GetCurrentVersion(); + ASSERT_LE(28, cur_version); + { + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(cur_version, s.ColumnInt(0)); + } + { + sql::Statement s(db.GetUniqueStatement( + "SELECT etag, last_modified from downloads")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(std::string(), s.ColumnString(0)); + EXPECT_EQ(std::string(), s.ColumnString(1)); + } + } +} + +TEST_F(HistoryBackendDBTest, PurgeArchivedDatabase) { + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(27)); + ASSERT_NO_FATAL_FAILURE(CreateArchivedDB()); + + ASSERT_TRUE(base::PathExists(history_dir_.Append(kArchivedHistoryFilename))); + + CreateBackendAndDatabase(); + DeleteBackend(); + + // We do not retain expired history entries in an archived database as of M37. + // Verify that any legacy archived database is deleted on start-up. + ASSERT_FALSE(base::PathExists(history_dir_.Append(kArchivedHistoryFilename))); +} + +TEST_F(HistoryBackendDBTest, MigrateDownloadMimeType) { + base::Time now(base::Time::Now()); + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(28)); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads (id, current_path, target_path, start_time, " + "received_bytes, total_bytes, state, danger_type, interrupt_reason, " + "end_time, opened, referrer, by_ext_id, by_ext_name, etag, " + "last_modified) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); + s.BindInt64(0, 1); + s.BindString(1, "current_path"); + s.BindString(2, "target_path"); + s.BindInt64(3, now.ToTimeT()); + s.BindInt64(4, 100); + s.BindInt64(5, 100); + s.BindInt(6, 1); + s.BindInt(7, 0); + s.BindInt(8, 0); + s.BindInt64(9, now.ToTimeT()); + s.BindInt(10, 1); + s.BindString(11, "referrer"); + s.BindString(12, "by extension ID"); + s.BindString(13, "by extension name"); + s.BindString(14, "etag"); + s.BindInt64(15, now.ToTimeT()); + ASSERT_TRUE(s.Run()); + } + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO downloads_url_chains (id, chain_index, url) VALUES " + "(?, ?, ?)")); + s.BindInt64(0, 4); + s.BindInt64(1, 0); + s.BindString(2, "url"); + ASSERT_TRUE(s.Run()); + } + } + // Re-open the db using the HistoryDatabase, which should migrate to the + // current version, creating themime_type abd original_mime_type columns. + CreateBackendAndDatabase(); + DeleteBackend(); + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + // The version should have been updated. + int cur_version = HistoryDatabase::GetCurrentVersion(); + ASSERT_LE(29, cur_version); + { + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(cur_version, s.ColumnInt(0)); + } + { + sql::Statement s(db.GetUniqueStatement( + "SELECT mime_type, original_mime_type from downloads")); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(std::string(), s.ColumnString(0)); + EXPECT_EQ(std::string(), s.ColumnString(1)); + } + } +} + +TEST_F(HistoryBackendDBTest, ConfirmDownloadRowCreateAndDelete) { + // Create the DB. + CreateBackendAndDatabase(); + + base::Time now(base::Time::Now()); + + // Add some downloads. + uint32 id1 = 1, id2 = 2, id3 = 3; + AddDownload(id1, DownloadState::COMPLETE, now); + AddDownload(id2, DownloadState::COMPLETE, now + base::TimeDelta::FromDays(2)); + AddDownload(id3, DownloadState::COMPLETE, now - base::TimeDelta::FromDays(2)); + + // Confirm that resulted in the correct number of rows in the DB. + DeleteBackend(); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + sql::Statement statement(db.GetUniqueStatement( + "Select Count(*) from downloads")); + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(3, statement.ColumnInt(0)); + + sql::Statement statement1(db.GetUniqueStatement( + "Select Count(*) from downloads_url_chains")); + EXPECT_TRUE(statement1.Step()); + EXPECT_EQ(3, statement1.ColumnInt(0)); + } + + // Delete some rows and make sure the results are still correct. + CreateBackendAndDatabase(); + db_->RemoveDownload(id2); + db_->RemoveDownload(id3); + DeleteBackend(); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + sql::Statement statement(db.GetUniqueStatement( + "Select Count(*) from downloads")); + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(1, statement.ColumnInt(0)); + + sql::Statement statement1(db.GetUniqueStatement( + "Select Count(*) from downloads_url_chains")); + EXPECT_TRUE(statement1.Step()); + EXPECT_EQ(1, statement1.ColumnInt(0)); + } +} + +TEST_F(HistoryBackendDBTest, DownloadNukeRecordsMissingURLs) { + CreateBackendAndDatabase(); + base::Time now(base::Time::Now()); + std::vector<GURL> url_chain; + DownloadRow download(base::FilePath(FILE_PATH_LITERAL("foo-path")), + base::FilePath(FILE_PATH_LITERAL("foo-path")), + url_chain, + GURL(std::string()), + "application/octet-stream", + "application/octet-stream", + now, + now, + std::string(), + std::string(), + 0, + 512, + DownloadState::COMPLETE, + DownloadDangerType::NOT_DANGEROUS, + kTestDownloadInterruptReasonNone, + 1, + 0, + "by_ext_id", + "by_ext_name"); + + // Creating records without any urls should fail. + EXPECT_FALSE(db_->CreateDownload(download)); + + download.url_chain.push_back(GURL("foo-url")); + EXPECT_TRUE(db_->CreateDownload(download)); + + // Pretend that the URLs were dropped. + DeleteBackend(); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + sql::Statement statement(db.GetUniqueStatement( + "DELETE FROM downloads_url_chains WHERE id=1")); + ASSERT_TRUE(statement.Run()); + } + CreateBackendAndDatabase(); + std::vector<DownloadRow> downloads; + db_->QueryDownloads(&downloads); + EXPECT_EQ(0U, downloads.size()); + + // QueryDownloads should have nuked the corrupt record. + DeleteBackend(); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + { + sql::Statement statement(db.GetUniqueStatement( + "SELECT count(*) from downloads")); + ASSERT_TRUE(statement.Step()); + EXPECT_EQ(0, statement.ColumnInt(0)); + } + } +} + +TEST_F(HistoryBackendDBTest, ConfirmDownloadInProgressCleanup) { + // Create the DB. + CreateBackendAndDatabase(); + + base::Time now(base::Time::Now()); + + // Put an IN_PROGRESS download in the DB. + AddDownload(1, DownloadState::IN_PROGRESS, now); + + // Confirm that they made it into the DB unchanged. + DeleteBackend(); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + sql::Statement statement(db.GetUniqueStatement( + "Select Count(*) from downloads")); + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(1, statement.ColumnInt(0)); + + sql::Statement statement1(db.GetUniqueStatement( + "Select state, interrupt_reason from downloads")); + EXPECT_TRUE(statement1.Step()); + EXPECT_EQ(DownloadStateToInt(DownloadState::IN_PROGRESS), + statement1.ColumnInt(0)); + EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone), + statement1.ColumnInt(1)); + EXPECT_FALSE(statement1.Step()); + } + + // Read in the DB through query downloads, then test that the + // right transformation was returned. + CreateBackendAndDatabase(); + std::vector<DownloadRow> results; + db_->QueryDownloads(&results); + ASSERT_EQ(1u, results.size()); + EXPECT_EQ(DownloadState::INTERRUPTED, results[0].state); + EXPECT_EQ(kTestDownloadInterruptReasonCrash, results[0].interrupt_reason); + + // Allow the update to propagate, shut down the DB, and confirm that + // the query updated the on disk database as well. + base::MessageLoop::current()->RunUntilIdle(); + DeleteBackend(); + { + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + sql::Statement statement(db.GetUniqueStatement( + "Select Count(*) from downloads")); + EXPECT_TRUE(statement.Step()); + EXPECT_EQ(1, statement.ColumnInt(0)); + + sql::Statement statement1(db.GetUniqueStatement( + "Select state, interrupt_reason from downloads")); + EXPECT_TRUE(statement1.Step()); + EXPECT_EQ(DownloadStateToInt(DownloadState::INTERRUPTED), + statement1.ColumnInt(0)); + EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonCrash), + statement1.ColumnInt(1)); + EXPECT_FALSE(statement1.Step()); + } +} + +TEST_F(HistoryBackendDBTest, MigratePresentations) { + // Create the db we want. Use 22 since segments didn't change in that time + // frame. + ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); + + const SegmentID segment_id = 2; + const URLID url_id = 3; + const GURL url("http://www.foo.com"); + const std::string url_name(VisitSegmentDatabase::ComputeSegmentName(url)); + const base::string16 title(base::ASCIIToUTF16("Title1")); + const base::Time segment_time(base::Time::Now()); + + { + // Re-open the db for manual manipulation. + sql::Connection db; + ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); + + // Add an entry to urls. + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO urls " + "(id, url, title, last_visit_time) VALUES " + "(?, ?, ?, ?)")); + s.BindInt64(0, url_id); + s.BindString(1, url.spec()); + s.BindString16(2, title); + s.BindInt64(3, segment_time.ToInternalValue()); + ASSERT_TRUE(s.Run()); + } + + // Add an entry to segments. + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO segments " + "(id, name, url_id, pres_index) VALUES " + "(?, ?, ?, ?)")); + s.BindInt64(0, segment_id); + s.BindString(1, url_name); + s.BindInt64(2, url_id); + s.BindInt(3, 4); // pres_index + ASSERT_TRUE(s.Run()); + } + + // And one to segment_usage. + { + sql::Statement s(db.GetUniqueStatement( + "INSERT INTO segment_usage " + "(id, segment_id, time_slot, visit_count) VALUES " + "(?, ?, ?, ?)")); + s.BindInt64(0, 4); // id. + s.BindInt64(1, segment_id); + s.BindInt64(2, segment_time.ToInternalValue()); + s.BindInt(3, 5); // visit count. + ASSERT_TRUE(s.Run()); + } + } + + // Re-open the db, triggering migration. + CreateBackendAndDatabase(); + + std::vector<PageUsageData*> results; + db_->QuerySegmentUsage(segment_time, 10, &results); + ASSERT_EQ(1u, results.size()); + EXPECT_EQ(url, results[0]->GetURL()); + EXPECT_EQ(segment_id, results[0]->GetID()); + EXPECT_EQ(title, results[0]->GetTitle()); + STLDeleteElements(&results); +} + +} // namespace +} // namespace history diff --git a/components/history/core/browser/history_backend_unittest.cc b/components/history/core/browser/history_backend_unittest.cc index b6d1d8e33..d16e89d 100644 --- a/components/history/core/browser/history_backend_unittest.cc +++ b/components/history/core/browser/history_backend_unittest.cc @@ -2,759 +2,3326 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// History unit tests come in two flavors: -// -// 1. The more complicated style is that the unit test creates a full history -// service. This spawns a background thread for the history backend, and -// all communication is asynchronous. This is useful for testing more -// complicated things or end-to-end behavior. -// -// 2. The simpler style is to create a history backend on this thread and -// access it directly without a HistoryService object. This is much simpler -// because communication is synchronous. Generally, sets should go through -// the history backend (since there is a lot of logic) but gets can come -// directly from the HistoryDatabase. This is because the backend generally -// has no logic in the getter except threading stuff, which we don't want -// to run. - #include "components/history/core/browser/history_backend.h" +#include <algorithm> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/prefs/pref_service.h" +#include "base/run_loop.h" +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" -#include "components/history/core/browser/download_constants.h" -#include "components/history/core/browser/download_row.h" +#include "components/favicon_base/favicon_usage_data.h" #include "components/history/core/browser/history_constants.h" -#include "components/history/core/browser/history_database.h" -#include "components/history/core/browser/page_usage_data.h" -#include "components/history/core/test/history_backend_base_test.h" +#include "components/history/core/browser/history_database_params.h" +#include "components/history/core/browser/history_service.h" +#include "components/history/core/browser/history_service_observer.h" +#include "components/history/core/browser/in_memory_database.h" +#include "components/history/core/browser/in_memory_history_backend.h" +#include "components/history/core/browser/keyword_search_term.h" +#include "components/history/core/browser/visit_delegate.h" +#include "components/history/core/browser/visit_filter.h" +#include "components/history/core/test/database_test_utils.h" +#include "components/history/core/test/history_client_fake_bookmarks.h" #include "components/history/core/test/test_history_database.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/png_codec.h" +#include "url/gurl.h" + +// This file only tests functionality where it is most convenient to call the +// backend directly. Most of the history backend functions are tested by the +// history unit test. Because of the elaborate callbacks involved, this is no +// harder than calling it directly for many things. -namespace history { namespace { -// This must be outside the anonymous namespace for the friend statement in -// HistoryBackend to work. -class HistoryBackendDBTest : public HistoryBackendBaseTest { +const int kTinyEdgeSize = 10; +const int kSmallEdgeSize = 16; +const int kLargeEdgeSize = 32; + +const char kAcceptLanguagesForTest[] = "en-US,en"; + +const gfx::Size kTinySize = gfx::Size(kTinyEdgeSize, kTinyEdgeSize); +const gfx::Size kSmallSize = gfx::Size(kSmallEdgeSize, kSmallEdgeSize); +const gfx::Size kLargeSize = gfx::Size(kLargeEdgeSize, kLargeEdgeSize); + +typedef base::Callback<void(const history::URLRow*, + const history::URLRow*, + const history::URLRow*)> + SimulateNotificationCallback; + +class HistoryClientMock : public history::HistoryClientFakeBookmarks { public: - HistoryBackendDBTest() {} - ~HistoryBackendDBTest() override {} + MOCK_METHOD0(BlockUntilBookmarksLoaded, void()); }; -TEST_F(HistoryBackendDBTest, ClearBrowsingData_Downloads) { - CreateBackendAndDatabase(); - - // Initially there should be nothing in the downloads database. - std::vector<DownloadRow> downloads; - db_->QueryDownloads(&downloads); - EXPECT_EQ(0U, downloads.size()); - - // Add a download, test that it was added correctly, remove it, test that it - // was removed. - base::Time now = base::Time(); - uint32 id = 1; - EXPECT_TRUE(AddDownload(id, DownloadState::COMPLETE, base::Time())); - db_->QueryDownloads(&downloads); - EXPECT_EQ(1U, downloads.size()); - - EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("current-path")), - downloads[0].current_path); - EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("target-path")), - downloads[0].target_path); - EXPECT_EQ(1UL, downloads[0].url_chain.size()); - EXPECT_EQ(GURL("foo-url"), downloads[0].url_chain[0]); - EXPECT_EQ(std::string("http://referrer.com/"), - std::string(downloads[0].referrer_url.spec())); - EXPECT_EQ(now, downloads[0].start_time); - EXPECT_EQ(now, downloads[0].end_time); - EXPECT_EQ(0, downloads[0].received_bytes); - EXPECT_EQ(512, downloads[0].total_bytes); - EXPECT_EQ(DownloadState::COMPLETE, downloads[0].state); - EXPECT_EQ(DownloadDangerType::NOT_DANGEROUS, downloads[0].danger_type); - EXPECT_EQ(kTestDownloadInterruptReasonNone, downloads[0].interrupt_reason); - EXPECT_FALSE(downloads[0].opened); - EXPECT_EQ("by_ext_id", downloads[0].by_ext_id); - EXPECT_EQ("by_ext_name", downloads[0].by_ext_name); - EXPECT_EQ("application/vnd.oasis.opendocument.text", downloads[0].mime_type); - EXPECT_EQ("application/octet-stream", downloads[0].original_mime_type); - - db_->QueryDownloads(&downloads); - EXPECT_EQ(1U, downloads.size()); - db_->RemoveDownload(id); - db_->QueryDownloads(&downloads); - EXPECT_EQ(0U, downloads.size()); -} - -TEST_F(HistoryBackendDBTest, MigrateDownloadsState) { - // Create the db we want. - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); - { - // Open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - - // Manually insert corrupted rows; there's infrastructure in place now to - // make this impossible, at least according to the test above. - for (int state = 0; state < 5; ++state) { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads (id, full_path, url, start_time, " - "received_bytes, total_bytes, state, end_time, opened) VALUES " - "(?, ?, ?, ?, ?, ?, ?, ?, ?)")); - s.BindInt64(0, 1 + state); - s.BindString(1, "path"); - s.BindString(2, "url"); - s.BindInt64(3, base::Time::Now().ToTimeT()); - s.BindInt64(4, 100); - s.BindInt64(5, 100); - s.BindInt(6, state); - s.BindInt64(7, base::Time::Now().ToTimeT()); - s.BindInt(8, state % 2); - ASSERT_TRUE(s.Run()); - } +void SimulateNotificationURLVisited(history::HistoryServiceObserver* observer, + const history::URLRow* row1, + const history::URLRow* row2, + const history::URLRow* row3) { + history::URLRows rows; + rows.push_back(*row1); + if (row2) + rows.push_back(*row2); + if (row3) + rows.push_back(*row3); + + base::Time visit_time; + history::RedirectList redirects; + for (const auto& row : rows) { + observer->OnURLVisited(nullptr, ui::PAGE_TRANSITION_LINK, row, redirects, + visit_time); } +} - // Re-open the db using the HistoryDatabase, which should migrate from version - // 22 to the current version, fixing just the row whose state was 3. - // Then close the db so that we can re-open it directly. - CreateBackendAndDatabase(); - DeleteBackend(); - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - { - // The version should have been updated. - int cur_version = HistoryDatabase::GetCurrentVersion(); - ASSERT_LT(22, cur_version); - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(cur_version, s.ColumnInt(0)); - } - { - sql::Statement statement(db.GetUniqueStatement( - "SELECT id, state, opened " - "FROM downloads " - "ORDER BY id")); - int counter = 0; - while (statement.Step()) { - EXPECT_EQ(1 + counter, statement.ColumnInt64(0)); - // The only thing that migration should have changed was state from 3 to - // 4. - EXPECT_EQ(((counter == 3) ? 4 : counter), statement.ColumnInt(1)); - EXPECT_EQ(counter % 2, statement.ColumnInt(2)); - ++counter; - } - EXPECT_EQ(5, counter); - } +void SimulateNotificationURLsModified(history::HistoryServiceObserver* observer, + const history::URLRow* row1, + const history::URLRow* row2, + const history::URLRow* row3) { + history::URLRows rows; + rows.push_back(*row1); + if (row2) + rows.push_back(*row2); + if (row3) + rows.push_back(*row3); + + observer->OnURLsModified(nullptr, rows); +} + +} // namespace + +namespace history { + +class HistoryBackendTestBase; + +// This must be a separate object since HistoryBackend manages its lifetime. +// This just forwards the messages we're interested in to the test object. +class HistoryBackendTestDelegate : public HistoryBackend::Delegate { + public: + explicit HistoryBackendTestDelegate(HistoryBackendTestBase* test) + : test_(test) {} + + void NotifyProfileError(sql::InitStatus init_status) override {} + void SetInMemoryBackend(scoped_ptr<InMemoryHistoryBackend> backend) override; + void NotifyFaviconChanged(const std::set<GURL>& urls) override; + void NotifyURLVisited(ui::PageTransition transition, + const URLRow& row, + const RedirectList& redirects, + base::Time visit_time) override; + void NotifyURLsModified(const URLRows& changed_urls) override; + void NotifyURLsDeleted(bool all_history, + bool expired, + const URLRows& deleted_rows, + const std::set<GURL>& favicon_urls) override; + void NotifyKeywordSearchTermUpdated(const URLRow& row, + KeywordID keyword_id, + const base::string16& term) override; + void NotifyKeywordSearchTermDeleted(URLID url_id) override; + void DBLoaded() override; + + private: + // Not owned by us. + HistoryBackendTestBase* test_; + + DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestDelegate); +}; + +class HistoryBackendTestBase : public testing::Test { + public: + typedef std::vector<std::pair<ui::PageTransition, URLRow>> URLVisitedList; + typedef std::vector<URLRows> URLsModifiedList; + typedef std::vector<std::pair<bool, bool>> URLsDeletedList; + + HistoryBackendTestBase() + : loaded_(false), favicon_changed_notifications_(0) {} + + ~HistoryBackendTestBase() override {} + + protected: + int favicon_changed_notifications() const { + return favicon_changed_notifications_; + } + + void ClearFaviconChangedNotificationCounter() { + favicon_changed_notifications_ = 0; + } + + int num_url_visited_notifications() const { + return url_visited_notifications_.size(); + } + + const URLVisitedList& url_visited_notifications() const { + return url_visited_notifications_; + } + + int num_urls_modified_notifications() const { + return urls_modified_notifications_.size(); + } + + const URLsModifiedList& urls_modified_notifications() const { + return urls_modified_notifications_; } + + const URLsDeletedList& urls_deleted_notifications() const { + return urls_deleted_notifications_; + } + + void ClearBroadcastedNotifications() { + url_visited_notifications_.clear(); + urls_modified_notifications_.clear(); + urls_deleted_notifications_.clear(); + } + + base::FilePath test_dir() { return test_dir_; } + + void NotifyFaviconChanged(const std::set<GURL>& changed_favicons) { + ++favicon_changed_notifications_; + } + + void NotifyURLVisited(ui::PageTransition transition, + const URLRow& row, + const RedirectList& redirects, + base::Time visit_time) { + // Send the notifications directly to the in-memory database. + mem_backend_->OnURLVisited(nullptr, transition, row, redirects, visit_time); + url_visited_notifications_.push_back(std::make_pair(transition, row)); + } + + void NotifyURLsModified(const URLRows& changed_urls) { + // Send the notifications directly to the in-memory database. + mem_backend_->OnURLsModified(nullptr, changed_urls); + urls_modified_notifications_.push_back(changed_urls); + } + + void NotifyURLsDeleted(bool all_history, + bool expired, + const URLRows& deleted_rows, + const std::set<GURL>& favicon_urls) { + mem_backend_->OnURLsDeleted(nullptr, all_history, expired, deleted_rows, + favicon_urls); + urls_deleted_notifications_.push_back(std::make_pair(all_history, expired)); + } + + void NotifyKeywordSearchTermUpdated(const URLRow& row, + KeywordID keyword_id, + const base::string16& term) { + mem_backend_->OnKeywordSearchTermUpdated(nullptr, row, keyword_id, term); + } + + void NotifyKeywordSearchTermDeleted(URLID url_id) { + mem_backend_->OnKeywordSearchTermDeleted(nullptr, url_id); + } + + history::HistoryClientFakeBookmarks history_client_; + scoped_refptr<HistoryBackend> backend_; // Will be NULL on init failure. + scoped_ptr<InMemoryHistoryBackend> mem_backend_; + bool loaded_; + + private: + friend class HistoryBackendTestDelegate; + + // testing::Test + void SetUp() override { + ClearFaviconChangedNotificationCounter(); + if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("BackendTest"), + &test_dir_)) + return; + backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this), + &history_client_); + backend_->Init(std::string(), false, + TestHistoryDatabaseParamsForPath(test_dir_)); + } + + void TearDown() override { + if (backend_.get()) + backend_->Closing(); + backend_ = NULL; + mem_backend_.reset(); + base::DeleteFile(test_dir_, true); + base::RunLoop().RunUntilIdle(); + history_client_.ClearAllBookmarks(); + } + + void SetInMemoryBackend(scoped_ptr<InMemoryHistoryBackend> backend) { + mem_backend_.swap(backend); + } + + // The types and details of notifications which were broadcasted. + int favicon_changed_notifications_; + URLVisitedList url_visited_notifications_; + URLsModifiedList urls_modified_notifications_; + URLsDeletedList urls_deleted_notifications_; + + base::MessageLoop message_loop_; + base::FilePath test_dir_; + + DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestBase); +}; + +void HistoryBackendTestDelegate::SetInMemoryBackend( + scoped_ptr<InMemoryHistoryBackend> backend) { + test_->SetInMemoryBackend(backend.Pass()); } -TEST_F(HistoryBackendDBTest, MigrateDownloadsReasonPathsAndDangerType) { - base::Time now(base::Time::Now()); - - // Create the db we want. The schema didn't change from 22->23, so just - // re-use the v22 file. - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - - // Manually insert some rows. - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads (id, full_path, url, start_time, " - "received_bytes, total_bytes, state, end_time, opened) VALUES " - "(?, ?, ?, ?, ?, ?, ?, ?, ?)")); - - int64 id = 0; - // Null path. - s.BindInt64(0, ++id); - s.BindString(1, std::string()); - s.BindString(2, "http://whatever.com/index.html"); - s.BindInt64(3, now.ToTimeT()); - s.BindInt64(4, 100); - s.BindInt64(5, 100); - s.BindInt(6, 1); - s.BindInt64(7, now.ToTimeT()); - s.BindInt(8, 1); - ASSERT_TRUE(s.Run()); - s.Reset(true); - - // Non-null path. - s.BindInt64(0, ++id); - s.BindString(1, "/path/to/some/file"); - s.BindString(2, "http://whatever.com/index1.html"); - s.BindInt64(3, now.ToTimeT()); - s.BindInt64(4, 100); - s.BindInt64(5, 100); - s.BindInt(6, 1); - s.BindInt64(7, now.ToTimeT()); - s.BindInt(8, 1); - ASSERT_TRUE(s.Run()); - } - - // Re-open the db using the HistoryDatabase, which should migrate from version - // 23 to 24, creating the new tables and creating the new path, reason, - // and danger columns. - CreateBackendAndDatabase(); - DeleteBackend(); - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - { - // The version should have been updated. - int cur_version = HistoryDatabase::GetCurrentVersion(); - ASSERT_LT(23, cur_version); - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(cur_version, s.ColumnInt(0)); - } - { - base::Time nowish(base::Time::FromTimeT(now.ToTimeT())); - - // Confirm downloads table is valid. - sql::Statement statement(db.GetUniqueStatement( - "SELECT id, interrupt_reason, current_path, target_path, " - " danger_type, start_time, end_time " - "FROM downloads ORDER BY id")); - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(1, statement.ColumnInt64(0)); - EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone), - statement.ColumnInt(1)); - EXPECT_EQ("", statement.ColumnString(2)); - EXPECT_EQ("", statement.ColumnString(3)); - // Implicit dependence on value of kDangerTypeNotDangerous from - // download_database.cc. - EXPECT_EQ(0, statement.ColumnInt(4)); - EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5)); - EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6)); - - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(2, statement.ColumnInt64(0)); - EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone), - statement.ColumnInt(1)); - EXPECT_EQ("/path/to/some/file", statement.ColumnString(2)); - EXPECT_EQ("/path/to/some/file", statement.ColumnString(3)); - EXPECT_EQ(0, statement.ColumnInt(4)); - EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5)); - EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6)); - - EXPECT_FALSE(statement.Step()); - } - { - // Confirm downloads_url_chains table is valid. - sql::Statement statement(db.GetUniqueStatement( - "SELECT id, chain_index, url FROM downloads_url_chains " - " ORDER BY id, chain_index")); - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(1, statement.ColumnInt64(0)); - EXPECT_EQ(0, statement.ColumnInt(1)); - EXPECT_EQ("http://whatever.com/index.html", statement.ColumnString(2)); - - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(2, statement.ColumnInt64(0)); - EXPECT_EQ(0, statement.ColumnInt(1)); - EXPECT_EQ("http://whatever.com/index1.html", statement.ColumnString(2)); - - EXPECT_FALSE(statement.Step()); +void HistoryBackendTestDelegate::NotifyFaviconChanged( + const std::set<GURL>& changed_favicons) { + test_->NotifyFaviconChanged(changed_favicons); +} + +void HistoryBackendTestDelegate::NotifyURLVisited(ui::PageTransition transition, + const URLRow& row, + const RedirectList& redirects, + base::Time visit_time) { + test_->NotifyURLVisited(transition, row, redirects, visit_time); +} + +void HistoryBackendTestDelegate::NotifyURLsModified( + const URLRows& changed_urls) { + test_->NotifyURLsModified(changed_urls); +} + +void HistoryBackendTestDelegate::NotifyURLsDeleted( + bool all_history, + bool expired, + const URLRows& deleted_rows, + const std::set<GURL>& favicon_urls) { + test_->NotifyURLsDeleted(all_history, expired, deleted_rows, favicon_urls); +} + +void HistoryBackendTestDelegate::NotifyKeywordSearchTermUpdated( + const URLRow& row, + KeywordID keyword_id, + const base::string16& term) { + test_->NotifyKeywordSearchTermUpdated(row, keyword_id, term); +} + +void HistoryBackendTestDelegate::NotifyKeywordSearchTermDeleted(URLID url_id) { + test_->NotifyKeywordSearchTermDeleted(url_id); +} + +void HistoryBackendTestDelegate::DBLoaded() { + test_->loaded_ = true; +} + +class HistoryBackendTest : public HistoryBackendTestBase { + public: + HistoryBackendTest() {} + ~HistoryBackendTest() override {} + + protected: + void AddRedirectChain(const char* sequence[], int nav_entry_id) { + AddRedirectChainWithTransitionAndTime( + sequence, nav_entry_id, ui::PAGE_TRANSITION_LINK, base::Time::Now()); + } + + void AddRedirectChainWithTransitionAndTime(const char* sequence[], + int nav_entry_id, + ui::PageTransition transition, + base::Time time) { + history::RedirectList redirects; + for (int i = 0; sequence[i] != NULL; ++i) + redirects.push_back(GURL(sequence[i])); + + ContextID context_id = reinterpret_cast<ContextID>(1); + history::HistoryAddPageArgs request( + redirects.back(), time, context_id, nav_entry_id, GURL(), + redirects, transition, history::SOURCE_BROWSED, + true); + backend_->AddPage(request); + } + + // Adds CLIENT_REDIRECT page transition. + // |url1| is the source URL and |url2| is the destination. + // |did_replace| is true if the transition is non-user initiated and the + // navigation entry for |url2| has replaced that for |url1|. The possibly + // updated transition code of the visit records for |url1| and |url2| is + // returned by filling in |*transition1| and |*transition2|, respectively. + // |time| is a time of the redirect. + void AddClientRedirect(const GURL& url1, + const GURL& url2, + bool did_replace, + base::Time time, + int* transition1, + int* transition2) { + ContextID dummy_context_id = reinterpret_cast<ContextID>(0x87654321); + history::RedirectList redirects; + if (url1.is_valid()) + redirects.push_back(url1); + if (url2.is_valid()) + redirects.push_back(url2); + HistoryAddPageArgs request( + url2, time, dummy_context_id, 0, url1, + redirects, ui::PAGE_TRANSITION_CLIENT_REDIRECT, + history::SOURCE_BROWSED, did_replace); + backend_->AddPage(request); + + *transition1 = GetTransition(url1); + *transition2 = GetTransition(url2); + } + + int GetTransition(const GURL& url) { + if (!url.is_valid()) + return 0; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url, &row); + VisitVector visits; + EXPECT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + return visits[0].transition; + } + + // Returns a vector with the small and large edge sizes. + const std::vector<int> GetEdgeSizesSmallAndLarge() { + std::vector<int> sizes_small_and_large; + sizes_small_and_large.push_back(kSmallEdgeSize); + sizes_small_and_large.push_back(kLargeEdgeSize); + return sizes_small_and_large; + } + + // Returns the number of icon mappings of |icon_type| to |page_url|. + size_t NumIconMappingsForPageURL(const GURL& page_url, + favicon_base::IconType icon_type) { + std::vector<IconMapping> icon_mappings; + backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_type, + &icon_mappings); + return icon_mappings.size(); + } + + // Returns the icon mappings for |page_url| sorted alphabetically by icon + // URL in ascending order. Returns true if there is at least one icon + // mapping. + bool GetSortedIconMappingsForPageURL( + const GURL& page_url, + std::vector<IconMapping>* icon_mappings) { + if (!backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + icon_mappings)) { + return false; } + std::sort(icon_mappings->begin(), icon_mappings->end(), + [](const history::IconMapping& a, const history::IconMapping& b) { + return a.icon_url < b.icon_url; + }); + return true; + } + + // Returns the favicon bitmaps for |icon_id| sorted by pixel size in + // ascending order. Returns true if there is at least one favicon bitmap. + bool GetSortedFaviconBitmaps(favicon_base::FaviconID icon_id, + std::vector<FaviconBitmap>* favicon_bitmaps) { + if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, favicon_bitmaps)) + return false; + std::sort( + favicon_bitmaps->begin(), favicon_bitmaps->end(), + [](const history::FaviconBitmap& a, const history::FaviconBitmap& b) { + return a.pixel_size.GetArea() < b.pixel_size.GetArea(); + }); + return true; + } + + // Returns true if there is exactly one favicon bitmap associated to + // |favicon_id|. If true, returns favicon bitmap in output parameter. + bool GetOnlyFaviconBitmap(const favicon_base::FaviconID icon_id, + FaviconBitmap* favicon_bitmap) { + std::vector<FaviconBitmap> favicon_bitmaps; + if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, &favicon_bitmaps)) + return false; + if (favicon_bitmaps.size() != 1) + return false; + *favicon_bitmap = favicon_bitmaps[0]; + return true; + } + + // Creates an |edge_size|x|edge_size| bitmap of |color|. + SkBitmap CreateBitmap(SkColor color, int edge_size) { + SkBitmap bitmap; + bitmap.allocN32Pixels(edge_size, edge_size); + bitmap.eraseColor(color); + return bitmap; + } + + // Returns true if |bitmap_data| is equal to |expected_data|. + bool BitmapDataEqual(char expected_data, + scoped_refptr<base::RefCountedMemory> bitmap_data) { + return bitmap_data.get() && + bitmap_data->size() == 1u && + *bitmap_data->front() == expected_data; + } + + // Returns true if |bitmap_data| is of |color|. + bool BitmapColorEqual(SkColor expected_color, + scoped_refptr<base::RefCountedMemory> bitmap_data) { + SkBitmap bitmap; + if (!gfx::PNGCodec::Decode(bitmap_data->front(), bitmap_data->size(), + &bitmap)) + return false; + SkAutoLockPixels bitmap_lock(bitmap); + return expected_color == bitmap.getColor(0, 0); + } + + private: + DISALLOW_COPY_AND_ASSIGN(HistoryBackendTest); +}; + +class InMemoryHistoryBackendTest : public HistoryBackendTestBase { + public: + InMemoryHistoryBackendTest() {} + ~InMemoryHistoryBackendTest() override {} + + protected: + void SimulateNotificationURLsDeleted(const URLRow* row1, + const URLRow* row2 = NULL, + const URLRow* row3 = NULL) { + URLRows rows; + rows.push_back(*row1); + if (row2) rows.push_back(*row2); + if (row3) rows.push_back(*row3); + + NotifyURLsDeleted(false, false, rows, std::set<GURL>()); + } + + size_t GetNumberOfMatchingSearchTerms(const int keyword_id, + const base::string16& prefix) { + std::vector<KeywordSearchTermVisit> matching_terms; + mem_backend_->db()->GetMostRecentKeywordSearchTerms( + keyword_id, prefix, 1, &matching_terms); + return matching_terms.size(); + } + + static URLRow CreateTestTypedURL() { + URLRow url_row(GURL("https://www.google.com/")); + url_row.set_id(10); + url_row.set_title(base::UTF8ToUTF16("Google Search")); + url_row.set_typed_count(1); + url_row.set_visit_count(1); + url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(1)); + return url_row; + } + + static URLRow CreateAnotherTestTypedURL() { + URLRow url_row(GURL("https://maps.google.com/")); + url_row.set_id(20); + url_row.set_title(base::UTF8ToUTF16("Google Maps")); + url_row.set_typed_count(2); + url_row.set_visit_count(3); + url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(2)); + return url_row; } + + static URLRow CreateTestNonTypedURL() { + URLRow url_row(GURL("https://news.google.com/")); + url_row.set_id(30); + url_row.set_title(base::UTF8ToUTF16("Google News")); + url_row.set_visit_count(5); + url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(3)); + return url_row; + } + + void PopulateTestURLsAndSearchTerms(URLRow* row1, + URLRow* row2, + const base::string16& term1, + const base::string16& term2); + + void TestAddingAndChangingURLRows( + const SimulateNotificationCallback& callback); + + static const KeywordID kTestKeywordId; + static const char kTestSearchTerm1[]; + static const char kTestSearchTerm2[]; + + private: + DISALLOW_COPY_AND_ASSIGN(InMemoryHistoryBackendTest); +}; + +const KeywordID InMemoryHistoryBackendTest::kTestKeywordId = 42; +const char InMemoryHistoryBackendTest::kTestSearchTerm1[] = "banana"; +const char InMemoryHistoryBackendTest::kTestSearchTerm2[] = "orange"; + +// http://crbug.com/114287 +#if defined(OS_WIN) +#define MAYBE_Loaded DISABLED_Loaded +#else +#define MAYBE_Loaded Loaded +#endif // defined(OS_WIN) +TEST_F(HistoryBackendTest, MAYBE_Loaded) { + ASSERT_TRUE(backend_.get()); + ASSERT_TRUE(loaded_); } -TEST_F(HistoryBackendDBTest, MigrateReferrer) { - base::Time now(base::Time::Now()); - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads (id, full_path, url, start_time, " - "received_bytes, total_bytes, state, end_time, opened) VALUES " - "(?, ?, ?, ?, ?, ?, ?, ?, ?)")); - int64 db_handle = 0; - s.BindInt64(0, ++db_handle); - s.BindString(1, "full_path"); - s.BindString(2, "http://whatever.com/index.html"); - s.BindInt64(3, now.ToTimeT()); - s.BindInt64(4, 100); - s.BindInt64(5, 100); - s.BindInt(6, 1); - s.BindInt64(7, now.ToTimeT()); - s.BindInt(8, 1); - ASSERT_TRUE(s.Run()); - } - // Re-open the db using the HistoryDatabase, which should migrate to version - // 26, creating the referrer column. - CreateBackendAndDatabase(); - DeleteBackend(); - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - // The version should have been updated. - int cur_version = HistoryDatabase::GetCurrentVersion(); - ASSERT_LE(26, cur_version); - { - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(cur_version, s.ColumnInt(0)); - } - { - sql::Statement s(db.GetUniqueStatement( - "SELECT referrer from downloads")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(std::string(), s.ColumnString(0)); - } +TEST_F(HistoryBackendTest, DeleteAll) { + ASSERT_TRUE(backend_.get()); + + // Add two favicons, each with two bitmaps. Note that we add favicon2 before + // adding favicon1. This is so that favicon1 one gets ID 2 autoassigned to + // the database, which will change when the other one is deleted. This way + // we can test that updating works properly. + GURL favicon_url1("http://www.google.com/favicon.ico"); + GURL favicon_url2("http://news.google.com/favicon.ico"); + favicon_base::FaviconID favicon2 = + backend_->thumbnail_db_->AddFavicon(favicon_url2, favicon_base::FAVICON); + favicon_base::FaviconID favicon1 = + backend_->thumbnail_db_->AddFavicon(favicon_url1, favicon_base::FAVICON); + + std::vector<unsigned char> data; + data.push_back('a'); + EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon1, + new base::RefCountedBytes(data), base::Time::Now(), kSmallSize)); + data[0] = 'b'; + EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon1, + new base::RefCountedBytes(data), base::Time::Now(), kLargeSize)); + + data[0] = 'c'; + EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2, + new base::RefCountedBytes(data), base::Time::Now(), kSmallSize)); + data[0] = 'd'; + EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2, + new base::RefCountedBytes(data), base::Time::Now(), kLargeSize)); + + // First visit two URLs. + URLRow row1(GURL("http://www.google.com/")); + row1.set_visit_count(2); + row1.set_typed_count(1); + row1.set_last_visit(base::Time::Now()); + backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1); + + URLRow row2(GURL("http://news.google.com/")); + row2.set_visit_count(1); + row2.set_last_visit(base::Time::Now()); + backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2); + + URLRows rows; + rows.push_back(row2); // Reversed order for the same reason as favicons. + rows.push_back(row1); + backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); + + URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL); + URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL); + + // Get the two visits for the URLs we just added. + VisitVector visits; + backend_->db_->GetVisitsForURL(row1_id, &visits); + ASSERT_EQ(1U, visits.size()); + + visits.clear(); + backend_->db_->GetVisitsForURL(row2_id, &visits); + ASSERT_EQ(1U, visits.size()); + + // The in-memory backend should have been set and it should have gotten the + // typed URL. + ASSERT_TRUE(mem_backend_.get()); + URLRow outrow1; + EXPECT_TRUE(mem_backend_->db_->GetRowForURL(row1.url(), NULL)); + + // Star row1. + history_client_.AddBookmark(row1.url()); + + // Now finally clear all history. + ClearBroadcastedNotifications(); + backend_->DeleteAllHistory(); + + // The first URL should be preserved but the time should be cleared. + EXPECT_TRUE(backend_->db_->GetRowForURL(row1.url(), &outrow1)); + EXPECT_EQ(row1.url(), outrow1.url()); + EXPECT_EQ(0, outrow1.visit_count()); + EXPECT_EQ(0, outrow1.typed_count()); + EXPECT_TRUE(base::Time() == outrow1.last_visit()); + + // The second row should be deleted. + URLRow outrow2; + EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &outrow2)); + + // All visits should be deleted for both URLs. + VisitVector all_visits; + backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0, + &all_visits); + ASSERT_EQ(0U, all_visits.size()); + + // We should have a favicon and favicon bitmaps for the first URL only. We + // look them up by favicon URL since the IDs may have changed. + favicon_base::FaviconID out_favicon1 = + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + favicon_url1, favicon_base::FAVICON, NULL); + EXPECT_TRUE(out_favicon1); + + std::vector<FaviconBitmap> favicon_bitmaps; + EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps( + out_favicon1, &favicon_bitmaps)); + ASSERT_EQ(2u, favicon_bitmaps.size()); + + FaviconBitmap favicon_bitmap1 = favicon_bitmaps[0]; + FaviconBitmap favicon_bitmap2 = favicon_bitmaps[1]; + + // Favicon bitmaps do not need to be in particular order. + if (favicon_bitmap1.pixel_size == kLargeSize) { + FaviconBitmap tmp_favicon_bitmap = favicon_bitmap1; + favicon_bitmap1 = favicon_bitmap2; + favicon_bitmap2 = tmp_favicon_bitmap; } + + EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap1.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap1.pixel_size); + + EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap2.bitmap_data)); + EXPECT_EQ(kLargeSize, favicon_bitmap2.pixel_size); + + favicon_base::FaviconID out_favicon2 = + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + favicon_url2, favicon_base::FAVICON, NULL); + EXPECT_FALSE(out_favicon2) << "Favicon not deleted"; + + // The remaining URL should still reference the same favicon, even if its + // ID has changed. + std::vector<IconMapping> mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + outrow1.url(), favicon_base::FAVICON, &mappings)); + EXPECT_EQ(1u, mappings.size()); + EXPECT_EQ(out_favicon1, mappings[0].icon_id); + + // The first URL should still be bookmarked. + EXPECT_TRUE(history_client_.IsBookmarked(row1.url())); + + // Check that we fire the notification about all history having been deleted. + ASSERT_EQ(1u, urls_deleted_notifications().size()); + EXPECT_TRUE(urls_deleted_notifications()[0].first); + EXPECT_FALSE(urls_deleted_notifications()[0].second); } -TEST_F(HistoryBackendDBTest, MigrateDownloadedByExtension) { - base::Time now(base::Time::Now()); - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(26)); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads (id, current_path, target_path, start_time, " - "received_bytes, total_bytes, state, danger_type, interrupt_reason, " - "end_time, opened, referrer) VALUES " - "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); - s.BindInt64(0, 1); - s.BindString(1, "current_path"); - s.BindString(2, "target_path"); - s.BindInt64(3, now.ToTimeT()); - s.BindInt64(4, 100); - s.BindInt64(5, 100); - s.BindInt(6, 1); - s.BindInt(7, 0); - s.BindInt(8, 0); - s.BindInt64(9, now.ToTimeT()); - s.BindInt(10, 1); - s.BindString(11, "referrer"); - ASSERT_TRUE(s.Run()); - } - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads_url_chains (id, chain_index, url) VALUES " - "(?, ?, ?)")); - s.BindInt64(0, 4); - s.BindInt64(1, 0); - s.BindString(2, "url"); - ASSERT_TRUE(s.Run()); +// Checks that adding a visit, then calling DeleteAll, and then trying to add +// data for the visited page works. This can happen when clearing the history +// immediately after visiting a page. +TEST_F(HistoryBackendTest, DeleteAllThenAddData) { + ASSERT_TRUE(backend_.get()); + + base::Time visit_time = base::Time::Now(); + GURL url("http://www.google.com/"); + HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(), + history::RedirectList(), + ui::PAGE_TRANSITION_KEYWORD_GENERATED, + history::SOURCE_BROWSED, false); + backend_->AddPage(request); + + // Check that a row was added. + URLRow outrow; + EXPECT_TRUE(backend_->db_->GetRowForURL(url, &outrow)); + + // Check that the visit was added. + VisitVector all_visits; + backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0, + &all_visits); + ASSERT_EQ(1U, all_visits.size()); + + // Clear all history. + backend_->DeleteAllHistory(); + + // The row should be deleted. + EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow)); + + // The visit should be deleted. + backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0, + &all_visits); + ASSERT_EQ(0U, all_visits.size()); + + // Try and set the title. + backend_->SetPageTitle(url, base::UTF8ToUTF16("Title")); + + // The row should still be deleted. + EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow)); + + // The visit should still be deleted. + backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0, + &all_visits); + ASSERT_EQ(0U, all_visits.size()); +} + +TEST_F(HistoryBackendTest, URLsNoLongerBookmarked) { + GURL favicon_url1("http://www.google.com/favicon.ico"); + GURL favicon_url2("http://news.google.com/favicon.ico"); + + std::vector<unsigned char> data; + data.push_back('1'); + favicon_base::FaviconID favicon1 = + backend_->thumbnail_db_->AddFavicon(favicon_url1, + favicon_base::FAVICON, + new base::RefCountedBytes(data), + base::Time::Now(), + gfx::Size()); + + data[0] = '2'; + favicon_base::FaviconID favicon2 = + backend_->thumbnail_db_->AddFavicon(favicon_url2, + favicon_base::FAVICON, + new base::RefCountedBytes(data), + base::Time::Now(), + gfx::Size()); + + // First visit two URLs. + URLRow row1(GURL("http://www.google.com/")); + row1.set_visit_count(2); + row1.set_typed_count(1); + row1.set_last_visit(base::Time::Now()); + EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1)); + + URLRow row2(GURL("http://news.google.com/")); + row2.set_visit_count(1); + row2.set_last_visit(base::Time::Now()); + EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2)); + + URLRows rows; + rows.push_back(row2); // Reversed order for the same reason as favicons. + rows.push_back(row1); + backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); + + URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL); + URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL); + + // Star the two URLs. + history_client_.AddBookmark(row1.url()); + history_client_.AddBookmark(row2.url()); + + // Delete url 2. + backend_->expirer_.DeleteURL(row2.url()); + EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL)); + VisitVector visits; + backend_->db_->GetVisitsForURL(row2_id, &visits); + EXPECT_EQ(0U, visits.size()); + // The favicon should still be valid. + EXPECT_EQ(favicon2, + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + favicon_url2, favicon_base::FAVICON, NULL)); + + // Unstar row2. + history_client_.DelBookmark(row2.url()); + + // Tell the backend it was unstarred. We have to explicitly do this as + // BookmarkModel isn't wired up to the backend during testing. + std::set<GURL> unstarred_urls; + unstarred_urls.insert(row2.url()); + backend_->URLsNoLongerBookmarked(unstarred_urls); + + // The URL should still not exist. + EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL)); + // And the favicon should be deleted. + EXPECT_EQ(0, + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + favicon_url2, favicon_base::FAVICON, NULL)); + + // Unstar row 1. + history_client_.DelBookmark(row1.url()); + + // Tell the backend it was unstarred. We have to explicitly do this as + // BookmarkModel isn't wired up to the backend during testing. + unstarred_urls.clear(); + unstarred_urls.insert(row1.url()); + backend_->URLsNoLongerBookmarked(unstarred_urls); + + // The URL should still exist (because there were visits). + EXPECT_EQ(row1_id, backend_->db_->GetRowForURL(row1.url(), NULL)); + + // There should still be visits. + visits.clear(); + backend_->db_->GetVisitsForURL(row1_id, &visits); + EXPECT_EQ(1U, visits.size()); + + // The favicon should still be valid. + EXPECT_EQ(favicon1, + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + favicon_url1, favicon_base::FAVICON, NULL)); +} + +// Tests a handful of assertions for a navigation with a type of +// KEYWORD_GENERATED. +TEST_F(HistoryBackendTest, KeywordGenerated) { + ASSERT_TRUE(backend_.get()); + + GURL url("http://google.com"); + + base::Time visit_time = base::Time::Now() - base::TimeDelta::FromDays(1); + HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(), + history::RedirectList(), + ui::PAGE_TRANSITION_KEYWORD_GENERATED, + history::SOURCE_BROWSED, false); + backend_->AddPage(request); + + // A row should have been added for the url. + URLRow row; + URLID url_id = backend_->db()->GetRowForURL(url, &row); + ASSERT_NE(0, url_id); + + // The typed count should be 1. + ASSERT_EQ(1, row.typed_count()); + + // KEYWORD_GENERATED urls should not be added to the segment db. + std::string segment_name = VisitSegmentDatabase::ComputeSegmentName(url); + EXPECT_EQ(0, backend_->db()->GetSegmentNamed(segment_name)); + + // One visit should be added. + VisitVector visits; + EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits)); + EXPECT_EQ(1U, visits.size()); + + // But no visible visits. + visits.clear(); + QueryOptions query_options; + query_options.max_count = 1; + backend_->db()->GetVisibleVisitsInRange(query_options, &visits); + EXPECT_TRUE(visits.empty()); + + // Going back to the same entry should not increment the typed count. + ui::PageTransition back_transition = ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK); + HistoryAddPageArgs back_request(url, visit_time, NULL, 0, GURL(), + history::RedirectList(), back_transition, + history::SOURCE_BROWSED, false); + backend_->AddPage(back_request); + url_id = backend_->db()->GetRowForURL(url, &row); + ASSERT_NE(0, url_id); + ASSERT_EQ(1, row.typed_count()); + + // Expire the visits. + std::set<GURL> restrict_urls; + backend_->expire_backend()->ExpireHistoryBetween(restrict_urls, visit_time, + base::Time::Now()); + + // The visit should have been nuked. + visits.clear(); + EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits)); + EXPECT_TRUE(visits.empty()); + + // As well as the url. + ASSERT_EQ(0, backend_->db()->GetRowForURL(url, &row)); +} + +TEST_F(HistoryBackendTest, ClientRedirect) { + ASSERT_TRUE(backend_.get()); + + int transition1; + int transition2; + + // Initial transition to page A. + GURL url_a("http://google.com/a"); + AddClientRedirect(GURL(), url_a, false, base::Time(), + &transition1, &transition2); + EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END); + + // User initiated redirect to page B. + GURL url_b("http://google.com/b"); + AddClientRedirect(url_a, url_b, false, base::Time(), + &transition1, &transition2); + EXPECT_TRUE(transition1 & ui::PAGE_TRANSITION_CHAIN_END); + EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END); + + // Non-user initiated redirect to page C. + GURL url_c("http://google.com/c"); + AddClientRedirect(url_b, url_c, true, base::Time(), + &transition1, &transition2); + EXPECT_FALSE(transition1 & ui::PAGE_TRANSITION_CHAIN_END); + EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END); +} + +TEST_F(HistoryBackendTest, AddPagesWithDetails) { + ASSERT_TRUE(backend_.get()); + + // Import one non-typed URL, and two recent and one expired typed URLs. + URLRow row1(GURL("https://news.google.com/")); + row1.set_visit_count(1); + row1.set_last_visit(base::Time::Now()); + URLRow row2(GURL("https://www.google.com/")); + row2.set_typed_count(1); + row2.set_last_visit(base::Time::Now()); + URLRow row3(GURL("https://mail.google.com/")); + row3.set_visit_count(1); + row3.set_typed_count(1); + row3.set_last_visit(base::Time::Now() - base::TimeDelta::FromDays(7 - 1)); + URLRow row4(GURL("https://maps.google.com/")); + row4.set_visit_count(1); + row4.set_typed_count(1); + row4.set_last_visit(base::Time::Now() - base::TimeDelta::FromDays(365 + 2)); + + URLRows rows; + rows.push_back(row1); + rows.push_back(row2); + rows.push_back(row3); + rows.push_back(row4); + backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); + + // Verify that recent URLs have ended up in the main |db_|, while the already + // expired URL has been ignored. + URLRow stored_row1, stored_row2, stored_row3, stored_row4; + EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1)); + EXPECT_NE(0, backend_->db_->GetRowForURL(row2.url(), &stored_row2)); + EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3)); + EXPECT_EQ(0, backend_->db_->GetRowForURL(row4.url(), &stored_row4)); + + // Ensure that a notification was fired for both typed and non-typed URLs. + // Further verify that the IDs in the notification are set to those that are + // in effect in the main database. The InMemoryHistoryBackend relies on this + // for caching. + ASSERT_EQ(1, num_urls_modified_notifications()); + + const URLRows& changed_urls = urls_modified_notifications()[0]; + EXPECT_EQ(3u, changed_urls.size()); + + URLRows::const_iterator it_row1 = + std::find_if(changed_urls.begin(), changed_urls.end(), + history::URLRow::URLRowHasURL(row1.url())); + ASSERT_NE(changed_urls.end(), it_row1); + EXPECT_EQ(stored_row1.id(), it_row1->id()); + + URLRows::const_iterator it_row2 = + std::find_if(changed_urls.begin(), changed_urls.end(), + history::URLRow::URLRowHasURL(row2.url())); + ASSERT_NE(changed_urls.end(), it_row2); + EXPECT_EQ(stored_row2.id(), it_row2->id()); + + URLRows::const_iterator it_row3 = + std::find_if(changed_urls.begin(), changed_urls.end(), + history::URLRow::URLRowHasURL(row3.url())); + ASSERT_NE(changed_urls.end(), it_row3); + EXPECT_EQ(stored_row3.id(), it_row3->id()); +} + +TEST_F(HistoryBackendTest, UpdateURLs) { + ASSERT_TRUE(backend_.get()); + + // Add three pages directly to the database. + URLRow row1(GURL("https://news.google.com/")); + row1.set_visit_count(1); + row1.set_last_visit(base::Time::Now()); + URLRow row2(GURL("https://maps.google.com/")); + row2.set_visit_count(2); + row2.set_last_visit(base::Time::Now()); + URLRow row3(GURL("https://www.google.com/")); + row3.set_visit_count(3); + row3.set_last_visit(base::Time::Now()); + + backend_->db_->AddURL(row1); + backend_->db_->AddURL(row2); + backend_->db_->AddURL(row3); + + // Now create changed versions of all URLRows by incrementing their visit + // counts, and in the meantime, also delete the second row from the database. + URLRow altered_row1, altered_row2, altered_row3; + backend_->db_->GetRowForURL(row1.url(), &altered_row1); + altered_row1.set_visit_count(42); + backend_->db_->GetRowForURL(row2.url(), &altered_row2); + altered_row2.set_visit_count(43); + backend_->db_->GetRowForURL(row3.url(), &altered_row3); + altered_row3.set_visit_count(44); + + backend_->db_->DeleteURLRow(altered_row2.id()); + + // Now try to update all three rows at once. The change to the second URLRow + // should be ignored, as it is no longer present in the DB. + URLRows rows; + rows.push_back(altered_row1); + rows.push_back(altered_row2); + rows.push_back(altered_row3); + EXPECT_EQ(2u, backend_->UpdateURLs(rows)); + + URLRow stored_row1, stored_row3; + EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1)); + EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3)); + EXPECT_EQ(altered_row1.visit_count(), stored_row1.visit_count()); + EXPECT_EQ(altered_row3.visit_count(), stored_row3.visit_count()); + + // Ensure that a notification was fired, and further verify that the IDs in + // the notification are set to those that are in effect in the main database. + // The InMemoryHistoryBackend relies on this for caching. + ASSERT_EQ(1, num_urls_modified_notifications()); + + const URLRows& changed_urls = urls_modified_notifications()[0]; + EXPECT_EQ(2u, changed_urls.size()); + + URLRows::const_iterator it_row1 = + std::find_if(changed_urls.begin(), changed_urls.end(), + history::URLRow::URLRowHasURL(row1.url())); + ASSERT_NE(changed_urls.end(), it_row1); + EXPECT_EQ(altered_row1.id(), it_row1->id()); + EXPECT_EQ(altered_row1.visit_count(), it_row1->visit_count()); + + URLRows::const_iterator it_row3 = + std::find_if(changed_urls.begin(), changed_urls.end(), + history::URLRow::URLRowHasURL(row3.url())); + ASSERT_NE(changed_urls.end(), it_row3); + EXPECT_EQ(altered_row3.id(), it_row3->id()); + EXPECT_EQ(altered_row3.visit_count(), it_row3->visit_count()); +} + +// This verifies that a notification is fired. In-depth testing of logic should +// be done in HistoryTest.SetTitle. +TEST_F(HistoryBackendTest, SetPageTitleFiresNotificationWithCorrectDetails) { + const char kTestUrlTitle[] = "Google Search"; + + ASSERT_TRUE(backend_.get()); + + // Add two pages, then change the title of the second one. + URLRow row1(GURL("https://news.google.com/")); + row1.set_typed_count(1); + row1.set_last_visit(base::Time::Now()); + URLRow row2(GURL("https://www.google.com/")); + row2.set_visit_count(2); + row2.set_last_visit(base::Time::Now()); + + URLRows rows; + rows.push_back(row1); + rows.push_back(row2); + backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); + + ClearBroadcastedNotifications(); + backend_->SetPageTitle(row2.url(), base::UTF8ToUTF16(kTestUrlTitle)); + + // Ensure that a notification was fired, and further verify that the IDs in + // the notification are set to those that are in effect in the main database. + // The InMemoryHistoryBackend relies on this for caching. + URLRow stored_row2; + EXPECT_TRUE(backend_->GetURL(row2.url(), &stored_row2)); + ASSERT_EQ(1, num_urls_modified_notifications()); + + const URLRows& changed_urls = urls_modified_notifications()[0]; + ASSERT_EQ(1u, changed_urls.size()); + EXPECT_EQ(base::UTF8ToUTF16(kTestUrlTitle), changed_urls[0].title()); + EXPECT_EQ(stored_row2.id(), changed_urls[0].id()); +} + +// There's no importer on Android. +#if !defined(OS_ANDROID) +TEST_F(HistoryBackendTest, ImportedFaviconsTest) { + // Setup test data - two Urls in the history, one with favicon assigned and + // one without. + GURL favicon_url1("http://www.google.com/favicon.ico"); + std::vector<unsigned char> data; + data.push_back('1'); + favicon_base::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon( + favicon_url1, + favicon_base::FAVICON, + base::RefCountedBytes::TakeVector(&data), + base::Time::Now(), + gfx::Size()); + URLRow row1(GURL("http://www.google.com/")); + row1.set_visit_count(1); + row1.set_last_visit(base::Time::Now()); + EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1)); + + URLRow row2(GURL("http://news.google.com/")); + row2.set_visit_count(1); + row2.set_last_visit(base::Time::Now()); + URLRows rows; + rows.push_back(row1); + rows.push_back(row2); + backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); + URLRow url_row1, url_row2; + EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0); + EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0); + EXPECT_EQ(1u, NumIconMappingsForPageURL(row1.url(), favicon_base::FAVICON)); + EXPECT_EQ(0u, NumIconMappingsForPageURL(row2.url(), favicon_base::FAVICON)); + + // Now provide one imported favicon for both URLs already in the registry. + // The new favicon should only be used with the URL that doesn't already have + // a favicon. + favicon_base::FaviconUsageDataList favicons; + favicon_base::FaviconUsageData favicon; + favicon.favicon_url = GURL("http://news.google.com/favicon.ico"); + favicon.png_data.push_back('2'); + favicon.urls.insert(row1.url()); + favicon.urls.insert(row2.url()); + favicons.push_back(favicon); + backend_->SetImportedFavicons(favicons); + EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0); + EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0); + + std::vector<IconMapping> mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + row1.url(), favicon_base::FAVICON, &mappings)); + EXPECT_EQ(1u, mappings.size()); + EXPECT_EQ(favicon1, mappings[0].icon_id); + EXPECT_EQ(favicon_url1, mappings[0].icon_url); + + mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + row2.url(), favicon_base::FAVICON, &mappings)); + EXPECT_EQ(1u, mappings.size()); + EXPECT_EQ(favicon.favicon_url, mappings[0].icon_url); + + // A URL should not be added to history (to store favicon), if + // the URL is not bookmarked. + GURL url3("http://mail.google.com"); + favicons.clear(); + favicon.favicon_url = GURL("http://mail.google.com/favicon.ico"); + favicon.png_data.push_back('3'); + favicon.urls.insert(url3); + favicons.push_back(favicon); + backend_->SetImportedFavicons(favicons); + URLRow url_row3; + EXPECT_TRUE(backend_->db_->GetRowForURL(url3, &url_row3) == 0); + + // If the URL is bookmarked, it should get added to history with 0 visits. + history_client_.AddBookmark(url3); + backend_->SetImportedFavicons(favicons); + EXPECT_FALSE(backend_->db_->GetRowForURL(url3, &url_row3) == 0); + EXPECT_TRUE(url_row3.visit_count() == 0); +} +#endif // !defined(OS_ANDROID) + +TEST_F(HistoryBackendTest, StripUsernamePasswordTest) { + ASSERT_TRUE(backend_.get()); + + GURL url("http://anyuser:anypass@www.google.com"); + GURL stripped_url("http://www.google.com"); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Visit the url with username, password. + backend_->AddPageVisit(url, base::Time::Now(), 0, + ui::PageTransitionFromInt( + ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)), + history::SOURCE_BROWSED); + + // Fetch the row information about stripped url from history db. + VisitVector visits; + URLID row_id = backend_->db_->GetRowForURL(stripped_url, NULL); + backend_->db_->GetVisitsForURL(row_id, &visits); + + // Check if stripped url is stored in database. + ASSERT_EQ(1U, visits.size()); +} + +TEST_F(HistoryBackendTest, AddPageVisitBackForward) { + ASSERT_TRUE(backend_.get()); + + GURL url("http://www.google.com"); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Visit the url after typing it. + backend_->AddPageVisit(url, base::Time::Now(), 0, + ui::PAGE_TRANSITION_TYPED, + history::SOURCE_BROWSED); + + // Ensure both the typed count and visit count are 1. + VisitVector visits; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + EXPECT_EQ(1, row.typed_count()); + EXPECT_EQ(1, row.visit_count()); + + // Visit the url again via back/forward. + backend_->AddPageVisit(url, base::Time::Now(), 0, + ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK), + history::SOURCE_BROWSED); + + // Ensure the typed count is still 1 but the visit count is 2. + id = backend_->db()->GetRowForURL(url, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + EXPECT_EQ(1, row.typed_count()); + EXPECT_EQ(2, row.visit_count()); +} + +TEST_F(HistoryBackendTest, AddPageVisitRedirectBackForward) { + ASSERT_TRUE(backend_.get()); + + GURL url1("http://www.google.com"); + GURL url2("http://www.chromium.org"); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Visit a typed URL with a redirect. + backend_->AddPageVisit(url1, base::Time::Now(), 0, + ui::PAGE_TRANSITION_TYPED, + history::SOURCE_BROWSED); + backend_->AddPageVisit(url2, base::Time::Now(), 0, + ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_CLIENT_REDIRECT), + history::SOURCE_BROWSED); + + // Ensure the redirected URL does not count as typed. + VisitVector visits; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url2, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + EXPECT_EQ(0, row.typed_count()); + EXPECT_EQ(1, row.visit_count()); + + // Visit the redirected url again via back/forward. + backend_->AddPageVisit(url2, base::Time::Now(), 0, + ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | + ui::PAGE_TRANSITION_FORWARD_BACK | + ui::PAGE_TRANSITION_CLIENT_REDIRECT), + history::SOURCE_BROWSED); + + // Ensure the typed count is still 1 but the visit count is 2. + id = backend_->db()->GetRowForURL(url2, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + EXPECT_EQ(0, row.typed_count()); + EXPECT_EQ(2, row.visit_count()); +} + +TEST_F(HistoryBackendTest, AddPageVisitSource) { + ASSERT_TRUE(backend_.get()); + + GURL url("http://www.google.com"); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Assume visiting the url from an externsion. + backend_->AddPageVisit( + url, base::Time::Now(), 0, ui::PAGE_TRANSITION_TYPED, + history::SOURCE_EXTENSION); + // Assume the url is imported from Firefox. + backend_->AddPageVisit(url, base::Time::Now(), 0, + ui::PAGE_TRANSITION_TYPED, + history::SOURCE_FIREFOX_IMPORTED); + // Assume this url is also synced. + backend_->AddPageVisit(url, base::Time::Now(), 0, + ui::PAGE_TRANSITION_TYPED, + history::SOURCE_SYNCED); + + // Fetch the row information about the url from history db. + VisitVector visits; + URLID row_id = backend_->db_->GetRowForURL(url, NULL); + backend_->db_->GetVisitsForURL(row_id, &visits); + + // Check if all the visits to the url are stored in database. + ASSERT_EQ(3U, visits.size()); + VisitSourceMap visit_sources; + ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); + ASSERT_EQ(3U, visit_sources.size()); + int sources = 0; + for (int i = 0; i < 3; i++) { + switch (visit_sources[visits[i].visit_id]) { + case history::SOURCE_EXTENSION: + sources |= 0x1; + break; + case history::SOURCE_FIREFOX_IMPORTED: + sources |= 0x2; + break; + case history::SOURCE_SYNCED: + sources |= 0x4; + default: + break; } } - // Re-open the db using the HistoryDatabase, which should migrate to version - // 27, creating the by_ext_id and by_ext_name columns. - CreateBackendAndDatabase(); - DeleteBackend(); - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - // The version should have been updated. - int cur_version = HistoryDatabase::GetCurrentVersion(); - ASSERT_LE(27, cur_version); - { - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(cur_version, s.ColumnInt(0)); - } - { - sql::Statement s(db.GetUniqueStatement( - "SELECT by_ext_id, by_ext_name from downloads")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(std::string(), s.ColumnString(0)); - EXPECT_EQ(std::string(), s.ColumnString(1)); - } + EXPECT_EQ(0x7, sources); +} + +TEST_F(HistoryBackendTest, AddPageVisitNotLastVisit) { + ASSERT_TRUE(backend_.get()); + + GURL url("http://www.google.com"); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Create visit times + base::Time recent_time = base::Time::Now(); + base::TimeDelta visit_age = base::TimeDelta::FromDays(3); + base::Time older_time = recent_time - visit_age; + + // Visit the url with recent time. + backend_->AddPageVisit(url, recent_time, 0, + ui::PageTransitionFromInt( + ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)), + history::SOURCE_BROWSED); + + // Add to the url a visit with older time (could be syncing from another + // client, etc.). + backend_->AddPageVisit(url, older_time, 0, + ui::PageTransitionFromInt( + ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)), + history::SOURCE_SYNCED); + + // Fetch the row information about url from history db. + VisitVector visits; + URLRow row; + URLID row_id = backend_->db_->GetRowForURL(url, &row); + backend_->db_->GetVisitsForURL(row_id, &visits); + + // Last visit time should be the most recent time, not the most recently added + // visit. + ASSERT_EQ(2U, visits.size()); + ASSERT_EQ(recent_time, row.last_visit()); +} + +TEST_F(HistoryBackendTest, AddPageVisitFiresNotificationWithCorrectDetails) { + ASSERT_TRUE(backend_.get()); + + GURL url1("http://www.google.com"); + GURL url2("http://maps.google.com"); + + // Clear all history. + backend_->DeleteAllHistory(); + ClearBroadcastedNotifications(); + + // Visit two distinct URLs, the second one twice. + backend_->AddPageVisit(url1, base::Time::Now(), 0, + ui::PAGE_TRANSITION_LINK, + history::SOURCE_BROWSED); + for (int i = 0; i < 2; ++i) { + backend_->AddPageVisit(url2, base::Time::Now(), 0, + ui::PAGE_TRANSITION_TYPED, + history::SOURCE_BROWSED); } + + URLRow stored_row1, stored_row2; + EXPECT_NE(0, backend_->db_->GetRowForURL(url1, &stored_row1)); + EXPECT_NE(0, backend_->db_->GetRowForURL(url2, &stored_row2)); + + // Expect that HistoryServiceObserver::OnURLVisited has been called 3 times, + // and that each time the URLRows have the correct URLs and IDs set. + ASSERT_EQ(3, num_url_visited_notifications()); + EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[0].first, + ui::PAGE_TRANSITION_LINK)); + EXPECT_EQ(stored_row1.id(), url_visited_notifications()[0].second.id()); + EXPECT_EQ(stored_row1.url(), url_visited_notifications()[0].second.url()); + + EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[1].first, + ui::PAGE_TRANSITION_TYPED)); + EXPECT_EQ(stored_row2.id(), url_visited_notifications()[1].second.id()); + EXPECT_EQ(stored_row2.url(), url_visited_notifications()[1].second.url()); + + EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[2].first, + ui::PAGE_TRANSITION_TYPED)); + EXPECT_EQ(stored_row2.id(), url_visited_notifications()[2].second.id()); + EXPECT_EQ(stored_row2.url(), url_visited_notifications()[2].second.url()); } -TEST_F(HistoryBackendDBTest, MigrateDownloadValidators) { - base::Time now(base::Time::Now()); - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(27)); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads (id, current_path, target_path, start_time, " - "received_bytes, total_bytes, state, danger_type, interrupt_reason, " - "end_time, opened, referrer, by_ext_id, by_ext_name) VALUES " - "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); - s.BindInt64(0, 1); - s.BindString(1, "current_path"); - s.BindString(2, "target_path"); - s.BindInt64(3, now.ToTimeT()); - s.BindInt64(4, 100); - s.BindInt64(5, 100); - s.BindInt(6, 1); - s.BindInt(7, 0); - s.BindInt(8, 0); - s.BindInt64(9, now.ToTimeT()); - s.BindInt(10, 1); - s.BindString(11, "referrer"); - s.BindString(12, "by extension ID"); - s.BindString(13, "by extension name"); - ASSERT_TRUE(s.Run()); - } - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads_url_chains (id, chain_index, url) VALUES " - "(?, ?, ?)")); - s.BindInt64(0, 4); - s.BindInt64(1, 0); - s.BindString(2, "url"); - ASSERT_TRUE(s.Run()); - } +TEST_F(HistoryBackendTest, AddPageArgsSource) { + ASSERT_TRUE(backend_.get()); + + GURL url("http://testpageargs.com"); + + // Assume this page is browsed by user. + HistoryAddPageArgs request1(url, base::Time::Now(), NULL, 0, GURL(), + history::RedirectList(), + ui::PAGE_TRANSITION_KEYWORD_GENERATED, + history::SOURCE_BROWSED, false); + backend_->AddPage(request1); + // Assume this page is synced. + HistoryAddPageArgs request2(url, base::Time::Now(), NULL, 0, GURL(), + history::RedirectList(), + ui::PAGE_TRANSITION_LINK, + history::SOURCE_SYNCED, false); + backend_->AddPage(request2); + // Assume this page is browsed again. + HistoryAddPageArgs request3(url, base::Time::Now(), NULL, 0, GURL(), + history::RedirectList(), + ui::PAGE_TRANSITION_TYPED, + history::SOURCE_BROWSED, false); + backend_->AddPage(request3); + + // Three visits should be added with proper sources. + VisitVector visits; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(3U, visits.size()); + VisitSourceMap visit_sources; + ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); + ASSERT_EQ(1U, visit_sources.size()); + EXPECT_EQ(history::SOURCE_SYNCED, visit_sources.begin()->second); +} + +TEST_F(HistoryBackendTest, AddVisitsSource) { + ASSERT_TRUE(backend_.get()); + + GURL url1("http://www.cnn.com"); + std::vector<VisitInfo> visits1, visits2; + visits1.push_back(VisitInfo( + base::Time::Now() - base::TimeDelta::FromDays(5), + ui::PAGE_TRANSITION_LINK)); + visits1.push_back(VisitInfo( + base::Time::Now() - base::TimeDelta::FromDays(1), + ui::PAGE_TRANSITION_LINK)); + visits1.push_back(VisitInfo( + base::Time::Now(), ui::PAGE_TRANSITION_LINK)); + + GURL url2("http://www.example.com"); + visits2.push_back(VisitInfo( + base::Time::Now() - base::TimeDelta::FromDays(10), + ui::PAGE_TRANSITION_LINK)); + visits2.push_back(VisitInfo(base::Time::Now(), ui::PAGE_TRANSITION_LINK)); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Add the visits. + backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); + backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED); + + // Verify the visits were added with their sources. + VisitVector visits; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url1, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(3U, visits.size()); + VisitSourceMap visit_sources; + ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); + ASSERT_EQ(3U, visit_sources.size()); + for (int i = 0; i < 3; i++) + EXPECT_EQ(history::SOURCE_IE_IMPORTED, visit_sources[visits[i].visit_id]); + id = backend_->db()->GetRowForURL(url2, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(2U, visits.size()); + ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); + ASSERT_EQ(2U, visit_sources.size()); + for (int i = 0; i < 2; i++) + EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]); +} + +TEST_F(HistoryBackendTest, GetMostRecentVisits) { + ASSERT_TRUE(backend_.get()); + + GURL url1("http://www.cnn.com"); + std::vector<VisitInfo> visits1; + visits1.push_back(VisitInfo( + base::Time::Now() - base::TimeDelta::FromDays(5), + ui::PAGE_TRANSITION_LINK)); + visits1.push_back(VisitInfo( + base::Time::Now() - base::TimeDelta::FromDays(1), + ui::PAGE_TRANSITION_LINK)); + visits1.push_back(VisitInfo( + base::Time::Now(), ui::PAGE_TRANSITION_LINK)); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Add the visits. + backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); + + // Verify the visits were added with their sources. + VisitVector visits; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url1, &row); + ASSERT_TRUE(backend_->db()->GetMostRecentVisitsForURL(id, 1, &visits)); + ASSERT_EQ(1U, visits.size()); + EXPECT_EQ(visits1[2].first, visits[0].visit_time); +} + +TEST_F(HistoryBackendTest, RemoveVisitsTransitions) { + ASSERT_TRUE(backend_.get()); + + // Clear all history. + backend_->DeleteAllHistory(); + + GURL url1("http://www.cnn.com"); + VisitInfo typed_visit( + base::Time::Now() - base::TimeDelta::FromDays(6), + ui::PAGE_TRANSITION_TYPED); + VisitInfo reload_visit( + base::Time::Now() - base::TimeDelta::FromDays(5), + ui::PAGE_TRANSITION_RELOAD); + VisitInfo link_visit( + base::Time::Now() - base::TimeDelta::FromDays(4), + ui::PAGE_TRANSITION_LINK); + std::vector<VisitInfo> visits_to_add; + visits_to_add.push_back(typed_visit); + visits_to_add.push_back(reload_visit); + visits_to_add.push_back(link_visit); + + // Add the visits. + backend_->AddVisits(url1, visits_to_add, history::SOURCE_SYNCED); + + // Verify that the various counts are what we expect. + VisitVector visits; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url1, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(3U, visits.size()); + ASSERT_EQ(1, row.typed_count()); + ASSERT_EQ(2, row.visit_count()); + + // Now, delete the typed visit and verify that typed_count is updated. + ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0]))); + id = backend_->db()->GetRowForURL(url1, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(2U, visits.size()); + ASSERT_EQ(0, row.typed_count()); + ASSERT_EQ(1, row.visit_count()); + + // Delete the reload visit now and verify that none of the counts have + // changed. + ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0]))); + id = backend_->db()->GetRowForURL(url1, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(1U, visits.size()); + ASSERT_EQ(0, row.typed_count()); + ASSERT_EQ(1, row.visit_count()); + + // Delete the last visit and verify that we delete the URL. + ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0]))); + ASSERT_EQ(0, backend_->db()->GetRowForURL(url1, &row)); +} + +TEST_F(HistoryBackendTest, RemoveVisitsSource) { + ASSERT_TRUE(backend_.get()); + + GURL url1("http://www.cnn.com"); + std::vector<VisitInfo> visits1, visits2; + visits1.push_back(VisitInfo( + base::Time::Now() - base::TimeDelta::FromDays(5), + ui::PAGE_TRANSITION_LINK)); + visits1.push_back(VisitInfo(base::Time::Now(), + ui::PAGE_TRANSITION_LINK)); + + GURL url2("http://www.example.com"); + visits2.push_back(VisitInfo( + base::Time::Now() - base::TimeDelta::FromDays(10), + ui::PAGE_TRANSITION_LINK)); + visits2.push_back(VisitInfo(base::Time::Now(), ui::PAGE_TRANSITION_LINK)); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Add the visits. + backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); + backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED); + + // Verify the visits of url1 were added. + VisitVector visits; + URLRow row; + URLID id = backend_->db()->GetRowForURL(url1, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(2U, visits.size()); + // Remove these visits. + ASSERT_TRUE(backend_->RemoveVisits(visits)); + + // Now check only url2's source in visit_source table. + VisitSourceMap visit_sources; + ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); + ASSERT_EQ(0U, visit_sources.size()); + id = backend_->db()->GetRowForURL(url2, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); + ASSERT_EQ(2U, visits.size()); + ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources)); + ASSERT_EQ(2U, visit_sources.size()); + for (int i = 0; i < 2; i++) + EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]); +} + +// Test for migration of adding visit_source table. +TEST_F(HistoryBackendTest, MigrationVisitSource) { + ASSERT_TRUE(backend_.get()); + backend_->Closing(); + backend_ = NULL; + + base::FilePath old_history_path; + ASSERT_TRUE(GetTestDataHistoryDir(&old_history_path)); + old_history_path = old_history_path.AppendASCII("HistoryNoSource"); + + // Copy history database file to current directory so that it will be deleted + // in Teardown. + base::FilePath new_history_path(test_dir()); + base::DeleteFile(new_history_path, true); + base::CreateDirectory(new_history_path); + base::FilePath new_history_file = new_history_path.Append(kHistoryFilename); + ASSERT_TRUE(base::CopyFile(old_history_path, new_history_file)); + + backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this), + &history_client_); + backend_->Init(std::string(), false, + TestHistoryDatabaseParamsForPath(new_history_path)); + backend_->Closing(); + backend_ = NULL; + + // Now the database should already be migrated. + // Check version first. + int cur_version = HistoryDatabase::GetCurrentVersion(); + sql::Connection db; + ASSERT_TRUE(db.Open(new_history_file)); + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + ASSERT_TRUE(s.Step()); + int file_version = s.ColumnInt(0); + EXPECT_EQ(cur_version, file_version); + + // Check visit_source table is created and empty. + s.Assign(db.GetUniqueStatement( + "SELECT name FROM sqlite_master WHERE name=\"visit_source\"")); + ASSERT_TRUE(s.Step()); + s.Assign(db.GetUniqueStatement("SELECT * FROM visit_source LIMIT 10")); + EXPECT_FALSE(s.Step()); +} + +// Test that SetFaviconMappingsForPageAndRedirects correctly updates icon +// mappings based on redirects, icon URLs and icon types. +TEST_F(HistoryBackendTest, SetFaviconMappingsForPageAndRedirects) { + // Init recent_redirects_ + const GURL url1("http://www.google.com"); + const GURL url2("http://www.google.com/m"); + URLRow url_info1(url1); + url_info1.set_visit_count(0); + url_info1.set_typed_count(0); + url_info1.set_last_visit(base::Time()); + url_info1.set_hidden(false); + backend_->db_->AddURL(url_info1); + + URLRow url_info2(url2); + url_info2.set_visit_count(0); + url_info2.set_typed_count(0); + url_info2.set_last_visit(base::Time()); + url_info2.set_hidden(false); + backend_->db_->AddURL(url_info2); + + history::RedirectList redirects; + redirects.push_back(url2); + redirects.push_back(url1); + backend_->recent_redirects_.Put(url1, redirects); + + const GURL icon_url1("http://www.google.com/icon"); + const GURL icon_url2("http://www.google.com/icon2"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); + + // Add a favicon. + backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON)); + + // Add one touch_icon + backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::TOUCH_ICON)); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); + + // Add one TOUCH_PRECOMPOSED_ICON + backend_->SetFavicons( + url1, favicon_base::TOUCH_PRECOMPOSED_ICON, icon_url1, bitmaps); + // The touch_icon was replaced. + EXPECT_EQ(0u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); + EXPECT_EQ( + 1u, + NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON)); + EXPECT_EQ( + 1u, + NumIconMappingsForPageURL(url2, favicon_base::TOUCH_PRECOMPOSED_ICON)); + + // Add a touch_icon. + backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); + // The TOUCH_PRECOMPOSED_ICON was replaced. + EXPECT_EQ( + 0u, + NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON)); + + // Add a different favicon. + backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url2, bitmaps); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON)); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON)); + EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON)); +} + +// Test that there is no churn in icon mappings from calling +// SetFavicons() twice with the same |bitmaps| parameter. +TEST_F(HistoryBackendTest, SetFaviconMappingsForPageDuplicates) { + const GURL url("http://www.google.com/"); + const GURL icon_url("http://www.google.com/icon"); + + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); + + backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps); + + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + url, favicon_base::FAVICON, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + IconMappingID mapping_id = icon_mappings[0].mapping_id; + + backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps); + + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + url, favicon_base::FAVICON, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + + // The same row in the icon_mapping table should be used for the mapping as + // before. + EXPECT_EQ(mapping_id, icon_mappings[0].mapping_id); +} + +// Test that calling SetFavicons() with FaviconBitmapData of different pixel +// sizes than the initially passed in FaviconBitmapData deletes the no longer +// used favicon bitmaps. +TEST_F(HistoryBackendTest, SetFaviconsDeleteBitmaps) { + const GURL page_url("http://www.google.com/"); + const GURL icon_url("http://www.google.com/icon"); + + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + // Test initial state. + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url, icon_mappings[0].icon_url); + EXPECT_EQ(favicon_base::FAVICON, icon_mappings[0].icon_type); + favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id; + + std::vector<FaviconBitmap> favicon_bitmaps; + EXPECT_TRUE(GetSortedFaviconBitmaps(favicon_id, &favicon_bitmaps)); + EXPECT_EQ(2u, favicon_bitmaps.size()); + FaviconBitmapID small_bitmap_id = favicon_bitmaps[0].bitmap_id; + EXPECT_NE(0, small_bitmap_id); + EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmaps[0].bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmaps[0].pixel_size); + FaviconBitmapID large_bitmap_id = favicon_bitmaps[1].bitmap_id; + EXPECT_NE(0, large_bitmap_id); + EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, favicon_bitmaps[1].bitmap_data)); + EXPECT_EQ(kLargeSize, favicon_bitmaps[1].pixel_size); + + // Call SetFavicons() with bitmap data for only the large bitmap. Check that + // the small bitmap is in fact deleted. + bitmaps.clear(); + bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kLargeEdgeSize)); + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + scoped_refptr<base::RefCountedMemory> bitmap_data_out; + gfx::Size pixel_size_out; + EXPECT_FALSE(backend_->thumbnail_db_->GetFaviconBitmap(small_bitmap_id, + NULL, NULL, &bitmap_data_out, &pixel_size_out)); + EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmap(large_bitmap_id, + NULL, NULL, &bitmap_data_out, &pixel_size_out)); + EXPECT_TRUE(BitmapColorEqual(SK_ColorWHITE, bitmap_data_out)); + EXPECT_EQ(kLargeSize, pixel_size_out); + + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); + + // Notifications should have been broadcast for each call to SetFavicons(). + EXPECT_EQ(2, favicon_changed_notifications()); +} + +// Test updating a single favicon bitmap's data via SetFavicons. +TEST_F(HistoryBackendTest, SetFaviconsReplaceBitmapData) { + const GURL page_url("http://www.google.com/"); + const GURL icon_url("http://www.google.com/icon"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + + // Add bitmap to the database. + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + favicon_base::FaviconID original_favicon_id = + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + icon_url, favicon_base::FAVICON, NULL); + EXPECT_NE(0, original_favicon_id); + FaviconBitmap original_favicon_bitmap; + EXPECT_TRUE( + GetOnlyFaviconBitmap(original_favicon_id, &original_favicon_bitmap)); + EXPECT_TRUE( + BitmapColorEqual(SK_ColorBLUE, original_favicon_bitmap.bitmap_data)); + + EXPECT_EQ(1, favicon_changed_notifications()); + + // Call SetFavicons() with completely identical data. + bitmaps[0] = CreateBitmap(SK_ColorBLUE, kSmallEdgeSize); + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + favicon_base::FaviconID updated_favicon_id = + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + icon_url, favicon_base::FAVICON, NULL); + EXPECT_NE(0, updated_favicon_id); + FaviconBitmap updated_favicon_bitmap; + EXPECT_TRUE( + GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap)); + EXPECT_TRUE( + BitmapColorEqual(SK_ColorBLUE, updated_favicon_bitmap.bitmap_data)); + + // Because the bitmap data is byte equivalent, no notifications should have + // been broadcasted. + EXPECT_EQ(1, favicon_changed_notifications()); + + // Call SetFavicons() with a different bitmap of the same size. + bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize); + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + updated_favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + icon_url, favicon_base::FAVICON, NULL); + EXPECT_NE(0, updated_favicon_id); + EXPECT_TRUE( + GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap)); + EXPECT_TRUE( + BitmapColorEqual(SK_ColorWHITE, updated_favicon_bitmap.bitmap_data)); + + // There should be no churn in FaviconIDs or FaviconBitmapIds even though + // the bitmap data changed. + EXPECT_EQ(original_favicon_bitmap.icon_id, updated_favicon_bitmap.icon_id); + EXPECT_EQ(original_favicon_bitmap.bitmap_id, + updated_favicon_bitmap.bitmap_id); + + // A notification should have been broadcasted as the favicon bitmap data has + // changed. + EXPECT_EQ(2, favicon_changed_notifications()); +} + +// Test that if two pages share the same FaviconID, changing the favicon for +// one page does not affect the other. +TEST_F(HistoryBackendTest, SetFaviconsSameFaviconURLForTwoPages) { + GURL icon_url("http://www.google.com/favicon.ico"); + GURL icon_url_new("http://www.google.com/favicon2.ico"); + GURL page_url1("http://www.google.com"); + GURL page_url2("http://www.google.ca"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); + + backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps); + + std::vector<GURL> icon_urls; + icon_urls.push_back(icon_url); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; + backend_->UpdateFaviconMappingsAndFetch(page_url2, + icon_urls, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results); + + // Check that the same FaviconID is mapped to both page URLs. + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + page_url1, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id; + EXPECT_NE(0, favicon_id); + + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + page_url2, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); + + // Change the icon URL that |page_url1| is mapped to. + bitmaps.clear(); + bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kSmallEdgeSize)); + backend_->SetFavicons( + page_url1, favicon_base::FAVICON, icon_url_new, bitmaps); + + // |page_url1| should map to a new FaviconID and have valid bitmap data. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + page_url1, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url_new, icon_mappings[0].icon_url); + EXPECT_NE(favicon_id, icon_mappings[0].icon_id); + + std::vector<FaviconBitmap> favicon_bitmaps; + EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps( + icon_mappings[0].icon_id, &favicon_bitmaps)); + EXPECT_EQ(1u, favicon_bitmaps.size()); + + // |page_url2| should still map to the same FaviconID and have valid bitmap + // data. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( + page_url2, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); + + favicon_bitmaps.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(favicon_id, + &favicon_bitmaps)); + EXPECT_EQ(2u, favicon_bitmaps.size()); + + // A notification should have been broadcast for each call to SetFavicons() + // and each call to UpdateFaviconMappingsAndFetch(). + EXPECT_EQ(3, favicon_changed_notifications()); +} + +// Test that no notifications are broadcast as a result of calling +// UpdateFaviconMappingsAndFetch() for an icon URL which is already +// mapped to the passed in page URL. +TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchNoChange) { + GURL page_url("http://www.google.com"); + GURL icon_url("http://www.google.com/favicon.ico"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + favicon_base::FaviconID icon_id = + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + icon_url, favicon_base::FAVICON, NULL); + EXPECT_NE(0, icon_id); + EXPECT_EQ(1, favicon_changed_notifications()); + + std::vector<GURL> icon_urls; + icon_urls.push_back(icon_url); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; + backend_->UpdateFaviconMappingsAndFetch(page_url, + icon_urls, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results); + + EXPECT_EQ(icon_id, + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + icon_url, favicon_base::FAVICON, NULL)); + + // No notification should have been broadcast as no icon mapping, favicon, + // or favicon bitmap was updated, added or removed. + EXPECT_EQ(1, favicon_changed_notifications()); +} + +// Test repeatedly calling MergeFavicon(). |page_url| is initially not known +// to the database. +TEST_F(HistoryBackendTest, MergeFaviconPageURLNotInDB) { + GURL page_url("http://www.google.com"); + GURL icon_url("http:/www.google.com/favicon.ico"); + + std::vector<unsigned char> data; + data.push_back('a'); + scoped_refptr<base::RefCountedBytes> bitmap_data( + new base::RefCountedBytes(data)); + + backend_->MergeFavicon( + page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); + + // |page_url| should now be mapped to |icon_url| and the favicon bitmap should + // not be expired. + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url, icon_mappings[0].icon_url); + + FaviconBitmap favicon_bitmap; + EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); + EXPECT_NE(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); + + data[0] = 'b'; + bitmap_data = new base::RefCountedBytes(data); + backend_->MergeFavicon( + page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); + + // |page_url| should still have a single favicon bitmap. The bitmap data + // should be updated. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url, icon_mappings[0].icon_url); + + EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); + EXPECT_EQ(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); +} + +// Test calling MergeFavicon() when |page_url| is known to the database. +TEST_F(HistoryBackendTest, MergeFaviconPageURLInDB) { + GURL page_url("http://www.google.com"); + GURL icon_url1("http:/www.google.com/favicon.ico"); + GURL icon_url2("http://www.google.com/favicon2.ico"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps); + + // Test initial state. + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); + + FaviconBitmap favicon_bitmap; + EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); + EXPECT_NE(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); + + EXPECT_EQ(1, favicon_changed_notifications()); + + // 1) Merge identical favicon bitmap. + std::vector<unsigned char> data; + gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data); + scoped_refptr<base::RefCountedBytes> bitmap_data( + new base::RefCountedBytes(data)); + backend_->MergeFavicon( + page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize); + + // All the data should stay the same and no notifications should have been + // sent. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); + + EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); + EXPECT_NE(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); + + EXPECT_EQ(1, favicon_changed_notifications()); + + // 2) Merge favicon bitmap of the same size. + data.clear(); + data.push_back('b'); + bitmap_data = new base::RefCountedBytes(data); + backend_->MergeFavicon( + page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize); + + // The small favicon bitmap at |icon_url1| should be overwritten. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); + + EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); + EXPECT_EQ(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); + + // 3) Merge favicon for the same icon URL, but a pixel size for which there is + // no favicon bitmap. + data[0] = 'c'; + bitmap_data = new base::RefCountedBytes(data); + backend_->MergeFavicon( + page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kTinySize); + + // A new favicon bitmap should be created and the preexisting favicon bitmap + // ('b') should be expired. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url1, icon_mappings[0].icon_url); + + std::vector<FaviconBitmap> favicon_bitmaps; + EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id, + &favicon_bitmaps)); + EXPECT_NE(base::Time(), favicon_bitmaps[0].last_updated); + EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data)); + EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size); + EXPECT_EQ(base::Time(), favicon_bitmaps[1].last_updated); + EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmaps[1].bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size); + + // 4) Merge favicon for an icon URL different from the icon URLs already + // mapped to page URL. + data[0] = 'd'; + bitmap_data = new base::RefCountedBytes(data); + backend_->MergeFavicon( + page_url, icon_url2, favicon_base::FAVICON, bitmap_data, kSmallSize); + + // The existing favicon bitmaps should be copied over to the newly created + // favicon at |icon_url2|. |page_url| should solely be mapped to |icon_url2|. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url2, icon_mappings[0].icon_url); + + favicon_bitmaps.clear(); + EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id, + &favicon_bitmaps)); + EXPECT_EQ(base::Time(), favicon_bitmaps[0].last_updated); + EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data)); + EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size); + // The favicon being merged should take precedence over the preexisting + // favicon bitmaps. + EXPECT_NE(base::Time(), favicon_bitmaps[1].last_updated); + EXPECT_TRUE(BitmapDataEqual('d', favicon_bitmaps[1].bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size); + + // A notification should have been broadcast for each call to SetFavicons() + // and MergeFavicon(). + EXPECT_EQ(4, favicon_changed_notifications()); +} + +// Test calling MergeFavicon() when |icon_url| is known to the database but not +// mapped to |page_url|. +TEST_F(HistoryBackendTest, MergeFaviconIconURLMappedToDifferentPageURL) { + GURL page_url1("http://www.google.com"); + GURL page_url2("http://news.google.com"); + GURL page_url3("http://maps.google.com"); + GURL icon_url("http:/www.google.com/favicon.ico"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + + backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps); + + // Test initial state. + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_url, icon_mappings[0].icon_url); + + FaviconBitmap favicon_bitmap; + EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap)); + EXPECT_NE(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); + + // 1) Merge in an identical favicon bitmap data but for a different page URL. + std::vector<unsigned char> data; + gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data); + scoped_refptr<base::RefCountedBytes> bitmap_data( + new base::RefCountedBytes(data)); + + backend_->MergeFavicon( + page_url2, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); + + favicon_base::FaviconID favicon_id = + backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + icon_url, favicon_base::FAVICON, NULL); + EXPECT_NE(0, favicon_id); + + EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap)); + EXPECT_NE(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); + + // 2) Merging a favicon bitmap with different bitmap data for the same icon + // URL should overwrite the small favicon bitmap at |icon_url|. + data.clear(); + data.push_back('b'); + bitmap_data = new base::RefCountedBytes(data); + backend_->MergeFavicon( + page_url3, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize); + + favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL( + icon_url, favicon_base::FAVICON, NULL); + EXPECT_NE(0, favicon_id); + + EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap)); + EXPECT_EQ(base::Time(), favicon_bitmap.last_updated); + EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data)); + EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size); + + // |icon_url| should be mapped to all three page URLs. + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); + + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url2, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); + + icon_mappings.clear(); + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url3, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(favicon_id, icon_mappings[0].icon_id); + + // A notification should have been broadcast for each call to SetFavicons() + // and MergeFavicon(). + EXPECT_EQ(3, favicon_changed_notifications()); +} + +// Test that MergeFavicon() does not add more than +// |kMaxFaviconBitmapsPerIconURL| to a favicon. +TEST_F(HistoryBackendTest, MergeFaviconMaxFaviconBitmapsPerIconURL) { + GURL page_url("http://www.google.com"); + std::string icon_url_string("http://www.google.com/favicon.ico"); + size_t replace_index = icon_url_string.size() - 1; + + std::vector<unsigned char> data; + data.push_back('a'); + scoped_refptr<base::RefCountedMemory> bitmap_data = + base::RefCountedBytes::TakeVector(&data); + + int pixel_size = 1; + for (size_t i = 0; i < kMaxFaviconBitmapsPerIconURL + 1; ++i) { + icon_url_string[replace_index] = '0' + i; + GURL icon_url(icon_url_string); + + backend_->MergeFavicon(page_url, + icon_url, + favicon_base::FAVICON, + bitmap_data, + gfx::Size(pixel_size, pixel_size)); + ++pixel_size; } - // Re-open the db using the HistoryDatabase, which should migrate to the - // current version, creating the etag and last_modified columns. - CreateBackendAndDatabase(); - DeleteBackend(); - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - // The version should have been updated. - int cur_version = HistoryDatabase::GetCurrentVersion(); - ASSERT_LE(28, cur_version); - { - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(cur_version, s.ColumnInt(0)); - } - { - sql::Statement s(db.GetUniqueStatement( - "SELECT etag, last_modified from downloads")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(std::string(), s.ColumnString(0)); - EXPECT_EQ(std::string(), s.ColumnString(1)); - } + + // There should be a single favicon mapped to |page_url| with exactly + // kMaxFaviconBitmapsPerIconURL favicon bitmaps. + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + std::vector<FaviconBitmap> favicon_bitmaps; + EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps( + icon_mappings[0].icon_id, &favicon_bitmaps)); + EXPECT_EQ(kMaxFaviconBitmapsPerIconURL, favicon_bitmaps.size()); +} + +// Tests that the favicon set by MergeFavicon() shows up in the result of +// GetFaviconsForURL(). +TEST_F(HistoryBackendTest, MergeFaviconShowsUpInGetFaviconsForURLResult) { + GURL page_url("http://www.google.com"); + GURL icon_url("http://www.google.com/favicon.ico"); + GURL merged_icon_url("http://wwww.google.com/favicon2.ico"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); + + // Set some preexisting favicons for |page_url|. + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + // Merge small favicon. + std::vector<unsigned char> data; + data.push_back('c'); + scoped_refptr<base::RefCountedBytes> bitmap_data( + new base::RefCountedBytes(data)); + backend_->MergeFavicon(page_url, + merged_icon_url, + favicon_base::FAVICON, + bitmap_data, + kSmallSize); + + // Request favicon bitmaps for both 1x and 2x to simulate request done by + // BookmarkModel::GetFavicon(). + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; + backend_->GetFaviconsForURL(page_url, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results); + + EXPECT_EQ(2u, bitmap_results.size()); + const favicon_base::FaviconRawBitmapResult& first_result = bitmap_results[0]; + const favicon_base::FaviconRawBitmapResult& result = + (first_result.pixel_size == kSmallSize) ? first_result + : bitmap_results[1]; + EXPECT_TRUE(BitmapDataEqual('c', result.bitmap_data)); +} + +// Tests GetFaviconsForURL with icon_types priority, +TEST_F(HistoryBackendTest, TestGetFaviconsForURLWithIconTypesPriority) { + GURL page_url("http://www.google.com"); + GURL icon_url("http://www.google.com/favicon.ico"); + GURL touch_icon_url("http://wwww.google.com/touch_icon.ico"); + + std::vector<SkBitmap> favicon_bitmaps; + favicon_bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16)); + favicon_bitmaps.push_back(CreateBitmap(SK_ColorRED, 32)); + + std::vector<SkBitmap> touch_bitmaps; + touch_bitmaps.push_back(CreateBitmap(SK_ColorWHITE, 64)); + + // Set some preexisting favicons for |page_url|. + backend_->SetFavicons( + page_url, favicon_base::FAVICON, icon_url, favicon_bitmaps); + backend_->SetFavicons( + page_url, favicon_base::TOUCH_ICON, touch_icon_url, touch_bitmaps); + + favicon_base::FaviconRawBitmapResult result; + std::vector<int> icon_types; + icon_types.push_back(favicon_base::FAVICON); + icon_types.push_back(favicon_base::TOUCH_ICON); + + backend_->GetLargestFaviconForURL(page_url, icon_types, 16, &result); + + // Verify the result icon is 32x32 favicon. + EXPECT_EQ(gfx::Size(32, 32), result.pixel_size); + EXPECT_EQ(favicon_base::FAVICON, result.icon_type); + + // Change Minimal size to 32x32 and verify the 64x64 touch icon returned. + backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result); + EXPECT_EQ(gfx::Size(64, 64), result.pixel_size); + EXPECT_EQ(favicon_base::TOUCH_ICON, result.icon_type); +} + +// Test the the first types of icon is returned if its size equal to the +// second types icon. +TEST_F(HistoryBackendTest, TestGetFaviconsForURLReturnFavicon) { + GURL page_url("http://www.google.com"); + GURL icon_url("http://www.google.com/favicon.ico"); + GURL touch_icon_url("http://wwww.google.com/touch_icon.ico"); + + std::vector<SkBitmap> favicon_bitmaps; + favicon_bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16)); + favicon_bitmaps.push_back(CreateBitmap(SK_ColorRED, 32)); + + std::vector<SkBitmap> touch_bitmaps; + touch_bitmaps.push_back(CreateBitmap(SK_ColorWHITE, 32)); + + // Set some preexisting favicons for |page_url|. + backend_->SetFavicons( + page_url, favicon_base::FAVICON, icon_url, favicon_bitmaps); + backend_->SetFavicons( + page_url, favicon_base::TOUCH_ICON, touch_icon_url, touch_bitmaps); + + favicon_base::FaviconRawBitmapResult result; + std::vector<int> icon_types; + icon_types.push_back(favicon_base::FAVICON); + icon_types.push_back(favicon_base::TOUCH_ICON); + + backend_->GetLargestFaviconForURL(page_url, icon_types, 16, &result); + + // Verify the result icon is 32x32 favicon. + EXPECT_EQ(gfx::Size(32, 32), result.pixel_size); + EXPECT_EQ(favicon_base::FAVICON, result.icon_type); + + // Change minimal size to 32x32 and verify the 32x32 favicon returned. + favicon_base::FaviconRawBitmapResult result1; + backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result1); + EXPECT_EQ(gfx::Size(32, 32), result1.pixel_size); + EXPECT_EQ(favicon_base::FAVICON, result1.icon_type); +} + +// Test the favicon is returned if its size is smaller than minimal size, +// because it is only one available. +TEST_F(HistoryBackendTest, TestGetFaviconsForURLReturnFaviconEvenItSmaller) { + GURL page_url("http://www.google.com"); + GURL icon_url("http://www.google.com/favicon.ico"); + + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16)); + + // Set preexisting favicons for |page_url|. + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + favicon_base::FaviconRawBitmapResult result; + std::vector<int> icon_types; + icon_types.push_back(favicon_base::FAVICON); + icon_types.push_back(favicon_base::TOUCH_ICON); + + backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result); + + // Verify 16x16 icon is returned, even it small than minimal_size. + EXPECT_EQ(gfx::Size(16, 16), result.pixel_size); + EXPECT_EQ(favicon_base::FAVICON, result.icon_type); +} + +// Test UpdateFaviconMapingsAndFetch() when multiple icon types are passed in. +TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchMultipleIconTypes) { + GURL page_url1("http://www.google.com"); + GURL page_url2("http://news.google.com"); + GURL page_url3("http://mail.google.com"); + GURL icon_urla("http://www.google.com/favicon1.ico"); + GURL icon_urlb("http://www.google.com/favicon2.ico"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + + // |page_url1| is mapped to |icon_urla| which if of type TOUCH_ICON. + backend_->SetFavicons( + page_url1, favicon_base::TOUCH_ICON, icon_urla, bitmaps); + + // |page_url2| is mapped to |icon_urlb| which is of type + // TOUCH_PRECOMPOSED_ICON. + backend_->SetFavicons( + page_url2, favicon_base::TOUCH_PRECOMPOSED_ICON, icon_urlb, bitmaps); + + std::vector<GURL> icon_urls; + icon_urls.push_back(icon_urla); + icon_urls.push_back(icon_urlb); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; + backend_->UpdateFaviconMappingsAndFetch( + page_url3, + icon_urls, + (favicon_base::TOUCH_ICON | favicon_base::TOUCH_PRECOMPOSED_ICON), + GetEdgeSizesSmallAndLarge(), + &bitmap_results); + + // |page_url1| and |page_url2| should still be mapped to the same icon URLs. + std::vector<IconMapping> icon_mappings; + EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1, + &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_urla, icon_mappings[0].icon_url); + EXPECT_EQ(favicon_base::TOUCH_ICON, icon_mappings[0].icon_type); + + icon_mappings.clear(); + EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url2, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_urlb, icon_mappings[0].icon_url); + EXPECT_EQ(favicon_base::TOUCH_PRECOMPOSED_ICON, icon_mappings[0].icon_type); + + // |page_url3| should be mapped only to |icon_urlb| as TOUCH_PRECOMPOSED_ICON + // is the largest IconType. + icon_mappings.clear(); + EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url3, &icon_mappings)); + EXPECT_EQ(1u, icon_mappings.size()); + EXPECT_EQ(icon_urlb, icon_mappings[0].icon_url); + EXPECT_EQ(favicon_base::TOUCH_PRECOMPOSED_ICON, icon_mappings[0].icon_type); +} + +// Test the results of GetFaviconsFromDB() when there are no found favicons. +TEST_F(HistoryBackendTest, GetFaviconsFromDBEmpty) { + const GURL page_url("http://www.google.com/"); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; + EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results)); + EXPECT_TRUE(bitmap_results.empty()); +} + +// Test the results of GetFaviconsFromDB() when there are matching favicons +// but there are no associated favicon bitmaps. +TEST_F(HistoryBackendTest, GetFaviconsFromDBNoFaviconBitmaps) { + const GURL page_url("http://www.google.com/"); + const GURL icon_url("http://www.google.com/icon1"); + + favicon_base::FaviconID icon_id = + backend_->thumbnail_db_->AddFavicon(icon_url, favicon_base::FAVICON); + EXPECT_NE(0, icon_id); + EXPECT_NE(0, backend_->thumbnail_db_->AddIconMapping(page_url, icon_id)); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; + EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results_out)); + EXPECT_TRUE(bitmap_results_out.empty()); +} + +// Test that GetFaviconsFromDB() returns results for the bitmaps which most +// closely match the passed in the desired pixel sizes. +TEST_F(HistoryBackendTest, GetFaviconsFromDBSelectClosestMatch) { + const GURL page_url("http://www.google.com/"); + const GURL icon_url("http://www.google.com/icon1"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kTinyEdgeSize)); + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize)); + + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; + EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results_out)); + + // The bitmap data for the small and large bitmaps should be returned as their + // sizes match exactly. + EXPECT_EQ(2u, bitmap_results_out.size()); + // No required order for results. + if (bitmap_results_out[0].pixel_size == kLargeSize) { + favicon_base::FaviconRawBitmapResult tmp_result = bitmap_results_out[0]; + bitmap_results_out[0] = bitmap_results_out[1]; + bitmap_results_out[1] = tmp_result; } + + EXPECT_FALSE(bitmap_results_out[0].expired); + EXPECT_TRUE( + BitmapColorEqual(SK_ColorBLUE, bitmap_results_out[0].bitmap_data)); + EXPECT_EQ(kSmallSize, bitmap_results_out[0].pixel_size); + EXPECT_EQ(icon_url, bitmap_results_out[0].icon_url); + EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[0].icon_type); + + EXPECT_FALSE(bitmap_results_out[1].expired); + EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, bitmap_results_out[1].bitmap_data)); + EXPECT_EQ(kLargeSize, bitmap_results_out[1].pixel_size); + EXPECT_EQ(icon_url, bitmap_results_out[1].icon_url); + EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[1].icon_type); } -TEST_F(HistoryBackendDBTest, PurgeArchivedDatabase) { - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(27)); - ASSERT_NO_FATAL_FAILURE(CreateArchivedDB()); - - ASSERT_TRUE(base::PathExists(history_dir_.Append(kArchivedHistoryFilename))); - - CreateBackendAndDatabase(); - DeleteBackend(); - - // We do not retain expired history entries in an archived database as of M37. - // Verify that any legacy archived database is deleted on start-up. - ASSERT_FALSE(base::PathExists(history_dir_.Append(kArchivedHistoryFilename))); -} - -TEST_F(HistoryBackendDBTest, MigrateDownloadMimeType) { - base::Time now(base::Time::Now()); - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(28)); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads (id, current_path, target_path, start_time, " - "received_bytes, total_bytes, state, danger_type, interrupt_reason, " - "end_time, opened, referrer, by_ext_id, by_ext_name, etag, " - "last_modified) VALUES " - "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); - s.BindInt64(0, 1); - s.BindString(1, "current_path"); - s.BindString(2, "target_path"); - s.BindInt64(3, now.ToTimeT()); - s.BindInt64(4, 100); - s.BindInt64(5, 100); - s.BindInt(6, 1); - s.BindInt(7, 0); - s.BindInt(8, 0); - s.BindInt64(9, now.ToTimeT()); - s.BindInt(10, 1); - s.BindString(11, "referrer"); - s.BindString(12, "by extension ID"); - s.BindString(13, "by extension name"); - s.BindString(14, "etag"); - s.BindInt64(15, now.ToTimeT()); - ASSERT_TRUE(s.Run()); - } - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO downloads_url_chains (id, chain_index, url) VALUES " - "(?, ?, ?)")); - s.BindInt64(0, 4); - s.BindInt64(1, 0); - s.BindString(2, "url"); - ASSERT_TRUE(s.Run()); - } +// Test the results of GetFaviconsFromDB() when called with different +// |icon_types|. +TEST_F(HistoryBackendTest, GetFaviconsFromDBIconType) { + const GURL page_url("http://www.google.com/"); + const GURL icon_url1("http://www.google.com/icon1.png"); + const GURL icon_url2("http://www.google.com/icon2.png"); + std::vector<SkBitmap> bitmaps; + bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)); + + std::vector<favicon_base::FaviconRawBitmapData> favicon_bitmap_data; + backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps); + backend_->SetFavicons(page_url, favicon_base::TOUCH_ICON, icon_url2, bitmaps); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; + EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results_out)); + + EXPECT_EQ(1u, bitmap_results_out.size()); + EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[0].icon_type); + EXPECT_EQ(icon_url1, bitmap_results_out[0].icon_url); + + bitmap_results_out.clear(); + EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, + favicon_base::TOUCH_ICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results_out)); + + EXPECT_EQ(1u, bitmap_results_out.size()); + EXPECT_EQ(favicon_base::TOUCH_ICON, bitmap_results_out[0].icon_type); + EXPECT_EQ(icon_url2, bitmap_results_out[0].icon_url); +} + +// Test that GetFaviconsFromDB() correctly sets the expired flag for bitmap +// reults. +TEST_F(HistoryBackendTest, GetFaviconsFromDBExpired) { + const GURL page_url("http://www.google.com/"); + const GURL icon_url("http://www.google.com/icon.png"); + + std::vector<unsigned char> data; + data.push_back('a'); + scoped_refptr<base::RefCountedBytes> bitmap_data( + base::RefCountedBytes::TakeVector(&data)); + base::Time last_updated = base::Time::FromTimeT(0); + favicon_base::FaviconID icon_id = backend_->thumbnail_db_->AddFavicon( + icon_url, favicon_base::FAVICON, bitmap_data, last_updated, kSmallSize); + EXPECT_NE(0, icon_id); + EXPECT_NE(0, backend_->thumbnail_db_->AddIconMapping(page_url, icon_id)); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out; + EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results_out)); + + EXPECT_EQ(1u, bitmap_results_out.size()); + EXPECT_TRUE(bitmap_results_out[0].expired); +} + +// Check that UpdateFaviconMappingsAndFetch() call back to the UI when there is +// no valid thumbnail database. +TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchNoDB) { + // Make the thumbnail database invalid. + backend_->thumbnail_db_.reset(); + + std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results; + + backend_->UpdateFaviconMappingsAndFetch(GURL(), + std::vector<GURL>(), + favicon_base::FAVICON, + GetEdgeSizesSmallAndLarge(), + &bitmap_results); + + EXPECT_TRUE(bitmap_results.empty()); +} + +TEST_F(HistoryBackendTest, QueryFilteredURLs) { + const char* google = "http://www.google.com/"; + const char* yahoo = "http://www.yahoo.com/"; + const char* yahoo_sports = "http://sports.yahoo.com/"; + const char* yahoo_sports_with_article1 = + "http://sports.yahoo.com/article1.htm"; + const char* yahoo_sports_with_article2 = + "http://sports.yahoo.com/article2.htm"; + const char* yahoo_sports_soccer = "http://sports.yahoo.com/soccer"; + const char* apple = "http://www.apple.com/"; + + // Clear all history. + backend_->DeleteAllHistory(); + + base::Time tested_time = base::Time::Now().LocalMidnight() + + base::TimeDelta::FromHours(4); + base::TimeDelta half_an_hour = base::TimeDelta::FromMinutes(30); + base::TimeDelta one_hour = base::TimeDelta::FromHours(1); + base::TimeDelta one_day = base::TimeDelta::FromDays(1); + + const ui::PageTransition kTypedTransition = + ui::PAGE_TRANSITION_TYPED; + const ui::PageTransition kKeywordGeneratedTransition = + ui::PAGE_TRANSITION_KEYWORD_GENERATED; + + const char* redirect_sequence[2]; + redirect_sequence[1] = NULL; + + redirect_sequence[0] = google; + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, kTypedTransition, + tested_time - one_day - half_an_hour * 2); + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, + kTypedTransition, tested_time - one_day); + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, + kTypedTransition, tested_time - half_an_hour / 2); + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, + kTypedTransition, tested_time); + + // Add a visit with a transition that will make sure that no segment gets + // created for this page (so the subsequent entries will have different URLIDs + // and SegmentIDs). + redirect_sequence[0] = apple; + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, kKeywordGeneratedTransition, + tested_time - one_day + one_hour * 6); + + redirect_sequence[0] = yahoo; + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, kTypedTransition, + tested_time - one_day + half_an_hour); + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, kTypedTransition, + tested_time - one_day + half_an_hour * 2); + + redirect_sequence[0] = yahoo_sports; + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, kTypedTransition, + tested_time - one_day - half_an_hour * 2); + AddRedirectChainWithTransitionAndTime( + redirect_sequence, 0, kTypedTransition, + tested_time - one_day); + int transition1, transition2; + AddClientRedirect(GURL(yahoo_sports), GURL(yahoo_sports_with_article1), false, + tested_time - one_day + half_an_hour, + &transition1, &transition2); + AddClientRedirect(GURL(yahoo_sports_with_article1), + GURL(yahoo_sports_with_article2), + false, + tested_time - one_day + half_an_hour * 2, + &transition1, &transition2); + + redirect_sequence[0] = yahoo_sports_soccer; + AddRedirectChainWithTransitionAndTime(redirect_sequence, 0, + kTypedTransition, + tested_time - half_an_hour); + backend_->Commit(); + + VisitFilter filter; + FilteredURLList filtered_list; + // Time limit is |tested_time| +/- 45 min. + base::TimeDelta three_quarters_of_an_hour = base::TimeDelta::FromMinutes(45); + filter.SetFilterTime(tested_time); + filter.SetFilterWidth(three_quarters_of_an_hour); + backend_->QueryFilteredURLs(100, filter, false, &filtered_list); + + ASSERT_EQ(4U, filtered_list.size()); + EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); + EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); + EXPECT_EQ(std::string(yahoo), filtered_list[2].url.spec()); + EXPECT_EQ(std::string(yahoo_sports), filtered_list[3].url.spec()); + + // Time limit is between |tested_time| and |tested_time| + 2 hours. + filter.SetFilterTime(tested_time + one_hour); + filter.SetFilterWidth(one_hour); + backend_->QueryFilteredURLs(100, filter, false, &filtered_list); + + ASSERT_EQ(3U, filtered_list.size()); + EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); + EXPECT_EQ(std::string(yahoo), filtered_list[1].url.spec()); + EXPECT_EQ(std::string(yahoo_sports), filtered_list[2].url.spec()); + + // Time limit is between |tested_time| - 2 hours and |tested_time|. + filter.SetFilterTime(tested_time - one_hour); + filter.SetFilterWidth(one_hour); + backend_->QueryFilteredURLs(100, filter, false, &filtered_list); + + ASSERT_EQ(3U, filtered_list.size()); + EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); + EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); + EXPECT_EQ(std::string(yahoo_sports), filtered_list[2].url.spec()); + + filter.ClearFilters(); + base::Time::Exploded exploded_time; + tested_time.LocalExplode(&exploded_time); + + // Today. + filter.SetFilterTime(tested_time); + filter.SetDayOfTheWeekFilter(static_cast<int>(exploded_time.day_of_week)); + backend_->QueryFilteredURLs(100, filter, false, &filtered_list); + + ASSERT_EQ(2U, filtered_list.size()); + EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); + EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); + + // Today + time limit - only yahoo_sports_soccer should fit. + filter.SetFilterTime(tested_time - base::TimeDelta::FromMinutes(40)); + filter.SetFilterWidth(base::TimeDelta::FromMinutes(20)); + backend_->QueryFilteredURLs(100, filter, false, &filtered_list); + + ASSERT_EQ(1U, filtered_list.size()); + EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[0].url.spec()); + + // Make sure we get debug data if we request it. + filter.SetFilterTime(tested_time); + filter.SetFilterWidth(one_hour * 2); + backend_->QueryFilteredURLs(100, filter, true, &filtered_list); + + // If the SegmentID is used by QueryFilteredURLs when generating the debug + // data instead of the URLID, the |total_visits| for the |yahoo_sports_soccer| + // entry will be zero instead of 1. + ASSERT_GE(filtered_list.size(), 2U); + EXPECT_EQ(std::string(google), filtered_list[0].url.spec()); + EXPECT_EQ(std::string(yahoo_sports_soccer), filtered_list[1].url.spec()); + EXPECT_EQ(4U, filtered_list[0].extended_info.total_visits); + EXPECT_EQ(1U, filtered_list[1].extended_info.total_visits); +} + +TEST_F(HistoryBackendTest, UpdateVisitDuration) { + // This unit test will test adding and deleting visit details information. + ASSERT_TRUE(backend_.get()); + + GURL url1("http://www.cnn.com"); + std::vector<VisitInfo> visit_info1, visit_info2; + base::Time start_ts = base::Time::Now() - base::TimeDelta::FromDays(5); + base::Time end_ts = start_ts + base::TimeDelta::FromDays(2); + visit_info1.push_back(VisitInfo(start_ts, ui::PAGE_TRANSITION_LINK)); + + GURL url2("http://www.example.com"); + visit_info2.push_back( + VisitInfo(base::Time::Now() - base::TimeDelta::FromDays(10), + ui::PAGE_TRANSITION_LINK)); + + // Clear all history. + backend_->DeleteAllHistory(); + + // Add the visits. + backend_->AddVisits(url1, visit_info1, history::SOURCE_BROWSED); + backend_->AddVisits(url2, visit_info2, history::SOURCE_BROWSED); + + // Verify the entries for both visits were added in visit_details. + VisitVector visits1, visits2; + URLRow row; + URLID url_id1 = backend_->db()->GetRowForURL(url1, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id1, &visits1)); + ASSERT_EQ(1U, visits1.size()); + EXPECT_EQ(0, visits1[0].visit_duration.ToInternalValue()); + + URLID url_id2 = backend_->db()->GetRowForURL(url2, &row); + ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id2, &visits2)); + ASSERT_EQ(1U, visits2.size()); + EXPECT_EQ(0, visits2[0].visit_duration.ToInternalValue()); + + // Update the visit to cnn.com. + backend_->UpdateVisitDuration(visits1[0].visit_id, end_ts); + + // Check the duration for visiting cnn.com was correctly updated. + ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id1, &visits1)); + ASSERT_EQ(1U, visits1.size()); + base::TimeDelta expected_duration = end_ts - start_ts; + EXPECT_EQ(expected_duration.ToInternalValue(), + visits1[0].visit_duration.ToInternalValue()); + + // Remove the visit to cnn.com. + ASSERT_TRUE(backend_->RemoveVisits(visits1)); +} + +// Test for migration of adding visit_duration column. +TEST_F(HistoryBackendTest, MigrationVisitDuration) { + ASSERT_TRUE(backend_.get()); + backend_->Closing(); + backend_ = NULL; + + base::FilePath old_history_path, old_history; + ASSERT_TRUE(GetTestDataHistoryDir(&old_history_path)); + old_history = old_history_path.AppendASCII("HistoryNoDuration"); + + // Copy history database file to current directory so that it will be deleted + // in Teardown. + base::FilePath new_history_path(test_dir()); + base::DeleteFile(new_history_path, true); + base::CreateDirectory(new_history_path); + base::FilePath new_history_file = new_history_path.Append(kHistoryFilename); + ASSERT_TRUE(base::CopyFile(old_history, new_history_file)); + + backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this), + &history_client_); + backend_->Init(std::string(), false, + TestHistoryDatabaseParamsForPath(new_history_path)); + backend_->Closing(); + backend_ = NULL; + + // Now the history database should already be migrated. + + // Check version in history database first. + int cur_version = HistoryDatabase::GetCurrentVersion(); + sql::Connection db; + ASSERT_TRUE(db.Open(new_history_file)); + sql::Statement s(db.GetUniqueStatement( + "SELECT value FROM meta WHERE key = 'version'")); + ASSERT_TRUE(s.Step()); + int file_version = s.ColumnInt(0); + EXPECT_EQ(cur_version, file_version); + + // Check visit_duration column in visits table is created and set to 0. + s.Assign(db.GetUniqueStatement( + "SELECT visit_duration FROM visits LIMIT 1")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(0, s.ColumnInt(0)); +} + +TEST_F(HistoryBackendTest, AddPageNoVisitForBookmark) { + ASSERT_TRUE(backend_.get()); + + GURL url("http://www.google.com"); + base::string16 title(base::UTF8ToUTF16("Bookmark title")); + backend_->AddPageNoVisitForBookmark(url, title); + + URLRow row; + backend_->GetURL(url, &row); + EXPECT_EQ(url, row.url()); + EXPECT_EQ(title, row.title()); + EXPECT_EQ(0, row.visit_count()); + + backend_->DeleteURL(url); + backend_->AddPageNoVisitForBookmark(url, base::string16()); + backend_->GetURL(url, &row); + EXPECT_EQ(url, row.url()); + EXPECT_EQ(base::UTF8ToUTF16(url.spec()), row.title()); + EXPECT_EQ(0, row.visit_count()); +} + +TEST_F(HistoryBackendTest, ExpireHistoryForTimes) { + ASSERT_TRUE(backend_.get()); + + HistoryAddPageArgs args[10]; + for (size_t i = 0; i < arraysize(args); ++i) { + args[i].url = GURL("http://example" + + std::string((i % 2 == 0 ? ".com" : ".net"))); + args[i].time = base::Time::FromInternalValue(i); + backend_->AddPage(args[i]); } - // Re-open the db using the HistoryDatabase, which should migrate to the - // current version, creating themime_type abd original_mime_type columns. - CreateBackendAndDatabase(); - DeleteBackend(); - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - // The version should have been updated. - int cur_version = HistoryDatabase::GetCurrentVersion(); - ASSERT_LE(29, cur_version); - { - sql::Statement s(db.GetUniqueStatement( - "SELECT value FROM meta WHERE key = 'version'")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(cur_version, s.ColumnInt(0)); - } - { - sql::Statement s(db.GetUniqueStatement( - "SELECT mime_type, original_mime_type from downloads")); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(std::string(), s.ColumnString(0)); - EXPECT_EQ(std::string(), s.ColumnString(1)); - } + EXPECT_EQ(base::Time(), backend_->GetFirstRecordedTimeForTest()); + + URLRow row; + for (size_t i = 0; i < arraysize(args); ++i) { + EXPECT_TRUE(backend_->GetURL(args[i].url, &row)); } + + std::set<base::Time> times; + times.insert(args[5].time); + backend_->ExpireHistoryForTimes(times, + base::Time::FromInternalValue(2), + base::Time::FromInternalValue(8)); + + EXPECT_EQ(base::Time::FromInternalValue(0), + backend_->GetFirstRecordedTimeForTest()); + + // Visits to http://example.com are untouched. + VisitVector visit_vector; + EXPECT_TRUE(backend_->GetVisitsForURL( + backend_->db_->GetRowForURL(GURL("http://example.com"), NULL), + &visit_vector)); + ASSERT_EQ(5u, visit_vector.size()); + EXPECT_EQ(base::Time::FromInternalValue(0), visit_vector[0].visit_time); + EXPECT_EQ(base::Time::FromInternalValue(2), visit_vector[1].visit_time); + EXPECT_EQ(base::Time::FromInternalValue(4), visit_vector[2].visit_time); + EXPECT_EQ(base::Time::FromInternalValue(6), visit_vector[3].visit_time); + EXPECT_EQ(base::Time::FromInternalValue(8), visit_vector[4].visit_time); + + // Visits to http://example.net between [2,8] are removed. + visit_vector.clear(); + EXPECT_TRUE(backend_->GetVisitsForURL( + backend_->db_->GetRowForURL(GURL("http://example.net"), NULL), + &visit_vector)); + ASSERT_EQ(2u, visit_vector.size()); + EXPECT_EQ(base::Time::FromInternalValue(1), visit_vector[0].visit_time); + EXPECT_EQ(base::Time::FromInternalValue(9), visit_vector[1].visit_time); + + EXPECT_EQ(base::Time::FromInternalValue(0), + backend_->GetFirstRecordedTimeForTest()); } -TEST_F(HistoryBackendDBTest, ConfirmDownloadRowCreateAndDelete) { - // Create the DB. - CreateBackendAndDatabase(); - - base::Time now(base::Time::Now()); - - // Add some downloads. - uint32 id1 = 1, id2 = 2, id3 = 3; - AddDownload(id1, DownloadState::COMPLETE, now); - AddDownload(id2, DownloadState::COMPLETE, now + base::TimeDelta::FromDays(2)); - AddDownload(id3, DownloadState::COMPLETE, now - base::TimeDelta::FromDays(2)); - - // Confirm that resulted in the correct number of rows in the DB. - DeleteBackend(); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - sql::Statement statement(db.GetUniqueStatement( - "Select Count(*) from downloads")); - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(3, statement.ColumnInt(0)); - - sql::Statement statement1(db.GetUniqueStatement( - "Select Count(*) from downloads_url_chains")); - EXPECT_TRUE(statement1.Step()); - EXPECT_EQ(3, statement1.ColumnInt(0)); - } - - // Delete some rows and make sure the results are still correct. - CreateBackendAndDatabase(); - db_->RemoveDownload(id2); - db_->RemoveDownload(id3); - DeleteBackend(); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - sql::Statement statement(db.GetUniqueStatement( - "Select Count(*) from downloads")); - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(1, statement.ColumnInt(0)); - - sql::Statement statement1(db.GetUniqueStatement( - "Select Count(*) from downloads_url_chains")); - EXPECT_TRUE(statement1.Step()); - EXPECT_EQ(1, statement1.ColumnInt(0)); - } -} - -TEST_F(HistoryBackendDBTest, DownloadNukeRecordsMissingURLs) { - CreateBackendAndDatabase(); - base::Time now(base::Time::Now()); - std::vector<GURL> url_chain; - DownloadRow download(base::FilePath(FILE_PATH_LITERAL("foo-path")), - base::FilePath(FILE_PATH_LITERAL("foo-path")), - url_chain, - GURL(std::string()), - "application/octet-stream", - "application/octet-stream", - now, - now, - std::string(), - std::string(), - 0, - 512, - DownloadState::COMPLETE, - DownloadDangerType::NOT_DANGEROUS, - kTestDownloadInterruptReasonNone, - 1, - 0, - "by_ext_id", - "by_ext_name"); - - // Creating records without any urls should fail. - EXPECT_FALSE(db_->CreateDownload(download)); - - download.url_chain.push_back(GURL("foo-url")); - EXPECT_TRUE(db_->CreateDownload(download)); - - // Pretend that the URLs were dropped. - DeleteBackend(); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - sql::Statement statement(db.GetUniqueStatement( - "DELETE FROM downloads_url_chains WHERE id=1")); - ASSERT_TRUE(statement.Run()); - } - CreateBackendAndDatabase(); - std::vector<DownloadRow> downloads; - db_->QueryDownloads(&downloads); - EXPECT_EQ(0U, downloads.size()); - - // QueryDownloads should have nuked the corrupt record. - DeleteBackend(); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - { - sql::Statement statement(db.GetUniqueStatement( - "SELECT count(*) from downloads")); - ASSERT_TRUE(statement.Step()); - EXPECT_EQ(0, statement.ColumnInt(0)); - } +TEST_F(HistoryBackendTest, ExpireHistory) { + ASSERT_TRUE(backend_.get()); + // Since history operations are dependent on the local timezone, make all + // entries relative to a fixed, local reference time. + base::Time reference_time = base::Time::UnixEpoch().LocalMidnight() + + base::TimeDelta::FromHours(12); + + // Insert 4 entries into the database. + HistoryAddPageArgs args[4]; + for (size_t i = 0; i < arraysize(args); ++i) { + args[i].url = GURL("http://example" + base::IntToString(i) + ".com"); + args[i].time = reference_time + base::TimeDelta::FromDays(i); + backend_->AddPage(args[i]); } -} -TEST_F(HistoryBackendDBTest, ConfirmDownloadInProgressCleanup) { - // Create the DB. - CreateBackendAndDatabase(); - - base::Time now(base::Time::Now()); - - // Put an IN_PROGRESS download in the DB. - AddDownload(1, DownloadState::IN_PROGRESS, now); - - // Confirm that they made it into the DB unchanged. - DeleteBackend(); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - sql::Statement statement(db.GetUniqueStatement( - "Select Count(*) from downloads")); - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(1, statement.ColumnInt(0)); - - sql::Statement statement1(db.GetUniqueStatement( - "Select state, interrupt_reason from downloads")); - EXPECT_TRUE(statement1.Step()); - EXPECT_EQ(DownloadStateToInt(DownloadState::IN_PROGRESS), - statement1.ColumnInt(0)); - EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone), - statement1.ColumnInt(1)); - EXPECT_FALSE(statement1.Step()); - } - - // Read in the DB through query downloads, then test that the - // right transformation was returned. - CreateBackendAndDatabase(); - std::vector<DownloadRow> results; - db_->QueryDownloads(&results); - ASSERT_EQ(1u, results.size()); - EXPECT_EQ(DownloadState::INTERRUPTED, results[0].state); - EXPECT_EQ(kTestDownloadInterruptReasonCrash, results[0].interrupt_reason); - - // Allow the update to propagate, shut down the DB, and confirm that - // the query updated the on disk database as well. - base::MessageLoop::current()->RunUntilIdle(); - DeleteBackend(); - { - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - sql::Statement statement(db.GetUniqueStatement( - "Select Count(*) from downloads")); - EXPECT_TRUE(statement.Step()); - EXPECT_EQ(1, statement.ColumnInt(0)); - - sql::Statement statement1(db.GetUniqueStatement( - "Select state, interrupt_reason from downloads")); - EXPECT_TRUE(statement1.Step()); - EXPECT_EQ(DownloadStateToInt(DownloadState::INTERRUPTED), - statement1.ColumnInt(0)); - EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonCrash), - statement1.ColumnInt(1)); - EXPECT_FALSE(statement1.Step()); - } -} - -TEST_F(HistoryBackendDBTest, MigratePresentations) { - // Create the db we want. Use 22 since segments didn't change in that time - // frame. - ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22)); - - const SegmentID segment_id = 2; - const URLID url_id = 3; - const GURL url("http://www.foo.com"); - const std::string url_name(VisitSegmentDatabase::ComputeSegmentName(url)); - const base::string16 title(base::ASCIIToUTF16("Title1")); - const base::Time segment_time(base::Time::Now()); - - { - // Re-open the db for manual manipulation. - sql::Connection db; - ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename))); - - // Add an entry to urls. - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO urls " - "(id, url, title, last_visit_time) VALUES " - "(?, ?, ?, ?)")); - s.BindInt64(0, url_id); - s.BindString(1, url.spec()); - s.BindString16(2, title); - s.BindInt64(3, segment_time.ToInternalValue()); - ASSERT_TRUE(s.Run()); - } + URLRow url_rows[4]; + for (unsigned int i = 0; i < arraysize(args); ++i) + ASSERT_TRUE(backend_->GetURL(args[i].url, &url_rows[i])); - // Add an entry to segments. - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO segments " - "(id, name, url_id, pres_index) VALUES " - "(?, ?, ?, ?)")); - s.BindInt64(0, segment_id); - s.BindString(1, url_name); - s.BindInt64(2, url_id); - s.BindInt(3, 4); // pres_index - ASSERT_TRUE(s.Run()); - } + std::vector<ExpireHistoryArgs> expire_list; + VisitVector visits; - // And one to segment_usage. - { - sql::Statement s(db.GetUniqueStatement( - "INSERT INTO segment_usage " - "(id, segment_id, time_slot, visit_count) VALUES " - "(?, ?, ?, ?)")); - s.BindInt64(0, 4); // id. - s.BindInt64(1, segment_id); - s.BindInt64(2, segment_time.ToInternalValue()); - s.BindInt(3, 5); // visit count. - ASSERT_TRUE(s.Run()); - } + // Passing an empty map should be a no-op. + backend_->ExpireHistory(expire_list); + backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); + EXPECT_EQ(4U, visits.size()); + + // Trying to delete an unknown URL with the time of the first visit should + // also be a no-op. + expire_list.resize(expire_list.size() + 1); + expire_list[0].SetTimeRangeForOneDay(args[0].time); + expire_list[0].urls.insert(GURL("http://google.does-not-exist")); + backend_->ExpireHistory(expire_list); + backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); + EXPECT_EQ(4U, visits.size()); + + // Now add the first URL with the same time -- it should get deleted. + expire_list.back().urls.insert(url_rows[0].url()); + backend_->ExpireHistory(expire_list); + + backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); + ASSERT_EQ(3U, visits.size()); + EXPECT_EQ(visits[0].url_id, url_rows[1].id()); + EXPECT_EQ(visits[1].url_id, url_rows[2].id()); + EXPECT_EQ(visits[2].url_id, url_rows[3].id()); + + // The first recorded time should also get updated. + EXPECT_EQ(backend_->GetFirstRecordedTimeForTest(), args[1].time); + + // Now delete the rest of the visits in one call. + for (unsigned int i = 1; i < arraysize(args); ++i) { + expire_list.resize(expire_list.size() + 1); + expire_list[i].SetTimeRangeForOneDay(args[i].time); + expire_list[i].urls.insert(args[i].url); } + backend_->ExpireHistory(expire_list); - // Re-open the db, triggering migration. - CreateBackendAndDatabase(); + backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits); + ASSERT_EQ(0U, visits.size()); +} + +TEST_F(HistoryBackendTest, DeleteMatchingUrlsForKeyword) { + // Set up urls and keyword_search_terms + GURL url1("https://www.bing.com/?q=bar"); + URLRow url_info1(url1); + url_info1.set_visit_count(0); + url_info1.set_typed_count(0); + url_info1.set_last_visit(base::Time()); + url_info1.set_hidden(false); + const URLID url1_id = backend_->db()->AddURL(url_info1); + EXPECT_NE(0, url1_id); + + KeywordID keyword_id = 1; + base::string16 keyword = base::UTF8ToUTF16("bar"); + ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL( + url1_id, keyword_id, keyword)); + + GURL url2("https://www.google.com/?q=bar"); + URLRow url_info2(url2); + url_info2.set_visit_count(0); + url_info2.set_typed_count(0); + url_info2.set_last_visit(base::Time()); + url_info2.set_hidden(false); + const URLID url2_id = backend_->db()->AddURL(url_info2); + EXPECT_NE(0, url2_id); + + KeywordID keyword_id2 = 2; + ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL( + url2_id, keyword_id2, keyword)); - std::vector<PageUsageData*> results; - db_->QuerySegmentUsage(segment_time, 10, &results); - ASSERT_EQ(1u, results.size()); - EXPECT_EQ(url, results[0]->GetURL()); - EXPECT_EQ(segment_id, results[0]->GetID()); - EXPECT_EQ(title, results[0]->GetTitle()); - STLDeleteElements(&results); + // Add another visit to the same URL + URLRow url_info3(url2); + url_info3.set_visit_count(0); + url_info3.set_typed_count(0); + url_info3.set_last_visit(base::Time()); + url_info3.set_hidden(false); + const URLID url3_id = backend_->db()->AddURL(url_info3); + EXPECT_NE(0, url3_id); + ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL( + url3_id, keyword_id2, keyword)); + + // Test that deletion works correctly + backend_->DeleteMatchingURLsForKeyword(keyword_id2, keyword); + + // Test that rows 2 and 3 are deleted, while 1 is intact + URLRow row; + EXPECT_TRUE(backend_->db()->GetURLRow(url1_id, &row)); + EXPECT_EQ(url1.spec(), row.url().spec()); + EXPECT_FALSE(backend_->db()->GetURLRow(url2_id, &row)); + EXPECT_FALSE(backend_->db()->GetURLRow(url3_id, &row)); + + // Test that corresponding keyword search terms are deleted for rows 2 & 3, + // but not for row 1 + EXPECT_TRUE(backend_->db()->GetKeywordSearchTermRow(url1_id, NULL)); + EXPECT_FALSE(backend_->db()->GetKeywordSearchTermRow(url2_id, NULL)); + EXPECT_FALSE(backend_->db()->GetKeywordSearchTermRow(url3_id, NULL)); +} + +// Simple test that removes a bookmark. This test exercises the code paths in +// History that block till bookmark bar model is loaded. +TEST_F(HistoryBackendTest, RemoveNotification) { + base::ScopedTempDir scoped_temp_dir; + EXPECT_TRUE(scoped_temp_dir.CreateUniqueTempDirUnderPath(test_dir())); + + // Add a URL. + GURL url("http://www.google.com"); + HistoryClientMock history_client; + history_client.AddBookmark(url); + scoped_ptr<HistoryService> service(new HistoryService( + &history_client, scoped_ptr<history::VisitDelegate>())); + EXPECT_TRUE( + service->Init(kAcceptLanguagesForTest, + TestHistoryDatabaseParamsForPath(scoped_temp_dir.path()))); + + service->AddPage( + url, base::Time::Now(), NULL, 1, GURL(), RedirectList(), + ui::PAGE_TRANSITION_TYPED, SOURCE_BROWSED, false); + + // This won't actually delete the URL, rather it'll empty out the visits. + // This triggers blocking on the BookmarkModel. + EXPECT_CALL(history_client, BlockUntilBookmarksLoaded()); + service->DeleteURL(url); +} + +// Test DeleteFTSIndexDatabases deletes expected files. +TEST_F(HistoryBackendTest, DeleteFTSIndexDatabases) { + ASSERT_TRUE(backend_.get()); + + base::FilePath history_path(test_dir()); + base::FilePath db1(history_path.AppendASCII("History Index 2013-05")); + base::FilePath db1_journal(db1.InsertBeforeExtensionASCII("-journal")); + base::FilePath db1_wal(db1.InsertBeforeExtensionASCII("-wal")); + base::FilePath db2_symlink(history_path.AppendASCII("History Index 2013-06")); + base::FilePath db2_actual(history_path.AppendASCII("Underlying DB")); + + // Setup dummy index database files. + const char* data = "Dummy"; + const size_t data_len = 5; + ASSERT_TRUE(base::WriteFile(db1, data, data_len)); + ASSERT_TRUE(base::WriteFile(db1_journal, data, data_len)); + ASSERT_TRUE(base::WriteFile(db1_wal, data, data_len)); + ASSERT_TRUE(base::WriteFile(db2_actual, data, data_len)); +#if defined(OS_POSIX) + EXPECT_TRUE(base::CreateSymbolicLink(db2_actual, db2_symlink)); +#endif + + // Delete all DTS index databases. + backend_->DeleteFTSIndexDatabases(); + EXPECT_FALSE(base::PathExists(db1)); + EXPECT_FALSE(base::PathExists(db1_wal)); + EXPECT_FALSE(base::PathExists(db1_journal)); + EXPECT_FALSE(base::PathExists(db2_symlink)); + EXPECT_TRUE(base::PathExists(db2_actual)); // Symlinks shouldn't be followed. +} + +// Common implementation for the two tests below, given that the only difference +// between them is the type of the notification sent out. +void InMemoryHistoryBackendTest::TestAddingAndChangingURLRows( + const SimulateNotificationCallback& callback) { + const char kTestTypedURLAlternativeTitle[] = "Google Search Again"; + const char kTestNonTypedURLAlternativeTitle[] = "Google News Again"; + + // Notify the in-memory database that a typed and non-typed URLRow (which were + // never before seen by the cache) have been modified. + URLRow row1(CreateTestTypedURL()); + URLRow row2(CreateTestNonTypedURL()); + callback.Run(&row1, &row2, nullptr); + + // The in-memory database should only pick up the typed URL, and should ignore + // the non-typed one. The typed URL should retain the ID that was present in + // the notification. + URLRow cached_row1, cached_row2; + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); + EXPECT_EQ(row1.id(), cached_row1.id()); + + // Try changing attributes (other than typed_count) for existing URLRows. + row1.set_title(base::UTF8ToUTF16(kTestTypedURLAlternativeTitle)); + row2.set_title(base::UTF8ToUTF16(kTestNonTypedURLAlternativeTitle)); + callback.Run(&row1, &row2, nullptr); + + // URLRows that are cached by the in-memory database should be updated. + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); + EXPECT_EQ(base::UTF8ToUTF16(kTestTypedURLAlternativeTitle), + cached_row1.title()); + + // Now decrease the typed count for the typed URLRow, and increase it for the + // previously non-typed URLRow. + row1.set_typed_count(0); + row2.set_typed_count(2); + callback.Run(&row1, &row2, nullptr); + + // The in-memory database should stop caching the first URLRow, and start + // caching the second URLRow. + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); + EXPECT_EQ(row2.id(), cached_row2.id()); + EXPECT_EQ(base::UTF8ToUTF16(kTestNonTypedURLAlternativeTitle), + cached_row2.title()); +} + +TEST_F(InMemoryHistoryBackendTest, OnURLsModified) { + TestAddingAndChangingURLRows(base::Bind( + &SimulateNotificationURLsModified, base::Unretained(mem_backend_.get()))); +} + +TEST_F(InMemoryHistoryBackendTest, OnURLsVisisted) { + TestAddingAndChangingURLRows(base::Bind( + &SimulateNotificationURLVisited, base::Unretained(mem_backend_.get()))); +} + +TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedPiecewise) { + // Add two typed and one non-typed URLRow to the in-memory database. + URLRow row1(CreateTestTypedURL()); + URLRow row2(CreateAnotherTestTypedURL()); + URLRow row3(CreateTestNonTypedURL()); + SimulateNotificationURLsModified(mem_backend_.get(), &row1, &row2, &row3); + + // Notify the in-memory database that the second typed URL and the non-typed + // URL has been deleted. + SimulateNotificationURLsDeleted(&row2, &row3); + + // Expect that the first typed URL remains intact, the second typed URL is + // correctly removed, and the non-typed URL does not magically appear. + URLRow cached_row1; + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), NULL)); + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row3.url(), NULL)); + EXPECT_EQ(row1.id(), cached_row1.id()); +} + +TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedEnMasse) { + // Add two typed and one non-typed URLRow to the in-memory database. + URLRow row1(CreateTestTypedURL()); + URLRow row2(CreateAnotherTestTypedURL()); + URLRow row3(CreateTestNonTypedURL()); + SimulateNotificationURLsModified(mem_backend_.get(), &row1, &row2, &row3); + + // Now notify the in-memory database that all history has been deleted. + mem_backend_->OnURLsDeleted(nullptr, true, false, URLRows(), + std::set<GURL>()); + + // Expect that everything goes away. + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row1.url(), NULL)); + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), NULL)); + EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row3.url(), NULL)); +} + +void InMemoryHistoryBackendTest::PopulateTestURLsAndSearchTerms( + URLRow* row1, + URLRow* row2, + const base::string16& term1, + const base::string16& term2) { + // Add a typed and a non-typed URLRow to the in-memory database. This time, + // though, do it through the history backend... + URLRows rows; + rows.push_back(*row1); + rows.push_back(*row2); + backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); + backend_->db()->GetRowForURL(row1->url(), row1); // Get effective IDs from + backend_->db()->GetRowForURL(row2->url(), row2); // the database. + + // ... so that we can also use that for adding the search terms. This way, we + // not only test that the notifications involved are handled correctly, but + // also that they are fired correctly (in the history backend). + backend_->SetKeywordSearchTermsForURL(row1->url(), kTestKeywordId, term1); + backend_->SetKeywordSearchTermsForURL(row2->url(), kTestKeywordId, term2); +} + +TEST_F(InMemoryHistoryBackendTest, SetKeywordSearchTerms) { + URLRow row1(CreateTestTypedURL()); + URLRow row2(CreateTestNonTypedURL()); + base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); + base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); + PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); + + // Both URLs now have associated search terms, so the in-memory database + // should cache both of them, regardless whether they have been typed or not. + URLRow cached_row1, cached_row2; + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2)); + EXPECT_EQ(row1.id(), cached_row1.id()); + EXPECT_EQ(row2.id(), cached_row2.id()); + + // Verify that lookups will actually return both search terms; and also check + // at the low level that the rows are there. + EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); + EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); + EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); + EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); +} + +TEST_F(InMemoryHistoryBackendTest, DeleteKeywordSearchTerms) { + URLRow row1(CreateTestTypedURL()); + URLRow row2(CreateTestNonTypedURL()); + base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); + base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); + PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); + + // Delete both search terms. This should be reflected in the in-memory DB. + backend_->DeleteKeywordSearchTermForURL(row1.url()); + backend_->DeleteKeywordSearchTermForURL(row2.url()); + + // The typed URL should remain intact. + // Note: we do not need to guarantee anything about the non-typed URL. + URLRow cached_row1; + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); + EXPECT_EQ(row1.id(), cached_row1.id()); + + // Verify that the search terms are no longer returned as results, and also + // check at the low level that they are gone for good. + EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); + EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); + EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); + EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); +} + +TEST_F(InMemoryHistoryBackendTest, DeleteAllSearchTermsForKeyword) { + URLRow row1(CreateTestTypedURL()); + URLRow row2(CreateTestNonTypedURL()); + base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); + base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); + PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); + + // Delete all corresponding search terms from the in-memory database. + KeywordID id = kTestKeywordId; + mem_backend_->DeleteAllSearchTermsForKeyword(id); + + // The typed URL should remain intact. + // Note: we do not need to guarantee anything about the non-typed URL. + URLRow cached_row1; + EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1)); + EXPECT_EQ(row1.id(), cached_row1.id()); + + // Verify that the search terms are no longer returned as results, and also + // check at the low level that they are gone for good. + EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); + EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); + EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); + EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); +} + +TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedWithSearchTerms) { + URLRow row1(CreateTestTypedURL()); + URLRow row2(CreateTestNonTypedURL()); + base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1)); + base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2)); + PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2); + + // Notify the in-memory database that the second typed URL has been deleted. + SimulateNotificationURLsDeleted(&row2); + + // Verify that the second term is no longer returned as result, and also check + // at the low level that it is gone for good. The term corresponding to the + // first URLRow should not be affected. + EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1)); + EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2)); + EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL)); + EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL)); } -} // namespace } // namespace history diff --git a/components/history/core/browser/history_constants.cc b/components/history/core/browser/history_constants.cc index 223cca8..1b18dc7 100644 --- a/components/history/core/browser/history_constants.cc +++ b/components/history/core/browser/history_constants.cc @@ -6,15 +6,18 @@ #include "base/files/file_path.h" -#define FPL FILE_PATH_LITERAL - namespace history { // filenames const base::FilePath::CharType kArchivedHistoryFilename[] = - FPL("Archived History"); -const base::FilePath::CharType kFaviconsFilename[] = FPL("Favicons"); -const base::FilePath::CharType kHistoryFilename[] = FPL("History"); -const base::FilePath::CharType kThumbnailsFilename[] = FPL("Thumbnails"); + FILE_PATH_LITERAL("Archived History"); +const base::FilePath::CharType kFaviconsFilename[] = + FILE_PATH_LITERAL("Favicons"); +const base::FilePath::CharType kHistoryFilename[] = + FILE_PATH_LITERAL("History"); +const base::FilePath::CharType kThumbnailsFilename[] = + FILE_PATH_LITERAL("Thumbnails"); +const base::FilePath::CharType kTopSitesFilename[] = + FILE_PATH_LITERAL("Top Sites"); } // namespace history diff --git a/components/history/core/browser/history_constants.h b/components/history/core/browser/history_constants.h index 4bbb478..875b099 100644 --- a/components/history/core/browser/history_constants.h +++ b/components/history/core/browser/history_constants.h @@ -14,6 +14,7 @@ extern const base::FilePath::CharType kArchivedHistoryFilename[]; extern const base::FilePath::CharType kFaviconsFilename[]; extern const base::FilePath::CharType kHistoryFilename[]; extern const base::FilePath::CharType kThumbnailsFilename[]; +extern const base::FilePath::CharType kTopSitesFilename[]; } // namespace history diff --git a/chrome/browser/history/history_database_unittest.cc b/components/history/core/browser/history_database_unittest.cc index 886d1e9..3f90bf8 100644 --- a/chrome/browser/history/history_database_unittest.cc +++ b/components/history/core/browser/history_database_unittest.cc @@ -8,7 +8,7 @@ #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/path_service.h" -#include "chrome/common/chrome_paths.h" +#include "components/history/core/test/database_test_utils.h" #include "components/history/core/test/test_history_database.h" #include "sql/init_status.h" #include "testing/gtest/include/gtest/gtest.h" @@ -25,10 +25,9 @@ TEST(HistoryDatabaseTest, DropBookmarks) { // Copy db file over that contains starred URLs. base::FilePath old_history_path; - PathService::Get(chrome::DIR_TEST_DATA, &old_history_path); - old_history_path = old_history_path.AppendASCII("bookmarks"); - old_history_path = old_history_path.Append( - FILE_PATH_LITERAL("History_with_starred")); + EXPECT_TRUE(GetTestDataHistoryDir(&old_history_path)); + old_history_path = + old_history_path.Append(FILE_PATH_LITERAL("History_with_starred")); base::CopyFile(old_history_path, db_file); // Load the DB twice. The first time it should migrate. Make sure that the diff --git a/chrome/browser/history/history_querying_unittest.cc b/components/history/core/browser/history_querying_unittest.cc index 0897589..535688c 100644 --- a/chrome/browser/history/history_querying_unittest.cc +++ b/components/history/core/browser/history_querying_unittest.cc @@ -16,9 +16,6 @@ #include "components/history/core/test/test_history_database.h" #include "testing/gtest/include/gtest/gtest.h" -using base::Time; -using base::TimeDelta; - // Tests the history service for querying functionality. namespace history { @@ -29,7 +26,7 @@ struct TestEntry { const char* url; const char* title; const int days_ago; - Time time; // Filled by SetUp. + base::Time time; // Filled by SetUp. } test_entries[] = { // This one is visited super long ago so it will be in a different database // from the next appearance of it at the end. @@ -74,8 +71,7 @@ bool NthResultIs(const QueryResults& results, class HistoryQueryTest : public testing::Test { public: - HistoryQueryTest() : nav_entry_id_(0) { - } + HistoryQueryTest() : nav_entry_id_(0) {} // Acts like a synchronous call to history's QueryHistory. void QueryHistory(const std::string& text_query, @@ -174,10 +170,10 @@ class HistoryQueryTest : public testing::Test { } // Fill the test data. - Time now = Time::Now().LocalMidnight(); + base::Time now = base::Time::Now().LocalMidnight(); for (size_t i = 0; i < arraysize(test_entries); i++) { test_entries[i].time = - now - (test_entries[i].days_ago * TimeDelta::FromDays(1)); + now - (test_entries[i].days_ago * base::TimeDelta::FromDays(1)); AddEntryToHistory(test_entries[i]); } } @@ -273,7 +269,8 @@ TEST_F(HistoryQueryTest, ReachedBeginning) { EXPECT_FALSE(results.reached_beginning()); // Try |begin_time| just later than the oldest visit. - options.begin_time = test_entries[0].time + TimeDelta::FromMicroseconds(1); + options.begin_time = + test_entries[0].time + base::TimeDelta::FromMicroseconds(1); QueryHistory(std::string(), options, &results); EXPECT_FALSE(results.reached_beginning()); QueryHistory("some", options, &results); @@ -287,7 +284,8 @@ TEST_F(HistoryQueryTest, ReachedBeginning) { EXPECT_TRUE(results.reached_beginning()); // Try |begin_time| just earlier than the oldest visit. - options.begin_time = test_entries[0].time - TimeDelta::FromMicroseconds(1); + options.begin_time = + test_entries[0].time - base::TimeDelta::FromMicroseconds(1); QueryHistory(std::string(), options, &results); EXPECT_TRUE(results.reached_beginning()); QueryHistory("some", options, &results); diff --git a/components/history/core/browser/in_memory_history_backend.h b/components/history/core/browser/in_memory_history_backend.h index 9335c9a..50828b9 100644 --- a/components/history/core/browser/in_memory_history_backend.h +++ b/components/history/core/browser/in_memory_history_backend.h @@ -35,8 +35,10 @@ class FilePath; namespace history { +class HistoryBackendTestBase; class HistoryService; class InMemoryDatabase; +class InMemoryHistoryBackendTest; class URLDatabase; class URLRow; diff --git a/chrome/browser/history/thumbnail_database_unittest.cc b/components/history/core/browser/thumbnail_database_unittest.cc index 4b552d2..631d0b2 100644 --- a/chrome/browser/history/thumbnail_database_unittest.cc +++ b/components/history/core/browser/thumbnail_database_unittest.cc @@ -11,8 +11,8 @@ #include "base/files/scoped_temp_dir.h" #include "base/memory/ref_counted_memory.h" #include "base/path_service.h" -#include "chrome/common/chrome_paths.h" #include "components/history/core/browser/thumbnail_database.h" +#include "components/history/core/test/database_test_utils.h" #include "sql/connection.h" #include "sql/recovery.h" #include "sql/test/scoped_error_ignorer.h" @@ -48,17 +48,6 @@ const GURL kIconUrl5 = GURL("http://www.bing.com/favicon.ico"); const gfx::Size kSmallSize = gfx::Size(16, 16); const gfx::Size kLargeSize = gfx::Size(32, 32); -// Create the test database at |db_path| from the golden file at -// |ascii_path| in the "History/" subdir of the test data dir. -WARN_UNUSED_RESULT bool CreateDatabaseFromSQL(const base::FilePath &db_path, - const char* ascii_path) { - base::FilePath sql_path; - if (!PathService::Get(chrome::DIR_TEST_DATA, &sql_path)) - return false; - sql_path = sql_path.AppendASCII("History").AppendASCII(ascii_path); - return sql::test::CreateDatabaseFromSQL(db_path, sql_path); -} - // Verify that the up-to-date database has the expected tables and // columns. Functional tests only check whether the things which // should be there are, but do not check if extraneous items are @@ -161,8 +150,7 @@ WARN_UNUSED_RESULT bool CheckPageHasIcon( class ThumbnailDatabaseTest : public testing::Test { public: - ThumbnailDatabaseTest() { - } + ThumbnailDatabaseTest() {} ~ThumbnailDatabaseTest() override {} // Initialize a thumbnail database instance from the SQL file at @@ -245,7 +233,7 @@ TEST_F(ThumbnailDatabaseTest, LastRequestedTime) { EXPECT_TRUE(db.SetFaviconBitmapLastRequestedTime(id, now)); EXPECT_TRUE(db.GetFaviconBitmap(id, NULL, &last_requested, NULL, NULL)); EXPECT_EQ(last_requested, now); - } +} TEST_F(ThumbnailDatabaseTest, DeleteIconMappings) { ThumbnailDatabase db(NULL); @@ -1098,8 +1086,8 @@ TEST_F(ThumbnailDatabaseTest, Recovery5) { // successfully, and result in the correct schema. TEST_F(ThumbnailDatabaseTest, WildSchema) { base::FilePath sql_path; - ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &sql_path)); - sql_path = sql_path.AppendASCII("History").AppendASCII("thumbnail_wild"); + EXPECT_TRUE(GetTestDataHistoryDir(&sql_path)); + sql_path = sql_path.AppendASCII("thumbnail_wild"); base::FileEnumerator fe( sql_path, false, base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.sql")); diff --git a/components/history/core/browser/top_sites_impl.cc b/components/history/core/browser/top_sites_impl.cc index c5b1873..4df526b 100644 --- a/components/history/core/browser/top_sites_impl.cc +++ b/components/history/core/browser/top_sites_impl.cc @@ -22,6 +22,7 @@ #include "base/task_runner.h" #include "base/values.h" #include "components/history/core/browser/history_backend.h" +#include "components/history/core/browser/history_constants.h" #include "components/history/core/browser/history_db_task.h" #include "components/history/core/browser/page_usage_data.h" #include "components/history/core/browser/top_sites_cache.h" diff --git a/chrome/browser/history/top_sites_impl_unittest.cc b/components/history/core/browser/top_sites_impl_unittest.cc index 25d3646..f57d814 100644 --- a/chrome/browser/history/top_sites_impl_unittest.cc +++ b/components/history/core/browser/top_sites_impl_unittest.cc @@ -2,45 +2,46 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "components/history/core/browser/top_sites_impl.h" + #include "base/bind.h" +#include "base/files/scoped_temp_dir.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/testing_pref_service.h" #include "base/strings/utf_string_conversions.h" #include "base/task/cancelable_task_tracker.h" -#include "chrome/browser/history/history_service_factory.h" -#include "chrome/browser/history/top_sites_factory.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/common/chrome_paths.h" -#include "chrome/test/base/testing_profile.h" +#include "components/history/core/browser/history_constants.h" +#include "components/history/core/browser/history_database_params.h" #include "components/history/core/browser/history_db_task.h" #include "components/history/core/browser/history_types.h" #include "components/history/core/browser/top_sites.h" #include "components/history/core/browser/top_sites_cache.h" -#include "components/history/core/browser/top_sites_impl.h" #include "components/history/core/browser/top_sites_observer.h" +#include "components/history/core/browser/visit_delegate.h" #include "components/history/core/test/history_unittest_base.h" -#include "content/public/test/test_browser_thread.h" +#include "components/history/core/test/test_history_database.h" +#include "components/history/core/test/wait_top_sites_loaded_observer.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" -#include "ui/gfx/codec/jpeg_codec.h" #include "url/gurl.h" -using content::BrowserThread; - namespace history { namespace { -static const char kPrepopulatedPageURL[] = +// Key for URL blacklist. +const char kBlacklistURLKey[] = "test.blacklist.url"; +const char kAcceptLanguages[] = "en-US,en"; + +const char kApplicationScheme[] = "application"; +const char kPrepopulatedPageURL[] = "http://www.google.com/int/chrome/welcome.html"; -// Create a TopSites implementation for testing. -scoped_refptr<RefcountedKeyedService> BuildTopSitesImpl( - content::BrowserContext* context) { - PrepopulatedPageList prepopulated_pages; - prepopulated_pages.push_back(PrepopulatedPage(GURL(kPrepopulatedPageURL), - base::string16(), -1, -1, 0)); - return TopSitesFactory::BuildTopSites(context, prepopulated_pages); +// Returns whether |url| can be added to history. +bool MockCanAddURLToHistory(const GURL& url) { + return url.is_valid() && !url.SchemeIs(kApplicationScheme); } // Used by WaitForHistory, see it for details. @@ -66,9 +67,7 @@ class WaitForHistoryTask : public HistoryDBTask { class TopSitesQuerier { public: TopSitesQuerier() - : number_of_callbacks_(0), - waiting_(false), - weak_ptr_factory_(this) {} + : number_of_callbacks_(0), waiting_(false), weak_ptr_factory_(this) {} // Queries top sites. If |wait| is true a nested message loop is run until the // callback is notified. @@ -92,9 +91,7 @@ class TopSitesQuerier { } } - void CancelRequest() { - weak_ptr_factory_.InvalidateWeakPtrs(); - } + void CancelRequest() { weak_ptr_factory_.InvalidateWeakPtrs(); } void set_urls(const MostVisitedURLList& urls) { urls_ = urls; } const MostVisitedURLList& urls() const { return urls_; } @@ -120,14 +117,6 @@ class TopSitesQuerier { DISALLOW_COPY_AND_ASSIGN(TopSitesQuerier); }; -// Extracts the data from |t1| into a SkBitmap. This is intended for usage of -// thumbnail data, which is stored as jpgs. -SkBitmap ExtractThumbnail(const base::RefCountedMemory& t1) { - scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(t1.front(), - t1.size())); - return image.get() ? *image : SkBitmap(); -} - // Returns true if t1 and t2 contain the same data. bool ThumbnailsAreEqual(base::RefCountedMemory* t1, base::RefCountedMemory* t2) { @@ -142,34 +131,26 @@ bool ThumbnailsAreEqual(base::RefCountedMemory* t1, class TopSitesImplTest : public HistoryUnitTestBase { public: - TopSitesImplTest() - : ui_thread_(BrowserThread::UI, &message_loop_), - db_thread_(BrowserThread::DB, &message_loop_) { - } + TopSitesImplTest() {} void SetUp() override { - profile_.reset(new TestingProfile); - if (CreateHistoryAndTopSites()) { - ASSERT_TRUE(profile_->CreateHistoryService(false, false)); - ResetTopSites(); - profile_->BlockUntilTopSitesLoaded(); - } + ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); + pref_service_.reset(new TestingPrefServiceSimple); + pref_service_->registry()->RegisterDictionaryPref(kBlacklistURLKey); + history_service_.reset( + new HistoryService(nullptr, scoped_ptr<VisitDelegate>())); + ASSERT_TRUE(history_service_->Init( + kAcceptLanguages, + TestHistoryDatabaseParamsForPath(scoped_temp_dir_.path()))); + ResetTopSites(); + WaitTopSitesLoaded(); } void TearDown() override { - profile_.reset(); - } - - // Returns true if history and top sites should be created in SetUp. - virtual bool CreateHistoryAndTopSites() { - return true; - } - - // Gets the thumbnail for |url| from TopSites. - SkBitmap GetThumbnail(const GURL& url) { - scoped_refptr<base::RefCountedMemory> data; - return top_sites()->GetPageThumbnail(url, false, &data) ? - ExtractThumbnail(*data.get()) : SkBitmap(); + DestroyTopSites(); + history_service_->Shutdown(); + history_service_.reset(); + pref_service_.reset(); } // Creates a bitmap of the specified color. Caller takes ownership. @@ -207,15 +188,9 @@ class TopSitesImplTest : public HistoryUnitTestBase { base::MessageLoop::current()->Run(); } - TopSitesImpl* top_sites() { - return static_cast<TopSitesImpl*>( - TopSitesFactory::GetForProfile(profile_.get()).get()); - } - TestingProfile* profile() {return profile_.get();} - HistoryService* history_service() { - return HistoryServiceFactory::GetForProfile( - profile_.get(), ServiceAccessType::EXPLICIT_ACCESS); - } + TopSitesImpl* top_sites() { return top_sites_impl_.get(); } + + HistoryService* history_service() { return history_service_.get(); } PrepopulatedPageList GetPrepopulatedPages() { return top_sites()->GetPrepopulatedPages(); @@ -236,9 +211,7 @@ class TopSitesImplTest : public HistoryUnitTestBase { // Quit the current message loop when invoked. Useful when running a nested // message loop. - void QuitCallback() { - base::MessageLoop::current()->Quit(); - } + void QuitCallback() { base::MessageLoop::current()->Quit(); } // Adds a page to history. void AddPageToHistory(const GURL& url) { @@ -246,8 +219,7 @@ class TopSitesImplTest : public HistoryUnitTestBase { redirects.push_back(url); history_service()->AddPage( url, base::Time::Now(), reinterpret_cast<ContextID>(1), 0, GURL(), - redirects, ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED, - false); + redirects, ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED, false); } // Adds a page to history. @@ -256,8 +228,7 @@ class TopSitesImplTest : public HistoryUnitTestBase { redirects.push_back(url); history_service()->AddPage( url, base::Time::Now(), reinterpret_cast<ContextID>(1), 0, GURL(), - redirects, ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED, - false); + redirects, ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED, false); history_service()->SetPageTitle(url, title); } @@ -274,9 +245,7 @@ class TopSitesImplTest : public HistoryUnitTestBase { } // Delets a url. - void DeleteURL(const GURL& url) { - history_service()->DeleteURL(url); - } + void DeleteURL(const GURL& url) { history_service()->DeleteURL(url); } // Returns true if the thumbnail equals the specified bytes. bool ThumbnailEqualsBytes(const gfx::Image& image, @@ -290,8 +259,7 @@ class TopSitesImplTest : public HistoryUnitTestBase { void RecreateTopSitesAndBlock() { // Recreate TopSites and wait for it to load. ResetTopSites(); - // As history already loaded we have to fake this call. - profile()->BlockUntilTopSitesLoaded(); + WaitTopSitesLoaded(); } // Wrappers that allow private TopSites functions to be called from the @@ -309,9 +277,7 @@ class TopSitesImplTest : public HistoryUnitTestBase { return top_sites()->AddForcedURL(url, time); } - void StartQueryForMostVisited() { - top_sites()->StartQueryForMostVisited(); - } + void StartQueryForMostVisited() { top_sites()->StartQueryForMostVisited(); } void SetLastNumUrlsChanged(size_t value) { top_sites()->last_num_urls_changed_ = value; @@ -319,9 +285,7 @@ class TopSitesImplTest : public HistoryUnitTestBase { size_t last_num_urls_changed() { return top_sites()->last_num_urls_changed_; } - base::TimeDelta GetUpdateDelay() { - return top_sites()->GetUpdateDelay(); - } + base::TimeDelta GetUpdateDelay() { return top_sites()->GetUpdateDelay(); } bool IsTopSitesLoaded() { return top_sites()->loaded_; } @@ -337,19 +301,45 @@ class TopSitesImplTest : public HistoryUnitTestBase { void ResetTopSites() { // TopSites shutdown takes some time as it happens on the DB thread and does - // not support the existence of two TopSitesImpl for a single profile (due - // to database locking). TestingProfile::DestroyTopSites() waits for the - // TopSites cleanup to complete before returning. - profile_->DestroyTopSites(); - TopSitesFactory::GetInstance()->SetTestingFactory(profile_.get(), - BuildTopSitesImpl); + // not support the existence of two TopSitesImpl for a location (due to + // database locking). DestroyTopSites() waits for the TopSites cleanup to + // complete before returning. + DestroyTopSites(); + DCHECK(!top_sites_impl_); + PrepopulatedPageList prepopulated_pages; + prepopulated_pages.push_back(PrepopulatedPage(GURL(kPrepopulatedPageURL), + base::string16(), -1, -1, 0)); + top_sites_impl_ = new TopSitesImpl( + pref_service_.get(), history_service_.get(), kBlacklistURLKey, + prepopulated_pages, base::Bind(MockCanAddURLToHistory)); + top_sites_impl_->Init(scoped_temp_dir_.path().Append(kTopSitesFilename), + message_loop_.task_runner()); + } + + void DestroyTopSites() { + if (top_sites_impl_) { + top_sites_impl_->ShutdownOnUIThread(); + top_sites_impl_ = nullptr; + + if (base::MessageLoop::current()) + base::MessageLoop::current()->RunUntilIdle(); + } + } + + void WaitTopSitesLoaded() { + DCHECK(top_sites_impl_); + WaitTopSitesLoadedObserver wait_top_sites_loaded_observer(top_sites_impl_); + wait_top_sites_loaded_observer.Run(); } private: + base::ScopedTempDir scoped_temp_dir_; base::MessageLoopForUI message_loop_; - content::TestBrowserThread ui_thread_; - content::TestBrowserThread db_thread_; - scoped_ptr<TestingProfile> profile_; + + scoped_ptr<TestingPrefServiceSimple> pref_service_; + scoped_ptr<HistoryService> history_service_; + scoped_refptr<TopSitesImpl> top_sites_impl_; + // To cancel HistoryService tasks. base::CancelableTaskTracker history_tracker_; @@ -383,9 +373,9 @@ static void AppendForcedMostVisitedURL(std::vector<MostVisitedURL>* list, // Same as AppendMostVisitedURL except that it adds a redirect from the first // URL to the second. -static void AppendMostVisitedURLWithRedirect( - std::vector<MostVisitedURL>* list, - const GURL& redirect_source, const GURL& redirect_dest) { +static void AppendMostVisitedURLWithRedirect(std::vector<MostVisitedURL>* list, + const GURL& redirect_source, + const GURL& redirect_dest) { MostVisitedURL mv; mv.url = redirect_dest; mv.redirects.push_back(redirect_source); @@ -528,7 +518,7 @@ TEST_F(TopSitesImplTest, SetPageThumbnail) { GURL url1a("http://google.com/"); GURL url1b("http://www.google.com/"); GURL url2("http://images.google.com/"); - GURL invalid_url("chrome://favicon/http://google.com/"); + GURL invalid_url("application://favicon/http://google.com/"); std::vector<MostVisitedURL> list; AppendMostVisitedURL(&list, url2); @@ -551,8 +541,8 @@ TEST_F(TopSitesImplTest, SetPageThumbnail) { ThumbnailScore high_score(0.0, true, true, now); // Setting the thumbnail for invalid pages should fail. - EXPECT_FALSE(top_sites()->SetPageThumbnail(invalid_url, - thumbnail, medium_score)); + EXPECT_FALSE( + top_sites()->SetPageThumbnail(invalid_url, thumbnail, medium_score)); // Setting the thumbnail for url2 should succeed, lower scores shouldn't // replace it, higher scores should. @@ -753,7 +743,9 @@ TEST_F(TopSitesImplTest, SaveForcedToDB) { // Get the original thumbnail for later comparison. Some compression can // happen in |top_sites| and we don't want to depend on that. - SkBitmap orig_thumbnail = GetThumbnail(GURL("http://forced1")); + scoped_refptr<base::RefCountedMemory> orig_thumbnail_data; + ASSERT_TRUE(top_sites()->GetPageThumbnail(GURL("http://forced1"), false, + &orig_thumbnail_data)); // Force-flush the cache to ensure we don't reread from it inadvertently. EmptyThreadSafeCache(); @@ -768,14 +760,11 @@ TEST_F(TopSitesImplTest, SaveForcedToDB) { ASSERT_EQ(4u + GetPrepopulatedPages().size(), querier.urls().size()); EXPECT_EQ(GURL("http://forced1"), querier.urls()[0].url); EXPECT_EQ(base::ASCIIToUTF16("forced1"), querier.urls()[0].title); - SkBitmap thumbnail = GetThumbnail(GURL("http://forced1")); - ASSERT_EQ(orig_thumbnail.getSize(), thumbnail.getSize()); - orig_thumbnail.lockPixels(); - thumbnail.lockPixels(); - EXPECT_EQ(0, memcmp(orig_thumbnail.getPixels(), thumbnail.getPixels(), - orig_thumbnail.getSize())); - thumbnail.unlockPixels(); - orig_thumbnail.unlockPixels(); + scoped_refptr<base::RefCountedMemory> thumbnail_data; + ASSERT_TRUE(top_sites()->GetPageThumbnail(GURL("http://forced1"), false, + &thumbnail_data)); + ASSERT_TRUE( + ThumbnailsAreEqual(orig_thumbnail_data.get(), thumbnail_data.get())); EXPECT_EQ(base::Time::FromJsTime(1000), querier.urls()[0].last_forced_time); EXPECT_EQ(GURL("http://forced2"), querier.urls()[1].url); EXPECT_EQ(base::Time::FromJsTime(2000), querier.urls()[1].last_forced_time); @@ -1019,7 +1008,7 @@ TEST_F(TopSitesImplTest, NotifyCallbacksWhenLoaded) { EXPECT_EQ(0, querier3.number_of_callbacks()); // Wait for loading to complete. - profile()->BlockUntilTopSitesLoaded(); + WaitTopSitesLoaded(); // Now we should have gotten the callbacks. EXPECT_EQ(1, querier1.number_of_callbacks()); @@ -1054,7 +1043,7 @@ TEST_F(TopSitesImplTest, NotifyCallbacksWhenLoaded) { EXPECT_EQ(0, querier4.number_of_callbacks()); // Wait for loading to complete. - profile()->BlockUntilTopSitesLoaded(); + WaitTopSitesLoaded(); // Now we should have gotten the callbacks. EXPECT_EQ(1, querier4.number_of_callbacks()); @@ -1104,7 +1093,7 @@ TEST_F(TopSitesImplTest, CancelingRequestsForTopSites) { querier2.CancelRequest(); // Wait for loading to complete. - profile()->BlockUntilTopSitesLoaded(); + WaitTopSitesLoaded(); // The first callback should succeed. EXPECT_EQ(1, querier1.number_of_callbacks()); @@ -1117,7 +1106,7 @@ TEST_F(TopSitesImplTest, CancelingRequestsForTopSites) { // Makes sure temporary thumbnails are copied over correctly. TEST_F(TopSitesImplTest, AddTemporaryThumbnail) { GURL unknown_url("http://news.google.com/"); - GURL invalid_url("chrome://thumb/http://google.com/"); + GURL invalid_url("application://thumb/http://google.com/"); GURL url1a("http://google.com/"); GURL url1b("http://www.google.com/"); @@ -1457,7 +1446,6 @@ TEST_F(TopSitesImplTest, SetForcedTopSites) { } TEST_F(TopSitesImplTest, SetForcedTopSitesWithCollisions) { - // Setup an old URL list in order to generate some collisions. MostVisitedURLList old_url_list; AppendForcedMostVisitedURL(&old_url_list, GURL("http://url/0"), 1000); @@ -1638,7 +1626,9 @@ TEST_F(TopSitesImplTest, AddForcedURL) { // Get the original thumbnail for later comparison. Some compression can // happen in |top_sites| and we don't want to depend on that. - SkBitmap orig_thumbnail = GetThumbnail(GURL("http://forced/5")); + scoped_refptr<base::RefCountedMemory> orig_thumbnail_data; + ASSERT_TRUE(top_sites()->GetPageThumbnail(GURL("http://forced/5"), false, + &orig_thumbnail_data)); EXPECT_TRUE(AddForcedURL(GURL("http://forced/5"), base::Time::FromJsTime(6000))); @@ -1647,14 +1637,11 @@ TEST_F(TopSitesImplTest, AddForcedURL) { querier.QueryAllTopSites(top_sites(), false, true); EXPECT_EQ("http://forced/5", querier.urls()[5].url.spec()); EXPECT_EQ(6000u, querier.urls()[5].last_forced_time.ToJsTime()); - SkBitmap thumbnail = GetThumbnail(GURL("http://forced/5")); - ASSERT_EQ(orig_thumbnail.getSize(), thumbnail.getSize()); - orig_thumbnail.lockPixels(); - thumbnail.lockPixels(); - EXPECT_EQ(0, memcmp(orig_thumbnail.getPixels(), thumbnail.getPixels(), - orig_thumbnail.getSize())); - thumbnail.unlockPixels(); - orig_thumbnail.unlockPixels(); + scoped_refptr<base::RefCountedMemory> thumbnail_data; + ASSERT_TRUE(top_sites()->GetPageThumbnail(GURL("http://forced/5"), false, + &thumbnail_data)); + ASSERT_TRUE( + ThumbnailsAreEqual(orig_thumbnail_data.get(), thumbnail_data.get())); } } // namespace history diff --git a/chrome/browser/history/typed_url_syncable_service_unittest.cc b/components/history/core/browser/typed_url_syncable_service_unittest.cc index 2b5cc77..55ea26b 100644 --- a/chrome/browser/history/typed_url_syncable_service_unittest.cc +++ b/components/history/core/browser/typed_url_syncable_service_unittest.cc @@ -10,7 +10,6 @@ #include "base/strings/utf_string_conversions.h" #include "components/history/core/browser/history_backend.h" #include "components/history/core/browser/history_types.h" -#include "content/public/browser/notification_types.h" #include "sync/api/fake_sync_change_processor.h" #include "sync/api/sync_change_processor_wrapper_for_test.h" #include "sync/api/sync_error.h" @@ -68,9 +67,7 @@ class TestHistoryBackend : public HistoryBackend { visits->end()); } - void DeleteVisitsForUrl(const URLID& id) { - local_db_visits_.erase(id); - } + void DeleteVisitsForUrl(const URLID& id) { local_db_visits_.erase(id); } private: ~TestHistoryBackend() override {} @@ -110,8 +107,7 @@ class TypedUrlSyncableServiceTest : public testing::Test { ui::PageTransition transition, int64 visit_time); - static bool URLsEqual(URLRow& row, - sync_pb::TypedUrlSpecifics& specifics) { + static bool URLsEqual(URLRow& row, sync_pb::TypedUrlSpecifics& specifics) { return ((row.url().spec().compare(specifics.url()) == 0) && (base::UTF16ToUTF8(row.title()).compare(specifics.title()) == 0) && (row.hidden() == specifics.hidden())); @@ -140,13 +136,12 @@ class TypedUrlSyncableServiceTest : public testing::Test { scoped_ptr<syncer::FakeSyncChangeProcessor> fake_change_processor_; }; -URLRow TypedUrlSyncableServiceTest::MakeTypedUrlRow( - const char* url, - const char* title, - int typed_count, - int64 last_visit, - bool hidden, - VisitVector* visits) { +URLRow TypedUrlSyncableServiceTest::MakeTypedUrlRow(const char* url, + const char* title, + int typed_count, + int64 last_visit, + bool hidden, + VisitVector* visits) { DCHECK(visits->empty()); // Give each URL a unique ID, to mimic the behavior of the real database. @@ -177,11 +172,10 @@ URLRow TypedUrlSyncableServiceTest::MakeTypedUrlRow( return history_url; } -void TypedUrlSyncableServiceTest::AddNewestVisit( - URLRow* url, - VisitVector* visits, - ui::PageTransition transition, - int64 visit_time) { +void TypedUrlSyncableServiceTest::AddNewestVisit(URLRow* url, + VisitVector* visits, + ui::PageTransition transition, + int64 visit_time) { base::Time time = base::Time::FromInternalValue(visit_time); visits->insert(visits->begin(), VisitRow(url->id(), time, 0, transition, 0)); @@ -194,11 +188,10 @@ void TypedUrlSyncableServiceTest::AddNewestVisit( url->set_visit_count(visits->size()); } -void TypedUrlSyncableServiceTest::AddOldestVisit( - URLRow* url, - VisitVector* visits, - ui::PageTransition transition, - int64 visit_time) { +void TypedUrlSyncableServiceTest::AddOldestVisit(URLRow* url, + VisitVector* visits, + ui::PageTransition transition, + int64 visit_time) { base::Time time = base::Time::FromInternalValue(visit_time); visits->push_back(VisitRow(url->id(), time, 0, transition, 0)); diff --git a/components/history/core/test/BUILD.gn b/components/history/core/test/BUILD.gn index 1577366..709a3a0 100644 --- a/components/history/core/test/BUILD.gn +++ b/components/history/core/test/BUILD.gn @@ -7,14 +7,20 @@ static_library("test") { sources = [ "database_test_utils.cc", "database_test_utils.h", - "history_backend_base_test.cc", - "history_backend_base_test.h", + "history_backend_db_base_test.cc", + "history_backend_db_base_test.h", "history_client_fake_bookmarks.cc", "history_client_fake_bookmarks.h", "history_unittest_base.cc", "history_unittest_base.h", "test_history_database.cc", "test_history_database.h", + "thumbnail-inl.h", + "thumbnail.cc", + "thumbnail.h", + "thumbnail_ios.mm", + "wait_top_sites_loaded_observer.cc", + "wait_top_sites_loaded_observer.h", ] deps = [ @@ -23,6 +29,11 @@ static_library("test") { "//sql", "//sql:test_support", "//testing/gtest", + "//ui/gfx", "//url", ] + + if (is_ios) { + sources -= [ "thumbnail.cc" ] + } } diff --git a/components/history/core/test/history_backend_base_test.cc b/components/history/core/test/history_backend_db_base_test.cc index 39bfa7b..e94efbd 100644 --- a/components/history/core/test/history_backend_base_test.cc +++ b/components/history/core/test/history_backend_db_base_test.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/history/core/test/history_backend_base_test.h" +#include "components/history/core/test/history_backend_db_base_test.h" #include "base/files/file_path.h" #include "base/strings/stringprintf.h" @@ -21,7 +21,7 @@ namespace history { // Delegate class for when we create a backend without a HistoryService. class BackendDelegate : public HistoryBackend::Delegate { public: - explicit BackendDelegate(HistoryBackendBaseTest* history_test) + explicit BackendDelegate(HistoryBackendDBBaseTest* history_test) : history_test_(history_test) {} // HistoryBackend::Delegate implementation. @@ -48,22 +48,22 @@ class BackendDelegate : public HistoryBackend::Delegate { void DBLoaded() override {} private: - HistoryBackendBaseTest* history_test_; + HistoryBackendDBBaseTest* history_test_; }; -HistoryBackendBaseTest::HistoryBackendBaseTest() : db_(nullptr) { +HistoryBackendDBBaseTest::HistoryBackendDBBaseTest() : db_(nullptr) { } -HistoryBackendBaseTest::~HistoryBackendBaseTest() { +HistoryBackendDBBaseTest::~HistoryBackendDBBaseTest() { } -void HistoryBackendBaseTest::SetUp() { +void HistoryBackendDBBaseTest::SetUp() { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - history_dir_ = temp_dir_.path().AppendASCII("HistoryBackendBaseTest"); + history_dir_ = temp_dir_.path().AppendASCII("HistoryBackendDBBaseTest"); ASSERT_TRUE(base::CreateDirectory(history_dir_)); } -void HistoryBackendBaseTest::TearDown() { +void HistoryBackendDBBaseTest::TearDown() { DeleteBackend(); // Make sure we don't have any event pending that could disrupt the next @@ -73,7 +73,7 @@ void HistoryBackendBaseTest::TearDown() { base::MessageLoop::current()->Run(); } -void HistoryBackendBaseTest::CreateBackendAndDatabase() { +void HistoryBackendDBBaseTest::CreateBackendAndDatabase() { backend_ = new HistoryBackend(new BackendDelegate(this), nullptr); backend_->Init(std::string(), false, TestHistoryDatabaseParamsForPath(history_dir_)); @@ -82,7 +82,7 @@ void HistoryBackendBaseTest::CreateBackendAndDatabase() { "HistoryBackend::Init"; } -void HistoryBackendBaseTest::CreateDBVersion(int version) { +void HistoryBackendDBBaseTest::CreateDBVersion(int version) { base::FilePath data_path; ASSERT_TRUE(GetTestDataHistoryDir(&data_path)); data_path = @@ -91,7 +91,7 @@ void HistoryBackendBaseTest::CreateDBVersion(int version) { ExecuteSQLScript(data_path, history_dir_.Append(kHistoryFilename))); } -void HistoryBackendBaseTest::CreateArchivedDB() { +void HistoryBackendDBBaseTest::CreateArchivedDB() { base::FilePath data_path; ASSERT_TRUE(GetTestDataHistoryDir(&data_path)); data_path = data_path.AppendASCII("archived_history.4.sql"); @@ -99,14 +99,14 @@ void HistoryBackendBaseTest::CreateArchivedDB() { data_path, history_dir_.Append(kArchivedHistoryFilename))); } -void HistoryBackendBaseTest::DeleteBackend() { +void HistoryBackendDBBaseTest::DeleteBackend() { if (backend_.get()) { backend_->Closing(); backend_ = nullptr; } } -bool HistoryBackendBaseTest::AddDownload(uint32 id, +bool HistoryBackendDBBaseTest::AddDownload(uint32 id, DownloadState state, base::Time time) { std::vector<GURL> url_chain; diff --git a/components/history/core/test/history_backend_base_test.h b/components/history/core/test/history_backend_db_base_test.h index cd4cb7d..e07ed32 100644 --- a/components/history/core/test/history_backend_base_test.h +++ b/components/history/core/test/history_backend_db_base_test.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_HISTORY_CORE_TEST_HISTORY_BACKEND_BASE_TEST_H_ -#define COMPONENTS_HISTORY_CORE_TEST_HISTORY_BACKEND_BASE_TEST_H_ +#ifndef COMPONENTS_HISTORY_CORE_TEST_HISTORY_BACKEND_DB_BASE_TEST_H_ +#define COMPONENTS_HISTORY_CORE_TEST_HISTORY_BACKEND_DB_BASE_TEST_H_ #include "base/files/file_path.h" #include "base/files/file_util.h" @@ -27,10 +27,10 @@ enum class DownloadState; // This must be outside the anonymous namespace for the friend statement in // HistoryBackend to work. -class HistoryBackendBaseTest : public HistoryUnitTestBase { +class HistoryBackendDBBaseTest : public HistoryUnitTestBase { public: - HistoryBackendBaseTest(); - ~HistoryBackendBaseTest() override; + HistoryBackendDBBaseTest(); + ~HistoryBackendDBBaseTest() override; protected: friend class BackendDelegate; @@ -66,4 +66,4 @@ class HistoryBackendBaseTest : public HistoryUnitTestBase { } // namespace history -#endif // COMPONENTS_HISTORY_CORE_TEST_HISTORY_BACKEND_BASE_TEST_H_ +#endif // COMPONENTS_HISTORY_CORE_TEST_HISTORY_BACKEND_DB_BASE_TEST_H_ diff --git a/components/history/core/test/thumbnail.cc b/components/history/core/test/thumbnail.cc new file mode 100644 index 0000000..6586dca --- /dev/null +++ b/components/history/core/test/thumbnail.cc @@ -0,0 +1,21 @@ +// Copyright 2015 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 "components/history/core/test/thumbnail.h" + +#include "components/history/core/test/thumbnail-inl.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/jpeg_codec.h" +#include "ui/gfx/image/image.h" + +namespace history { + +gfx::Image CreateGoogleThumbnailForTest() { + // Returned image takes ownership of decoded SkBitmap. + scoped_ptr<SkBitmap> thumbnail_bitmap( + gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail))); + return gfx::Image::CreateFrom1xBitmap(*thumbnail_bitmap); +} + +} // namespace diff --git a/components/history/core/test/thumbnail.h b/components/history/core/test/thumbnail.h new file mode 100644 index 0000000..77be3d5 --- /dev/null +++ b/components/history/core/test/thumbnail.h @@ -0,0 +1,19 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_HISTORY_CORE_TEST_THUMBNAIL_H_ +#define COMPONENTS_HISTORY_CORE_TEST_THUMBNAIL_H_ + +namespace gfx { +class Image; +} + +namespace history { + +// Returns a gfx::Image corresponding to kGoogleThumbnail data for test. +gfx::Image CreateGoogleThumbnailForTest(); + +} // namespace history + +#endif // COMPONENTS_HISTORY_CORE_TEST_THUMBNAIL_H_ diff --git a/components/history/core/test/thumbnail_ios.mm b/components/history/core/test/thumbnail_ios.mm new file mode 100644 index 0000000..d3b0cee --- /dev/null +++ b/components/history/core/test/thumbnail_ios.mm @@ -0,0 +1,30 @@ +// Copyright 2015 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 "components/history/core/test/thumbnail.h" + +#import <Foundation/Foundation.h> +#import <UIKit/UIKit.h> + +#import "base/mac/scoped_nsautorelease_pool.h" +#include "components/history/core/test/thumbnail-inl.h" +#include "ui/gfx/image/image.h" + +namespace history { + +gfx::Image CreateGoogleThumbnailForTest() { + base::mac::ScopedNSAutoreleasePool pool; + // -[NSData dataWithBytesNoCopy:length:freeWhenDone:] takes it first parameter + // as a void* but does not modify it (API is not const clean) so we need to + // use const_cast<> here. + NSData* data = + [NSData dataWithBytesNoCopy:const_cast<void*>(static_cast<const void*>( + kGoogleThumbnail)) + length:sizeof(kGoogleThumbnail) + freeWhenDone:NO]; + UIImage* image = [UIImage imageWithData:data scale:1]; + return gfx::Image([image retain]); +} + +} // namespace diff --git a/components/history/core/test/wait_top_sites_loaded_observer.cc b/components/history/core/test/wait_top_sites_loaded_observer.cc new file mode 100644 index 0000000..79b1a38 --- /dev/null +++ b/components/history/core/test/wait_top_sites_loaded_observer.cc @@ -0,0 +1,35 @@ +// Copyright 2015 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 "components/history/core/test/wait_top_sites_loaded_observer.h" + +#include "components/history/core/browser/top_sites.h" + +namespace history { + +WaitTopSitesLoadedObserver::WaitTopSitesLoadedObserver( + scoped_refptr<TopSites> top_sites) + : top_sites_(top_sites) { + if (top_sites_) + top_sites_->AddObserver(this); +} + +WaitTopSitesLoadedObserver::~WaitTopSitesLoadedObserver() { + if (top_sites_) + top_sites_->RemoveObserver(this); +} + +void WaitTopSitesLoadedObserver::Run() { + if (top_sites_ && !top_sites_->loaded()) + run_loop_.Run(); +} + +void WaitTopSitesLoadedObserver::TopSitesLoaded(TopSites* top_sites) { + run_loop_.Quit(); +} + +void WaitTopSitesLoadedObserver::TopSitesChanged(TopSites* top_sites) { +} + +} // namespace history diff --git a/components/history/core/test/wait_top_sites_loaded_observer.h b/components/history/core/test/wait_top_sites_loaded_observer.h new file mode 100644 index 0000000..d21cf7c --- /dev/null +++ b/components/history/core/test/wait_top_sites_loaded_observer.h @@ -0,0 +1,39 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_HISTORY_CORE_TEST_WAIT_TOP_SITES_LOADED_OBSERVER_H_ +#define COMPONENTS_HISTORY_CORE_TEST_WAIT_TOP_SITES_LOADED_OBSERVER_H_ + +#include "base/memory/ref_counted.h" +#include "base/run_loop.h" +#include "components/history/core/browser/top_sites_observer.h" + +namespace history { + +class TopSites; + +// Used to make sure TopSites has finished loading +class WaitTopSitesLoadedObserver : public TopSitesObserver { + public: + explicit WaitTopSitesLoadedObserver(scoped_refptr<TopSites> top_sites); + ~WaitTopSitesLoadedObserver() override; + + // Wait until TopSites has finished loading. Returns immediately if it has + // already been loaded. + void Run(); + + private: + // TopSitesObserver implementation. + void TopSitesLoaded(TopSites* top_sites) override; + void TopSitesChanged(TopSites* top_sites) override; + + scoped_refptr<TopSites> top_sites_; + base::RunLoop run_loop_; + + DISALLOW_COPY_AND_ASSIGN(WaitTopSitesLoadedObserver); +}; + +} // namespace history + +#endif // COMPONENTS_HISTORY_CORE_TEST_WAIT_TOP_SITES_LOADED_OBSERVER_H_ diff --git a/chrome/test/data/History/Favicons.v3.sql b/components/test/data/history/Favicons.v3.sql index 1cdfc6f..1cdfc6f 100644 --- a/chrome/test/data/History/Favicons.v3.sql +++ b/components/test/data/history/Favicons.v3.sql diff --git a/chrome/test/data/History/Favicons.v4.sql b/components/test/data/history/Favicons.v4.sql index d2549bf..d2549bf 100644 --- a/chrome/test/data/History/Favicons.v4.sql +++ b/components/test/data/history/Favicons.v4.sql diff --git a/chrome/test/data/History/Favicons.v5.sql b/components/test/data/history/Favicons.v5.sql index 655d282..655d282 100644 --- a/chrome/test/data/History/Favicons.v5.sql +++ b/components/test/data/history/Favicons.v5.sql diff --git a/chrome/test/data/History/Favicons.v6.sql b/components/test/data/history/Favicons.v6.sql index c27125b..c27125b 100644 --- a/chrome/test/data/History/Favicons.v6.sql +++ b/components/test/data/history/Favicons.v6.sql diff --git a/chrome/test/data/History/Favicons.v7.sql b/components/test/data/history/Favicons.v7.sql index 8b0fd2c..8b0fd2c 100644 --- a/chrome/test/data/History/Favicons.v7.sql +++ b/components/test/data/history/Favicons.v7.sql diff --git a/chrome/test/data/History/Favicons.v8.sql b/components/test/data/history/Favicons.v8.sql index 0905172..0905172 100644 --- a/chrome/test/data/History/Favicons.v8.sql +++ b/components/test/data/history/Favicons.v8.sql diff --git a/chrome/test/data/History/HistoryNoDuration b/components/test/data/history/HistoryNoDuration Binary files differindex 3d00e5a..3d00e5a 100644 --- a/chrome/test/data/History/HistoryNoDuration +++ b/components/test/data/history/HistoryNoDuration diff --git a/chrome/test/data/History/HistoryNoSource b/components/test/data/history/HistoryNoSource Binary files differindex 19b4b8e..19b4b8e 100644 --- a/chrome/test/data/History/HistoryNoSource +++ b/components/test/data/history/HistoryNoSource diff --git a/chrome/test/data/bookmarks/History_with_starred b/components/test/data/history/History_with_starred Binary files differindex 4b913b2..4b913b2 100644 --- a/chrome/test/data/bookmarks/History_with_starred +++ b/components/test/data/history/History_with_starred diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.corrupt_meta.disable b/components/test/data/history/thumbnail_wild/Favicons.corrupt_meta.disable index 05e46ef..05e46ef 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.corrupt_meta.disable +++ b/components/test/data/history/thumbnail_wild/Favicons.corrupt_meta.disable diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v2.init.sql b/components/test/data/history/thumbnail_wild/Favicons.v2.init.sql index 53b6b73..53b6b73 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v2.init.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v2.init.sql diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v3.init.sql b/components/test/data/history/thumbnail_wild/Favicons.v3.init.sql index 3453860..3453860 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v3.init.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v3.init.sql diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v4.init.sql b/components/test/data/history/thumbnail_wild/Favicons.v4.init.sql index 6c5738e..6c5738e 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v4.init.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v4.init.sql diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v5.icon_type.sql b/components/test/data/history/thumbnail_wild/Favicons.v5.icon_type.sql index a00a439..a00a439 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v5.icon_type.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v5.icon_type.sql diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v5.icon_type2.sql b/components/test/data/history/thumbnail_wild/Favicons.v5.icon_type2.sql index 25ceded..25ceded 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v5.icon_type2.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v5.icon_type2.sql diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v5.init.sql b/components/test/data/history/thumbnail_wild/Favicons.v5.init.sql index c92a110..c92a110 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v5.init.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v5.init.sql diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v5.sizes.sql b/components/test/data/history/thumbnail_wild/Favicons.v5.sizes.sql index 6dfa9e9..6dfa9e9 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v5.sizes.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v5.sizes.sql diff --git a/chrome/test/data/History/thumbnail_wild/Favicons.v6.init.sql b/components/test/data/history/thumbnail_wild/Favicons.v6.init.sql index 3014c37..3014c37 100644 --- a/chrome/test/data/History/thumbnail_wild/Favicons.v6.init.sql +++ b/components/test/data/history/thumbnail_wild/Favicons.v6.init.sql diff --git a/chrome/test/data/History/thumbnail_wild/README b/components/test/data/history/thumbnail_wild/README index efe0e53..efe0e53 100644 --- a/chrome/test/data/History/thumbnail_wild/README +++ b/components/test/data/history/thumbnail_wild/README |