// 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 // For std::modf. #include #include #include "base/command_line.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" #include "base/test/metrics/histogram_tester.h" #include "build/build_config.h" #include "content/browser/net/network_quality_observer_impl.h" #include "content/public/browser/render_process_host.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/shell/browser/shell.h" #include "net/base/network_change_notifier.h" #include "net/base/network_change_notifier_factory.h" #include "net/dns/mock_host_resolver.h" #include "net/log/test_net_log.h" #include "net/nqe/effective_connection_type.h" #include "services/network/test/test_network_quality_tracker.h" namespace { // Returns the total count of samples in |histogram|. int GetTotalSampleCount(base::HistogramTester* tester, const std::string& histogram) { int count = 0; std::vector buckets = tester->GetAllSamples(histogram); for (const auto& bucket : buckets) count += bucket.count; return count; } void VerifyRtt(base::TimeDelta expected_rtt, int32_t got_rtt_milliseconds) { EXPECT_EQ(0, got_rtt_milliseconds % 50) << " got_rtt_milliseconds=" << got_rtt_milliseconds; if (expected_rtt > base::TimeDelta::FromMilliseconds(3000)) expected_rtt = base::TimeDelta::FromMilliseconds(3000); // The difference between the actual and the estimate value should be within // 10%. Add 50 (bucket size used in Blink) to account for the cases when the // sample may spill over to the next bucket due to the added noise of 10%. // For example, if sample is 300 msec, after adding noise, it may become 330, // and after rounding off, it would spill over to the next bucket of 350 msec. EXPECT_GE((expected_rtt.InMilliseconds() * 0.1) + 50, std::abs(expected_rtt.InMilliseconds() - got_rtt_milliseconds)) << " expected_rtt=" << expected_rtt << " got_rtt_milliseconds=" << got_rtt_milliseconds; } void VerifyDownlinkKbps(double expected_kbps, double got_kbps) { // First verify that |got_kbps| is a multiple of 50. int quotient = static_cast(got_kbps / 50); // |mod| is the remainder left after dividing |got_kbps| by 50 while // restricting the quotient to integer. For example, if |got_kbps| is // 1050, then mod will be 0. If |got_kbps| is 1030, mod will be 30. double mod = got_kbps - 50 * quotient; EXPECT_LE(0.0, mod); EXPECT_GT(50.0, mod); // It is possible that |mod| is not exactly 0 because of floating point // computations. e.g., |got_kbps| may be 99.999999, in which case |mod| // will be 49.999999. EXPECT_TRUE(mod < (1e-5) || (50 - mod) < 1e-5) << " got_kbps=" << got_kbps; if (expected_kbps > 10000) expected_kbps = 10000; // The difference between the actual and the estimate value should be within // 10%. Add 50 (bucket size used in Blink) to account for the cases when the // sample may spill over to the next bucket due to the added noise of 10%. // For example, if sample is 300 kbps, after adding noise, it may become 330, // and after rounding off, it would spill over to the next bucket of 350 kbps. EXPECT_GE((expected_kbps * 0.1) + 50, std::abs(expected_kbps - got_kbps)) << " expected_kbps=" << expected_kbps << " got_kbps=" << got_kbps; } class MockNetworkChangeNotifierWifi : public net::NetworkChangeNotifier { public: void GetCurrentMaxBandwidthAndConnectionType( double* max_bandwidth_mbps, ConnectionType* connection_type) const override { *connection_type = NetworkChangeNotifier::CONNECTION_WIFI; *max_bandwidth_mbps = net::NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype( net::NetworkChangeNotifier::SUBTYPE_WIFI_N); } ConnectionType GetCurrentConnectionType() const override { return NetworkChangeNotifier::CONNECTION_WIFI; } }; } // namespace namespace content { class NetInfoBrowserTest : public content::ContentBrowserTest { public: NetInfoBrowserTest() : test_network_quality_tracker_( std::make_unique()) {} network::NetworkQualityTracker* GetNetworkQualityTracker() const { return test_network_quality_tracker_.get(); } protected: void SetUpCommandLine(base::CommandLine* command_line) override { // TODO(jkarlin): Once NetInfo is enabled on all platforms remove this // switch. command_line->AppendSwitch(switches::kEnableNetworkInformationDownlinkMax); // TODO(jkarlin): Remove this once downlinkMax is no longer // experimental. command_line->AppendSwitch( switches::kEnableExperimentalWebPlatformFeatures); } void SetUp() override { net::NetworkChangeNotifier::SetTestNotificationsOnly(true); content::ContentBrowserTest::SetUp(); } void SetUpOnMainThread() override { host_resolver()->AddRule("*", "127.0.0.1"); base::RunLoop().RunUntilIdle(); } static void SetConnectionType( net::NetworkChangeNotifier::ConnectionType type, net::NetworkChangeNotifier::ConnectionSubtype subtype) { net::NetworkChangeNotifier::NotifyObserversOfMaxBandwidthChangeForTests( net::NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype( subtype), type); base::RunLoop().RunUntilIdle(); } std::string RunScriptExtractString(const std::string& script) { return EvalJs(shell(), script, content::EXECUTE_SCRIPT_USE_MANUAL_REPLY) .ExtractString(); } bool RunScriptExtractBool(const std::string& script) { return EvalJs(shell(), script, content::EXECUTE_SCRIPT_USE_MANUAL_REPLY) .ExtractBool(); } double RunScriptExtractDouble(const std::string& script) { return EvalJs(shell(), script, content::EXECUTE_SCRIPT_USE_MANUAL_REPLY) .ExtractDouble(); } int RunScriptExtractInt(const std::string& script) { return EvalJs(shell(), script, content::EXECUTE_SCRIPT_USE_MANUAL_REPLY) .ExtractInt(); } private: std::unique_ptr test_network_quality_tracker_; }; // Make sure the type is correct when the page is first opened. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, VerifyNetworkStateInitialized) { // Mock out the NCN. net::NetworkChangeNotifier::DisableForTest disable_for_test; MockNetworkChangeNotifierWifi mock_notifier; EXPECT_TRUE(NavigateToURL(shell(), content::GetTestUrl("", "net_info.html"))); EXPECT_TRUE(RunScriptExtractBool("getOnLine()")); EXPECT_EQ("wifi", RunScriptExtractString("getType()")); EXPECT_EQ(net::NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype( net::NetworkChangeNotifier::SUBTYPE_WIFI_N), RunScriptExtractDouble("getDownlinkMax()")); } // Make sure that type changes in the browser make their way to // navigator.connection.type. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkChangePlumbsToNavigator) { EXPECT_TRUE(NavigateToURL(shell(), content::GetTestUrl("", "net_info.html"))); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI, net::NetworkChangeNotifier::SUBTYPE_WIFI_N); EXPECT_EQ("wifi", RunScriptExtractString("getType()")); EXPECT_EQ(net::NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype( net::NetworkChangeNotifier::SUBTYPE_WIFI_N), RunScriptExtractDouble("getDownlinkMax()")); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_ETHERNET, net::NetworkChangeNotifier::SUBTYPE_GIGABIT_ETHERNET); EXPECT_EQ("ethernet", RunScriptExtractString("getType()")); EXPECT_EQ(net::NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype( net::NetworkChangeNotifier::SUBTYPE_GIGABIT_ETHERNET), RunScriptExtractDouble("getDownlinkMax()")); } // Make sure that type changes in the browser make their way to // navigator.isOnline. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, IsOnline) { EXPECT_TRUE(NavigateToURL(shell(), content::GetTestUrl("", "net_info.html"))); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_ETHERNET, net::NetworkChangeNotifier::SUBTYPE_GIGABIT_ETHERNET); EXPECT_TRUE(RunScriptExtractBool("getOnLine()")); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_NONE, net::NetworkChangeNotifier::SUBTYPE_NONE); EXPECT_FALSE(RunScriptExtractBool("getOnLine()")); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI, net::NetworkChangeNotifier::SUBTYPE_WIFI_N); EXPECT_TRUE(RunScriptExtractBool("getOnLine()")); } // Creating a new render view shouldn't reinitialize Blink's // NetworkStateNotifier. See https://crbug.com/535081. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, TwoRenderViewsInOneProcess) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_ETHERNET, net::NetworkChangeNotifier::SUBTYPE_GIGABIT_ETHERNET); EXPECT_TRUE(NavigateToURL(shell(), content::GetTestUrl("", "net_info.html"))); EXPECT_TRUE(RunScriptExtractBool("getOnLine()")); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_NONE, net::NetworkChangeNotifier::SUBTYPE_NONE); EXPECT_FALSE(RunScriptExtractBool("getOnLine()")); // Open the same page in a new window on the same process. EXPECT_TRUE(ExecJs(shell(), "window.open(\"net_info.html\")")); // The network state should not have reinitialized to what it was when opening // the first window (online). EXPECT_FALSE(RunScriptExtractBool("getOnLine()")); } // Verify that when the network quality notifications are not sent, the // Javascript API returns a valid estimate that is multiple of 50 msec and 50 // kbps. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkQualityEstimatorNotInitialized) { base::HistogramTester histogram_tester; NetworkQualityObserverImpl impl(GetNetworkQualityTracker()); EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); // When NQE is not initialized, the javascript calls should return default // values. EXPECT_EQ(0, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(10000, RunScriptExtractDouble("getDownlink()") * 1000); } // Make sure the changes in the effective connection type are notified to the // render thread. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, EffectiveConnectionTypeChangeNotified) { base::HistogramTester histogram_tester; NetworkQualityObserverImpl impl(GetNetworkQualityTracker()); base::TimeDelta http_rtt(base::TimeDelta::FromMilliseconds(1000)); int32_t downstream_throughput_kbps = 300; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); FetchHistogramsFromChildProcesses(); int samples = GetTotalSampleCount(&histogram_tester, "NQE.RenderThreadNotified"); EXPECT_LT(0, samples); // Change effective connection type so that the renderer process is notified. // Changing the effective connection type from 2G to 3G is guaranteed to // generate the notification to the renderers, irrespective of the current // effective connection type. GetNetworkQualityTracker()->ReportEffectiveConnectionTypeForTesting( net::EFFECTIVE_CONNECTION_TYPE_2G); EXPECT_EQ("2g", RunScriptExtractString("getEffectiveType()")); GetNetworkQualityTracker()->ReportEffectiveConnectionTypeForTesting( net::EFFECTIVE_CONNECTION_TYPE_3G); EXPECT_EQ("3g", RunScriptExtractString("getEffectiveType()")); FetchHistogramsFromChildProcesses(); EXPECT_GT(GetTotalSampleCount(&histogram_tester, "NQE.RenderThreadNotified"), samples); } // Make sure the changes in the network quality are notified to the render // thread, and the changed network quality is accessible via Javascript API. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkQualityChangeNotified) { base::HistogramTester histogram_tester; NetworkQualityObserverImpl impl(GetNetworkQualityTracker()); base::TimeDelta http_rtt(base::TimeDelta::FromMilliseconds(1000)); int32_t downstream_throughput_kbps = 300; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); FetchHistogramsFromChildProcesses(); EXPECT_FALSE( histogram_tester.GetAllSamples("NQE.RenderThreadNotified").empty()); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); // Verify that the network quality change is accessible via Javascript API. http_rtt = base::TimeDelta::FromSeconds(10); downstream_throughput_kbps = 3000; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); base::RunLoop().RunUntilIdle(); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); } // Make sure the changes in the network quality are rounded to the nearest // 50 milliseconds or 50 kbps. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkQualityChangeRounded) { base::HistogramTester histogram_tester; NetworkQualityObserverImpl impl(GetNetworkQualityTracker()); // Verify that the network quality is rounded properly. base::TimeDelta http_rtt(base::TimeDelta::FromMilliseconds(103)); int32_t downstream_throughput_kbps = 8303; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); http_rtt = base::TimeDelta::FromMilliseconds(1103); downstream_throughput_kbps = 1307; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); base::RunLoop().RunUntilIdle(); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); http_rtt = base::TimeDelta::FromMilliseconds(2112); downstream_throughput_kbps = 2112; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); base::RunLoop().RunUntilIdle(); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); } // Make sure the network quality are rounded down when it exceeds the upper // limit. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkQualityChangeUpperLimit) { base::HistogramTester histogram_tester; NetworkQualityObserverImpl impl(GetNetworkQualityTracker()); base::TimeDelta http_rtt(base::TimeDelta::FromMilliseconds(12003)); int32_t downstream_throughput_kbps = 30300; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); } // Make sure the noise added to the network quality varies with the hostname. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkQualityRandomized) { base::HistogramTester histogram_tester; NetworkQualityObserverImpl impl(GetNetworkQualityTracker()); base::TimeDelta http_rtt(base::TimeDelta::FromMilliseconds(2000)); int32_t downstream_throughput_kbps = 3000; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); const int32_t rtt_noise_milliseconds = RunScriptExtractInt("getRtt()") - 2000; const int32_t downlink_noise_kbps = RunScriptExtractDouble("getDownlink()") * 1000 - 3000; // When the hostname is not changed, the noise should not change. EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); EXPECT_EQ(rtt_noise_milliseconds, RunScriptExtractInt("getRtt()") - 2000); EXPECT_EQ(downlink_noise_kbps, RunScriptExtractDouble("getDownlink()") * 1000 - 3000); // Verify that changing the hostname changes the noise. It is possible that // the hash of a different host also maps to the same bucket among 20 buckets. // Try 10 different hosts. This reduces the probability of failure of this // test to (1/20)^10 = 9,7 * 10^-14. for (size_t i = 0; i < 10; ++i) { // The noise added is a function of the hostname. Varying the hostname // should vary the noise. std::string fake_hostname = "example" + base::NumberToString(i) + ".com"; EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( fake_hostname, "/net_info.html"))); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); int32_t new_rtt_noise_milliseconds = RunScriptExtractInt("getRtt()") - 2000; int32_t new_downlink_noise_kbps = RunScriptExtractDouble("getDownlink()") * 1000 - 3000; if (rtt_noise_milliseconds != new_rtt_noise_milliseconds && downlink_noise_kbps != new_downlink_noise_kbps) { return; } } NOTREACHED() << "Noise not added to the network quality estimates"; } // Make sure the minor changes (<10%) in the network quality are not notified. IN_PROC_BROWSER_TEST_F(NetInfoBrowserTest, NetworkQualityChangeNotNotified) { base::HistogramTester histogram_tester; NetworkQualityObserverImpl impl(GetNetworkQualityTracker()); // Verify that the network quality is rounded properly. base::TimeDelta http_rtt(base::TimeDelta::FromMilliseconds(1123)); int32_t downstream_throughput_kbps = 1303; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/net_info.html"))); VerifyRtt(http_rtt, RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(downstream_throughput_kbps, RunScriptExtractDouble("getDownlink()") * 1000); // All the 3 metrics change by less than 10%. So, the observers are not // notified. http_rtt = base::TimeDelta::FromMilliseconds(1223); downstream_throughput_kbps = 1403; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); base::RunLoop().RunUntilIdle(); VerifyRtt(base::TimeDelta::FromMilliseconds(1100), RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(1300, RunScriptExtractDouble("getDownlink()") * 1000); // HTTP RTT has changed by more than 10% from the last notified value of // |network_quality_1|. The observers should be notified. http_rtt = base::TimeDelta::FromMilliseconds(2223); downstream_throughput_kbps = 1403; GetNetworkQualityTracker()->ReportRTTsAndThroughputForTesting( http_rtt, downstream_throughput_kbps); base::RunLoop().RunUntilIdle(); VerifyRtt(base::TimeDelta::FromMilliseconds(2200), RunScriptExtractInt("getRtt()")); VerifyDownlinkKbps(1400, RunScriptExtractDouble("getDownlink()") * 1000); } } // namespace content