blob: 1385381128a0351bd340c72e9517998a50edaab3 [file] [log] [blame]
// 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/context.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/domain_reliability/beacon.h"
#include "components/domain_reliability/dispatcher.h"
#include "components/domain_reliability/scheduler.h"
#include "components/domain_reliability/test_util.h"
#include "components/domain_reliability/uploader.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace domain_reliability {
namespace {
using base::Value;
typedef std::vector<const DomainReliabilityBeacon*> BeaconVector;
const char kBeaconOutcomeHistogram[] = "Net.DomainReliability.BeaconOutcome";
std::unique_ptr<DomainReliabilityBeacon> MakeCustomizedBeacon(
MockableTime* time,
std::string status,
std::string quic_error,
bool quic_port_migration_detected) {
std::unique_ptr<DomainReliabilityBeacon> beacon(
new DomainReliabilityBeacon());
beacon->url = GURL("https://localhost/");
beacon->status = status;
beacon->quic_error = quic_error;
beacon->chrome_error = net::ERR_CONNECTION_RESET;
beacon->server_ip = "127.0.0.1";
beacon->was_proxied = false;
beacon->protocol = "HTTP";
beacon->details.quic_broken = true;
beacon->details.quic_port_migration_detected = quic_port_migration_detected;
beacon->http_response_code = -1;
beacon->elapsed = base::TimeDelta::FromMilliseconds(250);
beacon->start_time = time->NowTicks() - beacon->elapsed;
beacon->upload_depth = 0;
beacon->sample_rate = 1.0;
beacon->network_isolation_key = net::NetworkIsolationKey();
return beacon;
}
std::unique_ptr<DomainReliabilityBeacon> MakeBeacon(MockableTime* time) {
return MakeCustomizedBeacon(time, "tcp.connection_reset" /* status */,
"" /* quic_error */,
false /* quic_port_migration_detected */);
}
std::unique_ptr<DomainReliabilityBeacon> MakeBeaconWithNetworkIsolationKey(
MockableTime* time,
const std::string& status,
const net::NetworkIsolationKey& network_isolation_key) {
std::unique_ptr<DomainReliabilityBeacon> beacon =
MakeCustomizedBeacon(time, status, "" /* quic_error */,
false /* quic_port_migration_detected */);
beacon->network_isolation_key = network_isolation_key;
return beacon;
}
// Create a status string from in integer. For eviction tests. Include string
// values before and after the string representation of the integer, to make
// sure only exact matches are found when searching a JSON string.
std::string StatusFromInt(int i) {
return base::StringPrintf("status%i.test", i);
}
template <typename ValueTypeFindResult,
typename ValueType,
ValueTypeFindResult (Value::*FindValueType)(base::StringPiece) const>
struct HasValue {
bool operator()(const Value& dict,
const std::string& key,
ValueType expected_value) {
ValueTypeFindResult actual_value = (dict.*FindValueType)(key);
if (actual_value)
EXPECT_EQ(expected_value, *actual_value);
return actual_value && (expected_value == *actual_value);
}
};
HasValue<absl::optional<bool>, bool, &Value::FindBoolPath> HasBooleanValue;
HasValue<absl::optional<double>, double, &Value::FindDoublePath> HasDoubleValue;
HasValue<absl::optional<int>, int, &Value::FindIntPath> HasIntegerValue;
HasValue<const std::string*, std::string, &Value::FindStringPath>
HasStringValue;
bool GetEntryFromReport(const Value* report,
size_t index,
const Value** entry_out) {
if (!report || !report->is_dict())
return false;
const Value* entries = report->FindListKey("entries");
if (!entries || index >= entries->GetList().size())
return false;
const Value& entry = entries->GetList()[index];
if (!entry.is_dict())
return false;
*entry_out = &entry;
return true;
}
class DomainReliabilityContextTest : public testing::Test {
protected:
DomainReliabilityContextTest()
: last_network_change_time_(time_.NowTicks()),
dispatcher_(&time_),
params_(MakeTestSchedulerParams()),
uploader_(
base::BindRepeating(&DomainReliabilityContextTest::OnUploadRequest,
base::Unretained(this))),
upload_reporter_string_("test-reporter"),
upload_allowed_callback_(base::BindRepeating(
&DomainReliabilityContextTest::UploadAllowedCallback,
base::Unretained(this))),
upload_pending_(false) {
// Make sure that the last network change does not overlap requests
// made in test cases, which start 250ms in the past (see |MakeBeacon|).
last_network_change_time_ = time_.NowTicks();
time_.Advance(base::TimeDelta::FromSeconds(1));
}
void InitContext(std::unique_ptr<const DomainReliabilityConfig> config) {
context_ = std::make_unique<DomainReliabilityContext>(
&time_, params_, upload_reporter_string_, &last_network_change_time_,
upload_allowed_callback_, &dispatcher_, &uploader_, std::move(config));
}
void ShutDownContext() { context_.reset(); }
base::TimeDelta min_delay() const { return params_.minimum_upload_delay; }
base::TimeDelta max_delay() const { return params_.maximum_upload_delay; }
base::TimeDelta retry_interval() const {
return params_.upload_retry_interval;
}
base::TimeDelta zero_delta() const {
return base::TimeDelta::FromMicroseconds(0);
}
bool upload_allowed_callback_pending() const {
return !upload_allowed_result_callback_.is_null();
}
bool upload_pending() const { return upload_pending_; }
const std::string& upload_report() const {
EXPECT_TRUE(upload_pending_);
return upload_report_;
}
int upload_max_depth() const {
EXPECT_TRUE(upload_pending_);
return upload_max_depth_;
}
const GURL& upload_url() const {
EXPECT_TRUE(upload_pending_);
return upload_url_;
}
const net::NetworkIsolationKey& upload_network_isolation_key() const {
EXPECT_TRUE(upload_pending_);
return upload_network_isolation_key_;
}
void CallUploadCallback(DomainReliabilityUploader::UploadResult result) {
ASSERT_TRUE(upload_pending_);
std::move(upload_callback_).Run(result);
upload_pending_ = false;
++num_uploads_completed_;
EXPECT_EQ(num_uploads_completed_, num_uploads_);
}
bool CheckNoBeacons() {
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
return beacons.empty();
}
const GURL& upload_allowed_origin() { return upload_allowed_origin_; }
void CallUploadAllowedResultCallback(bool allowed) {
DCHECK(!upload_allowed_result_callback_.is_null());
std::move(upload_allowed_result_callback_).Run(allowed);
}
MockTime time_;
base::TimeTicks last_network_change_time_;
DomainReliabilityDispatcher dispatcher_;
DomainReliabilityScheduler::Params params_;
MockUploader uploader_;
std::string upload_reporter_string_;
DomainReliabilityContext::UploadAllowedCallback upload_allowed_callback_;
std::unique_ptr<DomainReliabilityContext> context_;
private:
void OnUploadRequest(const std::string& report_json,
int max_upload_depth,
const GURL& upload_url,
const net::NetworkIsolationKey& network_isolation_key,
DomainReliabilityUploader::UploadCallback callback) {
EXPECT_EQ(num_uploads_completed_, num_uploads_);
ASSERT_FALSE(upload_pending_);
upload_report_ = report_json;
upload_max_depth_ = max_upload_depth;
upload_url_ = upload_url;
upload_network_isolation_key_ = network_isolation_key;
upload_callback_ = std::move(callback);
upload_pending_ = true;
++num_uploads_;
}
void UploadAllowedCallback(const GURL& origin,
base::OnceCallback<void(bool)> callback) {
upload_allowed_origin_ = origin;
upload_allowed_result_callback_ = std::move(callback);
}
int num_uploads_ = 0;
int num_uploads_completed_ = 0;
bool upload_pending_;
std::string upload_report_;
int upload_max_depth_;
GURL upload_url_;
net::NetworkIsolationKey upload_network_isolation_key_;
DomainReliabilityUploader::UploadCallback upload_callback_;
GURL upload_allowed_origin_;
base::OnceCallback<void(bool)> upload_allowed_result_callback_;
};
TEST_F(DomainReliabilityContextTest, Create) {
InitContext(MakeTestConfig());
EXPECT_TRUE(CheckNoBeacons());
}
TEST_F(DomainReliabilityContextTest, QueueBeacon) {
base::HistogramTester histograms;
InitContext(MakeTestConfig());
context_->OnBeacon(MakeBeacon(&time_));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
ShutDownContext();
histograms.ExpectBucketCount(
kBeaconOutcomeHistogram,
DomainReliabilityBeacon::Outcome::kContextShutDown, 1);
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 1);
}
TEST_F(DomainReliabilityContextTest, MaxNestedBeaconSchedules) {
InitContext(MakeTestConfig());
GURL url("http://example/always_report");
std::unique_ptr<DomainReliabilityBeacon> beacon = MakeBeacon(&time_);
beacon->upload_depth = DomainReliabilityContext::kMaxUploadDepthToSchedule;
context_->OnBeacon(std::move(beacon));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
}
TEST_F(DomainReliabilityContextTest, OverlyNestedBeaconDoesNotSchedule) {
InitContext(MakeTestConfig());
GURL url("http://example/always_report");
std::unique_ptr<DomainReliabilityBeacon> beacon = MakeBeacon(&time_);
beacon->upload_depth =
DomainReliabilityContext::kMaxUploadDepthToSchedule + 1;
context_->OnBeacon(std::move(beacon));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_FALSE(upload_allowed_callback_pending());
}
TEST_F(DomainReliabilityContextTest,
MaxNestedBeaconAfterOverlyNestedBeaconSchedules) {
base::HistogramTester histograms;
InitContext(MakeTestConfig());
// Add a beacon for a report that's too nested to schedule a beacon.
std::unique_ptr<DomainReliabilityBeacon> beacon = MakeBeacon(&time_);
beacon->upload_depth =
DomainReliabilityContext::kMaxUploadDepthToSchedule + 1;
context_->OnBeacon(std::move(beacon));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_FALSE(upload_allowed_callback_pending());
// Add a beacon for a report that should schedule a beacon, and make sure it
// doesn't schedule until the deadline.
beacon = MakeBeacon(&time_);
beacon->upload_depth = DomainReliabilityContext::kMaxUploadDepthToSchedule;
context_->OnBeacon(std::move(beacon));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(2u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// Check that both beacons were uploaded.
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
histograms.ExpectBucketCount(kBeaconOutcomeHistogram,
DomainReliabilityBeacon::Outcome::kUploaded, 2);
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 2);
EXPECT_TRUE(CheckNoBeacons());
ShutDownContext();
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 2);
}
TEST_F(DomainReliabilityContextTest, ReportUpload) {
base::HistogramTester histograms;
InitContext(MakeTestConfig());
context_->OnBeacon(
MakeCustomizedBeacon(&time_, "tcp.connection_reset", "", true));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
std::unique_ptr<Value> value =
base::JSONReader::ReadDeprecated(upload_report());
const Value* entry;
ASSERT_TRUE(GetEntryFromReport(value.get(), 0, &entry));
EXPECT_TRUE(HasStringValue(*entry, "failure_data.custom_error",
"net::ERR_CONNECTION_RESET"));
EXPECT_TRUE(HasBooleanValue(*entry, "network_changed", false));
EXPECT_TRUE(HasStringValue(*entry, "protocol", "HTTP"));
EXPECT_TRUE(HasBooleanValue(*entry, "quic_broken", true));
EXPECT_TRUE(HasBooleanValue(*entry, "quic_port_migration_detected", true));
// N.B.: Assumes max_delay is 5 minutes.
EXPECT_TRUE(HasIntegerValue(*entry, "request_age_ms", 300250));
EXPECT_TRUE(HasIntegerValue(*entry, "request_elapsed_ms", 250));
EXPECT_TRUE(HasDoubleValue(*entry, "sample_rate", 1.0));
EXPECT_TRUE(HasStringValue(*entry, "server_ip", "127.0.0.1"));
EXPECT_TRUE(HasStringValue(*entry, "status", "tcp.connection_reset"));
EXPECT_TRUE(HasStringValue(*entry, "url", "https://localhost/"));
EXPECT_TRUE(HasBooleanValue(*entry, "was_proxied", false));
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
histograms.ExpectBucketCount(kBeaconOutcomeHistogram,
DomainReliabilityBeacon::Outcome::kUploaded, 1);
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 1);
EXPECT_TRUE(CheckNoBeacons());
ShutDownContext();
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 1);
}
TEST_F(DomainReliabilityContextTest, ReportUploadFails) {
InitContext(MakeTestConfig());
context_->OnBeacon(
MakeCustomizedBeacon(&time_, "tcp.connection_reset", "", true));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
// The upload fails.
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::FAILURE;
CallUploadCallback(result);
// The beacon should still be pending.
beacons.clear();
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
// Another upload should be queued.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
}
// Make sure that requests with only one NetworkIsolationKey are uploaded at a
// time, in FIFO order.
TEST_F(DomainReliabilityContextTest, ReportUploadNetworkIsolationKey) {
const net::NetworkIsolationKey kNetworkIsolationKey1 =
net::NetworkIsolationKey::CreateTransient();
const net::NetworkIsolationKey kNetworkIsolationKey2 =
net::NetworkIsolationKey::CreateTransient();
const net::NetworkIsolationKey kNetworkIsolationKey3 =
net::NetworkIsolationKey::CreateTransient();
InitContext(MakeTestConfig());
// Three beacons with kNetworkIsolationKey1, two with kNetworkIsolationKey2,
// and one with kNetworkIsolationKey3. Have beacons with the same key both
// adjacent to each other, and separated by beacons with other keys. Give
// each a unique status, so it's easy to check which beacons are included in
// each report.
const char kStatusNik11[] = "nik1.status1";
const char kStatusNik12[] = "nik1.status2";
const char kStatusNik13[] = "nik1.status3";
const char kStatusNik21[] = "nik2.status1";
const char kStatusNik22[] = "nik2.status2";
const char kStatusNik31[] = "nik3.status1";
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik11,
kNetworkIsolationKey1));
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik12,
kNetworkIsolationKey1));
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik21,
kNetworkIsolationKey2));
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik31,
kNetworkIsolationKey3));
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik13,
kNetworkIsolationKey1));
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik22,
kNetworkIsolationKey2));
// All the beacons should be queued, in FIFO order.
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(6u, beacons.size());
EXPECT_EQ(kNetworkIsolationKey1, beacons[0]->network_isolation_key);
EXPECT_EQ(kStatusNik11, beacons[0]->status);
EXPECT_EQ(kNetworkIsolationKey1, beacons[1]->network_isolation_key);
EXPECT_EQ(kStatusNik12, beacons[1]->status);
EXPECT_EQ(kNetworkIsolationKey2, beacons[2]->network_isolation_key);
EXPECT_EQ(kStatusNik21, beacons[2]->status);
EXPECT_EQ(kNetworkIsolationKey3, beacons[3]->network_isolation_key);
EXPECT_EQ(kStatusNik31, beacons[3]->status);
EXPECT_EQ(kNetworkIsolationKey1, beacons[4]->network_isolation_key);
EXPECT_EQ(kStatusNik13, beacons[4]->status);
EXPECT_EQ(kNetworkIsolationKey2, beacons[5]->network_isolation_key);
EXPECT_EQ(kStatusNik22, beacons[5]->status);
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
EXPECT_EQ(kNetworkIsolationKey1, upload_network_isolation_key());
// Check that only the strings associated with the first NIK are present in
// the report.
EXPECT_NE(upload_report().find(kStatusNik11), std::string::npos);
EXPECT_NE(upload_report().find(kStatusNik12), std::string::npos);
EXPECT_NE(upload_report().find(kStatusNik13), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik21), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik22), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik31), std::string::npos);
// Complete upload.
DomainReliabilityUploader::UploadResult successful_result;
successful_result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(successful_result);
// There should still be 3 beacons queued, in the same order as before.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(3u, beacons.size());
EXPECT_EQ(kNetworkIsolationKey2, beacons[0]->network_isolation_key);
EXPECT_EQ(kStatusNik21, beacons[0]->status);
EXPECT_EQ(kNetworkIsolationKey3, beacons[1]->network_isolation_key);
EXPECT_EQ(kStatusNik31, beacons[1]->status);
EXPECT_EQ(kNetworkIsolationKey2, beacons[2]->network_isolation_key);
EXPECT_EQ(kStatusNik22, beacons[2]->status);
// The next upload should automatically trigger.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
EXPECT_EQ(kNetworkIsolationKey2, upload_network_isolation_key());
// Check that only the strings associated with the second NIK are present in
// the report.
EXPECT_EQ(upload_report().find(kStatusNik11), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik12), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik13), std::string::npos);
EXPECT_NE(upload_report().find(kStatusNik21), std::string::npos);
EXPECT_NE(upload_report().find(kStatusNik22), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik31), std::string::npos);
// Complete upload.
CallUploadCallback(successful_result);
// There should still be 1 beacon queued.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(1u, beacons.size());
EXPECT_EQ(kNetworkIsolationKey3, beacons[0]->network_isolation_key);
EXPECT_EQ(kStatusNik31, beacons[0]->status);
// The next upload should automatically trigger.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
EXPECT_EQ(kNetworkIsolationKey3, upload_network_isolation_key());
// Check that only the strings associated with the third NIK are present in
// the report.
EXPECT_EQ(upload_report().find(kStatusNik11), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik12), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik13), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik21), std::string::npos);
EXPECT_EQ(upload_report().find(kStatusNik22), std::string::npos);
EXPECT_NE(upload_report().find(kStatusNik31), std::string::npos);
// Complete upload.
CallUploadCallback(successful_result);
EXPECT_TRUE(CheckNoBeacons());
}
// Make sure that kMaxUploadDepthToSchedule is respected when requests have
// different NetworkIsolationKeys.
TEST_F(DomainReliabilityContextTest, ReportUploadDepthNetworkIsolationKey) {
const net::NetworkIsolationKey kNetworkIsolationKey1 =
net::NetworkIsolationKey::CreateTransient();
const net::NetworkIsolationKey kNetworkIsolationKey2 =
net::NetworkIsolationKey::CreateTransient();
InitContext(MakeTestConfig());
const char kStatusNik1ExceedsMaxDepth[] = "nik1.exceeds_max_depth";
const char kStatusNik2ExceedsMaxDepth[] = "nik2.exceeds_max_depth";
const char kStatusNik2MaxDepth[] = "nik2.max_depth";
// Add a beacon with kNetworkIsolationKey1 and a depth that exceeds the max
// depth to trigger an upload. No upload should be queued.
std::unique_ptr<DomainReliabilityBeacon> beacon =
MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik1ExceedsMaxDepth,
kNetworkIsolationKey1);
beacon->upload_depth =
DomainReliabilityContext::kMaxUploadDepthToSchedule + 1;
context_->OnBeacon(std::move(beacon));
time_.Advance(max_delay());
EXPECT_FALSE(upload_allowed_callback_pending());
// Add a beacon with kNetworkIsolationKey2 and a depth that exceeds the max
// depth to trigger an upload. No upload should be queued.
beacon = MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik2ExceedsMaxDepth,
kNetworkIsolationKey2);
beacon->upload_depth =
DomainReliabilityContext::kMaxUploadDepthToSchedule + 1;
context_->OnBeacon(std::move(beacon));
time_.Advance(max_delay());
EXPECT_FALSE(upload_allowed_callback_pending());
// Add a beacon with kNetworkIsolationKey2 and a depth that equals the max
// depth to trigger an upload. An upload should be queued.
beacon = MakeBeaconWithNetworkIsolationKey(&time_, kStatusNik2MaxDepth,
kNetworkIsolationKey2);
beacon->upload_depth = DomainReliabilityContext::kMaxUploadDepthToSchedule;
context_->OnBeacon(std::move(beacon));
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
// All the beacons should still be queued.
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(3u, beacons.size());
EXPECT_EQ(kNetworkIsolationKey1, beacons[0]->network_isolation_key);
EXPECT_EQ(kStatusNik1ExceedsMaxDepth, beacons[0]->status);
EXPECT_EQ(DomainReliabilityContext::kMaxUploadDepthToSchedule + 1,
beacons[0]->upload_depth);
EXPECT_EQ(kNetworkIsolationKey2, beacons[1]->network_isolation_key);
EXPECT_EQ(kStatusNik2ExceedsMaxDepth, beacons[1]->status);
EXPECT_EQ(DomainReliabilityContext::kMaxUploadDepthToSchedule + 1,
beacons[1]->upload_depth);
EXPECT_EQ(kNetworkIsolationKey2, beacons[2]->network_isolation_key);
EXPECT_EQ(kStatusNik2MaxDepth, beacons[2]->status);
EXPECT_EQ(DomainReliabilityContext::kMaxUploadDepthToSchedule,
beacons[2]->upload_depth);
// Start the upload.
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(DomainReliabilityContext::kMaxUploadDepthToSchedule + 1,
upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
EXPECT_EQ(kNetworkIsolationKey2, upload_network_isolation_key());
// Check that only the strings associated with the second NIK are present in
// the report.
EXPECT_EQ(upload_report().find(kStatusNik1ExceedsMaxDepth),
std::string::npos);
EXPECT_NE(upload_report().find(kStatusNik2ExceedsMaxDepth),
std::string::npos);
EXPECT_NE(upload_report().find(kStatusNik2MaxDepth), std::string::npos);
// Complete upload.
DomainReliabilityUploader::UploadResult successful_result;
successful_result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(successful_result);
// There should still be 1 beacon queued.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(1u, beacons.size());
EXPECT_EQ(kNetworkIsolationKey1, beacons[0]->network_isolation_key);
EXPECT_EQ(kStatusNik1ExceedsMaxDepth, beacons[0]->status);
EXPECT_EQ(DomainReliabilityContext::kMaxUploadDepthToSchedule + 1,
beacons[0]->upload_depth);
// No upload should be queued, since the depth is too high.
time_.Advance(max_delay());
EXPECT_FALSE(upload_allowed_callback_pending());
}
TEST_F(DomainReliabilityContextTest, UploadForbidden) {
InitContext(MakeTestConfig());
context_->OnBeacon(
MakeCustomizedBeacon(&time_, "tcp.connection_reset", "", true));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(false);
EXPECT_FALSE(upload_pending());
}
TEST_F(DomainReliabilityContextTest, NetworkChanged) {
InitContext(MakeTestConfig());
context_->OnBeacon(MakeBeacon(&time_));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
// Simulate a network change after the request but before the upload.
last_network_change_time_ = time_.NowTicks();
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
std::unique_ptr<Value> value =
base::JSONReader::ReadDeprecated(upload_report());
const Value* entry;
ASSERT_TRUE(GetEntryFromReport(value.get(), 0, &entry));
EXPECT_TRUE(HasBooleanValue(*entry, "network_changed", true));
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
EXPECT_TRUE(CheckNoBeacons());
}
// Always expecting granular QUIC errors if status is quic.protocol error.
TEST_F(DomainReliabilityContextTest,
ReportUploadWithQuicProtocolErrorAndQuicError) {
InitContext(MakeTestConfig());
context_->OnBeacon(MakeCustomizedBeacon(&time_, "quic.protocol",
"quic.invalid.stream_data", true));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
std::unique_ptr<Value> value =
base::JSONReader::ReadDeprecated(upload_report());
const Value* entry;
ASSERT_TRUE(GetEntryFromReport(value.get(), 0, &entry));
EXPECT_TRUE(HasBooleanValue(*entry, "quic_broken", true));
EXPECT_TRUE(HasBooleanValue(*entry, "quic_port_migration_detected", true));
EXPECT_TRUE(HasStringValue(*entry, "status", "quic.protocol"));
EXPECT_TRUE(HasStringValue(*entry, "quic_error", "quic.invalid.stream_data"));
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
EXPECT_TRUE(CheckNoBeacons());
}
// If status is not quic.protocol, expect no granular QUIC error to be reported.
TEST_F(DomainReliabilityContextTest,
ReportUploadWithNonQuicProtocolErrorAndNoQuicError) {
InitContext(MakeTestConfig());
context_->OnBeacon(MakeBeacon(&time_));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
std::unique_ptr<Value> value =
base::JSONReader::ReadDeprecated(upload_report());
const Value* entry;
ASSERT_TRUE(GetEntryFromReport(value.get(), 0, &entry));
EXPECT_TRUE(HasStringValue(*entry, "status", "tcp.connection_reset"));
EXPECT_FALSE(HasStringValue(*entry, "quic_error", ""));
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
EXPECT_TRUE(CheckNoBeacons());
}
// Edge cases that a non-QUIC protocol error with granular QUIC error reported,
// probably indicating state machine in http_network_transaction is working
// in a different way.
TEST_F(DomainReliabilityContextTest,
ReportUploadWithNonQuicProtocolErrorAndQuicError) {
InitContext(MakeTestConfig());
context_->OnBeacon(MakeCustomizedBeacon(&time_, "tcp.connection_reset",
"quic.invalid.stream_data", false));
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
std::unique_ptr<Value> value =
base::JSONReader::ReadDeprecated(upload_report());
const Value* entry;
ASSERT_TRUE(GetEntryFromReport(value.get(), 0, &entry));
EXPECT_TRUE(HasBooleanValue(*entry, "quic_broken", true));
EXPECT_TRUE(HasStringValue(*entry, "status", "tcp.connection_reset"));
EXPECT_TRUE(HasStringValue(*entry, "quic_error", "quic.invalid.stream_data"));
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
EXPECT_TRUE(CheckNoBeacons());
}
TEST_F(DomainReliabilityContextTest, ZeroSampleRate) {
std::unique_ptr<DomainReliabilityConfig> config(MakeTestConfig());
config->failure_sample_rate = 0.0;
InitContext(std::move(config));
BeaconVector beacons;
for (int i = 0; i < 100; i++) {
context_->OnBeacon(MakeBeacon(&time_));
EXPECT_TRUE(CheckNoBeacons());
}
}
TEST_F(DomainReliabilityContextTest, FractionalSampleRate) {
std::unique_ptr<DomainReliabilityConfig> config(MakeTestConfig());
config->failure_sample_rate = 0.5;
InitContext(std::move(config));
BeaconVector beacons;
do {
context_->OnBeacon(MakeBeacon(&time_));
context_->GetQueuedBeaconsForTesting(&beacons);
} while (beacons.empty());
EXPECT_EQ(1u, beacons.size());
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(0, upload_max_depth());
EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url());
std::unique_ptr<Value> value =
base::JSONReader::ReadDeprecated(upload_report());
const Value* entry;
ASSERT_TRUE(GetEntryFromReport(value.get(), 0, &entry));
EXPECT_TRUE(HasDoubleValue(*entry, "sample_rate", 0.5));
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
EXPECT_TRUE(CheckNoBeacons());
}
TEST_F(DomainReliabilityContextTest, FailureSampleOnly) {
std::unique_ptr<DomainReliabilityConfig> config(MakeTestConfig());
config->success_sample_rate = 0.0;
config->failure_sample_rate = 1.0;
InitContext(std::move(config));
BeaconVector beacons;
context_->OnBeacon(MakeBeacon(&time_));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
std::unique_ptr<DomainReliabilityBeacon> beacon(MakeBeacon(&time_));
beacon->status = "ok";
beacon->chrome_error = net::OK;
context_->OnBeacon(std::move(beacon));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
}
TEST_F(DomainReliabilityContextTest, SuccessSampleOnly) {
std::unique_ptr<DomainReliabilityConfig> config(MakeTestConfig());
config->success_sample_rate = 1.0;
config->failure_sample_rate = 0.0;
InitContext(std::move(config));
BeaconVector beacons;
context_->OnBeacon(MakeBeacon(&time_));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(0u, beacons.size());
std::unique_ptr<DomainReliabilityBeacon> beacon(MakeBeacon(&time_));
beacon->status = "ok";
beacon->chrome_error = net::OK;
context_->OnBeacon(std::move(beacon));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
}
TEST_F(DomainReliabilityContextTest, SampleAllBeacons) {
std::unique_ptr<DomainReliabilityConfig> config(MakeTestConfig());
config->success_sample_rate = 1.0;
config->failure_sample_rate = 1.0;
InitContext(std::move(config));
BeaconVector beacons;
context_->OnBeacon(MakeBeacon(&time_));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(1u, beacons.size());
std::unique_ptr<DomainReliabilityBeacon> beacon(MakeBeacon(&time_));
beacon->status = "ok";
beacon->chrome_error = net::OK;
context_->OnBeacon(std::move(beacon));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(2u, beacons.size());
}
TEST_F(DomainReliabilityContextTest, SampleNoBeacons) {
std::unique_ptr<DomainReliabilityConfig> config(MakeTestConfig());
config->success_sample_rate = 0.0;
config->failure_sample_rate = 0.0;
InitContext(std::move(config));
BeaconVector beacons;
context_->OnBeacon(MakeBeacon(&time_));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(0u, beacons.size());
std::unique_ptr<DomainReliabilityBeacon> beacon(MakeBeacon(&time_));
beacon->status = "ok";
beacon->chrome_error = net::OK;
context_->OnBeacon(std::move(beacon));
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(0u, beacons.size());
}
TEST_F(DomainReliabilityContextTest, ExpiredBeaconDoesNotUpload) {
base::HistogramTester histograms;
InitContext(MakeTestConfig());
std::unique_ptr<DomainReliabilityBeacon> beacon = MakeBeacon(&time_);
time_.Advance(base::TimeDelta::FromHours(2));
context_->OnBeacon(std::move(beacon));
time_.Advance(max_delay());
EXPECT_FALSE(upload_allowed_callback_pending());
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_TRUE(beacons.empty());
histograms.ExpectBucketCount(kBeaconOutcomeHistogram,
DomainReliabilityBeacon::Outcome::kExpired, 1);
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 1);
ShutDownContext();
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 1);
}
TEST_F(DomainReliabilityContextTest, EvictOldestBeacon) {
base::HistogramTester histograms;
InitContext(MakeTestConfig());
std::unique_ptr<DomainReliabilityBeacon> oldest_beacon = MakeBeacon(&time_);
const DomainReliabilityBeacon* oldest_beacon_ptr = oldest_beacon.get();
time_.Advance(base::TimeDelta::FromSeconds(1));
context_->OnBeacon(std::move(oldest_beacon));
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
std::unique_ptr<DomainReliabilityBeacon> beacon = MakeBeacon(&time_);
time_.Advance(base::TimeDelta::FromSeconds(1));
context_->OnBeacon(std::move(beacon));
}
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
EXPECT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (const DomainReliabilityBeacon* beacon : beacons) {
EXPECT_NE(oldest_beacon_ptr, beacon);
}
histograms.ExpectBucketCount(kBeaconOutcomeHistogram,
DomainReliabilityBeacon::Outcome::kEvicted, 1);
histograms.ExpectTotalCount(kBeaconOutcomeHistogram, 1);
ShutDownContext();
histograms.ExpectBucketCount(
kBeaconOutcomeHistogram,
DomainReliabilityBeacon::Outcome::kContextShutDown,
DomainReliabilityContext::kMaxQueuedBeacons);
histograms.ExpectTotalCount(kBeaconOutcomeHistogram,
1 + DomainReliabilityContext::kMaxQueuedBeacons);
}
// Test eviction when there's no active upload.
TEST_F(DomainReliabilityContextTest, Eviction) {
InitContext(MakeTestConfig());
// Add |DomainReliabilityContext::kMaxQueuedBeacons| beacons.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
context_->OnBeacon(
MakeCustomizedBeacon(&time_, StatusFromInt(i), "" /* quic_error */,
false /* quic_port_migration_detected */));
}
// No beacons should have been evicted.
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i));
}
// Add one more beacon.
context_->OnBeacon(MakeCustomizedBeacon(
&time_, StatusFromInt(DomainReliabilityContext::kMaxQueuedBeacons),
"" /* quic_error */, false /* quic_port_migration_detected */));
// The first beacon should have been evicted.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i + 1));
}
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// All beacons but the first should be in the report.
EXPECT_EQ(upload_report().find(StatusFromInt(0)), std::string::npos);
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_NE(upload_report().find(StatusFromInt(i + 1)), std::string::npos);
}
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
EXPECT_TRUE(CheckNoBeacons());
}
// Test eviction when there's an upload that eventually succeeds.
TEST_F(DomainReliabilityContextTest, EvictionDuringSuccessfulUpload) {
InitContext(MakeTestConfig());
// Add |DomainReliabilityContext::kMaxQueuedBeacons| beacons.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
context_->OnBeacon(
MakeCustomizedBeacon(&time_, StatusFromInt(i), "" /* quic_error */,
false /* quic_port_migration_detected */));
}
// No beacons should have been evicted.
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i));
}
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// All beacons should be in the report.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_NE(upload_report().find(StatusFromInt(i)), std::string::npos);
}
// Add one more beacon.
context_->OnBeacon(MakeCustomizedBeacon(
&time_, StatusFromInt(DomainReliabilityContext::kMaxQueuedBeacons),
"" /* quic_error */, false /* quic_port_migration_detected */));
// The first beacon should have been evicted.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i + 1));
}
// The upload completes.
DomainReliabilityUploader::UploadResult successful_result;
successful_result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(successful_result);
// The last beacon should still be queued.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(1u, beacons.size());
EXPECT_EQ(beacons[0]->status,
StatusFromInt(DomainReliabilityContext::kMaxQueuedBeacons));
// Another upload should have still been queued, for the new report. Wait for
// it to start uploading.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// Only the last beacon should be in the report.
EXPECT_NE(upload_report().find(
StatusFromInt(DomainReliabilityContext::kMaxQueuedBeacons)),
std::string::npos);
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(upload_report().find(StatusFromInt(i)), std::string::npos);
}
// The upload completes.
CallUploadCallback(successful_result);
EXPECT_TRUE(CheckNoBeacons());
}
// Test eviction when there's an upload that eventually fails.
TEST_F(DomainReliabilityContextTest, EvictionDuringUnsuccessfulUpload) {
InitContext(MakeTestConfig());
// Add |DomainReliabilityContext::kMaxQueuedBeacons| beacons.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
context_->OnBeacon(
MakeCustomizedBeacon(&time_, StatusFromInt(i), "" /* quic_error */,
false /* quic_port_migration_detected */));
}
// No beacons should have been evicted.
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i));
}
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// All beacons should be in the report.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_NE(upload_report().find(StatusFromInt(i)), std::string::npos);
}
// Add one more beacon.
context_->OnBeacon(MakeCustomizedBeacon(
&time_, StatusFromInt(DomainReliabilityContext::kMaxQueuedBeacons),
"" /* quic_error */, false /* quic_port_migration_detected */));
// The first beacon should have been evicted.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i + 1));
}
// The upload fails.
DomainReliabilityUploader::UploadResult result;
result.status = DomainReliabilityUploader::UploadResult::FAILURE;
CallUploadCallback(result);
// All beacons but the first should still be queued.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i + 1));
}
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// All beacons but the first should be in the report.
EXPECT_EQ(upload_report().find(StatusFromInt(0)), std::string::npos);
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_NE(upload_report().find(StatusFromInt(i + 1)), std::string::npos);
}
// The upload completes successfully.
result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(result);
EXPECT_TRUE(CheckNoBeacons());
}
// Test eviction of all initially pending reports when there's an upload that
// eventually succeeds.
TEST_F(DomainReliabilityContextTest, EvictAllDuringSuccessfulUpload) {
InitContext(MakeTestConfig());
// Add |DomainReliabilityContext::kMaxQueuedBeacons| beacons.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
context_->OnBeacon(
MakeCustomizedBeacon(&time_, StatusFromInt(i), "" /* quic_error */,
false /* quic_port_migration_detected */));
}
// No beacons should have been evicted.
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i));
}
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// All beacons should be in the report.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_NE(upload_report().find(StatusFromInt(i)), std::string::npos);
}
// Evict all beacons, twice. It's important to add a beacon after all beacons
// from the original report have already been deleted, to make sure that
// eviction works correctly once |uploading_beacons_size_| reaches 0.
for (size_t i = 0; i < 2 * DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
context_->OnBeacon(MakeCustomizedBeacon(
&time_, StatusFromInt(i + DomainReliabilityContext::kMaxQueuedBeacons),
"" /* quic_error */, false /* quic_port_migration_detected */));
}
// All the original beacons should have been evicted, as should the first
// |DomainReliabilityContext::kMaxQueuedBeacons| beacons from the above loop.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(
beacons[i]->status,
StatusFromInt(i + 2 * DomainReliabilityContext::kMaxQueuedBeacons));
}
// The upload succeeds, but no beacons should be removed, since all the
// original beacons have already been evicted.
DomainReliabilityUploader::UploadResult successful_result;
successful_result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(successful_result);
// The same beacons as before should be queued.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(
beacons[i]->status,
StatusFromInt(i + 2 * DomainReliabilityContext::kMaxQueuedBeacons));
}
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
// Check the expected beacons are in the report.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons * 3; ++i) {
if (i < DomainReliabilityContext::kMaxQueuedBeacons * 2) {
EXPECT_EQ(upload_report().find(StatusFromInt(i)), std::string::npos);
} else {
EXPECT_NE(upload_report().find(StatusFromInt(i)), std::string::npos);
}
}
// The upload completes successfully.
CallUploadCallback(successful_result);
EXPECT_TRUE(CheckNoBeacons());
}
// Make sure that evictions account for when there are different
// NetworkIsolationKeys in use.
TEST_F(DomainReliabilityContextTest,
EvictionDuringSuccessfulUploadNetworkIsolationKey) {
ASSERT_EQ(0u, DomainReliabilityContext::kMaxQueuedBeacons % 2)
<< "DomainReliabilityContext::kMaxQueuedBeacons must be even.";
InitContext(MakeTestConfig());
net::NetworkIsolationKey network_isolation_keys[] = {
net::NetworkIsolationKey::CreateTransient(),
net::NetworkIsolationKey::CreateTransient(),
};
// Add |DomainReliabilityContext::kMaxQueuedBeacons| beacons, using a
// different NetworkIsolationKey for every other beacon.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(
&time_, StatusFromInt(i), network_isolation_keys[i % 2]));
}
// No beacons should have been evicted.
BeaconVector beacons;
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i));
EXPECT_EQ(beacons[i]->network_isolation_key, network_isolation_keys[i % 2]);
}
// Wait for the report to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(network_isolation_keys[0], upload_network_isolation_key());
// All even-numbered beacons should be in the report.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
if (i % 2 == 0) {
EXPECT_NE(upload_report().find(StatusFromInt(i)), std::string::npos);
} else {
EXPECT_EQ(upload_report().find(StatusFromInt(i)), std::string::npos);
}
}
// Add two more beacons, using the same pattern as before
for (size_t i = 0; i < 2; ++i) {
context_->OnBeacon(MakeBeaconWithNetworkIsolationKey(
&time_, StatusFromInt(i + DomainReliabilityContext::kMaxQueuedBeacons),
network_isolation_keys[i % 2]));
}
// Only the first two beacons should have been evicted.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons, beacons.size());
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
EXPECT_EQ(beacons[i]->status, StatusFromInt(i + 2));
EXPECT_EQ(beacons[i]->network_isolation_key, network_isolation_keys[i % 2]);
}
// The upload succeeds. Every beacon using the first NetworkIsolationKey,
// except the second to last, should have been evicted.
DomainReliabilityUploader::UploadResult successful_result;
successful_result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
CallUploadCallback(successful_result);
// Check remaining beacons.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(DomainReliabilityContext::kMaxQueuedBeacons / 2 + 1,
beacons.size());
int beacon_index = 0;
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons; ++i) {
if (i % 2 == 0 && i < DomainReliabilityContext::kMaxQueuedBeacons - 2)
continue;
EXPECT_EQ(beacons[beacon_index]->status, StatusFromInt(i + 2));
EXPECT_EQ(beacons[beacon_index]->network_isolation_key,
network_isolation_keys[i % 2]);
beacon_index++;
}
// Another report should be queued. Wait for it to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(network_isolation_keys[1], upload_network_isolation_key());
// Check the expected beacons are in the report.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons + 2; ++i) {
if (i % 2 == 0 || i < 2) {
EXPECT_EQ(upload_report().find(StatusFromInt(i)), std::string::npos);
} else {
EXPECT_NE(upload_report().find(StatusFromInt(i)), std::string::npos);
}
}
// The upload completes successfully.
CallUploadCallback(successful_result);
// Check remaining beacons. There should only be one left.
context_->GetQueuedBeaconsForTesting(&beacons);
ASSERT_EQ(1u, beacons.size());
EXPECT_EQ(beacons[0]->status,
StatusFromInt(DomainReliabilityContext::kMaxQueuedBeacons));
EXPECT_EQ(beacons[0]->network_isolation_key, network_isolation_keys[0]);
// Another report should be queued. Wait for it to start being uploaded.
time_.Advance(max_delay());
EXPECT_TRUE(upload_allowed_callback_pending());
CallUploadAllowedResultCallback(true);
EXPECT_TRUE(upload_pending());
EXPECT_EQ(network_isolation_keys[0], upload_network_isolation_key());
// Check the expected beacons are in the report.
for (size_t i = 0; i < DomainReliabilityContext::kMaxQueuedBeacons + 2; ++i) {
if (i == DomainReliabilityContext::kMaxQueuedBeacons) {
EXPECT_NE(upload_report().find(StatusFromInt(i)), std::string::npos);
} else {
EXPECT_EQ(upload_report().find(StatusFromInt(i)), std::string::npos);
}
}
// The upload completes successfully.
CallUploadCallback(successful_result);
EXPECT_TRUE(CheckNoBeacons());
}
// TODO(juliatuttle): Add beacon_unittest.cc to test serialization.
} // namespace
} // namespace domain_reliability