summaryrefslogtreecommitdiff
path: root/chromium/net/dns/httpssvc_metrics_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/dns/httpssvc_metrics_unittest.cc')
-rw-r--r--chromium/net/dns/httpssvc_metrics_unittest.cc554
1 files changed, 554 insertions, 0 deletions
diff --git a/chromium/net/dns/httpssvc_metrics_unittest.cc b/chromium/net/dns/httpssvc_metrics_unittest.cc
new file mode 100644
index 00000000000..1ce51cf7608
--- /dev/null
+++ b/chromium/net/dns/httpssvc_metrics_unittest.cc
@@ -0,0 +1,554 @@
+// Copyright 2020 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 "net/dns/httpssvc_metrics.h"
+
+#include <string>
+#include <tuple>
+
+#include "base/feature_list.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "net/base/features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// int: number of domains
+// bool: extra leading comma
+// bool: extra trailing comma
+using DomainListQuirksTuple = std::tuple<int, bool, bool>;
+
+// bool: DnsHttpssvc feature is enabled
+// bool: DnsHttpssvcUseIntegrity feature param
+// bool: DnsHttpssvcUseHttpssvc feature param
+// bool: DnsHttpssvcControlDomainWildcard feature param
+using HttpssvcFeatureTuple = std::tuple<bool, bool, bool, bool>;
+
+// DomainListQuirksTuple: quirks for the experimental domain list.
+// DomainListQuirksTuple: quirks for the control domain list.
+// HttpssvcFeatureTuple: config for the whole DnsHttpssvc feature.
+using ParsingTestParamTuple = std::
+ tuple<DomainListQuirksTuple, DomainListQuirksTuple, HttpssvcFeatureTuple>;
+
+// bool: whether we are querying for an experimental domain or a control domain
+// HttpssvcFeatureTuple: config for the whole DnsHttpssvc feature.
+using MetricsTestParamTuple = std::tuple<bool, HttpssvcFeatureTuple>;
+
+// Create a comma-separated list of |domains| with the given |quirks|.
+std::string FlattenDomainList(const std::vector<std::string>& domains,
+ DomainListQuirksTuple quirks) {
+ int num_domains;
+ bool leading_comma, trailing_comma;
+ std::tie(num_domains, leading_comma, trailing_comma) = quirks;
+
+ CHECK_EQ(static_cast<size_t>(num_domains), domains.size());
+ std::string flattened = base::JoinString(domains, ",");
+ if (leading_comma)
+ flattened.insert(flattened.begin(), ',');
+ if (trailing_comma)
+ flattened.push_back(',');
+ return flattened;
+}
+
+// Intermediate representation constructed from test parameters.
+struct HttpssvcFeatureConfig {
+ HttpssvcFeatureConfig() = default;
+
+ explicit HttpssvcFeatureConfig(const HttpssvcFeatureTuple& feature_tuple,
+ base::StringPiece experiment_domains,
+ base::StringPiece control_domains)
+ : experiment_domains(experiment_domains.as_string()),
+ control_domains(control_domains.as_string()) {
+ std::tie(enabled, use_integrity, use_httpssvc, control_domain_wildcard) =
+ feature_tuple;
+ }
+
+ void Apply(base::test::ScopedFeatureList* scoped_feature_list) const {
+ if (!enabled) {
+ scoped_feature_list->InitAndDisableFeature(features::kDnsHttpssvc);
+ return;
+ }
+ auto stringify = [](bool b) -> std::string { return b ? "true" : "false"; };
+ scoped_feature_list->InitAndEnableFeatureWithParameters(
+ features::kDnsHttpssvc,
+ {
+ {"DnsHttpssvcUseHttpssvc", stringify(use_httpssvc)},
+ {"DnsHttpssvcUseIntegrity", stringify(use_integrity)},
+ {"DnsHttpssvcEnableQueryOverInsecure", "false"},
+ {"DnsHttpssvcExperimentDomains", experiment_domains},
+ {"DnsHttpssvcControlDomains", control_domains},
+ {"DnsHttpssvcControlDomainWildcard",
+ stringify(control_domain_wildcard)},
+ });
+ }
+
+ bool enabled = false;
+ bool use_integrity = false;
+ bool use_httpssvc = false;
+ bool control_domain_wildcard = false;
+ std::string experiment_domains;
+ std::string control_domains;
+};
+
+std::vector<std::string> GenerateDomainList(base::StringPiece label, int n) {
+ std::vector<std::string> domains;
+ for (int i = 0; i < n; i++) {
+ domains.push_back(base::StrCat(
+ {"domain", base::NumberToString(i), ".", label, ".example"}));
+ }
+ return domains;
+}
+
+// Base for testing domain list parsing functions in
+// net::features::dns_httpssvc_experiment.
+class HttpssvcDomainParsingTest
+ : public ::testing::TestWithParam<ParsingTestParamTuple> {
+ public:
+ void SetUp() override {
+ DomainListQuirksTuple domain_quirks_experimental;
+ DomainListQuirksTuple domain_quirks_control;
+ HttpssvcFeatureTuple httpssvc_feature;
+ std::tie(domain_quirks_experimental, domain_quirks_control,
+ httpssvc_feature) = GetParam();
+
+ expected_experiment_domains_ = GenerateDomainList(
+ "experiment", std::get<0>(domain_quirks_experimental));
+ expected_control_domains_ =
+ GenerateDomainList("control", std::get<0>(domain_quirks_control));
+
+ config_ = HttpssvcFeatureConfig(
+ httpssvc_feature,
+ FlattenDomainList(expected_experiment_domains_,
+ domain_quirks_experimental),
+ FlattenDomainList(expected_control_domains_, domain_quirks_control));
+ config_.Apply(&scoped_feature_list_);
+ }
+
+ const HttpssvcFeatureConfig& config() { return config_; }
+
+ protected:
+ // The expected results of parsing the comma-separated domain lists in
+ // |experiment_domains| and |control_domains|, respectively.
+ std::vector<std::string> expected_experiment_domains_;
+ std::vector<std::string> expected_control_domains_;
+
+ private:
+ HttpssvcFeatureConfig config_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// This instantiation tests the domain list parser against various quirks,
+// e.g. leading comma.
+INSTANTIATE_TEST_SUITE_P(
+ HttpssvcMetricsTestDomainParsing,
+ HttpssvcDomainParsingTest,
+ testing::Combine(
+ // DomainListQuirksTuple for experimental domains. To fight back
+ // combinatorial explosion of tests, this tuple is pared down more than
+ // the one for control domains. This should not significantly hurt test
+ // coverage because |IsExperimentDomain| and |IsControlDomain| rely on a
+ // shared helper function.
+ testing::Combine(testing::Values(0, 1),
+ testing::Values(false),
+ testing::Values(false)),
+ // DomainListQuirksTuple for control domains.
+ testing::Combine(testing::Range(0, 3),
+ testing::Bool(),
+ testing::Bool()),
+ // HttpssvcFeatureTuple
+ testing::Combine(
+ testing::Bool() /* DnsHttpssvc feature enabled? */,
+ testing::Bool() /* DnsHttpssvcUseIntegrity */,
+ testing::Values(false) /* DnsHttpssvcUseHttpssvc */,
+ testing::Values(false) /* DnsHttpssvcControlDomainWildcard */)));
+
+// Base for testing the metrics collection code in |HttpssvcMetrics|.
+class HttpssvcMetricsTest
+ : public ::testing::TestWithParam<MetricsTestParamTuple> {
+ public:
+ void SetUp() override {
+ HttpssvcFeatureTuple httpssvc_feature;
+ std::tie(querying_experimental_, httpssvc_feature) = GetParam();
+ config_ = HttpssvcFeatureConfig(httpssvc_feature, "", "");
+ config_.Apply(&scoped_feature_list_);
+ }
+
+ std::string BuildMetricNamePrefix() const {
+ return base::StrCat(
+ {"Net.DNS.HTTPSSVC.RecordIntegrity.", doh_provider_, "."});
+ }
+
+ template <typename T>
+ void ExpectSample(base::StringPiece name, base::Optional<T> sample) const {
+ if (sample)
+ histo().ExpectUniqueSample(name, *sample, 1);
+ else
+ histo().ExpectTotalCount(name, 0);
+ }
+
+ void ExpectSample(base::StringPiece name,
+ base::Optional<base::TimeDelta> sample) const {
+ base::Optional<int64_t> sample_ms;
+ if (sample)
+ sample_ms = {sample->InMilliseconds()};
+ ExpectSample<int64_t>(name, sample_ms);
+ }
+
+ void VerifyMetricsForExpectIntact(
+ base::Optional<HttpssvcDnsRcode> rcode,
+ base::Optional<bool> integrity,
+ base::Optional<bool> record_with_error,
+ base::Optional<base::TimeDelta> resolve_time_integrity,
+ base::Optional<base::TimeDelta> resolve_time_non_integrity,
+ base::Optional<int> resolve_time_ratio) const {
+ const std::string kPrefix =
+ base::StrCat({BuildMetricNamePrefix(), "ExpectIntact."});
+ const std::string kMetricDnsRcode = base::StrCat({kPrefix, "DnsRcode"});
+ const std::string kMetricIntegrity = base::StrCat({kPrefix, "Integrity"});
+ const std::string kMetricRecordWithError =
+ base::StrCat({kPrefix, "RecordWithError"});
+ const std::string kMetricResolveTimeIntegrity =
+ base::StrCat({kPrefix, "ResolveTimeIntegrityRecord"});
+ const std::string kMetricResolveTimeNonIntegrity =
+ base::StrCat({kPrefix, "ResolveTimeNonIntegrityRecord"});
+ const std::string kMetricResolveTimeRatio =
+ base::StrCat({kPrefix, "ResolveTimeRatio"});
+
+ ExpectSample(kMetricDnsRcode, rcode);
+ ExpectSample(kMetricIntegrity, integrity);
+ ExpectSample(kMetricRecordWithError, record_with_error);
+ ExpectSample(kMetricResolveTimeIntegrity, resolve_time_integrity);
+ ExpectSample(kMetricResolveTimeNonIntegrity, resolve_time_non_integrity);
+ ExpectSample(kMetricResolveTimeRatio, resolve_time_ratio);
+ }
+
+ void VerifyMetricsForExpectNoerror(
+ base::Optional<HttpssvcDnsRcode> rcode,
+ base::Optional<int> record_received,
+ base::Optional<base::TimeDelta> resolve_time_integrity,
+ base::Optional<base::TimeDelta> resolve_time_non_integrity,
+ base::Optional<int> resolve_time_ratio) const {
+ const std::string kPrefix =
+ base::StrCat({BuildMetricNamePrefix(), "ExpectNoerror."});
+ const std::string kMetricDnsRcode = base::StrCat({kPrefix, "DnsRcode"});
+ const std::string kMetricRecordReceived =
+ base::StrCat({kPrefix, "RecordReceived"});
+ const std::string kMetricResolveTimeIntegrity =
+ base::StrCat({kPrefix, "ResolveTimeIntegrityRecord"});
+ const std::string kMetricResolveTimeNonIntegrity =
+ base::StrCat({kPrefix, "ResolveTimeNonIntegrityRecord"});
+ const std::string kMetricResolveTimeRatio =
+ base::StrCat({kPrefix, "ResolveTimeRatio"});
+
+ ExpectSample(kMetricDnsRcode, rcode);
+ ExpectSample(kMetricRecordReceived, record_received);
+ ExpectSample(kMetricResolveTimeIntegrity, resolve_time_integrity);
+ ExpectSample(kMetricResolveTimeNonIntegrity, resolve_time_non_integrity);
+ ExpectSample(kMetricResolveTimeRatio, resolve_time_ratio);
+ }
+
+ void VerifyMetricsForExpectIntact() {
+ VerifyMetricsForExpectIntact(base::nullopt, base::nullopt, base::nullopt,
+ base::nullopt, base::nullopt, base::nullopt);
+ }
+
+ void VerifyMetricsForExpectNoerror() {
+ VerifyMetricsForExpectNoerror(base::nullopt, base::nullopt, base::nullopt,
+ base::nullopt, base::nullopt);
+ }
+
+ const base::HistogramTester& histo() const { return histogram_; }
+ const HttpssvcFeatureConfig& config() const { return config_; }
+
+ protected:
+ bool querying_experimental_;
+
+ private:
+ HttpssvcFeatureConfig config_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+ base::HistogramTester histogram_;
+ std::string doh_provider_ = "Other";
+};
+
+// This instantiation focuses on whether the correct metrics are recorded. The
+// domain list parser is already tested against encoding quirks in
+// |HttpssvcMetricsTestDomainParsing|, so we fix the quirks at false.
+INSTANTIATE_TEST_SUITE_P(
+ HttpssvcMetricsTestSimple,
+ HttpssvcMetricsTest,
+ testing::Combine(
+ // Whether we are querying an experimental domain.
+ testing::Bool(),
+ // HttpssvcFeatureTuple
+ testing::Combine(
+ testing::Values(true) /* DnsHttpssvc feature enabled? */,
+ testing::Values(true) /* DnsHttpssvcUseIntegrity */,
+ testing::Values(false) /* DnsHttpssvcUseHttpssvc */,
+ testing::Values(false) /* DnsHttpssvcControlDomainWildcard */)));
+
+TEST_P(HttpssvcDomainParsingTest, ParseFeatureParamIntegrityDomains) {
+ HttpssvcExperimentDomainCache domain_cache;
+
+ // We are not testing this feature param yet.
+ CHECK(!config().use_httpssvc);
+
+ const std::string kReservedDomain = "neither.example";
+ EXPECT_FALSE(domain_cache.IsExperimental(kReservedDomain));
+ EXPECT_EQ(domain_cache.IsControl(kReservedDomain),
+ config().enabled && config().control_domain_wildcard);
+
+ // If |config().use_integrity| is true, then we expect all domains in
+ // |expected_experiment_domains_| to be experimental (same goes for
+ // control domains). Otherwise, no domain should be considered experimental or
+ // control.
+
+ if (!config().enabled) {
+ // When the HTTPSSVC feature is disabled, no domain should be considered
+ // experimental or control.
+ for (const std::string& experiment_domain : expected_experiment_domains_) {
+ EXPECT_FALSE(domain_cache.IsExperimental(experiment_domain));
+ EXPECT_FALSE(domain_cache.IsControl(experiment_domain));
+ }
+ for (const std::string& control_domain : expected_control_domains_) {
+ EXPECT_FALSE(domain_cache.IsExperimental(control_domain));
+ EXPECT_FALSE(domain_cache.IsControl(control_domain));
+ }
+ } else if (config().use_integrity) {
+ for (const std::string& experiment_domain : expected_experiment_domains_) {
+ EXPECT_TRUE(domain_cache.IsExperimental(experiment_domain));
+ EXPECT_FALSE(domain_cache.IsControl(experiment_domain));
+ }
+ for (const std::string& control_domain : expected_control_domains_) {
+ EXPECT_FALSE(domain_cache.IsExperimental(control_domain));
+ EXPECT_TRUE(domain_cache.IsControl(control_domain));
+ }
+ return;
+ }
+}
+
+// Only record metrics for a non-integrity query.
+TEST_P(HttpssvcMetricsTest, AddressAndIntegrityMissing) {
+ if (!config().enabled || !config().use_integrity) {
+ VerifyMetricsForExpectIntact();
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+ const base::TimeDelta kResolveTime = base::TimeDelta::FromMilliseconds(10);
+ base::Optional<HttpssvcMetrics> metrics(querying_experimental_);
+ metrics->SaveForNonIntegrity(base::nullopt, kResolveTime,
+ HttpssvcDnsRcode::kNoError);
+ metrics.reset(); // Record the metrics to UMA.
+
+ VerifyMetricsForExpectIntact();
+ VerifyMetricsForExpectNoerror();
+}
+
+TEST_P(HttpssvcMetricsTest, AddressAndIntegrityIntact) {
+ if (!config().enabled || !config().use_integrity) {
+ VerifyMetricsForExpectIntact();
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+ const base::TimeDelta kResolveTime = base::TimeDelta::FromMilliseconds(10);
+ const base::TimeDelta kResolveTimeIntegrity =
+ base::TimeDelta::FromMilliseconds(15);
+ base::Optional<HttpssvcMetrics> metrics(querying_experimental_);
+ metrics->SaveForIntegrity(base::nullopt, HttpssvcDnsRcode::kNoError, {true},
+ kResolveTimeIntegrity);
+ metrics->SaveForNonIntegrity(base::nullopt, kResolveTime,
+ HttpssvcDnsRcode::kNoError);
+ metrics.reset(); // Record the metrics to UMA.
+
+ if (querying_experimental_) {
+ VerifyMetricsForExpectIntact(
+ base::nullopt /* rcode */, {true} /* integrity */,
+ base::nullopt /* record_with_error */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+
+ VerifyMetricsForExpectIntact();
+
+ VerifyMetricsForExpectNoerror(
+ {HttpssvcDnsRcode::kNoError} /* rcode */, {1} /* record_received */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+}
+
+// This test simulates an INTEGRITY response that includes no INTEGRITY records,
+// but does have an error value for the RCODE.
+TEST_P(HttpssvcMetricsTest, AddressAndIntegrityMissingWithRcode) {
+ if (!config().enabled || !config().use_integrity) {
+ VerifyMetricsForExpectIntact();
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+ const base::TimeDelta kResolveTime = base::TimeDelta::FromMilliseconds(10);
+ const base::TimeDelta kResolveTimeIntegrity =
+ base::TimeDelta::FromMilliseconds(15);
+
+ base::Optional<HttpssvcMetrics> metrics(querying_experimental_);
+ metrics->SaveForIntegrity(base::nullopt, HttpssvcDnsRcode::kNxDomain, {},
+ kResolveTimeIntegrity);
+ metrics->SaveForNonIntegrity(base::nullopt, kResolveTime,
+ HttpssvcDnsRcode::kNoError);
+ metrics.reset(); // Record the metrics to UMA.
+
+ if (querying_experimental_) {
+ VerifyMetricsForExpectIntact(
+ {HttpssvcDnsRcode::kNxDomain} /* rcode */,
+ base::nullopt /* integrity */, base::nullopt /* record_with_error */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+
+ VerifyMetricsForExpectIntact();
+
+ VerifyMetricsForExpectNoerror(
+ {HttpssvcDnsRcode::kNxDomain} /* rcode */,
+ base::nullopt /* record_received */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+}
+
+// This test simulates an INTEGRITY response that includes an intact INTEGRITY
+// record, but also has an error RCODE.
+TEST_P(HttpssvcMetricsTest, AddressAndIntegrityIntactWithRcode) {
+ if (!config().enabled || !config().use_integrity) {
+ VerifyMetricsForExpectIntact();
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+
+ const base::TimeDelta kResolveTime = base::TimeDelta::FromMilliseconds(10);
+ const base::TimeDelta kResolveTimeIntegrity =
+ base::TimeDelta::FromMilliseconds(15);
+
+ base::Optional<HttpssvcMetrics> metrics(querying_experimental_);
+ metrics->SaveForIntegrity(base::nullopt, HttpssvcDnsRcode::kNxDomain, {true},
+ kResolveTimeIntegrity);
+ metrics->SaveForNonIntegrity(base::nullopt, kResolveTime,
+ HttpssvcDnsRcode::kNoError);
+ metrics.reset(); // Record the metrics to UMA.
+
+ if (querying_experimental_) {
+ VerifyMetricsForExpectIntact(
+ // "DnsRcode" metric is omitted because we received an INTEGRITY record.
+ base::nullopt /* rcode */,
+ // "Integrity" metric is omitted because the RCODE is not NOERROR.
+ base::nullopt /* integrity */, {true} /* record_with_error */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+
+ VerifyMetricsForExpectIntact();
+
+ VerifyMetricsForExpectNoerror(
+ {HttpssvcDnsRcode::kNxDomain} /* rcode */, {true} /* record_received */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+}
+
+// This test simulates an INTEGRITY response that includes a mangled INTEGRITY
+// record *and* has an error RCODE.
+TEST_P(HttpssvcMetricsTest, AddressAndIntegrityMangledWithRcode) {
+ if (!config().enabled || !config().use_integrity) {
+ VerifyMetricsForExpectIntact();
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+ const base::TimeDelta kResolveTime = base::TimeDelta::FromMilliseconds(10);
+ const base::TimeDelta kResolveTimeIntegrity =
+ base::TimeDelta::FromMilliseconds(15);
+ base::Optional<HttpssvcMetrics> metrics(querying_experimental_);
+ metrics->SaveForIntegrity(base::nullopt, HttpssvcDnsRcode::kNxDomain, {false},
+ kResolveTimeIntegrity);
+ metrics->SaveForNonIntegrity(base::nullopt, kResolveTime,
+ HttpssvcDnsRcode::kNoError);
+ metrics.reset(); // Record the metrics to UMA.
+
+ if (querying_experimental_) {
+ VerifyMetricsForExpectIntact(
+ // "DnsRcode" metric is omitted because we received an INTEGRITY record.
+ base::nullopt /* rcode */,
+ // "Integrity" metric is omitted because the RCODE is not NOERROR.
+ base::nullopt /* integrity */, {true} /* record_with_error */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+
+ VerifyMetricsForExpectIntact();
+
+ VerifyMetricsForExpectNoerror(
+ {HttpssvcDnsRcode::kNxDomain} /* rcode */, {true} /* record_received */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+}
+
+// This test simulates successful address queries and an INTEGRITY query that
+// timed out.
+TEST_P(HttpssvcMetricsTest, AddressAndIntegrityTimedOut) {
+ if (!config().enabled || !config().use_integrity) {
+ VerifyMetricsForExpectIntact();
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+ const base::TimeDelta kResolveTime = base::TimeDelta::FromMilliseconds(10);
+ const base::TimeDelta kResolveTimeIntegrity =
+ base::TimeDelta::FromMilliseconds(15);
+ base::Optional<HttpssvcMetrics> metrics(querying_experimental_);
+ metrics->SaveForIntegrity(base::nullopt, HttpssvcDnsRcode::kTimedOut, {},
+ kResolveTimeIntegrity);
+ metrics->SaveForNonIntegrity(base::nullopt, kResolveTime,
+ HttpssvcDnsRcode::kNoError);
+ metrics.reset(); // Record the metrics to UMA.
+
+ if (querying_experimental_) {
+ VerifyMetricsForExpectIntact(
+ {HttpssvcDnsRcode::kTimedOut} /* rcode */,
+ // "Integrity" metric is omitted because the RCODE is not NOERROR.
+ base::nullopt /* integrity */, base::nullopt /* record_with_error */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+
+ VerifyMetricsForExpectNoerror();
+ return;
+ }
+
+ VerifyMetricsForExpectIntact();
+
+ VerifyMetricsForExpectNoerror(
+ {HttpssvcDnsRcode::kTimedOut} /* rcode */,
+ base::nullopt /* record_received */,
+ {kResolveTimeIntegrity} /* resolve_time_integrity */,
+ {kResolveTime} /* resolve_time_non_integrity */,
+ {15} /* resolve_time_ratio */);
+}
+
+} // namespace net