blob: c419b688fcb6a822510eb401188cdbc4bb3a8c88 [file] [log] [blame]
// Copyright 2020 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 "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 kInvalidPortSysPath[] = "/sys/class/typec/a-yz";
constexpr char kFakeUsbSysPath[] = "/sys/bus/usb/devices/usb1/1-1";
constexpr char kInvalidUsbSysPath[] = "/sys/bus/usb/devices/usb1/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) 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 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_; }
// 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_;
// 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_;
};
// Test UsbObserver to verify that right callback is called.
class TestUsbObserver : public UdevMonitor::UsbObserver {
public:
void OnDeviceAddedOrRemoved(const base::FilePath& path, bool added) {
if (added)
num_devices_++;
else
num_devices_--;
}
int GetNumDevices() { return num_devices_; }
private:
int num_devices_;
};
} // 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>();
usb_observer_ = std::make_unique<TestUsbObserver>();
monitor_ = std::make_unique<UdevMonitor>();
monitor_->AddTypecObserver(typec_observer_.get());
monitor_->AddUsbObserver(usb_observer_.get());
}
void TearDown() override {
monitor_.reset();
typec_observer_.reset();
usb_observer_.reset();
}
// Add a task environment to keep the FileDescriptorWatcher code happy.
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestTypecObserver> typec_observer_;
std::unique_ptr<TestUsbObserver> usb_observer_;
std::unique_ptr<UdevMonitor> monitor_;
};
TEST_F(UdevMonitorTest, TestBasic) {
// 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(kUsbSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kTypeCSubsystem)))
.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, TestHotplug) {
// Create a socket-pair; to help poke the udev monitoring logic.
auto fds = std::make_unique<brillo::ScopedSocketPair>();
// 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.
auto monitor = std::make_unique<brillo::MockUdevMonitor>();
EXPECT_CALL(*monitor,
FilterAddMatchSubsystemDeviceType(StrEq(kUsbSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, FilterAddMatchSubsystemDeviceType(
StrEq(kTypeCSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, EnableReceiving()).WillOnce(Return(true));
EXPECT_CALL(*monitor, GetFileDescriptor()).WillOnce(Return(fds->left));
EXPECT_CALL(*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(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, TestInvalidPortSyspath) {
// Create a socket-pair; to help poke the udev monitoring logic.
auto fds = std::make_unique<brillo::ScopedSocketPair>();
// 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.
auto monitor = std::make_unique<brillo::MockUdevMonitor>();
EXPECT_CALL(*monitor,
FilterAddMatchSubsystemDeviceType(StrEq(kUsbSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, FilterAddMatchSubsystemDeviceType(
StrEq(kTypeCSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, EnableReceiving()).WillOnce(Return(true));
EXPECT_CALL(*monitor, GetFileDescriptor()).WillOnce(Return(fds->left));
EXPECT_CALL(*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(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, TestCableAndAltModeAddition) {
// 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(kUsbSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kTypeCSubsystem)))
.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, TestPartnerChanged) {
// Create a socket-pair; to help poke the udev monitoring logic.
auto fds = std::make_unique<brillo::ScopedSocketPair>();
// 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.
auto monitor = std::make_unique<brillo::MockUdevMonitor>();
EXPECT_CALL(*monitor,
FilterAddMatchSubsystemDeviceType(StrEq(kUsbSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, FilterAddMatchSubsystemDeviceType(
StrEq(kTypeCSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, EnableReceiving()).WillOnce(Return(true));
EXPECT_CALL(*monitor, GetFileDescriptor()).WillOnce(Return(fds->left));
EXPECT_CALL(*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(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, TestPortChanged) {
// Create a socket-pair; to help poke the udev monitoring logic.
auto fds = std::make_unique<brillo::ScopedSocketPair>();
// 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.
auto monitor = std::make_unique<brillo::MockUdevMonitor>();
EXPECT_CALL(*monitor,
FilterAddMatchSubsystemDeviceType(StrEq(kUsbSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, FilterAddMatchSubsystemDeviceType(
StrEq(kTypeCSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, EnableReceiving()).WillOnce(Return(true));
EXPECT_CALL(*monitor, GetFileDescriptor()).WillOnce(Return(fds->left));
EXPECT_CALL(*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(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 that a USB device can be detected after init.
TEST_F(UdevMonitorTest, TestUsbDeviceScan) {
// Create the Mock Udev objects and function invocation expectations.
auto list_entry = std::make_unique<brillo::MockUdevListEntry>();
EXPECT_CALL(*list_entry, GetName()).WillOnce(Return(kFakeUsbSysPath));
EXPECT_CALL(*list_entry, GetNext()).WillOnce(Return(ByMove(nullptr)));
auto enumerate = std::make_unique<brillo::MockUdevEnumerate>();
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kUsbSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, AddMatchSubsystem(StrEq(kTypeCSubsystem)))
.WillOnce(Return(true));
EXPECT_CALL(*enumerate, ScanDevices()).WillOnce(Return(true));
EXPECT_CALL(*enumerate, GetListEntry())
.WillOnce(Return(ByMove(std::move(list_entry))));
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, usb_observer_->GetNumDevices());
}
// Check that a USB device add/remove can be detected through monitoring.
TEST_F(UdevMonitorTest, TestUsbDeviceAddRemove) {
// Create a socket-pair; to help poke the udev monitoring logic.
auto fds = std::make_unique<brillo::ScopedSocketPair>();
// Fake the calls for USB add.
auto usb_device_add = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*usb_device_add, GetSysPath()).WillOnce(Return(kFakeUsbSysPath));
EXPECT_CALL(*usb_device_add, GetAction()).WillOnce(Return("add"));
// Fake the calls for USB remove.
auto usb_device_remove = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*usb_device_remove, GetSysPath())
.WillOnce(Return(kFakeUsbSysPath));
EXPECT_CALL(*usb_device_remove, GetAction()).WillOnce(Return("remove"));
// Create the Mock Udev objects and function invocation expectations.
auto monitor = std::make_unique<brillo::MockUdevMonitor>();
EXPECT_CALL(*monitor,
FilterAddMatchSubsystemDeviceType(StrEq(kUsbSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, FilterAddMatchSubsystemDeviceType(
StrEq(kTypeCSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, EnableReceiving()).WillOnce(Return(true));
EXPECT_CALL(*monitor, GetFileDescriptor()).WillOnce(Return(fds->left));
EXPECT_CALL(*monitor, ReceiveDevice())
.WillOnce(Return(ByMove(std::move(usb_device_add))))
.WillOnce(Return(ByMove(std::move(usb_device_remove))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateMonitorFromNetlink(StrEq(kUdevMonitorName)))
.WillOnce(Return(ByMove(std::move(monitor))));
monitor_->SetUdev(std::move(udev));
EXPECT_THAT(0, usb_observer_->GetNumDevices());
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, usb_observer_->GetNumDevices());
monitor_->HandleUdevEvent();
EXPECT_THAT(0, usb_observer_->GetNumDevices());
}
// Test that invalid syspath does not make callback.
TEST_F(UdevMonitorTest, TestInvalidUsbDeviceSyspath) {
// Create a socket-pair; to help poke the udev monitoring logic.
auto fds = std::make_unique<brillo::ScopedSocketPair>();
// Fake the calls for USB add.
auto usb_device = std::make_unique<brillo::MockUdevDevice>();
EXPECT_CALL(*usb_device, GetSysPath()).WillOnce(Return(kInvalidUsbSysPath));
EXPECT_CALL(*usb_device, GetAction()).WillOnce(Return("add"));
// Create the Mock Udev objects and function invocation expectations.
auto monitor = std::make_unique<brillo::MockUdevMonitor>();
EXPECT_CALL(*monitor,
FilterAddMatchSubsystemDeviceType(StrEq(kUsbSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, FilterAddMatchSubsystemDeviceType(
StrEq(kTypeCSubsystem), nullptr))
.WillOnce(Return(true));
EXPECT_CALL(*monitor, EnableReceiving()).WillOnce(Return(true));
EXPECT_CALL(*monitor, GetFileDescriptor()).WillOnce(Return(fds->left));
EXPECT_CALL(*monitor, ReceiveDevice())
.WillOnce(Return(ByMove(std::move(usb_device))));
auto udev = std::make_unique<brillo::MockUdev>();
EXPECT_CALL(*udev, CreateMonitorFromNetlink(StrEq(kUdevMonitorName)))
.WillOnce(Return(ByMove(std::move(monitor))));
monitor_->SetUdev(std::move(udev));
EXPECT_THAT(0, usb_observer_->GetNumDevices());
ASSERT_TRUE(monitor_->BeginMonitoring());
// Manually call HandleUdevEvent. Effectively this equivalent to triggering
// the event handler using the FileDescriptorWatcher.
monitor_->HandleUdevEvent();
EXPECT_THAT(0, usb_observer_->GetNumDevices());
}
} // namespace typecd