// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/network_change_notifier_win.h" #include #include #include #include "base/functional/bind.h" #include "base/run_loop.h" #include "base/task/sequenced_task_runner.h" #include "base/task/single_thread_task_runner.h" #include "base/test/scoped_os_info_override_win.h" #include "base/win/windows_version.h" #include "net/base/network_change_notifier.h" #include "net/base/network_change_notifier_factory.h" #include "net/base/network_cost_change_notifier_win.h" #include "net/test/test_connection_cost_observer.h" #include "net/test/test_with_task_environment.h" #include "net/test/win/fake_network_cost_manager.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::AtLeast; using ::testing::Invoke; using ::testing::Return; using ::testing::StrictMock; namespace net { // Subclass of NetworkChangeNotifierWin that overrides functions so that no // Windows API networking function results effect tests. class TestNetworkChangeNotifierWin : public NetworkChangeNotifierWin { public: TestNetworkChangeNotifierWin() { last_computed_connection_type_ = NetworkChangeNotifier::CONNECTION_UNKNOWN; last_announced_offline_ = false; sequence_runner_for_registration_ = base::SequencedTaskRunner::GetCurrentDefault(); } TestNetworkChangeNotifierWin(const TestNetworkChangeNotifierWin&) = delete; TestNetworkChangeNotifierWin& operator=(const TestNetworkChangeNotifierWin&) = delete; ~TestNetworkChangeNotifierWin() override { // This is needed so we don't try to stop watching for IP address changes, // as we never actually started. set_is_watching(false); } // From NetworkChangeNotifierWin. void RecomputeCurrentConnectionTypeOnBlockingSequence( base::OnceCallback reply_callback) const override { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(reply_callback), NetworkChangeNotifier::CONNECTION_UNKNOWN)); } // From NetworkChangeNotifierWin. MOCK_METHOD0(WatchForAddressChangeInternal, bool()); // Allow tests to compare results with the default implementation that does // not depend on the `INetworkCostManager` Windows OS API. The default // implementation is used as a fall back when `INetworkCostManager` fails. ConnectionCost GetCurrentConnectionCostFromDefaultImplementationForTesting() { return NetworkChangeNotifier::GetCurrentConnectionCost(); } }; class TestIPAddressObserver : public NetworkChangeNotifier::IPAddressObserver { public: TestIPAddressObserver() { NetworkChangeNotifier::AddIPAddressObserver(this); } TestIPAddressObserver(const TestIPAddressObserver&) = delete; TestIPAddressObserver& operator=(const TestIPAddressObserver&) = delete; ~TestIPAddressObserver() override { NetworkChangeNotifier::RemoveIPAddressObserver(this); } MOCK_METHOD0(OnIPAddressChanged, void()); }; class NetworkChangeNotifierWinTest : public TestWithTaskEnvironment { public: // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal // success. Expects that |network_change_notifier_| has just been created, so // it's not watching anything yet, and there have been no previous // WatchForAddressChangeInternal failures. void StartWatchingAndSucceed() { EXPECT_FALSE(network_change_notifier_.is_watching()); EXPECT_EQ(0, network_change_notifier_.sequential_failures()); EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0); EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) .WillOnce(Return(true)); network_change_notifier_.WatchForAddressChange(); EXPECT_TRUE(network_change_notifier_.is_watching()); EXPECT_EQ(0, network_change_notifier_.sequential_failures()); // If a task to notify observers of the IP address change event was // incorrectly posted, make sure it gets run to trigger a failure. base::RunLoop().RunUntilIdle(); } // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal // failure. void StartWatchingAndFail() { EXPECT_FALSE(network_change_notifier_.is_watching()); EXPECT_EQ(0, network_change_notifier_.sequential_failures()); EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0); EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) // Due to an expected race, it's theoretically possible for more than // one call to occur, though unlikely. .Times(AtLeast(1)) .WillRepeatedly(Return(false)); network_change_notifier_.WatchForAddressChange(); EXPECT_FALSE(network_change_notifier_.is_watching()); EXPECT_LT(0, network_change_notifier_.sequential_failures()); // If a task to notify observers of the IP address change event was // incorrectly posted, make sure it gets run. base::RunLoop().RunUntilIdle(); } // Simulates a network change event, resulting in a call to OnObjectSignaled. // The resulting call to WatchForAddressChangeInternal then succeeds. void SignalAndSucceed() { EXPECT_TRUE(network_change_notifier_.is_watching()); EXPECT_EQ(0, network_change_notifier_.sequential_failures()); EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1); EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) .WillOnce(Return(true)); network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE); EXPECT_TRUE(network_change_notifier_.is_watching()); EXPECT_EQ(0, network_change_notifier_.sequential_failures()); // Run the task to notify observers of the IP address change event. base::RunLoop().RunUntilIdle(); } // Simulates a network change event, resulting in a call to OnObjectSignaled. // The resulting call to WatchForAddressChangeInternal then fails. void SignalAndFail() { EXPECT_TRUE(network_change_notifier_.is_watching()); EXPECT_EQ(0, network_change_notifier_.sequential_failures()); EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1); EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) // Due to an expected race, it's theoretically possible for more than // one call to occur, though unlikely. .Times(AtLeast(1)) .WillRepeatedly(Return(false)); network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE); EXPECT_FALSE(network_change_notifier_.is_watching()); EXPECT_LT(0, network_change_notifier_.sequential_failures()); // Run the task to notify observers of the IP address change event. base::RunLoop().RunUntilIdle(); } // Runs the message loop until WatchForAddressChange is called again, as a // result of the already posted task after a WatchForAddressChangeInternal // failure. Simulates a success on the resulting call to // WatchForAddressChangeInternal. void RetryAndSucceed() { EXPECT_FALSE(network_change_notifier_.is_watching()); EXPECT_LT(0, network_change_notifier_.sequential_failures()); base::RunLoop run_loop; EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()) .WillOnce(Invoke(&run_loop, &base::RunLoop::QuitWhenIdle)); EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) .WillOnce(Return(true)); run_loop.Run(); EXPECT_TRUE(network_change_notifier_.is_watching()); EXPECT_EQ(0, network_change_notifier_.sequential_failures()); } // Runs the message loop until WatchForAddressChange is called again, as a // result of the already posted task after a WatchForAddressChangeInternal // failure. Simulates a failure on the resulting call to // WatchForAddressChangeInternal. void RetryAndFail() { base::RunLoop loop; EXPECT_FALSE(network_change_notifier_.is_watching()); EXPECT_LT(0, network_change_notifier_.sequential_failures()); int initial_sequential_failures = network_change_notifier_.sequential_failures(); EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0); EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal()) // Due to an expected race, it's theoretically possible for more than // one call to occur, though unlikely. .Times(AtLeast(1)) .WillRepeatedly(Invoke([&loop]() { loop.QuitWhenIdle(); return false; })); loop.Run(); EXPECT_FALSE(network_change_notifier_.is_watching()); EXPECT_LT(initial_sequential_failures, network_change_notifier_.sequential_failures()); // If a task to notify observers of the IP address change event was // incorrectly posted, make sure it gets run. base::RunLoop().RunUntilIdle(); } NetworkChangeNotifier::ConnectionCost GetCurrentConnectionCost() { return network_change_notifier_.GetCurrentConnectionCost(); } NetworkChangeNotifier::ConnectionCost GetCurrentConnectionCostFromDefaultImplementationForTesting() { return network_change_notifier_ .GetCurrentConnectionCostFromDefaultImplementationForTesting(); } protected: FakeNetworkCostManagerEnvironment fake_network_cost_manager_environment_; private: // Note that the order of declaration here is important. // Allows creating a new NetworkChangeNotifier. Must be created before // |network_change_notifier_| and destroyed after it to avoid DCHECK failures. NetworkChangeNotifier::DisableForTest disable_for_test_; StrictMock network_change_notifier_; // Must be created after |network_change_notifier_|, so it can add itself as // an IPAddressObserver. StrictMock test_ip_address_observer_; }; TEST_F(NetworkChangeNotifierWinTest, NetChangeWinBasic) { StartWatchingAndSucceed(); } TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStart) { StartWatchingAndFail(); } TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartOnce) { StartWatchingAndFail(); RetryAndSucceed(); } TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartTwice) { StartWatchingAndFail(); RetryAndFail(); RetryAndSucceed(); } TEST_F(NetworkChangeNotifierWinTest, NetChangeWinSignal) { StartWatchingAndSucceed(); SignalAndSucceed(); } TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalOnce) { StartWatchingAndSucceed(); SignalAndFail(); RetryAndSucceed(); } TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalTwice) { StartWatchingAndSucceed(); SignalAndFail(); RetryAndFail(); RetryAndSucceed(); } TEST_F(NetworkChangeNotifierWinTest, GetCurrentCost) { if (base::win::GetVersion() < NetworkCostChangeNotifierWin::kSupportedOsVersion) { GTEST_SKIP(); } fake_network_cost_manager_environment_.SetCost( NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED); // Wait for `NetworkCostChangeNotifierWin` to finish initializing. RunUntilIdle(); EXPECT_EQ(GetCurrentConnectionCost(), NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED); fake_network_cost_manager_environment_.SetCost( NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_METERED); // Wait for `NetworkCostChangeNotifierWin` to handle the cost changed event. RunUntilIdle(); EXPECT_EQ(GetCurrentConnectionCost(), NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_METERED); } TEST_F(NetworkChangeNotifierWinTest, CostChangeObserver) { if (base::win::GetVersion() < NetworkCostChangeNotifierWin::kSupportedOsVersion) { GTEST_SKIP(); } fake_network_cost_manager_environment_.SetCost( NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED); // Wait for `NetworkCostChangeNotifierWin` to finish initializing. RunUntilIdle(); TestConnectionCostObserver cost_observer; NetworkChangeNotifier::AddConnectionCostObserver(&cost_observer); fake_network_cost_manager_environment_.SetCost( NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_METERED); cost_observer.WaitForConnectionCostChanged(); ASSERT_EQ(cost_observer.cost_changed_calls(), 1u); EXPECT_EQ(cost_observer.last_cost_changed_input(), NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_METERED); NetworkChangeNotifier::RemoveConnectionCostObserver(&cost_observer); } // Uses the fake implementation of `INetworkCostManager` to simulate `GetCost()` // returning an error `HRESULT`. class NetworkChangeNotifierWinCostErrorTest : public NetworkChangeNotifierWinTest { void SetUp() override { if (base::win::GetVersion() < NetworkCostChangeNotifierWin::kSupportedOsVersion) { GTEST_SKIP(); } fake_network_cost_manager_environment_.SimulateError( NetworkCostManagerStatus::kErrorGetCostFailed); NetworkChangeNotifierWinTest::SetUp(); } }; TEST_F(NetworkChangeNotifierWinCostErrorTest, CostError) { // Wait for `NetworkCostChangeNotifierWin` to finish initializing, which // should fail with an error. RunUntilIdle(); // `NetworkChangeNotifierWin` must use the default implementation when // `NetworkCostChangeNotifierWin` returns an unknown cost. EXPECT_EQ(GetCurrentConnectionCost(), GetCurrentConnectionCostFromDefaultImplementationForTesting()); } // Override the Windows OS version to simulate running on an OS that does not // support `INetworkCostManager`. class NetworkChangeNotifierWinCostUnsupportedOsTest : public NetworkChangeNotifierWinTest { public: NetworkChangeNotifierWinCostUnsupportedOsTest() : os_override_(base::test::ScopedOSInfoOverride::Type::kWinServer2016) {} protected: base::test::ScopedOSInfoOverride os_override_; }; TEST_F(NetworkChangeNotifierWinCostUnsupportedOsTest, CostWithUnsupportedOS) { // Wait for `NetworkCostChangeNotifierWin` to finish initializing, which // should initialize with an unknown cost on an unsupported OS. RunUntilIdle(); // `NetworkChangeNotifierWin` must use the default implementation when // `NetworkCostChangeNotifierWin` returns an unknown cost. EXPECT_EQ(GetCurrentConnectionCost(), GetCurrentConnectionCostFromDefaultImplementationForTesting()); } } // namespace net