1// Copyright (c) 2012 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 "base/base_paths.h"
6#include "base/compiler_specific.h"
7#include "base/files/file_util.h"
8#include "base/path_service.h"
9#include "base/strings/string_util.h"
10#include "base/test/perf_time_logger.h"
11#include "net/base/net_errors.h"
12#include "net/dns/mock_host_resolver.h"
13#include "net/proxy/proxy_info.h"
14#include "net/proxy/proxy_resolver_v8.h"
15#include "net/test/spawned_test_server/spawned_test_server.h"
16#include "testing/gtest/include/gtest/gtest.h"
17
18#if defined(OS_WIN)
19#include "net/proxy/proxy_resolver_winhttp.h"
20#elif defined(OS_MACOSX)
21#include "net/proxy/proxy_resolver_mac.h"
22#endif
23
24// This class holds the URL to use for resolving, and the expected result.
25// We track the expected result in order to make sure the performance
26// test is actually resolving URLs properly, otherwise the perf numbers
27// are meaningless :-)
28struct PacQuery {
29  const char* query_url;
30  const char* expected_result;
31};
32
33// Entry listing which PAC scripts to load, and which URLs to try resolving.
34// |queries| should be terminated by {NULL, NULL}. A sentinel is used
35// rather than a length, to simplify using initializer lists.
36struct PacPerfTest {
37  const char* pac_name;
38  PacQuery queries[100];
39
40  // Returns the actual number of entries in |queries| (assumes NULL sentinel).
41  int NumQueries() const;
42};
43
44// List of performance tests.
45static PacPerfTest kPerfTests[] = {
46  // This test uses an ad-blocker PAC script. This script is very heavily
47  // regular expression oriented, and has no dependencies on the current
48  // IP address, or DNS resolving of hosts.
49  { "no-ads.pac",
50    { // queries:
51      {"http://www.google.com", "DIRECT"},
52      {"http://www.imdb.com/photos/cmsicons/x", "PROXY 0.0.0.0:3421"},
53      {"http://www.imdb.com/x", "DIRECT"},
54      {"http://www.staples.com/", "DIRECT"},
55      {"http://www.staples.com/pixeltracker/x", "PROXY 0.0.0.0:3421"},
56      {"http://www.staples.com/pixel/x", "DIRECT"},
57      {"http://www.foobar.com", "DIRECT"},
58      {"http://www.foobarbaz.com/x/y/z", "DIRECT"},
59      {"http://www.testurl1.com/index.html", "DIRECT"},
60      {"http://www.testurl2.com", "DIRECT"},
61      {"https://www.sample/pirate/arrrrrr", "DIRECT"},
62      {NULL, NULL}
63    },
64  },
65};
66
67int PacPerfTest::NumQueries() const {
68  for (size_t i = 0; i < arraysize(queries); ++i) {
69    if (queries[i].query_url == NULL)
70      return i;
71  }
72  NOTREACHED();  // Bad definition.
73  return 0;
74}
75
76// The number of URLs to resolve when testing a PAC script.
77const int kNumIterations = 500;
78
79// Helper class to run through all the performance tests using the specified
80// proxy resolver implementation.
81class PacPerfSuiteRunner {
82 public:
83  // |resolver_name| is the label used when logging the results.
84  PacPerfSuiteRunner(net::ProxyResolver* resolver,
85                     const std::string& resolver_name)
86      : resolver_(resolver),
87        resolver_name_(resolver_name),
88        test_server_(
89            net::SpawnedTestServer::TYPE_HTTP,
90            net::SpawnedTestServer::kLocalhost,
91            base::FilePath(
92                FILE_PATH_LITERAL("net/data/proxy_resolver_perftest"))) {
93  }
94
95  void RunAllTests() {
96    ASSERT_TRUE(test_server_.Start());
97    for (size_t i = 0; i < arraysize(kPerfTests); ++i) {
98      const PacPerfTest& test_data = kPerfTests[i];
99      RunTest(test_data.pac_name,
100              test_data.queries,
101              test_data.NumQueries());
102    }
103  }
104
105 private:
106  void RunTest(const std::string& script_name,
107               const PacQuery* queries,
108               int queries_len) {
109    if (!resolver_->expects_pac_bytes()) {
110      GURL pac_url =
111          test_server_.GetURL(std::string("files/") + script_name);
112      int rv = resolver_->SetPacScript(
113          net::ProxyResolverScriptData::FromURL(pac_url),
114          net::CompletionCallback());
115      EXPECT_EQ(net::OK, rv);
116    } else {
117      LoadPacScriptIntoResolver(script_name);
118    }
119
120    // Do a query to warm things up. In the case of internal-fetch proxy
121    // resolvers, the first resolve will be slow since it has to download
122    // the PAC script.
123    {
124      net::ProxyInfo proxy_info;
125      int result = resolver_->GetProxyForURL(
126          GURL("http://www.warmup.com"), &proxy_info, net::CompletionCallback(),
127          NULL, net::BoundNetLog());
128      ASSERT_EQ(net::OK, result);
129    }
130
131    // Start the perf timer.
132    std::string perf_test_name = resolver_name_ + "_" + script_name;
133    base::PerfTimeLogger timer(perf_test_name.c_str());
134
135    for (int i = 0; i < kNumIterations; ++i) {
136      // Round-robin between URLs to resolve.
137      const PacQuery& query = queries[i % queries_len];
138
139      // Resolve.
140      net::ProxyInfo proxy_info;
141      int result = resolver_->GetProxyForURL(
142          GURL(query.query_url), &proxy_info, net::CompletionCallback(), NULL,
143          net::BoundNetLog());
144
145      // Check that the result was correct. Note that ToPacString() and
146      // ASSERT_EQ() are fast, so they won't skew the results.
147      ASSERT_EQ(net::OK, result);
148      ASSERT_EQ(query.expected_result, proxy_info.ToPacString());
149    }
150
151    // Print how long the test ran for.
152    timer.Done();
153  }
154
155  // Read the PAC script from disk and initialize the proxy resolver with it.
156  void LoadPacScriptIntoResolver(const std::string& script_name) {
157    base::FilePath path;
158    PathService::Get(base::DIR_SOURCE_ROOT, &path);
159    path = path.AppendASCII("net");
160    path = path.AppendASCII("data");
161    path = path.AppendASCII("proxy_resolver_perftest");
162    path = path.AppendASCII(script_name);
163
164    // Try to read the file from disk.
165    std::string file_contents;
166    bool ok = base::ReadFileToString(path, &file_contents);
167
168    // If we can't load the file from disk, something is misconfigured.
169    LOG_IF(ERROR, !ok) << "Failed to read file: " << path.value();
170    ASSERT_TRUE(ok);
171
172    // Load the PAC script into the ProxyResolver.
173    int rv = resolver_->SetPacScript(
174        net::ProxyResolverScriptData::FromUTF8(file_contents),
175        net::CompletionCallback());
176    EXPECT_EQ(net::OK, rv);
177  }
178
179  net::ProxyResolver* resolver_;
180  std::string resolver_name_;
181  net::SpawnedTestServer test_server_;
182};
183
184#if defined(OS_WIN)
185TEST(ProxyResolverPerfTest, ProxyResolverWinHttp) {
186  net::ProxyResolverWinHttp resolver;
187  PacPerfSuiteRunner runner(&resolver, "ProxyResolverWinHttp");
188  runner.RunAllTests();
189}
190#elif defined(OS_MACOSX)
191TEST(ProxyResolverPerfTest, ProxyResolverMac) {
192  net::ProxyResolverMac resolver;
193  PacPerfSuiteRunner runner(&resolver, "ProxyResolverMac");
194  runner.RunAllTests();
195}
196#endif
197
198class MockJSBindings : public net::ProxyResolverV8::JSBindings {
199 public:
200  MockJSBindings() {}
201
202  virtual void Alert(const base::string16& message) OVERRIDE {
203    CHECK(false);
204  }
205
206  virtual bool ResolveDns(const std::string& host,
207                          ResolveDnsOperation op,
208                          std::string* output,
209                          bool* terminate) OVERRIDE {
210    CHECK(false);
211    return false;
212  }
213
214  virtual void OnError(int line_number,
215                       const base::string16& message) OVERRIDE {
216    CHECK(false);
217  }
218};
219
220TEST(ProxyResolverPerfTest, ProxyResolverV8) {
221  net::ProxyResolverV8::EnsureIsolateCreated();
222
223  MockJSBindings js_bindings;
224  net::ProxyResolverV8 resolver;
225  resolver.set_js_bindings(&js_bindings);
226  PacPerfSuiteRunner runner(&resolver, "ProxyResolverV8");
227  runner.RunAllTests();
228}
229