1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/page_load_metrics/integration_tests/metric_integration_test.h"
6 
7 #include "base/strings/stringprintf.h"
8 #include "base/test/trace_event_analyzer.h"
9 #include "chrome/browser/ui/browser.h"
10 #include "chrome/common/chrome_switches.h"
11 #include "chrome/test/base/ui_test_utils.h"
12 #include "content/public/browser/tracing_controller.h"
13 #include "content/public/common/content_switches.h"
14 #include "net/dns/mock_host_resolver.h"
15 #include "net/test/embedded_test_server/http_request.h"
16 #include "net/test/embedded_test_server/http_response.h"
17 #include "services/metrics/public/cpp/ukm_builders.h"
18 
19 using base::CommandLine;
20 using base::OnceClosure;
21 using base::RunLoop;
22 using base::StringPiece;
23 using base::TimeDelta;
24 using base::trace_event::TraceConfig;
25 using content::TracingController;
26 using content::WebContents;
27 using net::test_server::BasicHttpResponse;
28 using net::test_server::HttpRequest;
29 using net::test_server::HttpResponse;
30 using trace_analyzer::TraceAnalyzer;
31 using ukm::TestUkmRecorder;
32 using ukm::builders::PageLoad;
33 using ukm::mojom::UkmEntry;
34 
35 MetricIntegrationTest::MetricIntegrationTest() = default;
36 MetricIntegrationTest::~MetricIntegrationTest() = default;
37 
SetUpOnMainThread()38 void MetricIntegrationTest::SetUpOnMainThread() {
39   host_resolver()->AddRule("*", "127.0.0.1");
40   embedded_test_server()->ServeFilesFromSourceDirectory(
41       "chrome/browser/page_load_metrics/integration_tests/data");
42   embedded_test_server()->ServeFilesFromSourceDirectory(
43       "third_party/blink/web_tests/external/wpt");
44   content::SetupCrossSiteRedirector(embedded_test_server());
45 
46   ukm_recorder_.emplace();
47   histogram_tester_.emplace();
48 }
49 
ServeDelayed(const std::string & url,const std::string & content,TimeDelta delay)50 void MetricIntegrationTest::ServeDelayed(const std::string& url,
51                                          const std::string& content,
52                                          TimeDelta delay) {
53   embedded_test_server()->RegisterRequestHandler(
54       base::BindRepeating(&HandleRequest, url, content, delay));
55 }
56 
Serve(const std::string & url,const std::string & content)57 void MetricIntegrationTest::Serve(const std::string& url,
58                                   const std::string& content) {
59   ServeDelayed(url, content, TimeDelta());
60 }
61 
Start()62 void MetricIntegrationTest::Start() {
63   ASSERT_TRUE(embedded_test_server()->Start());
64 }
65 
Load(const std::string & relative_url)66 void MetricIntegrationTest::Load(const std::string& relative_url) {
67   GURL url = embedded_test_server()->GetURL("example.com", relative_url);
68   ui_test_utils::NavigateToURL(browser(), url);
69 }
70 
LoadHTML(const std::string & content)71 void MetricIntegrationTest::LoadHTML(const std::string& content) {
72   Serve("/test.html", content);
73   Start();
74   Load("/test.html");
75 }
76 
StartTracing(const std::vector<std::string> & categories)77 void MetricIntegrationTest::StartTracing(
78     const std::vector<std::string>& categories) {
79   RunLoop wait_for_tracing;
80   TracingController::GetInstance()->StartTracing(
81       TraceConfig(
82           base::StringPrintf("{\"included_categories\": [\"%s\"]}",
83                              base::JoinString(categories, "\", \"").c_str())),
84       wait_for_tracing.QuitClosure());
85   wait_for_tracing.Run();
86 }
87 
StopTracing(std::string & trace_output)88 void MetricIntegrationTest::StopTracing(std::string& trace_output) {
89   RunLoop wait_for_tracing;
90   TracingController::GetInstance()->StopTracing(
91       TracingController::CreateStringEndpoint(base::BindOnce(
92           [](OnceClosure quit_closure, std::string* output,
93              std::unique_ptr<std::string> trace_str) {
94             *output = *trace_str;
95             std::move(quit_closure).Run();
96           },
97           wait_for_tracing.QuitClosure(), &trace_output)));
98   wait_for_tracing.Run();
99 }
100 
StopTracingAndAnalyze()101 std::unique_ptr<TraceAnalyzer> MetricIntegrationTest::StopTracingAndAnalyze() {
102   std::string trace_str;
103   StopTracing(trace_str);
104   return std::unique_ptr<TraceAnalyzer>(TraceAnalyzer::Create(trace_str));
105 }
106 
web_contents() const107 WebContents* MetricIntegrationTest::web_contents() const {
108   return browser()->tab_strip_model()->GetActiveWebContents();
109 }
110 
SetUpCommandLine(CommandLine * command_line)111 void MetricIntegrationTest::SetUpCommandLine(CommandLine* command_line) {
112   // Set a default window size for consistency.
113   command_line->AppendSwitchASCII(switches::kWindowSize, "800,600");
114   command_line->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures);
115 
116   content::IsolateAllSitesForTesting(command_line);
117 }
118 
HandleRequest(const std::string & relative_url,const std::string & content,TimeDelta delay,const HttpRequest & request)119 std::unique_ptr<HttpResponse> MetricIntegrationTest::HandleRequest(
120     const std::string& relative_url,
121     const std::string& content,
122     TimeDelta delay,
123     const HttpRequest& request) {
124   if (request.relative_url != relative_url)
125     return nullptr;
126   if (!delay.is_zero())
127     base::PlatformThread::Sleep(delay);
128   auto response = std::make_unique<BasicHttpResponse>();
129   response->set_code(net::HTTP_OK);
130   response->set_content(content);
131   response->set_content_type("text/html; charset=utf-8");
132   return std::move(response);
133 }
134 
ExpectUKMPageLoadMetric(StringPiece metric_name,int64_t expected_value)135 void MetricIntegrationTest::ExpectUKMPageLoadMetric(StringPiece metric_name,
136                                                     int64_t expected_value) {
137   std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
138       ukm_recorder().GetMergedEntriesByName(PageLoad::kEntryName);
139   EXPECT_EQ(1ul, merged_entries.size());
140   const auto& kv = merged_entries.begin();
141   TestUkmRecorder::ExpectEntryMetric(kv->second.get(), metric_name,
142                                      expected_value);
143 }
144 
ExpectUKMPageLoadMetricNear(StringPiece metric_name,double expected_value,double epsilon)145 void MetricIntegrationTest::ExpectUKMPageLoadMetricNear(StringPiece metric_name,
146                                                         double expected_value,
147                                                         double epsilon) {
148   std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
149       ukm_recorder().GetMergedEntriesByName(PageLoad::kEntryName);
150 
151   EXPECT_EQ(1ul, merged_entries.size());
152   const auto& kv = merged_entries.begin();
153   const int64_t* recorded =
154       TestUkmRecorder::GetEntryMetric(kv->second.get(), metric_name);
155   EXPECT_NE(recorded, nullptr);
156   EXPECT_NEAR(*recorded, expected_value, epsilon);
157 }
158 
ExpectUniqueUMAPageLoadMetricNear(StringPiece metric_name,double expected_value)159 void MetricIntegrationTest::ExpectUniqueUMAPageLoadMetricNear(
160     StringPiece metric_name,
161     double expected_value) {
162   EXPECT_EQ(histogram_tester_->GetAllSamples(metric_name).size(), 1u)
163       << "There should be one sample for " << metric_name.data();
164   // UMA uses integer buckets so check that the value is in the bucket of
165   // |expected_value| or in the bucket of |expected_value| +- 1.
166   EXPECT_TRUE(
167       histogram_tester_->GetBucketCount(metric_name, expected_value) == 1 ||
168       histogram_tester_->GetBucketCount(metric_name, expected_value + 1.0) ==
169           1 ||
170       histogram_tester_->GetBucketCount(metric_name, expected_value - 1.0) == 1)
171       << "The sample for " << metric_name.data()
172       << " is not near the expected value!";
173 }
174