| // 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 <memory> |
| #include <utility> |
| #include <vector> |
| |
| #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<void(ConnectionType)> 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_METHOD1(OnIPAddressChanged, |
| void(NetworkChangeNotifier::IPAddressChangeType)); |
| }; |
| |
| 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(NetworkChangeNotifier::IP_ADDRESS_CHANGE_NORMAL)) |
| .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(NetworkChangeNotifier::IP_ADDRESS_CHANGE_NORMAL)) |
| .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(NetworkChangeNotifier::IP_ADDRESS_CHANGE_NORMAL)) |
| .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(NetworkChangeNotifier::IP_ADDRESS_CHANGE_NORMAL)) |
| .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(NetworkChangeNotifier::IP_ADDRESS_CHANGE_NORMAL)) |
| .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(NetworkChangeNotifier::IP_ADDRESS_CHANGE_NORMAL)) |
| .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([&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<TestNetworkChangeNotifierWin> network_change_notifier_; |
| |
| // Must be created after |network_change_notifier_|, so it can add itself as |
| // an IPAddressObserver. |
| StrictMock<TestIPAddressObserver> 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 |