// // Copyright (C) 2014 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "update_engine/update_manager/real_shill_provider.h" #include <memory> #include <utility> #include <base/time/time.h> #include <brillo/make_unique_ptr.h> #include <brillo/message_loops/fake_message_loop.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <shill/dbus-constants.h> #include <shill/dbus-proxies.h> #include <shill/dbus-proxy-mocks.h> #include "update_engine/common/fake_clock.h" #include "update_engine/common/test_utils.h" #include "update_engine/dbus_test_utils.h" #include "update_engine/fake_shill_proxy.h" #include "update_engine/update_manager/umtest_utils.h" using base::Time; using base::TimeDelta; using chromeos_update_engine::FakeClock; using org::chromium::flimflam::ManagerProxyMock; using org::chromium::flimflam::ServiceProxyMock; using std::unique_ptr; using testing::Mock; using testing::Return; using testing::SetArgPointee; using testing::_; namespace { // Fake service paths. const char* const kFakeEthernetServicePath = "/fake/ethernet/service"; const char* const kFakeWifiServicePath = "/fake/wifi/service"; const char* const kFakeWimaxServicePath = "/fake/wimax/service"; const char* const kFakeBluetoothServicePath = "/fake/bluetooth/service"; const char* const kFakeCellularServicePath = "/fake/cellular/service"; const char* const kFakeVpnServicePath = "/fake/vpn/service"; const char* const kFakeUnknownServicePath = "/fake/unknown/service"; } // namespace namespace chromeos_update_manager { class UmRealShillProviderTest : public ::testing::Test { protected: // Initialize the RealShillProvider under test. void SetUp() override { fake_clock_.SetWallclockTime(InitTime()); loop_.SetAsCurrent(); provider_.reset(new RealShillProvider(&fake_shill_proxy_, &fake_clock_)); ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_.GetManagerProxy(); // The PropertyChanged signal should be subscribed to. MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER( manager_property_changed_, *manager_proxy_mock, PropertyChanged); } void TearDown() override { provider_.reset(); // Check for leaked callbacks on the main loop. EXPECT_FALSE(loop_.PendingTasks()); } // These methods generate fixed timestamps for use in faking the current time. Time InitTime() { Time::Exploded now_exp; now_exp.year = 2014; now_exp.month = 3; now_exp.day_of_week = 2; now_exp.day_of_month = 18; now_exp.hour = 8; now_exp.minute = 5; now_exp.second = 33; now_exp.millisecond = 675; return Time::FromLocalExploded(now_exp); } Time ConnChangedTime() { return InitTime() + TimeDelta::FromSeconds(10); } // Sets the default_service object path in the response from the // ManagerProxyMock instance. void SetManagerReply(const char* default_service, bool reply_succeeds); // Sets the |service_type|, |physical_technology| and |service_tethering| // properties in the mocked service |service_path|. If any of the three // const char* is a nullptr, the corresponding property will not be included // in the response. // Returns the mock object pointer, owned by the |fake_shill_proxy_|. ServiceProxyMock* SetServiceReply(const std::string& service_path, const char* service_type, const char* physical_technology, const char* service_tethering); void InitWithDefaultService(const char* default_service) { SetManagerReply(default_service, true); // Check that provider initializes correctly. EXPECT_TRUE(provider_->Init()); // RunOnce to notify the signal handler was connected properly. EXPECT_TRUE(loop_.RunOnce(false)); } // Sends a signal informing the provider about a default connection // |service_path|. Sets the fake connection change time in // |conn_change_time_p| if provided. void SendDefaultServiceSignal(const std::string& service_path, Time* conn_change_time_p) { const Time conn_change_time = ConnChangedTime(); fake_clock_.SetWallclockTime(conn_change_time); ASSERT_TRUE(manager_property_changed_.IsHandlerRegistered()); manager_property_changed_.signal_callback().Run( shill::kDefaultServiceProperty, dbus::ObjectPath(service_path)); fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5)); if (conn_change_time_p) *conn_change_time_p = conn_change_time; } // Sets up expectations for detection of a connection |service_path| with type // |shill_type_str| and tethering mode |shill_tethering_str|. Ensures that the // new connection status and change time are properly detected by the // provider. Writes the fake connection change time to |conn_change_time_p|, // if provided. void SetupConnectionAndAttrs(const std::string& service_path, const char* shill_type, const char* shill_tethering, Time* conn_change_time_p) { SetServiceReply(service_path, shill_type, nullptr, shill_tethering); // Note: We don't setup this |service_path| as the default service path but // we instead send a signal notifying the change since the code won't call // GetProperties on the Manager object at this point. // Send a signal about a new default service. Time conn_change_time; SendDefaultServiceSignal(service_path, &conn_change_time); // Query the connection status, ensure last change time reported correctly. UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected()); UmTestUtils::ExpectVariableHasValue(conn_change_time, provider_->var_conn_last_changed()); // Write the connection change time to the output argument. if (conn_change_time_p) *conn_change_time_p = conn_change_time; } // Sets up a connection and tests that its type is being properly detected by // the provider. void SetupConnectionAndTestType(const char* service_path, const char* shill_type, ConnectionType expected_conn_type) { // Set up and test the connection, record the change time. Time conn_change_time; SetupConnectionAndAttrs(service_path, shill_type, shill::kTetheringNotDetectedState, &conn_change_time); // Query the connection type, ensure last change time did not change. UmTestUtils::ExpectVariableHasValue(expected_conn_type, provider_->var_conn_type()); UmTestUtils::ExpectVariableHasValue(conn_change_time, provider_->var_conn_last_changed()); } // Sets up a connection and tests that its tethering mode is being properly // detected by the provider. void SetupConnectionAndTestTethering( const char* service_path, const char* shill_tethering, ConnectionTethering expected_conn_tethering) { // Set up and test the connection, record the change time. Time conn_change_time; SetupConnectionAndAttrs( service_path, shill::kTypeEthernet, shill_tethering, &conn_change_time); // Query the connection tethering, ensure last change time did not change. UmTestUtils::ExpectVariableHasValue(expected_conn_tethering, provider_->var_conn_tethering()); UmTestUtils::ExpectVariableHasValue(conn_change_time, provider_->var_conn_last_changed()); } brillo::FakeMessageLoop loop_{nullptr}; FakeClock fake_clock_; chromeos_update_engine::FakeShillProxy fake_shill_proxy_; // The registered signal handler for the signal Manager.PropertyChanged. chromeos_update_engine::dbus_test_utils::MockSignalHandler< void(const std::string&, const brillo::Any&)> manager_property_changed_; unique_ptr<RealShillProvider> provider_; }; void UmRealShillProviderTest::SetManagerReply(const char* default_service, bool reply_succeeds) { ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_.GetManagerProxy(); if (!reply_succeeds) { EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _)) .WillOnce(Return(false)); return; } // Create a dictionary of properties and optionally include the default // service. brillo::VariantDictionary reply_dict; reply_dict["SomeOtherProperty"] = 0xC0FFEE; if (default_service) { reply_dict[shill::kDefaultServiceProperty] = dbus::ObjectPath(default_service); } EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _)) .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true))); } ServiceProxyMock* UmRealShillProviderTest::SetServiceReply( const std::string& service_path, const char* service_type, const char* physical_technology, const char* service_tethering) { brillo::VariantDictionary reply_dict; reply_dict["SomeOtherProperty"] = 0xC0FFEE; if (service_type) reply_dict[shill::kTypeProperty] = std::string(service_type); if (physical_technology) { reply_dict[shill::kPhysicalTechnologyProperty] = std::string(physical_technology); } if (service_tethering) reply_dict[shill::kTetheringProperty] = std::string(service_tethering); ServiceProxyMock* service_proxy_mock = new ServiceProxyMock(); // Plumb return value into mock object. EXPECT_CALL(*service_proxy_mock, GetProperties(_, _, _)) .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true))); fake_shill_proxy_.SetServiceForPath( dbus::ObjectPath(service_path), brillo::make_unique_ptr(service_proxy_mock)); return service_proxy_mock; } // Query the connection status, type and time last changed, as they were set // during initialization (no signals). TEST_F(UmRealShillProviderTest, ReadBaseValues) { InitWithDefaultService("/"); // Query the provider variables. UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected()); UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type()); UmTestUtils::ExpectVariableHasValue(InitTime(), provider_->var_conn_last_changed()); } // Ensure that invalid DBus paths are ignored. TEST_F(UmRealShillProviderTest, InvalidServicePath) { InitWithDefaultService("invalid"); UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected()); UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type()); UmTestUtils::ExpectVariableHasValue(InitTime(), provider_->var_conn_last_changed()); } // Ensure that a service path property including a different type is ignored. TEST_F(UmRealShillProviderTest, InvalidServicePathType) { ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_.GetManagerProxy(); brillo::VariantDictionary reply_dict; reply_dict[shill::kDefaultServiceProperty] = "/not/an/object/path"; EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _)) .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true))); EXPECT_TRUE(provider_->Init()); EXPECT_TRUE(loop_.RunOnce(false)); UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected()); } // Test that Ethernet connection is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTypeEthernet) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeEthernetServicePath, shill::kTypeEthernet, ConnectionType::kEthernet); } // Test that Wifi connection is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTypeWifi) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeWifiServicePath, shill::kTypeWifi, ConnectionType::kWifi); } // Test that Wimax connection is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTypeWimax) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeWimaxServicePath, shill::kTypeWimax, ConnectionType::kWimax); } // Test that Bluetooth connection is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTypeBluetooth) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeBluetoothServicePath, shill::kTypeBluetooth, ConnectionType::kBluetooth); } // Test that Cellular connection is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTypeCellular) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeCellularServicePath, shill::kTypeCellular, ConnectionType::kCellular); } // Test that an unknown connection is identified as such. TEST_F(UmRealShillProviderTest, ReadConnTypeUnknown) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeUnknownServicePath, "FooConnectionType", ConnectionType::kUnknown); } // Tests that VPN connection is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTypeVpn) { InitWithDefaultService("/"); // Mock logic for returning a default service path and its type. SetServiceReply(kFakeVpnServicePath, shill::kTypeVPN, shill::kTypeWifi, shill::kTetheringNotDetectedState); // Send a signal about a new default service. Time conn_change_time; SendDefaultServiceSignal(kFakeVpnServicePath, &conn_change_time); // Query the connection type, ensure last change time reported correctly. UmTestUtils::ExpectVariableHasValue(ConnectionType::kWifi, provider_->var_conn_type()); UmTestUtils::ExpectVariableHasValue(conn_change_time, provider_->var_conn_last_changed()); } // Ensure that the connection type is properly cached in the provider through // subsequent variable readings. TEST_F(UmRealShillProviderTest, ConnTypeCacheUsed) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeEthernetServicePath, shill::kTypeEthernet, ConnectionType::kEthernet); UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet, provider_->var_conn_type()); } // Ensure that the cached connection type remains valid even when a default // connection signal occurs but the connection is not changed. TEST_F(UmRealShillProviderTest, ConnTypeCacheRemainsValid) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeEthernetServicePath, shill::kTypeEthernet, ConnectionType::kEthernet); SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr); UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet, provider_->var_conn_type()); } // Ensure that the cached connection type is invalidated and re-read when the // default connection changes. TEST_F(UmRealShillProviderTest, ConnTypeCacheInvalidated) { InitWithDefaultService("/"); SetupConnectionAndTestType(kFakeEthernetServicePath, shill::kTypeEthernet, ConnectionType::kEthernet); SetupConnectionAndTestType(kFakeWifiServicePath, shill::kTypeWifi, ConnectionType::kWifi); } // Test that a non-tethering mode is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTetheringNotDetected) { InitWithDefaultService("/"); SetupConnectionAndTestTethering(kFakeWifiServicePath, shill::kTetheringNotDetectedState, ConnectionTethering::kNotDetected); } // Test that a suspected tethering mode is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTetheringSuspected) { InitWithDefaultService("/"); SetupConnectionAndTestTethering(kFakeWifiServicePath, shill::kTetheringSuspectedState, ConnectionTethering::kSuspected); } // Test that a confirmed tethering mode is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTetheringConfirmed) { InitWithDefaultService("/"); SetupConnectionAndTestTethering(kFakeWifiServicePath, shill::kTetheringConfirmedState, ConnectionTethering::kConfirmed); } // Test that an unknown tethering mode is identified as such. TEST_F(UmRealShillProviderTest, ReadConnTetheringUnknown) { InitWithDefaultService("/"); SetupConnectionAndTestTethering(kFakeWifiServicePath, "FooConnTethering", ConnectionTethering::kUnknown); } // Ensure that the connection tethering mode is properly cached in the provider. TEST_F(UmRealShillProviderTest, ConnTetheringCacheUsed) { InitWithDefaultService("/"); SetupConnectionAndTestTethering(kFakeEthernetServicePath, shill::kTetheringNotDetectedState, ConnectionTethering::kNotDetected); UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected, provider_->var_conn_tethering()); } // Ensure that the cached connection tethering mode remains valid even when a // default connection signal occurs but the connection is not changed. TEST_F(UmRealShillProviderTest, ConnTetheringCacheRemainsValid) { InitWithDefaultService("/"); SetupConnectionAndTestTethering(kFakeEthernetServicePath, shill::kTetheringNotDetectedState, ConnectionTethering::kNotDetected); SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr); UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected, provider_->var_conn_tethering()); } // Ensure that the cached connection tethering mode is invalidated and re-read // when the default connection changes. TEST_F(UmRealShillProviderTest, ConnTetheringCacheInvalidated) { InitWithDefaultService("/"); SetupConnectionAndTestTethering(kFakeEthernetServicePath, shill::kTetheringNotDetectedState, ConnectionTethering::kNotDetected); SetupConnectionAndTestTethering(kFakeWifiServicePath, shill::kTetheringConfirmedState, ConnectionTethering::kConfirmed); } // Fake two DBus signals prompting a default connection change, but otherwise // give the same service path. Check connection status and the time it was last // changed, making sure that it is the time when the first signal was sent (and // not the second). TEST_F(UmRealShillProviderTest, ReadLastChangedTimeTwoSignals) { InitWithDefaultService("/"); // Send a default service signal twice, advancing the clock in between. Time conn_change_time; SetupConnectionAndAttrs(kFakeEthernetServicePath, shill::kTypeEthernet, shill::kTetheringNotDetectedState, &conn_change_time); // This will set the service path to the same value, so it should not call // GetProperties() again. SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr); // Query the connection status, ensure last change time reported as the first // time the signal was sent. UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected()); UmTestUtils::ExpectVariableHasValue(conn_change_time, provider_->var_conn_last_changed()); } // Make sure that the provider initializes correctly even if shill is not // responding, that variables can be obtained, and that they all return a null // value (indicating that the underlying values were not set). TEST_F(UmRealShillProviderTest, NoInitConnStatusReadBaseValues) { // Initialize the provider, no initial connection status response. SetManagerReply(nullptr, false); EXPECT_TRUE(provider_->Init()); EXPECT_TRUE(loop_.RunOnce(false)); UmTestUtils::ExpectVariableNotSet(provider_->var_is_connected()); UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type()); UmTestUtils::ExpectVariableNotSet(provider_->var_conn_last_changed()); } // Test that, once a signal is received, the connection status and other info // can be read correctly. TEST_F(UmRealShillProviderTest, NoInitConnStatusReadConnTypeEthernet) { // Initialize the provider with no initial connection status response. SetManagerReply(nullptr, false); EXPECT_TRUE(provider_->Init()); EXPECT_TRUE(loop_.RunOnce(false)); SetupConnectionAndAttrs(kFakeEthernetServicePath, shill::kTypeEthernet, shill::kTetheringNotDetectedState, nullptr); UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected()); } } // namespace chromeos_update_manager