blob: ea4da77bc01632aa8e282c5f72d496045ac70e97 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/aggregation_service/aggregatable_report_scheduler.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/metrics/histogram_base.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/threading/sequence_bound.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/aggregation_service/aggregation_service.h"
#include "content/browser/aggregation_service/aggregation_service_storage.h"
#include "content/browser/aggregation_service/aggregation_service_test_utils.h"
#include "content/public/common/content_switches.h"
#include "services/network/public/mojom/network_change_manager.mojom.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
using testing::_;
using testing::Field;
using testing::Property;
// Will be used to verify the sequence of expected function calls.
using Checkpoint = ::testing::MockFunction<void(int)>;
// TODO(alexmt): Consider changing tests to avoid the assumption that this time
// is after `base::Time::Now()`.
constexpr auto kExampleTime =
base::Time::FromMillisecondsSinceUnixEpoch(1652984901234);
} // namespace
class AggregatableReportSchedulerTest : public testing::Test {
public:
AggregatableReportSchedulerTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
storage_context_(base::DefaultClock::GetInstance()) {}
void SetUp() override {
scheduler_ = std::make_unique<AggregatableReportScheduler>(
&storage_context_,
/*on_scheduled_report_time_reached=*/mock_callback_.Get());
}
protected:
void VerifyHistograms(base::HistogramBase::Count32 timer_fired_count) {
histograms_.ExpectTotalCount(
"PrivacySandbox.AggregationService.Scheduler.TimerFireDelay",
timer_fired_count);
histograms_.ExpectTotalCount(
"PrivacySandbox.AggregationService.Storage.RequestsRetrievalTime",
timer_fired_count);
}
base::test::TaskEnvironment task_environment_;
TestAggregationServiceStorageContext storage_context_;
base::MockRepeatingCallback<void(
std::vector<AggregationServiceStorage::RequestAndId>)>
mock_callback_;
std::unique_ptr<AggregatableReportScheduler> scheduler_;
base::HistogramTester histograms_;
};
TEST_F(AggregatableReportSchedulerTest,
ScheduleRequest_RetrievedAtAppropriateTime) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
AggregatableReportRequest expected_request =
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value();
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 0u);
run_loop.Quit();
}));
run_loop.Run();
}
Checkpoint checkpoint;
{
testing::InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run)
.WillOnce(
[&expected_request](
std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
ASSERT_EQ(requests_and_ids.size(), 1u);
EXPECT_TRUE(aggregation_service::ReportRequestsEqual(
requests_and_ids[0].request, expected_request));
});
}
scheduler_->ScheduleRequest(
aggregation_service::CloneReportRequest(expected_request));
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 1u);
run_loop.Quit();
}));
run_loop.Run();
}
base::TimeDelta fast_forward_required = kExampleTime - base::Time::Now();
task_environment_.FastForwardBy(fast_forward_required -
base::Microseconds(1));
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 1u);
run_loop.Quit();
}));
run_loop.Run();
}
checkpoint.Call(1);
task_environment_.FastForwardBy(base::Microseconds(1));
VerifyHistograms(/*timer_fired_count=*/1);
}
TEST_F(AggregatableReportSchedulerTest,
InProgressRequestCompleted_DeletedFromStorage) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
EXPECT_CALL(mock_callback_, Run);
base::TimeDelta fast_forward_required = kExampleTime - base::Time::Now();
task_environment_.FastForwardBy(fast_forward_required);
// The request is still stored even while it's in-progress.
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 1u);
run_loop.Quit();
}));
run_loop.Run();
}
// Request IDs are incremented from 1.
scheduler_->NotifyInProgressRequestSucceeded(
AggregationServiceStorage::RequestId(1));
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 0u);
run_loop.Quit();
}));
run_loop.Run();
}
VerifyHistograms(/*timer_fired_count=*/1);
}
TEST_F(AggregatableReportSchedulerTest,
FinalSendAttemptFailed_DeletedFromStorage) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
EXPECT_CALL(mock_callback_, Run);
base::TimeDelta fast_forward_required = kExampleTime - base::Time::Now();
task_environment_.FastForwardBy(fast_forward_required);
// The request is still stored even while it's in-progress.
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 1u);
run_loop.Quit();
}));
run_loop.Run();
}
// Request IDs are incremented from 1.
bool will_retry = scheduler_->NotifyInProgressRequestFailed(
AggregationServiceStorage::RequestId(1),
/*previous_failed_attempts=*/AggregatableReportScheduler::kMaxRetries);
EXPECT_FALSE(will_retry);
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 0u);
run_loop.Quit();
}));
run_loop.Run();
}
VerifyHistograms(/*timer_fired_count=*/1);
}
TEST_F(AggregatableReportSchedulerTest,
InProgressRequestFailed_UpdateStorageAndReschedule) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
auto request = AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay);
scheduler_->ScheduleRequest(std::move(request.value()));
base::TimeDelta fast_forward_required = kExampleTime - base::Time::Now();
int before_first_notification = 0;
int before_first_retry = 1;
int after_first_retry = 2;
int before_second_retry = 3;
int after_second_retry = 4;
Checkpoint checkpoint;
{
testing::InSequence seq;
EXPECT_CALL(mock_callback_, Run)
.Times(1); // Called once for the initial request
EXPECT_CALL(checkpoint, Call(before_first_notification));
// First delay not expired yet
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(before_first_retry));
// First retry done
EXPECT_CALL(mock_callback_, Run).Times(1);
EXPECT_CALL(checkpoint, Call(after_first_retry));
// Second delay not expired yet
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(before_second_retry));
// Second retry done
EXPECT_CALL(mock_callback_, Run).Times(1);
EXPECT_CALL(checkpoint, Call(after_second_retry));
}
task_environment_.FastForwardBy(fast_forward_required);
checkpoint.Call(before_first_notification);
// Request is still in storage and its number of failed attempts has been
// incremented.
EXPECT_TRUE(scheduler_->NotifyInProgressRequestFailed(
AggregationServiceStorage::RequestId(1), /*previous_failed_attempts=*/0));
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_THAT(
requests_and_ids,
testing::ElementsAre(Field(
"request",
&AggregationServiceStorage::RequestAndId::request,
Property("failed_send_attempts()",
&AggregatableReportRequest::failed_send_attempts,
1))));
run_loop.Quit();
}));
run_loop.Run();
}
task_environment_.FastForwardBy(base::Minutes(5) - base::Microseconds(1));
checkpoint.Call(before_first_retry);
task_environment_.FastForwardBy(base::Microseconds(1));
checkpoint.Call(after_first_retry);
EXPECT_TRUE(scheduler_->NotifyInProgressRequestFailed(
AggregationServiceStorage::RequestId(1), 1));
task_environment_.FastForwardBy(base::Minutes(15) - base::Microseconds(1));
checkpoint.Call(before_second_retry);
task_environment_.FastForwardBy(base::Microseconds(1));
checkpoint.Call(after_second_retry);
// It should not retry anymore
EXPECT_FALSE(scheduler_->NotifyInProgressRequestFailed(
AggregationServiceStorage::RequestId(1), /*previous_failed_attempts=*/2));
VerifyHistograms(/*timer_fired_count=*/3);
}
TEST_F(AggregatableReportSchedulerTest,
MultipleRequests_RetrievedAtAppropriateTime) {
// Test both simultaneous and non-simultaneous reports.
std::vector<base::Time> scheduled_report_times = {
kExampleTime, kExampleTime, kExampleTime + base::Hours(1)};
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
Checkpoint checkpoint;
{
testing::InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run)
.WillOnce([](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
ASSERT_EQ(requests_and_ids.size(), 2u);
// Ignore request ordering. Storage IDs should be incremented from 1
EXPECT_EQ(base::flat_set<AggregationServiceStorage::RequestId>(
{requests_and_ids[0].id, requests_and_ids[1].id}),
base::flat_set<AggregationServiceStorage::RequestId>(
{AggregationServiceStorage::RequestId(1),
AggregationServiceStorage::RequestId(2)}));
});
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(mock_callback_, Run)
.WillOnce([](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
ASSERT_EQ(requests_and_ids.size(), 1u);
EXPECT_EQ(requests_and_ids[0].id,
AggregationServiceStorage::RequestId(3));
});
}
for (base::Time scheduled_report_time : scheduled_report_times) {
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = scheduled_report_time;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
}
{
base::RunLoop run_loop;
storage_context_.GetStorage()
.AsyncCall(&AggregationServiceStorage::GetRequestsReportingOnOrBefore)
.WithArgs(base::Time::Max(), /*limit=*/std::nullopt)
.Then(base::BindLambdaForTesting(
[&run_loop](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
EXPECT_EQ(requests_and_ids.size(), 3u);
run_loop.Quit();
}));
run_loop.Run();
}
checkpoint.Call(1);
task_environment_.FastForwardBy(kExampleTime - base::Time::Now());
checkpoint.Call(2);
task_environment_.FastForwardBy(base::Hours(1));
VerifyHistograms(/*timer_fired_count=*/2);
}
TEST_F(AggregatableReportSchedulerTest,
MultipleRequestsReturned_OrderedByReportTime) {
// Order them to check reports are not returned by storage order.
std::vector<base::Time> scheduled_report_times = {
kExampleTime, kExampleTime + base::Hours(3),
kExampleTime + base::Hours(1), kExampleTime + base::Hours(2)};
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
Checkpoint checkpoint;
{
testing::InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run)
.WillOnce([](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
ASSERT_EQ(requests_and_ids.size(), 2u);
// Ordered correctly. Storage IDs should be incremented from 1.
EXPECT_EQ(requests_and_ids[0].id,
AggregationServiceStorage::RequestId(1));
EXPECT_EQ(requests_and_ids[1].id,
AggregationServiceStorage::RequestId(3));
});
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(mock_callback_, Run)
.WillOnce([](std::vector<AggregationServiceStorage::RequestAndId>
requests_and_ids) {
ASSERT_EQ(requests_and_ids.size(), 2u);
// Ordered correctly. Storage IDs should be incremented from 1.
EXPECT_EQ(requests_and_ids[0].id,
AggregationServiceStorage::RequestId(4));
EXPECT_EQ(requests_and_ids[1].id,
AggregationServiceStorage::RequestId(2));
});
}
for (base::Time scheduled_report_time : scheduled_report_times) {
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = scheduled_report_time;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
}
checkpoint.Call(1);
// Can't use `FastForwardBy()` as that will result in one call per report
// request.
task_environment_.AdvanceClock(kExampleTime + base::Hours(1) -
base::Time::Now());
task_environment_.RunUntilIdle();
checkpoint.Call(2);
// Can't use `FastForwardBy()` as that will result in one call per report
// request.
task_environment_.AdvanceClock(base::Hours(2));
task_environment_.RunUntilIdle();
VerifyHistograms(/*timer_fired_count=*/2);
}
TEST_F(AggregatableReportSchedulerTest,
NetworkOffline_ReportsAreNotRetrievedUntilOnline) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_NONE); // Offline
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
base::TimeDelta fast_forward_required = kExampleTime - base::Time::Now();
Checkpoint checkpoint;
{
testing::InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run);
}
// Need to fast forward beyond the report time so that it's in the past and
// will be updated.
task_environment_.FastForwardBy(fast_forward_required +
base::Microseconds(1));
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_UNKNOWN); // Online
checkpoint.Call(1);
// As the new report should've been delayed from 'now', we fast forward
// through that delay to trigger the report.
task_environment_.FastForwardBy(
AggregatableReportScheduler::kOfflineReportTimeMaximumDelay);
VerifyHistograms(/*timer_fired_count=*/1);
}
TEST_F(AggregatableReportSchedulerTest,
OnlineConnectionChanges_ReportsAreNotRetrieved) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_3G);
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
Checkpoint checkpoint;
{
testing::InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run);
}
// Deliberately avoid running tasks so that the connection change and time
// advance can be "atomic", which is necessary because
// `AttributionStorage::AdjustOfflineReportTimes()` only adjusts times for
// reports that should have been sent before now. In other words, the call to
// `AdjustOfflineReportTimes()` would have no effect if we used
// `FastForwardBy()` here, and we wouldn't be able to detect it below.
base::TimeDelta fast_forward_required = kExampleTime - base::Time::Now();
task_environment_.AdvanceClock(fast_forward_required + base::Microseconds(1));
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_4G);
checkpoint.Call(1);
// Cause any scheduled tasks to run. If the report was delayed, this wouldn't
// be late enough for the report to be sent. There is a tiny chance that the
// report was only delayed by 0 or 1 microsecond, but this flake is rare
// enough to ignore (1 in 30 million runs).
task_environment_.FastForwardBy(base::TimeDelta());
VerifyHistograms(/*timer_fired_count=*/1);
}
TEST_F(AggregatableReportSchedulerTest,
StorageLimitReached_ReportSilentlyDropped) {
// Attempt to schedule one too many reports.
for (int i = 0;
i < AggregationService::kMaxStoredReportsPerReportingOrigin + 1; ++i) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
}
// One report has been silently dropped.
EXPECT_CALL(
mock_callback_,
Run(Property(&std::vector<AggregationServiceStorage::RequestAndId>::size,
AggregationService::kMaxStoredReportsPerReportingOrigin)));
task_environment_.FastForwardBy(kExampleTime - base::Time::Now());
VerifyHistograms(/*timer_fired_count=*/1);
}
class AggregatableReportSchedulerDeveloperModeTest
: public AggregatableReportSchedulerTest {
public:
AggregatableReportSchedulerDeveloperModeTest() {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kPrivateAggregationDeveloperMode);
}
};
TEST_F(AggregatableReportSchedulerDeveloperModeTest,
NetworkOffline_ReportsAreSentImmediatelyWhenOnline) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_NONE); // Offline
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.scheduled_report_time = kExampleTime;
scheduler_->ScheduleRequest(
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(expected_shared_info),
AggregatableReportRequest::DelayType::ScheduledWithReducedDelay)
.value());
base::TimeDelta fast_forward_required = kExampleTime - base::Time::Now();
Checkpoint checkpoint;
{
testing::InSequence seq;
EXPECT_CALL(mock_callback_, Run).Times(0);
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(mock_callback_, Run);
}
// Need to fast forward beyond the report time so that it's in the past and
// will be updated.
task_environment_.FastForwardBy(fast_forward_required +
base::Microseconds(1));
checkpoint.Call(1);
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_UNKNOWN); // Online
// With the developer mode flag, the report should be sent immediately, so all
// we need to do is run any pending tasks.
task_environment_.RunUntilIdle();
VerifyHistograms(/*timer_fired_count=*/1);
}
} // namespace content