// Copyright (c) 2013 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/proxy/proxy_resolver_v8_tracing.h" #include #include #include "base/files/file_util.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" #include "base/test/histogram_tester.h" #include "base/threading/platform_thread.h" #include "base/threading/thread_checker.h" #include "base/values.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "net/dns/host_cache.h" #include "net/dns/mock_host_resolver.h" #include "net/log/net_log.h" #include "net/proxy/proxy_info.h" #include "net/test/event_waiter.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" namespace net { namespace { class ProxyResolverV8TracingTest : public testing::Test { public: void TearDown() override { // Drain any pending messages, which may be left over from cancellation. // This way they get reliably run as part of the current test, rather than // spilling into the next test's execution. base::RunLoop().RunUntilIdle(); } protected: // TODO(eroman): Remove when done gathering data for crbug.com/593759 void ExpectHistogramBucketCount(PacResultForStrippedUrl bucket, size_t expected_total) { histograms_.ExpectUniqueSample(kHistogramPacResultForStrippedUrl, static_cast(bucket), expected_total); } // TODO(eroman): Remove when done gathering data for crbug.com/593759 void ExpectHistogramTotal(size_t expected_total) { histograms_.ExpectTotalCount(kHistogramPacResultForStrippedUrl, expected_total); } private: base::HistogramTester histograms_; }; scoped_refptr LoadScriptData(const char* filename) { base::FilePath path; PathService::Get(base::DIR_SOURCE_ROOT, &path); path = path.AppendASCII("net"); path = path.AppendASCII("data"); path = path.AppendASCII("proxy_resolver_v8_tracing_unittest"); path = path.AppendASCII(filename); // Try to read the file from disk. std::string file_contents; bool ok = base::ReadFileToString(path, &file_contents); // If we can't load the file from disk, something is misconfigured. EXPECT_TRUE(ok) << "Failed to read file: " << path.value(); // Load the PAC script into the ProxyResolver. return ProxyResolverScriptData::FromUTF8(file_contents); } class MockBindings { public: explicit MockBindings(HostResolver* host_resolver) : host_resolver_(host_resolver) {} void Alert(const base::string16& message) { alerts_.push_back(base::UTF16ToASCII(message)); } void OnError(int line_number, const base::string16& error) { waiter_.NotifyEvent(EVENT_ERROR); errors_.push_back(std::make_pair(line_number, base::UTF16ToASCII(error))); if (!error_callback_.is_null()) error_callback_.Run(); } HostResolver* host_resolver() { return host_resolver_; } std::vector GetAlerts() { return alerts_; } std::vector> GetErrors() { return errors_; } void RunOnError(const base::Closure& callback) { error_callback_ = callback; waiter_.WaitForEvent(EVENT_ERROR); } scoped_ptr CreateBindings() { return make_scoped_ptr(new ForwardingBindings(this)); } private: class ForwardingBindings : public ProxyResolverV8Tracing::Bindings { public: explicit ForwardingBindings(MockBindings* bindings) : bindings_(bindings) {} // ProxyResolverV8Tracing::Bindings overrides. void Alert(const base::string16& message) override { DCHECK(thread_checker_.CalledOnValidThread()); bindings_->Alert(message); } void OnError(int line_number, const base::string16& error) override { DCHECK(thread_checker_.CalledOnValidThread()); bindings_->OnError(line_number, error); } BoundNetLog GetBoundNetLog() override { DCHECK(thread_checker_.CalledOnValidThread()); return BoundNetLog(); } HostResolver* GetHostResolver() override { DCHECK(thread_checker_.CalledOnValidThread()); return bindings_->host_resolver(); } private: MockBindings* bindings_; base::ThreadChecker thread_checker_; }; enum Event { EVENT_ERROR, }; std::vector alerts_; std::vector> errors_; HostResolver* const host_resolver_; base::Closure error_callback_; EventWaiter waiter_; }; scoped_ptr CreateResolver( scoped_ptr bindings, const char* filename) { scoped_ptr resolver; scoped_ptr factory( ProxyResolverV8TracingFactory::Create()); TestCompletionCallback callback; scoped_ptr request; factory->CreateProxyResolverV8Tracing(LoadScriptData(filename), std::move(bindings), &resolver, callback.callback(), &request); EXPECT_EQ(OK, callback.WaitForResult()); EXPECT_TRUE(resolver); return resolver; } TEST_F(ProxyResolverV8TracingTest, Simple) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "simple.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount(PacResultForStrippedUrl::SUCCESS, 1); ExpectHistogramTotal(1); EXPECT_EQ("foo:99", proxy_info.proxy_server().ToURI()); EXPECT_EQ(0u, host_resolver.num_resolve()); // There were no alerts or errors. EXPECT_TRUE(mock_bindings.GetAlerts().empty()); EXPECT_TRUE(mock_bindings.GetErrors().empty()); } TEST_F(ProxyResolverV8TracingTest, AlertUrl) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "alert_url.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/path"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount(PacResultForStrippedUrl::SUCCESS_DIFFERENT_ALERTS, 1); ExpectHistogramTotal(1); EXPECT_EQ("foobar:99", proxy_info.proxy_server().ToURI()); EXPECT_EQ(0u, host_resolver.num_resolve()); // There was 1 alerts and no errors. EXPECT_EQ(1u, mock_bindings.GetAlerts().size()); EXPECT_EQ("https://foo/path", mock_bindings.GetAlerts()[0]); EXPECT_TRUE(mock_bindings.GetErrors().empty()); } TEST_F(ProxyResolverV8TracingTest, JavascriptError) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "error.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://throw-an-error/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, callback.WaitForResult()); ExpectHistogramTotal(0); EXPECT_EQ(0u, host_resolver.num_resolve()); // Check the output -- there was 1 alert and 1 javascript error. ASSERT_EQ(1u, mock_bindings.GetAlerts().size()); EXPECT_EQ("Prepare to DIE!", mock_bindings.GetAlerts()[0]); ASSERT_EQ(1u, mock_bindings.GetErrors().size()); EXPECT_EQ(5, mock_bindings.GetErrors()[0].first); EXPECT_EQ("Uncaught TypeError: Cannot read property 'split' of null", mock_bindings.GetErrors()[0].second); } TEST_F(ProxyResolverV8TracingTest, TooManyAlerts) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "too_many_alerts.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount( PacResultForStrippedUrl::SKIPPED_FALLBACK_BLOCKING_DNS, 1); ExpectHistogramTotal(1); // Iteration1 does a DNS resolve // Iteration2 exceeds the alert buffer // Iteration3 runs in blocking mode and completes EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI()); EXPECT_EQ(1u, host_resolver.num_resolve()); // No errors. EXPECT_TRUE(mock_bindings.GetErrors().empty()); // Check the alerts -- the script generated 50 alerts. std::vector alerts = mock_bindings.GetAlerts(); ASSERT_EQ(50u, alerts.size()); for (size_t i = 0; i < alerts.size(); i++) { EXPECT_EQ("Gee, all these alerts are silly!", alerts[i]); } } // Verify that buffered alerts cannot grow unboundedly, even when the message is // empty string. TEST_F(ProxyResolverV8TracingTest, TooManyEmptyAlerts) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver( mock_bindings.CreateBindings(), "too_many_empty_alerts.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount( PacResultForStrippedUrl::SKIPPED_FALLBACK_BLOCKING_DNS, 1); ExpectHistogramTotal(1); EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI()); EXPECT_EQ(1u, host_resolver.num_resolve()); // No errors. EXPECT_TRUE(mock_bindings.GetErrors().empty()); // Check the alerts -- the script generated 1000 alerts. std::vector alerts = mock_bindings.GetAlerts(); ASSERT_EQ(1000u, alerts.size()); for (size_t i = 0; i < alerts.size(); i++) { EXPECT_EQ("", alerts[i]); } } // This test runs a PAC script that issues a sequence of DNS resolves. The test // verifies the final result, and that the underlying DNS resolver received // the correct set of queries. TEST_F(ProxyResolverV8TracingTest, Dns) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRuleForAddressFamily( "host1", ADDRESS_FAMILY_IPV4, "166.155.144.44"); host_resolver.rules() ->AddIPLiteralRule("host1", "::1,192.168.1.1", std::string()); host_resolver.rules()->AddSimulatedFailure("host2"); host_resolver.rules()->AddRule("host3", "166.155.144.33"); host_resolver.rules()->AddRule("host5", "166.155.144.55"); host_resolver.rules()->AddSimulatedFailure("host6"); host_resolver.rules()->AddRuleForAddressFamily( "*", ADDRESS_FAMILY_IPV4, "122.133.144.155"); host_resolver.rules()->AddRule("*", "133.122.100.200"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount(PacResultForStrippedUrl::SUCCESS, 1); ExpectHistogramTotal(1); // The test does 13 DNS resolution, however only 7 of them are unique. EXPECT_EQ(7u, host_resolver.num_resolve()); const char* kExpectedResult = "122.133.144.155-" // myIpAddress() "null-" // dnsResolve('') "__1_192.168.1.1-" // dnsResolveEx('host1') "null-" // dnsResolve('host2') "166.155.144.33-" // dnsResolve('host3') "122.133.144.155-" // myIpAddress() "166.155.144.33-" // dnsResolve('host3') "__1_192.168.1.1-" // dnsResolveEx('host1') "122.133.144.155-" // myIpAddress() "null-" // dnsResolve('host2') "-" // dnsResolveEx('host6') "133.122.100.200-" // myIpAddressEx() "166.155.144.44" // dnsResolve('host1') ":99"; EXPECT_EQ(kExpectedResult, proxy_info.proxy_server().ToURI()); // No errors. EXPECT_TRUE(mock_bindings.GetErrors().empty()); // The script generated 1 alert. ASSERT_EQ(1u, mock_bindings.GetAlerts().size()); EXPECT_EQ("iteration: 7", mock_bindings.GetAlerts()[0]); } // This test runs a PAC script that does "myIpAddress()" followed by // "dnsResolve()". This requires 2 restarts. However once the HostResolver's // cache is warmed, subsequent calls should take 0 restarts. TEST_F(ProxyResolverV8TracingTest, DnsChecksCache) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRule("foopy", "166.155.144.11"); host_resolver.rules()->AddRule("*", "122.133.144.155"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "simple_dns.js"); TestCompletionCallback callback1; TestCompletionCallback callback2; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foopy/req1"), &proxy_info, callback1.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback1.WaitForResult()); // This fails because executing FindProxyForURL() PAC script modifies global // state each time, changing the result that is returned. ExpectHistogramBucketCount(PacResultForStrippedUrl::FAIL_DIFFERENT_PROXY_LIST, 1); ExpectHistogramTotal(1); // The test does 2 DNS resolutions. EXPECT_EQ(2u, host_resolver.num_resolve()); // The first request took 2 restarts, hence on g_iteration=3. EXPECT_EQ("166.155.144.11:3", proxy_info.proxy_server().ToURI()); resolver->GetProxyForURL(GURL("http://foopy/req2"), &proxy_info, callback2.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback2.WaitForResult()); // The histograms are unchanged because the second invocation is for an // http:// URL. ExpectHistogramBucketCount(PacResultForStrippedUrl::FAIL_DIFFERENT_PROXY_LIST, 1); ExpectHistogramTotal(1); EXPECT_EQ(4u, host_resolver.num_resolve()); // This time no restarts were required, so g_iteration incremented by 1. // TODO(eroman): Additionally the counter was incremented once by the // diagnostics code that ran FindProxyForURL() with a stripped URL // (should really be :4 and not :5). EXPECT_EQ("166.155.144.11:5", proxy_info.proxy_server().ToURI()); // There were no alerts or errors. EXPECT_TRUE(mock_bindings.GetAlerts().empty()); EXPECT_TRUE(mock_bindings.GetErrors().empty()); } // This test runs a weird PAC script that was designed to defeat the DNS tracing // optimization. The proxy resolver should detect the inconsistency and // fall-back to synchronous mode execution. TEST_F(ProxyResolverV8TracingTest, FallBackToSynchronous1) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRule("host1", "166.155.144.11"); host_resolver.rules()->AddRule("crazy4", "133.199.111.4"); host_resolver.rules()->AddRule("*", "122.133.144.155"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "global_sideffects1.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount( PacResultForStrippedUrl::SKIPPED_FALLBACK_BLOCKING_DNS, 1); ExpectHistogramTotal(1); // The script itself only does 2 DNS resolves per execution, however it // constructs the hostname using a global counter which changes on each // invocation. EXPECT_EQ(3u, host_resolver.num_resolve()); EXPECT_EQ("166.155.144.11-133.199.111.4:100", proxy_info.proxy_server().ToURI()); // No errors. EXPECT_TRUE(mock_bindings.GetErrors().empty()); ASSERT_EQ(1u, mock_bindings.GetAlerts().size()); EXPECT_EQ("iteration: 4", mock_bindings.GetAlerts()[0]); } // This test runs a weird PAC script that was designed to defeat the DNS tracing // optimization. The proxy resolver should detect the inconsistency and // fall-back to synchronous mode execution. TEST_F(ProxyResolverV8TracingTest, FallBackToSynchronous2) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRule("host1", "166.155.144.11"); host_resolver.rules()->AddRule("host2", "166.155.144.22"); host_resolver.rules()->AddRule("host3", "166.155.144.33"); host_resolver.rules()->AddRule("host4", "166.155.144.44"); host_resolver.rules()->AddRule("*", "122.133.144.155"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "global_sideffects2.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount( PacResultForStrippedUrl::SKIPPED_FALLBACK_BLOCKING_DNS, 1); ExpectHistogramTotal(1); EXPECT_EQ(3u, host_resolver.num_resolve()); EXPECT_EQ("166.155.144.44:100", proxy_info.proxy_server().ToURI()); // There were no alerts or errors. EXPECT_TRUE(mock_bindings.GetAlerts().empty()); EXPECT_TRUE(mock_bindings.GetErrors().empty()); } // This test runs a weird PAC script that yields a never ending sequence // of DNS resolves when restarting. Running it will hit the maximum // DNS resolves per request limit (20) after which every DNS resolve will // fail. TEST_F(ProxyResolverV8TracingTest, InfiniteDNSSequence) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRule("host*", "166.155.144.11"); host_resolver.rules()->AddRule("*", "122.133.144.155"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "global_sideffects3.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); // Was not called because this is an http:// URL. ExpectHistogramTotal(0); EXPECT_EQ(20u, host_resolver.num_resolve()); EXPECT_EQ( "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-" "null:21", proxy_info.proxy_server().ToURI()); // No errors. EXPECT_TRUE(mock_bindings.GetErrors().empty()); // 1 alert. EXPECT_EQ(1u, mock_bindings.GetAlerts().size()); EXPECT_EQ("iteration: 21", mock_bindings.GetAlerts()[0]); } // This test runs a weird PAC script that yields a never ending sequence // of DNS resolves when restarting. Running it will hit the maximum // DNS resolves per request limit (20) after which every DNS resolve will // fail. TEST_F(ProxyResolverV8TracingTest, InfiniteDNSSequence2) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRule("host*", "166.155.144.11"); host_resolver.rules()->AddRule("*", "122.133.144.155"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "global_sideffects4.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); // Was not called because this is an http:// URL. ExpectHistogramTotal(0); EXPECT_EQ(20u, host_resolver.num_resolve()); EXPECT_EQ("null21:34", proxy_info.proxy_server().ToURI()); // No errors. EXPECT_TRUE(mock_bindings.GetErrors().empty()); // 1 alert. EXPECT_EQ(1u, mock_bindings.GetAlerts().size()); EXPECT_EQ("iteration: 21", mock_bindings.GetAlerts()[0]); } TEST_F(ProxyResolverV8TracingTest, DifferentResultBasedOnUrl) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "return_url_as_proxy.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/path1"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramTotal(1); ExpectHistogramBucketCount(PacResultForStrippedUrl::FAIL_DIFFERENT_PROXY_LIST, 1); EXPECT_EQ("httpsx3Ax2Fx2Ffoox2Fpath1:99", proxy_info.proxy_server().ToURI()); EXPECT_EQ(0u, host_resolver.num_resolve()); // There were no alerts or errors. EXPECT_TRUE(mock_bindings.GetAlerts().empty()); EXPECT_TRUE(mock_bindings.GetErrors().empty()); } TEST_F(ProxyResolverV8TracingTest, ErrorDependingOnUrl) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver( mock_bindings.CreateBindings(), "error_depending_on_url.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/DontThrowError"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramTotal(1); ExpectHistogramBucketCount(PacResultForStrippedUrl::FAIL_ERROR, 1); EXPECT_EQ("foopy:42", proxy_info.proxy_server().ToURI()); EXPECT_EQ(0u, host_resolver.num_resolve()); // There were no alerts or errors. EXPECT_TRUE(mock_bindings.GetAlerts().empty()); EXPECT_TRUE(mock_bindings.GetErrors().empty()); } TEST_F(ProxyResolverV8TracingTest, DnsDependingOnUrl) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRule("host", "166.155.144.55"); // Catch-all that will be used for myIpAddress(). host_resolver.rules()->AddRule("*", "133.122.100.200"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns_depending_on_url.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("https://foo/UseMyIpAddress"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); ExpectHistogramBucketCount(PacResultForStrippedUrl::FAIL_ABANDONED, 1); ExpectHistogramTotal(1); EXPECT_EQ("foopy:47", proxy_info.proxy_server().ToURI()); // No errors. EXPECT_TRUE(mock_bindings.GetErrors().empty()); ASSERT_EQ(0u, mock_bindings.GetAlerts().size()); } void DnsDuringInitHelper(bool synchronous_host_resolver) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.set_synchronous_mode(synchronous_host_resolver); host_resolver.rules()->AddRule("host1", "91.13.12.1"); host_resolver.rules()->AddRule("host2", "91.13.12.2"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns_during_init.js"); // Initialization did 2 dnsResolves. EXPECT_EQ(2u, host_resolver.num_resolve()); host_resolver.rules()->ClearRules(); host_resolver.GetHostCache()->clear(); host_resolver.rules()->AddRule("host1", "145.88.13.3"); host_resolver.rules()->AddRule("host2", "137.89.8.45"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); // Fetched host1 and host2 again, since the ones done during initialization // should not have been cached. EXPECT_EQ(4u, host_resolver.num_resolve()); EXPECT_EQ("91.13.12.1-91.13.12.2-145.88.13.3-137.89.8.45:99", proxy_info.proxy_server().ToURI()); // 2 alerts. ASSERT_EQ(2u, mock_bindings.GetAlerts().size()); EXPECT_EQ("Watsup", mock_bindings.GetAlerts()[0]); EXPECT_EQ("Watsup2", mock_bindings.GetAlerts()[1]); } // Tests a PAC script which does DNS resolves during initialization. TEST_F(ProxyResolverV8TracingTest, DnsDuringInit) { // Test with both both a host resolver that always completes asynchronously, // and then again with one that completes synchronously. DnsDuringInitHelper(false); DnsDuringInitHelper(true); } void CrashCallback(int) { // Be extra sure that if the callback ever gets invoked, the test will fail. CHECK(false); } // Start some requests, cancel them all, and then destroy the resolver. // Note the execution order for this test can vary. Since multiple // threads are involved, the cancellation may be received a different // times. TEST_F(ProxyResolverV8TracingTest, CancelAll) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddSimulatedFailure("*"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns.js"); const size_t kNumRequests = 5; ProxyInfo proxy_info[kNumRequests]; ProxyResolver::RequestHandle request[kNumRequests]; for (size_t i = 0; i < kNumRequests; ++i) { resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info[i], base::Bind(&CrashCallback), &request[i], mock_bindings.CreateBindings()); } for (size_t i = 0; i < kNumRequests; ++i) { resolver->CancelRequest(request[i]); } } // Note the execution order for this test can vary. Since multiple // threads are involved, the cancellation may be received a different // times. TEST_F(ProxyResolverV8TracingTest, CancelSome) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddSimulatedFailure("*"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns.js"); ProxyInfo proxy_info1; ProxyInfo proxy_info2; ProxyResolver::RequestHandle request1; ProxyResolver::RequestHandle request2; TestCompletionCallback callback; resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info1, base::Bind(&CrashCallback), &request1, mock_bindings.CreateBindings()); resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info2, callback.callback(), &request2, mock_bindings.CreateBindings()); resolver->CancelRequest(request1); EXPECT_EQ(OK, callback.WaitForResult()); } // Cancel a request after it has finished running on the worker thread, and has // posted a task the completion task back to origin thread. TEST_F(ProxyResolverV8TracingTest, CancelWhilePendingCompletionTask) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddSimulatedFailure("*"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "error.js"); ProxyInfo proxy_info1; ProxyInfo proxy_info2; ProxyResolver::RequestHandle request1; ProxyResolver::RequestHandle request2; TestCompletionCallback callback; resolver->GetProxyForURL(GURL("http://throw-an-error/"), &proxy_info1, base::Bind(&CrashCallback), &request1, mock_bindings.CreateBindings()); // Wait until the first request has finished running on the worker thread. // Cancel the first request, while it is running its completion task on // the origin thread. mock_bindings.RunOnError(base::Bind(&ProxyResolverV8Tracing::CancelRequest, base::Unretained(resolver.get()), request1)); // Start another request, to make sure it is able to complete. resolver->GetProxyForURL(GURL("http://i-have-no-idea-what-im-doing/"), &proxy_info2, callback.callback(), &request2, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); EXPECT_EQ("i-approve-this-message:42", proxy_info2.proxy_server().ToURI()); } // This implementation of HostResolver allows blocking until a resolve request // has been received. The resolve requests it receives will never be completed. class BlockableHostResolver : public HostResolver { public: BlockableHostResolver() : num_cancelled_requests_(0), waiting_for_resolve_(false) {} int Resolve(const RequestInfo& info, RequestPriority priority, AddressList* addresses, const CompletionCallback& callback, RequestHandle* out_req, const BoundNetLog& net_log) override { EXPECT_FALSE(callback.is_null()); EXPECT_TRUE(out_req); if (!action_.is_null()) action_.Run(); // Indicate to the caller that a request was received. EXPECT_TRUE(waiting_for_resolve_); base::MessageLoop::current()->QuitWhenIdle(); // This line is intentionally after action_.Run(), since one of the // tests does a cancellation inside of Resolve(), and it is more // interesting if *out_req hasn't been written yet at that point. *out_req = reinterpret_cast(1); // Magic value. // Return ERR_IO_PENDING as this request will NEVER be completed. // Expectation is for the caller to later cancel the request. return ERR_IO_PENDING; } int ResolveFromCache(const RequestInfo& info, AddressList* addresses, const BoundNetLog& net_log) override { NOTREACHED(); return ERR_DNS_CACHE_MISS; } void CancelRequest(RequestHandle req) override { EXPECT_EQ(reinterpret_cast(1), req); num_cancelled_requests_++; } void SetAction(const base::Callback& action) { action_ = action; } // Waits until Resolve() has been called. void WaitUntilRequestIsReceived() { waiting_for_resolve_ = true; base::MessageLoop::current()->Run(); DCHECK(waiting_for_resolve_); waiting_for_resolve_ = false; } int num_cancelled_requests() const { return num_cancelled_requests_; } private: int num_cancelled_requests_; bool waiting_for_resolve_; base::Callback action_; }; // This cancellation test exercises a more predictable cancellation codepath -- // when the request has an outstanding DNS request in flight. TEST_F(ProxyResolverV8TracingTest, CancelWhileOutstandingNonBlockingDns) { BlockableHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns.js"); ProxyInfo proxy_info1; ProxyInfo proxy_info2; ProxyResolver::RequestHandle request1; ProxyResolver::RequestHandle request2; resolver->GetProxyForURL(GURL("http://foo/req1"), &proxy_info1, base::Bind(&CrashCallback), &request1, mock_bindings.CreateBindings()); host_resolver.WaitUntilRequestIsReceived(); resolver->GetProxyForURL(GURL("http://foo/req2"), &proxy_info2, base::Bind(&CrashCallback), &request2, mock_bindings.CreateBindings()); host_resolver.WaitUntilRequestIsReceived(); resolver->CancelRequest(request1); resolver->CancelRequest(request2); EXPECT_EQ(2, host_resolver.num_cancelled_requests()); // After leaving this scope, the ProxyResolver is destroyed. // This should not cause any problems, as the outstanding work // should have been cancelled. } void CancelRequestAndPause(ProxyResolverV8Tracing* resolver, ProxyResolver::RequestHandle request) { resolver->CancelRequest(request); // Sleep for a little bit. This makes it more likely for the worker // thread to have returned from its call, and serves as a regression // test for http://crbug.com/173373. base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30)); } // In non-blocking mode, the worker thread actually does block for // a short time to see if the result is in the DNS cache. Test // cancellation while the worker thread is waiting on this event. TEST_F(ProxyResolverV8TracingTest, CancelWhileBlockedInNonBlockingDns) { BlockableHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns.js"); ProxyInfo proxy_info; ProxyResolver::RequestHandle request; resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, base::Bind(&CrashCallback), &request, mock_bindings.CreateBindings()); host_resolver.SetAction( base::Bind(CancelRequestAndPause, resolver.get(), request)); host_resolver.WaitUntilRequestIsReceived(); } // Cancel the request while there is a pending DNS request, however before // the request is sent to the host resolver. TEST_F(ProxyResolverV8TracingTest, CancelWhileBlockedInNonBlockingDns2) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "dns.js"); ProxyInfo proxy_info; ProxyResolver::RequestHandle request; resolver->GetProxyForURL(GURL("http://foo/"), &proxy_info, base::Bind(&CrashCallback), &request, mock_bindings.CreateBindings()); // Wait a bit, so the DNS task has hopefully been posted. The test will // work whatever the delay is here, but it is most useful if the delay // is large enough to allow a task to be posted back. base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); resolver->CancelRequest(request); EXPECT_EQ(0u, host_resolver.num_resolve()); } TEST_F(ProxyResolverV8TracingTest, CancelCreateResolverWhileOutstandingBlockingDns) { BlockableHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr factory( ProxyResolverV8TracingFactory::Create()); scoped_ptr resolver; scoped_ptr request; factory->CreateProxyResolverV8Tracing( LoadScriptData("dns_during_init.js"), mock_bindings.CreateBindings(), &resolver, base::Bind(&CrashCallback), &request); host_resolver.WaitUntilRequestIsReceived(); request.reset(); EXPECT_EQ(1, host_resolver.num_cancelled_requests()); } TEST_F(ProxyResolverV8TracingTest, DeleteFactoryWhileOutstandingBlockingDns) { BlockableHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr resolver; scoped_ptr request; { scoped_ptr factory( ProxyResolverV8TracingFactory::Create()); factory->CreateProxyResolverV8Tracing( LoadScriptData("dns_during_init.js"), mock_bindings.CreateBindings(), &resolver, base::Bind(&CrashCallback), &request); host_resolver.WaitUntilRequestIsReceived(); } EXPECT_EQ(1, host_resolver.num_cancelled_requests()); } TEST_F(ProxyResolverV8TracingTest, ErrorLoadingScript) { BlockableHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); scoped_ptr factory( ProxyResolverV8TracingFactory::Create()); scoped_ptr resolver; scoped_ptr request; TestCompletionCallback callback; factory->CreateProxyResolverV8Tracing( LoadScriptData("error_on_load.js"), mock_bindings.CreateBindings(), &resolver, callback.callback(), &request); EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, callback.WaitForResult()); EXPECT_FALSE(resolver); } // This tests that the execution of a PAC script is terminated when the DNS // dependencies are missing. If the test fails, then it will hang. TEST_F(ProxyResolverV8TracingTest, Terminate) { MockCachingHostResolver host_resolver; MockBindings mock_bindings(&host_resolver); host_resolver.rules()->AddRule("host1", "182.111.0.222"); host_resolver.rules()->AddRule("host2", "111.33.44.55"); scoped_ptr resolver = CreateResolver(mock_bindings.CreateBindings(), "terminate.js"); TestCompletionCallback callback; ProxyInfo proxy_info; resolver->GetProxyForURL(GURL("http://foopy/req1"), &proxy_info, callback.callback(), NULL, mock_bindings.CreateBindings()); EXPECT_EQ(OK, callback.WaitForResult()); // The test does 2 DNS resolutions. EXPECT_EQ(2u, host_resolver.num_resolve()); EXPECT_EQ("foopy:3", proxy_info.proxy_server().ToURI()); // No errors or alerts. EXPECT_TRUE(mock_bindings.GetErrors().empty()); EXPECT_TRUE(mock_bindings.GetAlerts().empty()); } // Tests that multiple instances of ProxyResolverV8Tracing can coexist and run // correctly at the same time. This is relevant because at the moment (time // this test was written) each ProxyResolverV8Tracing creates its own thread to // run V8 on, however each thread is operating on the same v8::Isolate. TEST_F(ProxyResolverV8TracingTest, MultipleResolvers) { // ------------------------ // Setup resolver0 // ------------------------ MockHostResolver host_resolver0; MockBindings mock_bindings0(&host_resolver0); host_resolver0.rules()->AddRuleForAddressFamily( "host1", ADDRESS_FAMILY_IPV4, "166.155.144.44"); host_resolver0.rules() ->AddIPLiteralRule("host1", "::1,192.168.1.1", std::string()); host_resolver0.rules()->AddSimulatedFailure("host2"); host_resolver0.rules()->AddRule("host3", "166.155.144.33"); host_resolver0.rules()->AddRule("host5", "166.155.144.55"); host_resolver0.rules()->AddSimulatedFailure("host6"); host_resolver0.rules()->AddRuleForAddressFamily( "*", ADDRESS_FAMILY_IPV4, "122.133.144.155"); host_resolver0.rules()->AddRule("*", "133.122.100.200"); scoped_ptr resolver0 = CreateResolver(mock_bindings0.CreateBindings(), "dns.js"); // ------------------------ // Setup resolver1 // ------------------------ scoped_ptr resolver1 = CreateResolver(mock_bindings0.CreateBindings(), "dns.js"); // ------------------------ // Setup resolver2 // ------------------------ scoped_ptr resolver2 = CreateResolver(mock_bindings0.CreateBindings(), "simple.js"); // ------------------------ // Setup resolver3 // ------------------------ MockHostResolver host_resolver3; MockBindings mock_bindings3(&host_resolver3); host_resolver3.rules()->AddRule("foo", "166.155.144.33"); scoped_ptr resolver3 = CreateResolver(mock_bindings3.CreateBindings(), "simple_dns.js"); // ------------------------ // Queue up work for each resolver (which will be running in parallel). // ------------------------ ProxyResolverV8Tracing* resolver[] = { resolver0.get(), resolver1.get(), resolver2.get(), resolver3.get(), }; const size_t kNumResolvers = arraysize(resolver); const size_t kNumIterations = 20; const size_t kNumResults = kNumResolvers * kNumIterations; TestCompletionCallback callback[kNumResults]; ProxyInfo proxy_info[kNumResults]; for (size_t i = 0; i < kNumResults; ++i) { size_t resolver_i = i % kNumResolvers; resolver[resolver_i]->GetProxyForURL( GURL("http://foo/"), &proxy_info[i], callback[i].callback(), NULL, resolver_i == 3 ? mock_bindings3.CreateBindings() : mock_bindings0.CreateBindings()); } // ------------------------ // Verify all of the results. // ------------------------ const char* kExpectedForDnsJs = "122.133.144.155-" // myIpAddress() "null-" // dnsResolve('') "__1_192.168.1.1-" // dnsResolveEx('host1') "null-" // dnsResolve('host2') "166.155.144.33-" // dnsResolve('host3') "122.133.144.155-" // myIpAddress() "166.155.144.33-" // dnsResolve('host3') "__1_192.168.1.1-" // dnsResolveEx('host1') "122.133.144.155-" // myIpAddress() "null-" // dnsResolve('host2') "-" // dnsResolveEx('host6') "133.122.100.200-" // myIpAddressEx() "166.155.144.44" // dnsResolve('host1') ":99"; for (size_t i = 0; i < kNumResults; ++i) { size_t resolver_i = i % kNumResolvers; EXPECT_EQ(OK, callback[i].WaitForResult()); std::string proxy_uri = proxy_info[i].proxy_server().ToURI(); if (resolver_i == 0 || resolver_i == 1) { EXPECT_EQ(kExpectedForDnsJs, proxy_uri); } else if (resolver_i == 2) { EXPECT_EQ("foo:99", proxy_uri); } else if (resolver_i == 3) { EXPECT_EQ("166.155.144.33:", proxy_uri.substr(0, proxy_uri.find(':') + 1)); } else { NOTREACHED(); } } } } // namespace } // namespace net