blob: 78bdd93a4fcd2c067636bb0cac42b509e48430de [file] [log] [blame]
// Copyright (c) 2014 The Chromium OS 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 "update_engine/update_manager/real_shill_provider.h"
#include <memory>
#include <utility>
#include <base/time/time.h>
#include <chromeos/dbus/service_constants.h>
#include <glib.h>
#include <gtest/gtest.h>
#include "update_engine/fake_clock.h"
#include "update_engine/mock_dbus_wrapper.h"
#include "update_engine/test_utils.h"
#include "update_engine/update_manager/umtest_utils.h"
using base::Time;
using base::TimeDelta;
using chromeos_update_engine::FakeClock;
using chromeos_update_engine::MockDBusWrapper;
using chromeos_update_engine::test_utils::GValueFree;
using chromeos_update_engine::test_utils::GValueNewString;
using std::pair;
using std::unique_ptr;
using testing::A;
using testing::Eq;
using testing::Mock;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::StrEq;
using testing::StrictMock;
using testing::_;
namespace {
// Fake dbus-glib objects. These should be different values, to ease diagnosis
// of errors.
DBusGConnection* const kFakeConnection = reinterpret_cast<DBusGConnection*>(1);
DBusGProxy* const kFakeManagerProxy = reinterpret_cast<DBusGProxy*>(2);
DBusGProxy* const kFakeEthernetServiceProxy = reinterpret_cast<DBusGProxy*>(3);
DBusGProxy* const kFakeWifiServiceProxy = reinterpret_cast<DBusGProxy*>(4);
DBusGProxy* const kFakeWimaxServiceProxy = reinterpret_cast<DBusGProxy*>(5);
DBusGProxy* const kFakeBluetoothServiceProxy = reinterpret_cast<DBusGProxy*>(6);
DBusGProxy* const kFakeCellularServiceProxy = reinterpret_cast<DBusGProxy*>(7);
DBusGProxy* const kFakeVpnServiceProxy = reinterpret_cast<DBusGProxy*>(8);
DBusGProxy* const kFakeUnknownServiceProxy = reinterpret_cast<DBusGProxy*>(9);
// 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:
void SetUp() override {
// By default, initialize the provider so that it gets an initial connection
// status from shill. This simulates the common case where shill is
// available and responding during RealShillProvider initialization.
Init(true);
}
void TearDown() override {
Shutdown();
}
// Initialize the RealShillProvider under test. If |do_init_conn_status| is
// true, configure mocks to respond to the initial connection status check
// with shill. Otherwise, the initial check will fail.
void Init(bool do_init_conn_status) {
// Properly shutdown a previously initialized provider.
if (provider_.get())
Shutdown();
provider_.reset(new RealShillProvider(&mock_dbus_, &fake_clock_));
ASSERT_NE(nullptr, provider_.get());
fake_clock_.SetWallclockTime(InitTime());
// A DBus connection should only be obtained once.
EXPECT_CALL(mock_dbus_, BusGet(_, _)).WillOnce(
Return(kFakeConnection));
// A manager proxy should only be obtained once.
EXPECT_CALL(mock_dbus_, ProxyNewForName(
kFakeConnection, StrEq(shill::kFlimflamServiceName),
StrEq(shill::kFlimflamServicePath),
StrEq(shill::kFlimflamManagerInterface)))
.WillOnce(Return(kFakeManagerProxy));
// The PropertyChanged signal should be subscribed to.
EXPECT_CALL(mock_dbus_, ProxyAddSignal_2(
kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
G_TYPE_STRING, G_TYPE_VALUE))
.WillOnce(Return());
EXPECT_CALL(mock_dbus_, ProxyConnectSignal(
kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
_, _, _))
.WillOnce(
DoAll(SaveArg<2>(reinterpret_cast<void (**)()>(&signal_handler_)),
SaveArg<3>(&signal_data_),
Return()));
// Mock a response to an initial connection check (optional).
GHashTable* manager_properties = nullptr;
if (do_init_conn_status) {
pair<const char*, const char*> manager_pairs[] = {
{shill::kDefaultServiceProperty, "/"},
};
manager_properties = SetupGetPropertiesOkay(
kFakeManagerProxy, arraysize(manager_pairs), manager_pairs);
} else {
SetupGetPropertiesFail(kFakeManagerProxy);
}
// Check that provider initializes correctly.
ASSERT_TRUE(provider_->Init());
// All mocked calls should have been exercised by now.
Mock::VerifyAndClear(&mock_dbus_);
// Release properties hash table (if provided).
if (manager_properties)
g_hash_table_unref(manager_properties);
}
// Deletes the RealShillProvider under test.
void Shutdown() {
// Make sure that DBus resources get freed.
EXPECT_CALL(mock_dbus_, ProxyDisconnectSignal(
kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
Eq(reinterpret_cast<void (*)()>(signal_handler_)),
Eq(signal_data_)))
.WillOnce(Return());
EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeManagerProxy)).WillOnce(Return());
provider_.reset();
// All mocked calls should have been exercised by now.
Mock::VerifyAndClear(&mock_dbus_);
}
// 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 up a successful mock "GetProperties" call on |proxy|, writing a hash
// table containing |num_entries| entries formed by key/value pairs from
// |key_val_pairs| and returning true. Keys and values are plain C strings
// (const char*). The proxy call is expected to be made exactly once. Returns
// a pointer to a newly allocated hash table, which should be unreffed with
// g_hash_table_unref() when done.
GHashTable* SetupGetPropertiesOkay(
DBusGProxy* proxy, size_t num_entries,
pair<const char*, const char*>* key_val_pairs) {
// Allocate and populate the hash table.
GHashTable* properties = g_hash_table_new_full(g_str_hash, g_str_equal,
free, GValueFree);
for (size_t i = 0; i < num_entries; i++) {
g_hash_table_insert(properties, strdup(key_val_pairs[i].first),
GValueNewString(key_val_pairs[i].second));
}
// Set mock expectations.
EXPECT_CALL(mock_dbus_,
ProxyCall_0_1(proxy, StrEq(shill::kGetPropertiesFunction),
_, A<GHashTable**>()))
.WillOnce(DoAll(SetArgPointee<3>(g_hash_table_ref(properties)),
Return(true)));
return properties;
}
// Sets up a failing mock "GetProperties" call on |proxy|, returning false.
// The proxy call is expected to be made exactly once.
void SetupGetPropertiesFail(DBusGProxy* proxy) {
EXPECT_CALL(mock_dbus_,
ProxyCall_0_1(proxy, StrEq(shill::kGetPropertiesFunction),
_, A<GHashTable**>()))
.WillOnce(Return(false));
}
// Sends a signal informing the provider about a default connection
// |service_path|. Returns the fake connection change time.
Time SendDefaultServiceSignal(const char* service_path) {
auto default_service_gval = GValueNewString(service_path);
const Time conn_change_time = ConnChangedTime();
fake_clock_.SetWallclockTime(conn_change_time);
signal_handler_(kFakeManagerProxy, shill::kDefaultServiceProperty,
default_service_gval, signal_data_);
fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5));
GValueFree(default_service_gval);
return 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 char* service_path,
DBusGProxy* service_proxy,
const char* shill_type_str,
const char* shill_tethering_str,
Time* conn_change_time_p) {
// Mock logic for querying the default service attributes.
EXPECT_CALL(mock_dbus_,
ProxyNewForName(
kFakeConnection, StrEq(shill::kFlimflamServiceName),
StrEq(service_path),
StrEq(shill::kFlimflamServiceInterface)))
.WillOnce(Return(service_proxy));
EXPECT_CALL(mock_dbus_, ProxyUnref(service_proxy)).WillOnce(Return());
pair<const char*, const char*> service_pairs[] = {
{shill::kTypeProperty, shill_type_str},
{shill::kTetheringProperty, shill_tethering_str},
};
auto service_properties = SetupGetPropertiesOkay(
service_proxy, arraysize(service_pairs), service_pairs);
// Send a signal about a new default service.
auto conn_change_time = SendDefaultServiceSignal(service_path);
// Release the service properties hash tables.
g_hash_table_unref(service_properties);
// 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,
DBusGProxy* service_proxy,
const char* shill_type_str,
ConnectionType expected_conn_type) {
// Set up and test the connection, record the change time.
Time conn_change_time;
SetupConnectionAndAttrs(service_path, service_proxy, shill_type_str,
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, DBusGProxy* service_proxy,
const char* shill_tethering_str,
ConnectionTethering expected_conn_tethering) {
// Set up and test the connection, record the change time.
Time conn_change_time;
SetupConnectionAndAttrs(service_path, service_proxy, shill::kTypeEthernet,
shill_tethering_str, &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());
}
StrictMock<MockDBusWrapper> mock_dbus_;
FakeClock fake_clock_;
unique_ptr<RealShillProvider> provider_;
void (*signal_handler_)(DBusGProxy*, const char*, GValue*, void*);
void* signal_data_;
};
// Query the connection status, type and time last changed, as they were set
// during initialization (no signals).
TEST_F(UmRealShillProviderTest, ReadBaseValues) {
// 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());
}
// Test that Ethernet connection is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTypeEthernet) {
SetupConnectionAndTestType(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
shill::kTypeEthernet,
ConnectionType::kEthernet);
}
// Test that Wifi connection is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTypeWifi) {
SetupConnectionAndTestType(kFakeWifiServicePath,
kFakeWifiServiceProxy,
shill::kTypeWifi,
ConnectionType::kWifi);
}
// Test that Wimax connection is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTypeWimax) {
SetupConnectionAndTestType(kFakeWimaxServicePath,
kFakeWimaxServiceProxy,
shill::kTypeWimax,
ConnectionType::kWimax);
}
// Test that Bluetooth connection is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTypeBluetooth) {
SetupConnectionAndTestType(kFakeBluetoothServicePath,
kFakeBluetoothServiceProxy,
shill::kTypeBluetooth,
ConnectionType::kBluetooth);
}
// Test that Cellular connection is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTypeCellular) {
SetupConnectionAndTestType(kFakeCellularServicePath,
kFakeCellularServiceProxy,
shill::kTypeCellular,
ConnectionType::kCellular);
}
// Test that an unknown connection is identified as such.
TEST_F(UmRealShillProviderTest, ReadConnTypeUnknown) {
SetupConnectionAndTestType(kFakeUnknownServicePath,
kFakeUnknownServiceProxy,
"FooConnectionType",
ConnectionType::kUnknown);
}
// Tests that VPN connection is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTypeVpn) {
// Mock logic for returning a default service path and its type.
EXPECT_CALL(mock_dbus_, ProxyNewForName(
kFakeConnection, StrEq(shill::kFlimflamServiceName),
StrEq(kFakeVpnServicePath), StrEq(shill::kFlimflamServiceInterface)))
.WillOnce(Return(kFakeVpnServiceProxy));
EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeVpnServiceProxy)).WillOnce(Return());
pair<const char*, const char*> service_pairs[] = {
{shill::kTypeProperty, shill::kTypeVPN},
{shill::kPhysicalTechnologyProperty, shill::kTypeWifi},
};
auto service_properties = SetupGetPropertiesOkay(kFakeVpnServiceProxy,
arraysize(service_pairs),
service_pairs);
// Send a signal about a new default service.
Time conn_change_time = SendDefaultServiceSignal(kFakeVpnServicePath);
// 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());
// Release properties hash tables.
g_hash_table_unref(service_properties);
}
// Ensure that the connection type is properly cached in the provider through
// subsequent variable readings.
TEST_F(UmRealShillProviderTest, ConnTypeCacheUsed) {
SetupConnectionAndTestType(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
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) {
SetupConnectionAndTestType(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
shill::kTypeEthernet,
ConnectionType::kEthernet);
SendDefaultServiceSignal(kFakeEthernetServicePath);
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) {
SetupConnectionAndTestType(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
shill::kTypeEthernet,
ConnectionType::kEthernet);
SetupConnectionAndTestType(kFakeWifiServicePath,
kFakeWifiServiceProxy,
shill::kTypeWifi,
ConnectionType::kWifi);
}
// Test that a non-tethering mode is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTetheringNotDetected) {
SetupConnectionAndTestTethering(kFakeWifiServicePath,
kFakeWifiServiceProxy,
shill::kTetheringNotDetectedState,
ConnectionTethering::kNotDetected);
}
// Test that a suspected tethering mode is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTetheringSuspected) {
SetupConnectionAndTestTethering(kFakeWifiServicePath,
kFakeWifiServiceProxy,
shill::kTetheringSuspectedState,
ConnectionTethering::kSuspected);
}
// Test that a confirmed tethering mode is identified correctly.
TEST_F(UmRealShillProviderTest, ReadConnTetheringConfirmed) {
SetupConnectionAndTestTethering(kFakeWifiServicePath,
kFakeWifiServiceProxy,
shill::kTetheringConfirmedState,
ConnectionTethering::kConfirmed);
}
// Test that an unknown tethering mode is identified as such.
TEST_F(UmRealShillProviderTest, ReadConnTetheringUnknown) {
SetupConnectionAndTestTethering(kFakeWifiServicePath,
kFakeWifiServiceProxy,
"FooConnTethering",
ConnectionTethering::kUnknown);
}
// Ensure that the connection tethering mode is properly cached in the provider.
TEST_F(UmRealShillProviderTest, ConnTetheringCacheUsed) {
SetupConnectionAndTestTethering(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
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) {
SetupConnectionAndTestTethering(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
shill::kTetheringNotDetectedState,
ConnectionTethering::kNotDetected);
SendDefaultServiceSignal(kFakeEthernetServicePath);
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) {
SetupConnectionAndTestTethering(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
shill::kTetheringNotDetectedState,
ConnectionTethering::kNotDetected);
SetupConnectionAndTestTethering(kFakeWifiServicePath,
kFakeWifiServiceProxy,
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) {
// Send a default service signal twice, advancing the clock in between.
Time conn_change_time;
SetupConnectionAndAttrs(kFakeEthernetServicePath, kFakeEthernetServiceProxy,
shill::kTypeEthernet,
shill::kTetheringNotDetectedState, &conn_change_time);
SendDefaultServiceSignal(kFakeEthernetServicePath);
// 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());
}
// 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) {
// Re-initialize the provider, no initial connection status response.
Init(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) {
// Re-initialize the provider, no initial connection status response.
Init(false);
SetupConnectionAndTestType(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
shill::kTypeEthernet,
ConnectionType::kEthernet);
}
} // namespace chromeos_update_manager