// 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 #include #include #include #include "base/bind.h" #include "base/json/json_reader.h" #include "base/memory/scoped_ptr.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::DictionaryValue; using base::ListValue; using base::Value; typedef std::vector BeaconVector; scoped_ptr MakeCustomizedBeacon( MockableTime* time, std::string status, std::string quic_error) { scoped_ptr 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->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; return beacon; } scoped_ptr MakeBeacon(MockableTime* time) { return MakeCustomizedBeacon(time, "tcp.connection_reset", ""); } template struct HasValue { bool operator()(const DictionaryValue& dict, const std::string& key, ValueType expected_value) { ValueType actual_value; bool got_value = (dict.*GetValueType)(key, &actual_value); if (got_value) EXPECT_EQ(expected_value, actual_value); return got_value && (expected_value == actual_value); } }; HasValue HasBooleanValue; HasValue HasDoubleValue; HasValue HasIntegerValue; HasValue HasStringValue; bool GetEntryFromReport(const Value* report, size_t index, const DictionaryValue** entry_out) { const DictionaryValue* report_dict; const ListValue* entries; return report && report->GetAsDictionary(&report_dict) && report_dict->GetList("entries", &entries) && entries->GetDictionary(index, entry_out); } class DomainReliabilityContextTest : public testing::Test { protected: DomainReliabilityContextTest() : last_network_change_time_(time_.NowTicks()), dispatcher_(&time_), params_(MakeTestSchedulerParams()), uploader_(base::Bind(&DomainReliabilityContextTest::OnUploadRequest, base::Unretained(this))), upload_reporter_string_("test-reporter"), 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(scoped_ptr config) { context_.reset(new DomainReliabilityContext( &time_, params_, upload_reporter_string_, &last_network_change_time_, &dispatcher_, &uploader_, std::move(config))); } 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 TimeDelta::FromMicroseconds(0); } 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_; } void CallUploadCallback(DomainReliabilityUploader::UploadResult result) { ASSERT_TRUE(upload_pending_); upload_callback_.Run(result); upload_pending_ = false; } bool CheckNoBeacons() { BeaconVector beacons; context_->GetQueuedBeaconsForTesting(&beacons); return beacons.empty(); } MockTime time_; base::TimeTicks last_network_change_time_; DomainReliabilityDispatcher dispatcher_; DomainReliabilityScheduler::Params params_; MockUploader uploader_; std::string upload_reporter_string_; scoped_ptr context_; private: void OnUploadRequest( const std::string& report_json, int max_upload_depth, const GURL& upload_url, const DomainReliabilityUploader::UploadCallback& callback) { ASSERT_FALSE(upload_pending_); upload_report_ = report_json; upload_max_depth_ = max_upload_depth; upload_url_ = upload_url; upload_callback_ = callback; upload_pending_ = true; } bool upload_pending_; std::string upload_report_; int upload_max_depth_; GURL upload_url_; DomainReliabilityUploader::UploadCallback upload_callback_; }; TEST_F(DomainReliabilityContextTest, Create) { InitContext(MakeTestConfig()); EXPECT_TRUE(CheckNoBeacons()); } TEST_F(DomainReliabilityContextTest, Report) { InitContext(MakeTestConfig()); context_->OnBeacon(MakeBeacon(&time_)); BeaconVector beacons; context_->GetQueuedBeaconsForTesting(&beacons); EXPECT_EQ(1u, beacons.size()); } TEST_F(DomainReliabilityContextTest, MaxNestedBeaconSchedules) { InitContext(MakeTestConfig()); GURL url("http://example/always_report"); scoped_ptr 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_pending()); } TEST_F(DomainReliabilityContextTest, OverlyNestedBeaconDoesNotSchedule) { InitContext(MakeTestConfig()); GURL url("http://example/always_report"); scoped_ptr 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_pending()); } TEST_F(DomainReliabilityContextTest, MaxNestedBeaconAfterOverlyNestedBeaconSchedules) { InitContext(MakeTestConfig()); // Add a beacon for a report that's too nested to schedule a beacon. scoped_ptr 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_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_pending()); // Check that both beacons were uploaded. DomainReliabilityUploader::UploadResult result; result.status = DomainReliabilityUploader::UploadResult::SUCCESS; CallUploadCallback(result); EXPECT_TRUE(CheckNoBeacons()); } TEST_F(DomainReliabilityContextTest, ReportUpload) { InitContext(MakeTestConfig()); context_->OnBeacon(MakeBeacon(&time_)); BeaconVector beacons; context_->GetQueuedBeaconsForTesting(&beacons); EXPECT_EQ(1u, beacons.size()); time_.Advance(max_delay()); EXPECT_TRUE(upload_pending()); EXPECT_EQ(0, upload_max_depth()); EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url()); scoped_ptr value = base::JSONReader::Read(upload_report()); const DictionaryValue* 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)); // 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); EXPECT_TRUE(CheckNoBeacons()); } 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_pending()); EXPECT_EQ(0, upload_max_depth()); EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url()); scoped_ptr value = base::JSONReader::Read(upload_report()); const DictionaryValue* 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")); BeaconVector beacons; context_->GetQueuedBeaconsForTesting(&beacons); EXPECT_EQ(1u, beacons.size()); time_.Advance(max_delay()); EXPECT_TRUE(upload_pending()); EXPECT_EQ(0, upload_max_depth()); EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url()); scoped_ptr value = base::JSONReader::Read(upload_report()); const DictionaryValue* entry; ASSERT_TRUE(GetEntryFromReport(value.get(), 0, &entry)); EXPECT_TRUE(HasBooleanValue(*entry, "quic_broken", 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_pending()); EXPECT_EQ(0, upload_max_depth()); EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url()); scoped_ptr value = base::JSONReader::Read(upload_report()); const DictionaryValue* 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")); BeaconVector beacons; context_->GetQueuedBeaconsForTesting(&beacons); EXPECT_EQ(1u, beacons.size()); time_.Advance(max_delay()); EXPECT_TRUE(upload_pending()); EXPECT_EQ(0, upload_max_depth()); EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url()); scoped_ptr value = base::JSONReader::Read(upload_report()); const DictionaryValue* 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) { scoped_ptr 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) { scoped_ptr 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_pending()); EXPECT_EQ(0, upload_max_depth()); EXPECT_EQ(GURL("https://exampleuploader/upload"), upload_url()); scoped_ptr value = base::JSONReader::Read(upload_report()); const DictionaryValue* 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) { scoped_ptr 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()); scoped_ptr 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) { scoped_ptr 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()); scoped_ptr 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) { scoped_ptr 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()); scoped_ptr 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) { scoped_ptr 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()); scoped_ptr beacon(MakeBeacon(&time_)); beacon->status = "ok"; beacon->chrome_error = net::OK; context_->OnBeacon(std::move(beacon)); context_->GetQueuedBeaconsForTesting(&beacons); EXPECT_EQ(0u, beacons.size()); } // TODO(ttuttle): Add beacon_unittest.cc to test serialization. } // namespace } // namespace domain_reliability