// Copyright 2014 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/domain_reliability/scheduler.h" #include #include "base/bind.h" #include "base/time/time.h" #include "components/domain_reliability/config.h" #include "components/domain_reliability/test_util.h" #include "components/domain_reliability/uploader.h" #include "components/domain_reliability/util.h" #include "testing/gtest/include/gtest/gtest.h" namespace domain_reliability { namespace { using base::TimeDelta; using base::TimeTicks; class DomainReliabilitySchedulerTest : public testing::Test { public: DomainReliabilitySchedulerTest() : num_collectors_(0), params_(MakeTestSchedulerParams()), callback_called_(false) {} void CreateScheduler(int num_collectors) { DCHECK_LT(0, num_collectors); DCHECK(!scheduler_); num_collectors_ = num_collectors; scheduler_.reset(new DomainReliabilityScheduler( &time_, num_collectors_, params_, base::Bind(&DomainReliabilitySchedulerTest::ScheduleUploadCallback, base::Unretained(this)))); scheduler_->MakeDeterministicForTesting(); } void NotifySuccessfulUpload() { DomainReliabilityUploader::UploadResult result; result.status = DomainReliabilityUploader::UploadResult::SUCCESS; scheduler_->OnUploadComplete(result); } void NotifyFailedUpload() { DomainReliabilityUploader::UploadResult result; result.status = DomainReliabilityUploader::UploadResult::FAILURE; scheduler_->OnUploadComplete(result); } void NotifyRetryAfterUpload(base::TimeDelta retry_after) { DomainReliabilityUploader::UploadResult result; result.status = DomainReliabilityUploader::UploadResult::RETRY_AFTER; result.retry_after = retry_after; scheduler_->OnUploadComplete(result); } ::testing::AssertionResult CheckNoPendingUpload() { DCHECK(scheduler_); if (!callback_called_) return ::testing::AssertionSuccess(); return ::testing::AssertionFailure() << "expected no upload, got upload between " << callback_min_.InSeconds() << " and " << callback_max_.InSeconds() << " seconds from now"; } ::testing::AssertionResult CheckPendingUpload(TimeDelta expected_min, TimeDelta expected_max) { DCHECK(scheduler_); DCHECK_LE(expected_min.InMicroseconds(), expected_max.InMicroseconds()); if (callback_called_ && expected_min == callback_min_ && expected_max == callback_max_) { callback_called_ = false; return ::testing::AssertionSuccess(); } if (callback_called_) { return ::testing::AssertionFailure() << "expected upload between " << expected_min.InSeconds() << " and " << expected_max.InSeconds() << " seconds from now, " << "got upload between " << callback_min_.InSeconds() << " and " << callback_max_.InSeconds() << " seconds from now"; } else { return ::testing::AssertionFailure() << "expected upload between " << expected_min.InSeconds() << " and " << expected_max.InSeconds() << " seconds from now, " << "got no upload"; } } ::testing::AssertionResult CheckStartingUpload(size_t expected_collector) { DCHECK(scheduler_); DCHECK_GT(num_collectors_, expected_collector); size_t collector = scheduler_->OnUploadStart(); if (collector == expected_collector) return ::testing::AssertionSuccess(); return ::testing::AssertionFailure() << "expected upload to collector " << expected_collector << ", got upload to collector " << collector; } TimeDelta min_delay() const { return params_.minimum_upload_delay; } TimeDelta max_delay() const { return params_.maximum_upload_delay; } TimeDelta retry_interval() const { return params_.upload_retry_interval; } TimeDelta zero_delta() const { return base::TimeDelta::FromMicroseconds(0); } protected: void ScheduleUploadCallback(TimeDelta min, TimeDelta max) { callback_called_ = true; callback_min_ = min; callback_max_ = max; } MockTime time_; size_t num_collectors_; DomainReliabilityScheduler::Params params_; scoped_ptr scheduler_; bool callback_called_; TimeDelta callback_min_; TimeDelta callback_max_; }; TEST_F(DomainReliabilitySchedulerTest, Create) { CreateScheduler(1); } TEST_F(DomainReliabilitySchedulerTest, UploadNotPendingWithoutBeacon) { CreateScheduler(1); ASSERT_TRUE(CheckNoPendingUpload()); } TEST_F(DomainReliabilitySchedulerTest, SuccessfulUploads) { CreateScheduler(1); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); ASSERT_TRUE(CheckStartingUpload(0)); NotifySuccessfulUpload(); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); ASSERT_TRUE(CheckStartingUpload(0)); NotifySuccessfulUpload(); } TEST_F(DomainReliabilitySchedulerTest, RetryAfter) { CreateScheduler(1); base::TimeDelta retry_after_interval = base::TimeDelta::FromMinutes(30); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); ASSERT_TRUE(CheckStartingUpload(0)); NotifyRetryAfterUpload(retry_after_interval); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(retry_after_interval, retry_after_interval)); time_.Advance(retry_after_interval); ASSERT_TRUE(CheckStartingUpload(0)); NotifySuccessfulUpload(); } TEST_F(DomainReliabilitySchedulerTest, Failover) { CreateScheduler(2); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); ASSERT_TRUE(CheckStartingUpload(0)); NotifyFailedUpload(); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(zero_delta(), max_delay() - min_delay())); // Don't need to advance; should retry immediately. ASSERT_TRUE(CheckStartingUpload(1)); NotifySuccessfulUpload(); } TEST_F(DomainReliabilitySchedulerTest, FailedAllCollectors) { CreateScheduler(2); // T = 0 scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); // T = min_delay ASSERT_TRUE(CheckStartingUpload(0)); NotifyFailedUpload(); ASSERT_TRUE(CheckPendingUpload(zero_delta(), max_delay() - min_delay())); // Don't need to advance; should retry immediately. ASSERT_TRUE(CheckStartingUpload(1)); NotifyFailedUpload(); ASSERT_TRUE(CheckPendingUpload(retry_interval(), max_delay() - min_delay())); time_.Advance(retry_interval()); // T = min_delay + retry_interval ASSERT_TRUE(CheckStartingUpload(0)); NotifyFailedUpload(); ASSERT_TRUE(CheckPendingUpload( zero_delta(), max_delay() - min_delay() - retry_interval())); ASSERT_TRUE(CheckStartingUpload(1)); NotifyFailedUpload(); } // Make sure that the scheduler uses the first available collector at upload // time, even if it wasn't available at scheduling time. TEST_F(DomainReliabilitySchedulerTest, DetermineCollectorAtUpload) { CreateScheduler(2); // T = 0 scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); // T = min_delay ASSERT_TRUE(CheckStartingUpload(0)); NotifyFailedUpload(); ASSERT_TRUE(CheckPendingUpload(zero_delta(), max_delay() - min_delay())); time_.Advance(retry_interval()); // T = min_delay + retry_interval; collector 0 should be active again. ASSERT_TRUE(CheckStartingUpload(0)); NotifySuccessfulUpload(); } TEST_F(DomainReliabilitySchedulerTest, BeaconWhilePending) { CreateScheduler(1); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); // Second beacon should not call callback again. scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckNoPendingUpload()); time_.Advance(min_delay()); // No pending upload after beacon. ASSERT_TRUE(CheckStartingUpload(0)); NotifySuccessfulUpload(); ASSERT_TRUE(CheckNoPendingUpload()); } TEST_F(DomainReliabilitySchedulerTest, BeaconWhileUploading) { CreateScheduler(1); scheduler_->OnBeaconAdded(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); // If a beacon arrives during the upload, a new upload should be pending. ASSERT_TRUE(CheckStartingUpload(0)); scheduler_->OnBeaconAdded(); NotifySuccessfulUpload(); ASSERT_TRUE(CheckPendingUpload(min_delay(), max_delay())); time_.Advance(min_delay()); ASSERT_TRUE(CheckStartingUpload(0)); NotifySuccessfulUpload(); ASSERT_TRUE(CheckNoPendingUpload()); } } // namespace } // namespace domain_reliability