blob: 479c9ec8e48c18f0c9b40a82cfec0e8034a2c3da [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";
// A really dumb observer to verify that UdevMonitor is invoking the right
// callbacks.
class TestObserver : public UdevMonitor::Observer {
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_++; }
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_; }
private:
int num_partners_;
int num_ports_;
int num_cables_;
int num_cable_altmodes_;
int num_partner_change_events_;
};
} // namespace
class UdevMonitorTest : public ::testing::Test {
public:
UdevMonitorTest()
: task_environment_(
base::test::TaskEnvironment::MainThreadType::IO,
base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC) {}
protected:
void SetUp() override {
observer_ = std::make_unique<TestObserver>();
monitor_ = std::make_unique<UdevMonitor>();
monitor_->AddObserver(observer_.get());
}
void TearDown() override {
monitor_.reset();
observer_.reset();
}
// Add a task environment to keep the FileDescriptorWatcher code happy.
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestObserver> 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(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, observer_->GetNumPorts());
ASSERT_TRUE(monitor_->ScanDevices());
EXPECT_THAT(1, observer_->GetNumPorts());
EXPECT_THAT(1, 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(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, observer_->GetNumPorts());
EXPECT_THAT(0, 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, observer_->GetNumPorts());
monitor_->HandleUdevEvent();
EXPECT_THAT(1, observer_->GetNumPartners());
monitor_->HandleUdevEvent();
EXPECT_THAT(0, observer_->GetNumPartners());
monitor_->HandleUdevEvent();
EXPECT_THAT(1, 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(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, 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(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, observer_->GetNumCables());
EXPECT_THAT(1, 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(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, 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, observer_->GetNumPartnerChangeEvents());
}
} // namespace typecd