diff options
Diffstat (limited to 'chromium/components/doodle/doodle_service_unittest.cc')
-rw-r--r-- | chromium/components/doodle/doodle_service_unittest.cc | 412 |
1 files changed, 374 insertions, 38 deletions
diff --git a/chromium/components/doodle/doodle_service_unittest.cc b/chromium/components/doodle/doodle_service_unittest.cc index 48b3a10cade..8f6fc7e1011 100644 --- a/chromium/components/doodle/doodle_service_unittest.cc +++ b/chromium/components/doodle/doodle_service_unittest.cc @@ -10,6 +10,13 @@ #include "base/bind.h" #include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/test/histogram_tester.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/test/test_mock_time_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "components/prefs/testing_pref_service.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -32,9 +39,10 @@ class FakeDoodleFetcher : public DoodleFetcher { size_t num_pending_callbacks() const { return callbacks_.size(); } void ServeAllCallbacks(DoodleState state, + base::TimeDelta time_to_live, const base::Optional<DoodleConfig>& config) { for (auto& callback : callbacks_) { - std::move(callback).Run(state, config); + std::move(callback).Run(state, time_to_live, config); } callbacks_.clear(); } @@ -49,28 +57,66 @@ class MockDoodleObserver : public DoodleService::Observer { void(const base::Optional<DoodleConfig>&)); }; -} // namespace - -// Equality operator for DoodleConfigs, for use by testing::Eq. -// Note: This must be outside of the anonymous namespace. -bool operator==(const DoodleConfig& lhs, const DoodleConfig& rhs) { - return lhs.IsEquivalent(rhs); +DoodleConfig CreateConfig(DoodleType type) { + return DoodleConfig(type, DoodleImage(GURL("https://doodle.com/image.jpg"))); } +} // namespace + class DoodleServiceTest : public testing::Test { public: - DoodleServiceTest() : fetcher_(nullptr) { + DoodleServiceTest() + : task_runner_(new base::TestMockTimeTaskRunner()), + task_runner_handle_(task_runner_), + tick_clock_(task_runner_->GetMockTickClock()), + fetcher_(nullptr), + expiry_timer_(nullptr) { + DoodleService::RegisterProfilePrefs(pref_service_.registry()); + + task_runner_->FastForwardBy(base::TimeDelta::FromHours(12345)); + + // Set the minimum refresh interval to 0 by default, so tests don't have to + // worry about it. The tests that care set it explicitly. + RecreateServiceWithZeroRefreshInterval(); + } + + void DestroyService() { service_ = nullptr; } + + void RecreateServiceWithZeroRefreshInterval() { + RecreateService(/*min_refresh_interval=*/base::TimeDelta()); + } + + void RecreateService(base::Optional<base::TimeDelta> refresh_interval) { + auto expiry_timer = base::MakeUnique<base::OneShotTimer>(tick_clock_.get()); + expiry_timer->SetTaskRunner(task_runner_); + expiry_timer_ = expiry_timer.get(); + auto fetcher = base::MakeUnique<FakeDoodleFetcher>(); fetcher_ = fetcher.get(); - service_ = base::MakeUnique<DoodleService>(std::move(fetcher)); + + service_ = base::MakeUnique<DoodleService>( + &pref_service_, std::move(fetcher), std::move(expiry_timer), + task_runner_->GetMockClock(), task_runner_->GetMockTickClock(), + refresh_interval); } DoodleService* service() { return service_.get(); } FakeDoodleFetcher* fetcher() { return fetcher_; } + base::TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); } + private: + TestingPrefServiceSimple pref_service_; + + scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; + base::ThreadTaskRunnerHandle task_runner_handle_; + std::unique_ptr<base::TickClock> tick_clock_; + std::unique_ptr<DoodleService> service_; + + // Weak, owned by the service. FakeDoodleFetcher* fetcher_; + base::OneShotTimer* expiry_timer_; }; TEST_F(DoodleServiceTest, FetchesConfigOnRefresh) { @@ -82,9 +128,9 @@ TEST_F(DoodleServiceTest, FetchesConfigOnRefresh) { EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(1u)); // Serve it (with an arbitrary config). - DoodleConfig config; - config.doodle_type = DoodleType::SIMPLE; - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); // The config should be available. EXPECT_THAT(service()->config(), Eq(config)); @@ -95,15 +141,103 @@ TEST_F(DoodleServiceTest, FetchesConfigOnRefresh) { EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(1u)); // Serve it with a different config. - DoodleConfig other_config; - other_config.doodle_type = DoodleType::SLIDESHOW; - DCHECK(!config.IsEquivalent(other_config)); - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, other_config); + DoodleConfig other_config = CreateConfig(DoodleType::SLIDESHOW); + DCHECK(config != other_config); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), other_config); // The config should have been updated. EXPECT_THAT(service()->config(), Eq(other_config)); } +TEST_F(DoodleServiceTest, PersistsConfig) { + // Load some doodle config. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + config.large_image.url = GURL("https://doodle.com/doodle.jpg"); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Re-create the service. It should have persisted the config, and load it + // again automatically. + RecreateServiceWithZeroRefreshInterval(); + EXPECT_THAT(service()->config(), Eq(config)); +} + +TEST_F(DoodleServiceTest, PersistsExpiryDate) { + // Load some doodle config. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + config.large_image.url = GURL("https://doodle.com/doodle.jpg"); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Destroy the service, and let some time pass. + DestroyService(); + task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(15)); + + // Remove the abandoned expiry task from the task runner. + ASSERT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u)); + task_runner()->ClearPendingTasks(); + + // Re-create the service. The persisted config should have been loaded again. + RecreateServiceWithZeroRefreshInterval(); + EXPECT_THAT(service()->config(), Eq(config)); + + // Its time-to-live should have been updated. + EXPECT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u)); + EXPECT_THAT(task_runner()->NextPendingTaskDelay(), + Eq(base::TimeDelta::FromMinutes(45))); +} + +TEST_F(DoodleServiceTest, PersistedConfigExpires) { + // Load some doodle config. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + config.large_image.url = GURL("https://doodle.com/doodle.jpg"); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Destroy the service, and let enough time pass so that the config expires. + DestroyService(); + task_runner()->FastForwardBy(base::TimeDelta::FromHours(1)); + + // Re-create the service. The persisted config should have been discarded + // because it is expired. + RecreateServiceWithZeroRefreshInterval(); + EXPECT_THAT(service()->config(), Eq(base::nullopt)); +} + +TEST_F(DoodleServiceTest, RespectsMinRefreshInterval) { + // Create a service with the default refresh interval. + RecreateService(/*min_refresh_interval=*/base::nullopt); + + // Load some doodle config. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + config.large_image.url = GURL("https://doodle.com/doodle.jpg"); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Let some time pass (less than the refresh interval). + task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(10)); + + // Request a refresh. This should get ignored. + service()->Refresh(); + EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(0u)); + + // Let more time pass, so we get past the refresh interval. + task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(6)); + + // Now the refresh request should be honored again. + service()->Refresh(); + EXPECT_THAT(fetcher()->num_pending_callbacks(), Eq(1u)); +} + TEST_F(DoodleServiceTest, CallsObserverOnConfigReceived) { StrictMock<MockDoodleObserver> observer; service()->AddObserver(&observer); @@ -116,10 +250,10 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigReceived) { ASSERT_THAT(fetcher()->num_pending_callbacks(), Eq(1u)); // Serve it (with an arbitrary config). The observer should get notified. - DoodleConfig config; - config.doodle_type = DoodleType::SIMPLE; + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(config))); - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); // Remove the observer before the service gets destroyed. service()->RemoveObserver(&observer); @@ -128,9 +262,9 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigReceived) { TEST_F(DoodleServiceTest, CallsObserverOnConfigRemoved) { // Load some doodle config. service()->Refresh(); - DoodleConfig config; - config.doodle_type = DoodleType::SIMPLE; - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); ASSERT_THAT(service()->config(), Eq(config)); // Register an observer and request a refresh. @@ -142,7 +276,8 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigRemoved) { // Serve the request with an empty doodle config. The observer should get // notified. EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(base::nullopt))); - fetcher()->ServeAllCallbacks(DoodleState::NO_DOODLE, base::nullopt); + fetcher()->ServeAllCallbacks(DoodleState::NO_DOODLE, base::TimeDelta(), + base::nullopt); // Remove the observer before the service gets destroyed. service()->RemoveObserver(&observer); @@ -151,9 +286,9 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigRemoved) { TEST_F(DoodleServiceTest, CallsObserverOnConfigUpdated) { // Load some doodle config. service()->Refresh(); - DoodleConfig config; - config.doodle_type = DoodleType::SIMPLE; - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); ASSERT_THAT(service()->config(), Eq(config)); // Register an observer and request a refresh. @@ -164,22 +299,22 @@ TEST_F(DoodleServiceTest, CallsObserverOnConfigUpdated) { // Serve the request with a different doodle config. The observer should get // notified. - DoodleConfig other_config; - other_config.doodle_type = DoodleType::SLIDESHOW; - DCHECK(!config.IsEquivalent(other_config)); + DoodleConfig other_config = CreateConfig(DoodleType::SLIDESHOW); + DCHECK(config != other_config); EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(other_config))); - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, other_config); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), other_config); // Remove the observer before the service gets destroyed. service()->RemoveObserver(&observer); } -TEST_F(DoodleServiceTest, DoesNotCallObserverWhenConfigEquivalent) { +TEST_F(DoodleServiceTest, DoesNotCallObserverIfConfigEquivalent) { // Load some doodle config. service()->Refresh(); - DoodleConfig config; - config.doodle_type = DoodleType::SIMPLE; - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, config); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); ASSERT_THAT(service()->config(), Eq(config)); // Register an observer and request a refresh. @@ -190,13 +325,214 @@ TEST_F(DoodleServiceTest, DoesNotCallObserverWhenConfigEquivalent) { // Serve the request with an equivalent doodle config. The observer should // *not* get notified. - DoodleConfig equivalent_config; - equivalent_config.doodle_type = DoodleType::SIMPLE; - DCHECK(config.IsEquivalent(equivalent_config)); - fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, equivalent_config); + DoodleConfig equivalent_config = CreateConfig(DoodleType::SIMPLE); + DCHECK(config == equivalent_config); + fetcher()->ServeAllCallbacks( + DoodleState::AVAILABLE, base::TimeDelta::FromHours(1), equivalent_config); // Remove the observer before the service gets destroyed. service()->RemoveObserver(&observer); } +TEST_F(DoodleServiceTest, CallsObserverWhenConfigExpires) { + // Load some doodle config. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Make sure the task arrived at the timer's task runner. + EXPECT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u)); + EXPECT_THAT(task_runner()->NextPendingTaskDelay(), + Eq(base::TimeDelta::FromHours(1))); + + // Register an observer. + StrictMock<MockDoodleObserver> observer; + service()->AddObserver(&observer); + + // Fast-forward time so that the expiry task will run. The observer should get + // notified that there's no config anymore. + EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(base::nullopt))); + task_runner()->FastForwardBy(base::TimeDelta::FromHours(1)); + + // Remove the observer before the service gets destroyed. + service()->RemoveObserver(&observer); +} + +TEST_F(DoodleServiceTest, DisregardsAlreadyExpiredConfigs) { + StrictMock<MockDoodleObserver> observer; + service()->AddObserver(&observer); + + ASSERT_THAT(service()->config(), Eq(base::nullopt)); + + // Load an already-expired config. This should have no effect; in particular + // no call to the observer. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromSeconds(0), config); + EXPECT_THAT(service()->config(), Eq(base::nullopt)); + + // Load a doodle config as usual. Nothing to see here. + service()->Refresh(); + EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(config))); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Now load an expired config again. The cached one should go away. + service()->Refresh(); + EXPECT_CALL(observer, OnDoodleConfigUpdated(Eq(base::nullopt))); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromSeconds(0), config); + + // Remove the observer before the service gets destroyed. + service()->RemoveObserver(&observer); +} + +TEST_F(DoodleServiceTest, ClampsTimeToLive) { + // Load a config with an excessive time-to-live. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromDays(100), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // The time-to-live should have been clamped to a reasonable maximum. + ASSERT_THAT(task_runner()->GetPendingTaskCount(), Eq(1u)); + EXPECT_THAT(task_runner()->NextPendingTaskDelay(), + Eq(base::TimeDelta::FromDays(30))); +} + +TEST_F(DoodleServiceTest, RecordsMetricsForSuccessfulDownload) { + base::HistogramTester histograms; + + // Load a doodle config as usual, but let it take some time. + service()->Refresh(); + task_runner()->FastForwardBy(base::TimeDelta::FromSeconds(5)); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Metrics should have been recorded. + histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome", + 0, // OUTCOME_NEW_DOODLE + 1); + histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 1); + histograms.ExpectTimeBucketCount("Doodle.ConfigDownloadTime", + base::TimeDelta::FromSeconds(5), 1); +} + +TEST_F(DoodleServiceTest, RecordsMetricsForEmptyDownload) { + base::HistogramTester histograms; + + // Send a "no doodle" response after some time. + service()->Refresh(); + task_runner()->FastForwardBy(base::TimeDelta::FromSeconds(5)); + fetcher()->ServeAllCallbacks(DoodleState::NO_DOODLE, base::TimeDelta(), + base::nullopt); + ASSERT_THAT(service()->config(), Eq(base::nullopt)); + + // Metrics should have been recorded. + histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome", + 3, // OUTCOME_NO_DOODLE + 1); + histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 1); + histograms.ExpectTimeBucketCount("Doodle.ConfigDownloadTime", + base::TimeDelta::FromSeconds(5), 1); +} + +TEST_F(DoodleServiceTest, RecordsMetricsForFailedDownload) { + base::HistogramTester histograms; + + // Let the download fail after some time. + service()->Refresh(); + task_runner()->FastForwardBy(base::TimeDelta::FromSeconds(5)); + fetcher()->ServeAllCallbacks(DoodleState::DOWNLOAD_ERROR, base::TimeDelta(), + base::nullopt); + ASSERT_THAT(service()->config(), Eq(base::nullopt)); + + // The outcome should have been recorded, but not the time - we only record + // that for successful downloads. + histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome", + 5, // OUTCOME_DOWNLOAD_ERROR + 1); + histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0); +} + +TEST_F(DoodleServiceTest, RecordsMetricsForEarlyRefreshRequest) { + // Create a service with some refresh interval. + RecreateService(/*min_refresh_interval=*/base::TimeDelta::FromMinutes(10)); + + // Load a doodle config as usual. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + base::HistogramTester histograms; + + // Request a refresh before the min refresh interval has passed. + task_runner()->FastForwardBy(base::TimeDelta::FromMinutes(5)); + service()->Refresh(); + + // This should not have resulted in a request. + ASSERT_THAT(fetcher()->num_pending_callbacks(), Eq(0u)); + + // The outcome should still have been recorded, but not the time - we only + // record that for successful downloads. + histograms.ExpectUniqueSample("Doodle.ConfigDownloadOutcome", + 7, // OUTCOME_REFRESH_INTERVAL_NOT_PASSED + 1); + histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0); +} + +TEST_F(DoodleServiceTest, DoesNotRecordMetricsAtStartup) { + // Creating the service should not emit any histogram samples. + base::HistogramTester histograms; + RecreateServiceWithZeroRefreshInterval(); + ASSERT_THAT(service()->config(), Eq(base::nullopt)); + histograms.ExpectTotalCount("Doodle.ConfigDownloadOutcome", 0); + histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0); +} + +TEST_F(DoodleServiceTest, DoesNotRecordMetricsAtStartupWithConfig) { + // Load a doodle config as usual. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + // Recreating the service should not emit any histogram samples, even though + // a config gets loaded. + base::HistogramTester histograms; + RecreateServiceWithZeroRefreshInterval(); + ASSERT_THAT(service()->config(), Eq(config)); + histograms.ExpectTotalCount("Doodle.ConfigDownloadOutcome", 0); + histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0); +} + +TEST_F(DoodleServiceTest, DoesNotRecordMetricsWhenConfigExpires) { + // Load some doodle config. + service()->Refresh(); + DoodleConfig config = CreateConfig(DoodleType::SIMPLE); + fetcher()->ServeAllCallbacks(DoodleState::AVAILABLE, + base::TimeDelta::FromHours(1), config); + ASSERT_THAT(service()->config(), Eq(config)); + + base::HistogramTester histograms; + + // Fast-forward time so that the config expires. + task_runner()->FastForwardBy(base::TimeDelta::FromHours(1)); + ASSERT_THAT(service()->config(), Eq(base::nullopt)); + + // This should not have resulted in any metrics being emitted. + histograms.ExpectTotalCount("Doodle.ConfigDownloadOutcome", 0); + histograms.ExpectTotalCount("Doodle.ConfigDownloadTime", 0); +} + } // namespace doodle |