blob: da1477a81f7999ed9a07ccd866dfeac6d4d56655 [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 <base/memory/scoped_ptr.h>
#include <base/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/policy_manager/real_shill_provider.h"
#include "update_engine/policy_manager/pmtest_utils.h"
#include "update_engine/test_utils.h"
using base::Time;
using base::TimeDelta;
using chromeos_update_engine::FakeClock;
using chromeos_update_engine::GValueNewString;
using chromeos_update_engine::GValueFree;
using chromeos_update_engine::MockDBusWrapper;
using std::pair;
using testing::_;
using testing::Eq;
using testing::NiceMock;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::StrEq;
using testing::StrictMock;
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_policy_manager {
class PmRealShillProviderTest : public ::testing::Test {
protected:
virtual void SetUp() {
provider_.reset(new RealShillProvider(&mock_dbus_, &fake_clock_));
PMTEST_ASSERT_NOT_NULL(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>(&signal_handler_),
SaveArg<3>(&signal_data_),
Return()));
// Default service should be checked once during initialization.
pair<const char*, const char*> manager_pairs[] = {
{shill::kDefaultServiceProperty, "/"},
};
auto manager_properties = MockGetProperties(kFakeManagerProxy, false,
1, manager_pairs);
// Check that provider initializes corrrectly.
ASSERT_TRUE(provider_->Init());
// Release properties hash table.
g_hash_table_unref(manager_properties);
}
virtual void TearDown() {
// Make sure that DBus resources get freed.
EXPECT_CALL(mock_dbus_, ProxyDisconnectSignal(
kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
Eq(signal_handler_), Eq(signal_data_)))
.WillOnce(Return());
EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeManagerProxy)).WillOnce(Return());
provider_.reset();
// Verify and clear all expectations.
testing::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 mock "GetProperties" call on |proxy| that returns a hash table
// containing |num_entries| entries formed by subsequent key/value pairs. Keys
// and values are plain C strings (const char*). Mock will be expected to call
// exactly once, unless |allow_multiple_calls| is true, in which case it's
// allowed to be called multiple times. Returns a pointer to a newly allocated
// hash table, which should be unreffed with g_hash_table_unref() when done.
GHashTable* MockGetProperties(DBusGProxy* proxy, bool allow_multiple_calls,
size_t num_entries,
pair<const char*, const char*>* key_val_pairs) {
// Allocate and populate the hash table.
auto 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 expectation and actions.
auto action = DoAll(SetArgPointee<3>(g_hash_table_ref(properties)),
Return(true));
if (allow_multiple_calls) {
EXPECT_CALL(mock_dbus_, ProxyCall_0_1(
proxy, StrEq(shill::kGetPropertiesFunction), _, _))
.WillRepeatedly(action);
} else {
EXPECT_CALL(mock_dbus_, ProxyCall_0_1(
proxy, StrEq(shill::kGetPropertiesFunction), _, _))
.WillOnce(action);
}
return properties;
}
// Programs the mock DBus interface to return an non-VPN service and ensures
// that the shill provider reads it correctly, including updating the last
// changed time on each of the queries.
void SetConnectionAndTestType(const char* service_path,
DBusGProxy* service_proxy,
const char* shill_type_str,
ConnectionType expected_conn_type) {
// Send a signal about a new default service.
auto callback = reinterpret_cast<ShillConnector::PropertyChangedHandler>(
signal_handler_);
auto default_service_gval = GValueNewString(service_path);
Time conn_change_time = ConnChangedTime();
fake_clock_.SetWallclockTime(conn_change_time);
callback(kFakeManagerProxy, shill::kDefaultServiceProperty,
default_service_gval, signal_data_);
fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5));
GValueFree(default_service_gval);
// Query the connection status, ensure last change time reported correctly.
scoped_ptr<const bool> is_connected(
provider_->var_is_connected()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(is_connected.get());
EXPECT_TRUE(*is_connected);
scoped_ptr<const Time> conn_last_changed_1(
provider_->var_conn_last_changed()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(conn_last_changed_1.get());
EXPECT_EQ(conn_change_time, *conn_last_changed_1);
// Mock logic for querying the type of the default service.
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},
};
auto service_properties = MockGetProperties(
service_proxy, false,
arraysize(service_pairs), service_pairs);
// Query the connection type, ensure last change time did not change.
scoped_ptr<const ConnectionType> conn_type(
provider_->var_conn_type()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(conn_type.get());
EXPECT_EQ(expected_conn_type, *conn_type);
scoped_ptr<const Time> conn_last_changed_2(
provider_->var_conn_last_changed()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(conn_last_changed_2.get());
EXPECT_EQ(*conn_last_changed_1, *conn_last_changed_2);
// Release properties hash tables.
g_hash_table_unref(service_properties);
}
const TimeDelta default_timeout_ = TimeDelta::FromSeconds(1);
StrictMock<MockDBusWrapper> mock_dbus_;
FakeClock fake_clock_;
scoped_ptr<RealShillProvider> provider_;
GCallback signal_handler_;
// FIXME 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.
TEST_F(PmRealShillProviderTest, ReadDefaultValues) {
// Query the provider variables.
scoped_ptr<const bool> is_connected(
provider_->var_is_connected()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(is_connected.get());
EXPECT_FALSE(*is_connected);
scoped_ptr<const ConnectionType> conn_type(
provider_->var_conn_type()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NULL(conn_type.get());
scoped_ptr<const Time> conn_last_changed(
provider_->var_conn_last_changed()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(conn_last_changed.get());
EXPECT_EQ(InitTime(), *conn_last_changed);
}
// Test that Ethernet connection is identified correctly.
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedViaEthernet) {
SetConnectionAndTestType(kFakeEthernetServicePath,
kFakeEthernetServiceProxy,
shill::kTypeEthernet,
ConnectionType::kEthernet);
}
// Test that Wifi connection is identified correctly.
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedViaWifi) {
SetConnectionAndTestType(kFakeWifiServicePath,
kFakeWifiServiceProxy,
shill::kTypeWifi,
ConnectionType::kWifi);
}
// Test that Wimax connection is identified correctly.
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedViaWimax) {
SetConnectionAndTestType(kFakeWimaxServicePath,
kFakeWimaxServiceProxy,
shill::kTypeWimax,
ConnectionType::kWimax);
}
// Test that Bluetooth connection is identified correctly.
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedViaBluetooth) {
SetConnectionAndTestType(kFakeBluetoothServicePath,
kFakeBluetoothServiceProxy,
shill::kTypeBluetooth,
ConnectionType::kBluetooth);
}
// Test that Cellular connection is identified correctly.
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedViaCellular) {
SetConnectionAndTestType(kFakeCellularServicePath,
kFakeCellularServiceProxy,
shill::kTypeCellular,
ConnectionType::kCellular);
}
// Test that an unknown connection is identified as such.
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedViaUnknown) {
SetConnectionAndTestType(kFakeUnknownServicePath,
kFakeUnknownServiceProxy,
"FooConnectionType",
ConnectionType::kUnknown);
}
// Tests that VPN connection is identified correctly.
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedViaVpn) {
// Send a signal about a new default service.
auto callback = reinterpret_cast<ShillConnector::PropertyChangedHandler>(
signal_handler_);
auto default_service_gval = GValueNewString(kFakeVpnServicePath);
Time conn_change_time = ConnChangedTime();
fake_clock_.SetWallclockTime(conn_change_time);
callback(kFakeManagerProxy, shill::kDefaultServiceProperty,
default_service_gval, signal_data_);
fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5));
GValueFree(default_service_gval);
// 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 = MockGetProperties(
kFakeVpnServiceProxy, false, arraysize(service_pairs), service_pairs);
// Query the connection type, ensure last change time reported correctly.
scoped_ptr<const ConnectionType> conn_type(
provider_->var_conn_type()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(conn_type.get());
EXPECT_EQ(ConnectionType::kWifi, *conn_type);
scoped_ptr<const Time> conn_last_changed(
provider_->var_conn_last_changed()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(conn_last_changed.get());
EXPECT_EQ(conn_change_time, *conn_last_changed);
// Release properties hash tables.
g_hash_table_unref(service_properties);
}
// Fake two DBus signal prompting about a default connection change, but
// otherwise give the same service path. Check connection status and the time is
// was last changed, make sure it is the same time when the first signal was
// sent (and not the second).
TEST_F(PmRealShillProviderTest, ReadChangedValuesConnectedTwoSignals) {
// Send a default service signal twice, advancing the clock in between.
auto callback = reinterpret_cast<ShillConnector::PropertyChangedHandler>(
signal_handler_);
auto default_service_gval = GValueNewString(kFakeEthernetServicePath);
Time conn_change_time = ConnChangedTime();
fake_clock_.SetWallclockTime(conn_change_time);
callback(kFakeManagerProxy, shill::kDefaultServiceProperty,
default_service_gval, signal_data_);
fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5));
callback(kFakeManagerProxy, shill::kDefaultServiceProperty,
default_service_gval, signal_data_);
GValueFree(default_service_gval);
// Query the connection status, ensure last change time reported correctly.
scoped_ptr<const bool> is_connected(
provider_->var_is_connected()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(is_connected.get());
EXPECT_TRUE(*is_connected);
scoped_ptr<const Time> conn_last_changed(
provider_->var_conn_last_changed()->GetValue(default_timeout_, NULL));
PMTEST_ASSERT_NOT_NULL(conn_last_changed.get());
EXPECT_EQ(conn_change_time, *conn_last_changed);
}
} // namespace chromeos_policy_manager