blob: 9c2d733f6329bd15320796e08ab1073416f14307 [file] [log] [blame]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "typecd/udev_monitor.h"
#include <base/files/file_util.h>
#include <base/test/task_environment.h>
#include <brillo/udev/mock_udev.h>
#include <brillo/udev/mock_udev_device.h>
#include <brillo/udev/mock_udev_enumerate.h>
#include <brillo/udev/mock_udev_list_entry.h>
#include <brillo/udev/mock_udev_monitor.h>
#include <brillo/unittest_utils.h>
#include <gtest/gtest.h>
#include "typecd/test_constants.h"
using testing::_;
using testing::ByMove;
using testing::Return;
using testing::StrEq;
namespace typecd {
namespace {
constexpr char kFakePd0SysPath[] = "/sys/class/usb_power_delivery/pd0";
constexpr char kInvalidPortSysPath[] = "/sys/class/typec/a-yz";
constexpr char kInvalidPdSyspath[] = "/sys/class/usb_power_delivery/a-yz";
// A really dumb typec observer to verify that UdevMonitor is invoking the right
// callbacks.
class TestTypecObserver : public UdevMonitor::TypecObserver {
public:
void OnPortAddedOrRemoved(const base::FilePath& path,
int port_num,
bool added) override {
if (added)
num_ports_++;
else
num_ports_--;
};
void OnPartnerAddedOrRemoved(const base::FilePath& path,
int port_num,
bool added,
bool is_hotplug) override {
if (added)
num_partners_++;
else
num_partners_--;
};
void OnPartnerAltModeAddedOrRemoved(const base::FilePath& path,
int port_num,
bool added) override{};
void OnCableAddedOrRemoved(const base::FilePath& path,
int port_num,
bool added) override {
if (added)
num_cables_++;
else
num_cables_--;
};
void OnCablePlugAdded(const base::FilePath& path, int port_num) override {}
void OnCableAltModeAdded(const base::FilePath& path, int port_num) override {
num_cable_altmodes_++;
};
void OnPdDeviceAddedOrRemoved(const base::FilePath& path,
bool added) override {
if (added)
num_pd_devices_++;
else
num_pd_devices_--;
};
void OnPartnerChanged(int port_num) override { num_partner_change_events_++; }
void OnPortChanged(int port_num) override {
port_change_tracker_[port_num] = true;
}
int GetNumPorts() { return num_ports_; }
int GetNumPartners() { return num_partners_; }
int GetNumCables() { return num_cables_; }
int GetNumCableAltModes() { return num_cable_altmodes_; }
int GetNumPartnerChangeEvents() { return num_partner_change_events_; }
int GetNumPdDevices() { return num_pd_devices_; }
// Helper to return whether a port change event was received for |port_num|.
bool PortChanged(int port_num) {
if (port_change_tracker_.find(port_num) == port_change_tracker_.end())
return false;
else
return port_change_tracker_[port_num];
}
// Helper to reset |port_change_tracker_| state for |port_num|.
void ResetPortChanged(int port_num) {
port_change_tracker_[port_num] = false;
}
private:
int num_partners_;
int num_ports_;
int num_cables_;
int num_cable_altmodes_;
int num_partner_change_events_;
int num_pd_devices_;
// Map to check whether a change was detected for a particular port:
// Key = port number, Value = boolean value indicating whether a change event
// was received.
//
// The map entries should be cleared before checking for any changes.
std::map<int, bool> port_change_tracker_;
};
} // namespace
class UdevMonitorTest : public ::testing::Test {
public:
UdevMonitorTest()
: task_environment_(
base::test::TaskEnvironment::MainThreadType::IO,
base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC) {}
protected:
void SetUp() override {
typec_observer_ = std::make_unique<TestTypecObserver>();
monitor_ = std::make_unique<UdevMonitor>();
monitor_->AddTypecObserver(typec_observer_.get());
}
void TearDown() override {
mock_udev_monitor_.reset();
monitor_.reset();
typec_observer_.reset();
}
// Helper function to collect MockUdevMonitor initialization code in 1 place.
void InitMockUdevMonitor() {
mock_udev_monitor_ = std::make_unique<brillo::MockUdevMonitor>();
EXPECT_CALL(*mock_udev_monitor_, FilterAddMatchSubsystemDeviceType(
StrEq(kTypeCSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*mock_udev_monitor_, FilterAddMatchSubsystemDeviceType(
StrEq(kUsbPdSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*mock_udev_monitor_, EnableReceiving()).WillOnce(Return(true));
fds_ = std::make_unique<brillo::ScopedSocketPair>();
EXPECT_CALL(*mock_udev_monitor_, GetFileDescriptor())
.WillOnce(Return(fds_->left));
}
// Add a task environment to keep the FileDescriptorWatcher code happy.
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestTypecObserver> typec_observer_;
std::unique_ptr<UdevMonitor> monitor_;
std::unique_ptr<brillo::MockUdevMonitor> mock_udev_monitor_;
// A valid socket pair used to make MockUdevMonitor happy.
std::unique_ptr<brillo::ScopedSocketPair> fds_;
};
TEST_F(UdevMonitorTest, Basic) {
// Create the Mock Udev objects and function invocation expectations.
auto list_entry2 = std::make_unique<brillo::MockUdevListEntry>();
EXPECT_CALL(*list_entry2, GetName())
.WillOnce(Return(kFakePort0PartnerSysPath));
EXPECT_CALL(*list_entry2, GetNext()).WillOnce(Return(ByMove(nullptr)));
auto list_entry1 = std::make_unique<brillo::MockUdevListEntry>();
EXPECT_CALL(*list_entry1, GetName()).WillOnce(Return(kFakePort0SysPath));
EXPECT_CALL(*list_entry1, GetNext())
.WillOnce(Return(ByMove(std::move(list_entry2))));
auto enumerate = std::make_unique<brillo::MockUdevEnumerate>();
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kTypeCSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kUsbPdSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, ScanDevices()).WillOnce(Return(true));
EXPECT_CALL(*enumerate, GetListEntry())
.WillOnce(Return(ByMove(std::move(list_entry1))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateEnumerate())
.WillOnce(Return(ByMove(std::move(enumerate))));
monitor_->SetUdev(std::move(udev));
EXPECT_THAT(0, typec_observer_->GetNumPorts());
ASSERT_TRUE(monitor_->ScanDevices());
EXPECT_THAT(1, typec_observer_->GetNumPorts());
EXPECT_THAT(1, typec_observer_->GetNumPartners());
}
// Check that a port and partner can be detected after init. Also check whether
// a subsequent partner removal is detected correctly.
TEST_F(UdevMonitorTest, Hotplug) {
// Fake the calls for port add.
auto device_port = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*device_port, GetSysPath()).WillOnce(Return(kFakePort0SysPath));
EXPECT_CALL(*device_port, GetAction()).WillOnce(Return("add"));
// Fake the calls for partner add.
auto device_partner_add = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*device_partner_add, GetSysPath())
.WillOnce(Return(kFakePort0PartnerSysPath));
EXPECT_CALL(*device_partner_add, GetAction()).WillOnce(Return("add"));
// Fake the calls for partner remove.
auto device_partner_remove = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*device_partner_remove, GetSysPath())
.WillOnce(Return(kFakePort0PartnerSysPath));
EXPECT_CALL(*device_partner_remove, GetAction()).WillOnce(Return("remove"));
// Fake the calls for cable add.
auto device_cable_add = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*device_cable_add, GetSysPath())
.WillOnce(Return(kFakePort0CableSysPath));
EXPECT_CALL(*device_cable_add, GetAction()).WillOnce(Return("add"));
// Create the Mock Udev objects and function invocation expectations.
InitMockUdevMonitor();
EXPECT_CALL(*mock_udev_monitor_, ReceiveDevice())
.WillOnce(Return(ByMove(std::move(device_port))))
.WillOnce(Return(ByMove(std::move(device_partner_add))))
.WillOnce(Return(ByMove(std::move(device_partner_remove))))
.WillOnce(Return(ByMove(std::move(device_cable_add))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateMonitorFromNetlink(StrEq(kUdevMonitorName)))
.WillOnce(Return(ByMove(std::move(mock_udev_monitor_))));
monitor_->SetUdev(std::move(udev));
EXPECT_THAT(0, typec_observer_->GetNumPorts());
EXPECT_THAT(0, typec_observer_->GetNumCables());
// Skip initial scanning, since we are only interested in testing hotplug.
ASSERT_TRUE(monitor_->BeginMonitoring());
// It's too tedious to poke the socket pair to actually trigger the
// FileDescriptorWatcher without it running repeatedly.
//
// Instead we manually call HandleUdevEvent. Effectively this equivalent to
// triggering the event handler using the FileDescriptorWatcher.
monitor_->HandleUdevEvent();
EXPECT_THAT(1, typec_observer_->GetNumPorts());
monitor_->HandleUdevEvent();
EXPECT_THAT(1, typec_observer_->GetNumPartners());
monitor_->HandleUdevEvent();
EXPECT_THAT(0, typec_observer_->GetNumPartners());
monitor_->HandleUdevEvent();
EXPECT_THAT(1, typec_observer_->GetNumCables());
}
// Test that the udev handler correctly handles invalid port sysfs paths.
TEST_F(UdevMonitorTest, InvalidPortSyspath) {
// Fake the calls for port add.
auto device_port = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*device_port, GetSysPath()).WillOnce(Return(kInvalidPortSysPath));
EXPECT_CALL(*device_port, GetAction()).WillOnce(Return("add"));
// Create the Mock Udev objects and function invocation expectations.
InitMockUdevMonitor();
EXPECT_CALL(*mock_udev_monitor_, ReceiveDevice())
.WillOnce(Return(ByMove(std::move(device_port))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateMonitorFromNetlink(StrEq(kUdevMonitorName)))
.WillOnce(Return(ByMove(std::move(mock_udev_monitor_))));
monitor_->SetUdev(std::move(udev));
// Skip initial scanning, since we are only interested in testing hotplug.
ASSERT_TRUE(monitor_->BeginMonitoring());
// Manually call HandleUdevEvent. Effectively this equivalent to triggering
// the event handler using the FileDescriptorWatcher.
monitor_->HandleUdevEvent();
EXPECT_THAT(0, typec_observer_->GetNumPorts());
}
// Test that the monitor can detect cable creation and SOP' alternate mode
// addition. Also checks that an SOP'' alternate mode addition is ignored.
TEST_F(UdevMonitorTest, CableAndAltModeAddition) {
// Create the Mock Udev objects and function invocation expectations.
// Unsupported SOP'' alternate mode.
auto list_entry3 = std::make_unique<brillo::MockUdevListEntry>();
EXPECT_CALL(*list_entry3, GetName())
.WillOnce(Return(kFakePort0SOPDoublePrimeAltModeSysPath));
EXPECT_CALL(*list_entry3, GetNext())
.WillOnce(Return(ByMove(std::move(nullptr))));
// SOP' alternate mode.
auto list_entry2 = std::make_unique<brillo::MockUdevListEntry>();
EXPECT_CALL(*list_entry2, GetName())
.WillOnce(Return(kFakePort0SOPPrimeAltModeSysPath));
EXPECT_CALL(*list_entry2, GetNext())
.WillOnce(Return(ByMove(std::move(list_entry3))));
// Cable.
auto list_entry1 = std::make_unique<brillo::MockUdevListEntry>();
EXPECT_CALL(*list_entry1, GetName()).WillOnce(Return(kFakePort0CableSysPath));
EXPECT_CALL(*list_entry1, GetNext())
.WillOnce(Return(ByMove(std::move(list_entry2))));
auto enumerate = std::make_unique<brillo::MockUdevEnumerate>();
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kTypeCSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kUsbPdSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, ScanDevices()).WillOnce(Return(true));
EXPECT_CALL(*enumerate, GetListEntry())
.WillOnce(Return(ByMove(std::move(list_entry1))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateEnumerate())
.WillOnce(Return(ByMove(std::move(enumerate))));
monitor_->SetUdev(std::move(udev));
ASSERT_TRUE(monitor_->ScanDevices());
EXPECT_THAT(1, typec_observer_->GetNumCables());
EXPECT_THAT(1, typec_observer_->GetNumCableAltModes());
}
// Check that a basic partner change event gets detected correctly.
TEST_F(UdevMonitorTest, PartnerChanged) {
// Fake the calls for partner change.
auto device_partner_change = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*device_partner_change, GetSysPath())
.WillOnce(Return(kFakePort0PartnerSysPath));
EXPECT_CALL(*device_partner_change, GetAction()).WillOnce(Return("change"));
// Create the Mock Udev objects and function invocation expectations.
InitMockUdevMonitor();
EXPECT_CALL(*mock_udev_monitor_, ReceiveDevice())
.WillOnce(Return(ByMove(std::move(device_partner_change))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateMonitorFromNetlink(StrEq(kUdevMonitorName)))
.WillOnce(Return(ByMove(std::move(mock_udev_monitor_))));
monitor_->SetUdev(std::move(udev));
EXPECT_THAT(0, typec_observer_->GetNumPartnerChangeEvents());
// Skip initial scanning, since we are only interested in testing the change
// event.
ASSERT_TRUE(monitor_->BeginMonitoring());
// It's too tedious to poke the socket pair to actually trigger the
// FileDescriptorWatcher without it running repeatedly.
//
// Instead we manually call HandleUdevEvent. Effectively this is equivalent to
// triggering the event handler using the FileDescriptorWatcher.
monitor_->HandleUdevEvent();
EXPECT_THAT(1, typec_observer_->GetNumPartnerChangeEvents());
}
// Check that a basic port change event gets detected correctly.
TEST_F(UdevMonitorTest, PortChanged) {
// Fake the calls for port change.
auto device_port_change = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*device_port_change, GetSysPath())
.WillOnce(Return(kFakePort0SysPath));
EXPECT_CALL(*device_port_change, GetAction()).WillOnce(Return("change"));
// Create the Mock Udev objects and function invocation expectations.
InitMockUdevMonitor();
EXPECT_CALL(*mock_udev_monitor_, ReceiveDevice())
.WillOnce(Return(ByMove(std::move(device_port_change))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateMonitorFromNetlink(StrEq(kUdevMonitorName)))
.WillOnce(Return(ByMove(std::move(mock_udev_monitor_))));
monitor_->SetUdev(std::move(udev));
// Prep the typec observer state for future events.
typec_observer_->ResetPortChanged(0);
EXPECT_FALSE(typec_observer_->PortChanged(0));
// Skip initial scanning, since we are only interested in testing the change
// event.
ASSERT_TRUE(monitor_->BeginMonitoring());
// It's too tedious to poke the socket pair to actually trigger the
// FileDescriptorWatcher without it running repeatedly.
//
// Instead we manually call HandleUdevEvent. Effectively this is equivalent to
// triggering the event handler using the FileDescriptorWatcher.
monitor_->HandleUdevEvent();
EXPECT_TRUE(typec_observer_->PortChanged(0));
}
// Check USB PD device event parsing for addition, removal and
// invalid filepath cases.
TEST_F(UdevMonitorTest, PdDevice) {
// Create a socket-pair; to help poke the udev monitoring logic.
auto fds = std::make_unique<brillo::ScopedSocketPair>();
// Fake the calls for PD device add.
auto pd_add = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*pd_add, GetSysPath()).WillOnce(Return(kFakePd0SysPath));
EXPECT_CALL(*pd_add, GetAction()).WillOnce(Return("add"));
// Fake the calls for PD device remove.
auto pd_remove = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*pd_remove, GetSysPath()).WillOnce(Return(kFakePd0SysPath));
EXPECT_CALL(*pd_remove, GetAction()).WillOnce(Return("remove"));
// Fake the calls for PD device with an invalid path.
auto pd_invalid = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*pd_invalid, GetSysPath()).WillOnce(Return(kInvalidPdSyspath));
EXPECT_CALL(*pd_invalid, GetAction()).WillOnce(Return("add"));
// Create the Mock Udev objects and function invocation expectations.
InitMockUdevMonitor();
EXPECT_CALL(*mock_udev_monitor_, ReceiveDevice())
.WillOnce(Return(ByMove(std::move(pd_add))))
.WillOnce(Return(ByMove(std::move(pd_remove))))
.WillOnce(Return(ByMove(std::move(pd_invalid))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateMonitorFromNetlink(StrEq(kUdevMonitorName)))
.WillOnce(Return(ByMove(std::move(mock_udev_monitor_))));
monitor_->SetUdev(std::move(udev));
EXPECT_THAT(0, typec_observer_->GetNumPdDevices());
// Skip initial scanning, since we are only interested in testing the parsing
// capability for PD devices..
ASSERT_TRUE(monitor_->BeginMonitoring());
// It's too tedious to poke the socket pair to actually trigger the
// FileDescriptorWatcher without it running repeatedly.
//
// Instead we manually call HandleUdevEvent. Effectively this equivalent to
// triggering the event handler using the FileDescriptorWatcher.
monitor_->HandleUdevEvent();
EXPECT_THAT(1, typec_observer_->GetNumPdDevices());
monitor_->HandleUdevEvent();
EXPECT_THAT(0, typec_observer_->GetNumPdDevices());
monitor_->HandleUdevEvent();
EXPECT_THAT(0, typec_observer_->GetNumPdDevices());
}
} // namespace typecd