blob: 9432e52bf5ef0303d69ca9d34a951b88e3a8be0f [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/device/serial/bluetooth_serial_device_enumerator.h"
#include <memory>
#include <optional>
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/cpp/device_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
using ::base::test::InvokeFuture;
using ::base::test::TestFuture;
using ::testing::NiceMock;
using ::testing::Return;
constexpr char kTestDeviceAddress[] = "11:12:13:14:15:16";
constexpr char kTestDeviceName[] = "Device w/SPP";
std::unique_ptr<device::MockBluetoothDevice> CreateDevice(
device::MockBluetoothAdapter* adapter,
const char* device_name,
const std::string& device_address) {
return std::make_unique<NiceMock<device::MockBluetoothDevice>>(
adapter, /*bluetooth_class=*/0u, device_name, device_address,
/*paired=*/true, /*connected=*/true);
}
class MockSerialPortEnumeratorObserver
: public SerialDeviceEnumerator::Observer {
public:
MOCK_METHOD1(OnPortAdded, void(const mojom::SerialPortInfo&));
MOCK_METHOD1(OnPortRemoved, void(const mojom::SerialPortInfo&));
MOCK_METHOD1(OnPortConnectedStateChanged, void(const mojom::SerialPortInfo&));
};
class BluetoothSerialDeviceEnumeratorTest : public testing::Test {
public:
BluetoothSerialDeviceEnumeratorTest() = default;
scoped_refptr<base::SingleThreadTaskRunner> adapter_runner() {
return task_environment_.GetMainThreadTaskRunner();
}
private:
base::test::TaskEnvironment task_environment_;
};
} // namespace
TEST_F(BluetoothSerialDeviceEnumeratorTest, ConstructDestruct) {
auto mock_adapter =
base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
base::RunLoop run_loop;
mock_adapter->Initialize(run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(mock_adapter->IsInitialized());
device::BluetoothAdapterFactory::Get()->SetAdapterForTesting(mock_adapter);
BluetoothSerialDeviceEnumerator enumerator(adapter_runner());
// Prevent memory leak warning.
enumerator.SynchronouslyResetHelperForTesting();
}
TEST_F(BluetoothSerialDeviceEnumeratorTest, ConstructWaitForAdapter) {
auto mock_adapter =
base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
{
base::RunLoop run_loop;
mock_adapter->Initialize(run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(mock_adapter->IsInitialized());
}
device::BluetoothAdapterFactory::Get()->SetAdapterForTesting(mock_adapter);
BluetoothSerialDeviceEnumerator enumerator(adapter_runner());
// Wait for the enumerator to complete its initial enumeration.
TestFuture<std::vector<device::mojom::SerialPortInfoPtr>> get_devices_future;
enumerator.GetDevicesAfterInitialEnumeration(
get_devices_future.GetCallback());
ASSERT_TRUE(get_devices_future.Wait());
// Prevent memory leak warning.
enumerator.SynchronouslyResetHelperForTesting();
}
TEST_F(BluetoothSerialDeviceEnumeratorTest, GetDevices) {
auto mock_adapter =
base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
base::RunLoop run_loop;
mock_adapter->Initialize(run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(mock_adapter->IsInitialized());
auto mock_device =
CreateDevice(mock_adapter.get(), kTestDeviceName, kTestDeviceAddress);
mock_device->AddUUID(GetSerialPortProfileUUID());
mock_adapter->AddMockDevice(std::move(mock_device));
EXPECT_CALL(*mock_adapter, GetDevices())
.WillOnce(Return(mock_adapter->GetConstMockDevices()));
device::BluetoothAdapterFactory::Get()->SetAdapterForTesting(mock_adapter);
BluetoothSerialDeviceEnumerator enumerator(adapter_runner());
// Before the initial enumeration GetDevices returns no ports.
EXPECT_TRUE(enumerator.GetDevices().empty());
// Check that the port is found during initial enumeration.
TestFuture<std::vector<device::mojom::SerialPortInfoPtr>> get_devices_future;
enumerator.GetDevicesAfterInitialEnumeration(
get_devices_future.GetCallback());
EXPECT_EQ(1u, get_devices_future.Get().size());
// After the initial enumeration GetDevices returns the port.
EXPECT_EQ(1u, enumerator.GetDevices().size());
// Prevent memory leak warning.
enumerator.SynchronouslyResetHelperForTesting();
}
TEST_F(BluetoothSerialDeviceEnumeratorTest, CreateWithDevice) {
auto mock_adapter =
base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
{
base::RunLoop run_loop;
mock_adapter->Initialize(run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(mock_adapter->IsInitialized());
}
auto mock_device =
CreateDevice(mock_adapter.get(), kTestDeviceName, kTestDeviceAddress);
mock_device->AddUUID(GetSerialPortProfileUUID());
mock_adapter->AddMockDevice(std::move(mock_device));
EXPECT_CALL(*mock_adapter, GetDevices())
.WillOnce(Return(mock_adapter->GetConstMockDevices()));
device::BluetoothAdapterFactory::Get()->SetAdapterForTesting(mock_adapter);
BluetoothSerialDeviceEnumerator enumerator(adapter_runner());
MockSerialPortEnumeratorObserver observer;
enumerator.AddObserver(&observer);
TestFuture<const device::mojom::SerialPortInfo&> port_future;
EXPECT_CALL(observer, OnPortAdded).WillOnce(InvokeFuture(port_future));
EXPECT_FALSE(port_future.Get().token.is_empty());
EXPECT_EQ(base::FilePath::FromASCII(kTestDeviceAddress),
port_future.Get().path);
EXPECT_EQ(kTestDeviceName, port_future.Get().display_name);
EXPECT_EQ(std::nullopt, port_future.Get().serial_number);
EXPECT_EQ(mojom::SerialPortType::BLUETOOTH_CLASSIC_RFCOMM,
port_future.Get().type);
EXPECT_FALSE(port_future.Get().has_vendor_id);
EXPECT_EQ(0x0, port_future.Get().vendor_id);
EXPECT_FALSE(port_future.Get().has_product_id);
EXPECT_EQ(0x0, port_future.Get().product_id);
TestFuture<std::vector<device::mojom::SerialPortInfoPtr>> get_devices_future;
enumerator.GetDevicesAfterInitialEnumeration(
get_devices_future.GetCallback());
ASSERT_EQ(1u, get_devices_future.Get().size());
auto address = enumerator.GetAddressFromToken(port_future.Get().token);
ASSERT_TRUE(address);
EXPECT_EQ(*address, kTestDeviceAddress);
EXPECT_EQ(GetSerialPortProfileUUID(),
enumerator.GetServiceClassIdFromToken(port_future.Get().token));
const base::UnguessableToken unused_token;
EXPECT_FALSE(enumerator.GetAddressFromToken(unused_token));
EXPECT_FALSE(enumerator.GetServiceClassIdFromToken(unused_token).IsValid());
// Second add - which will be skipped.
const BluetoothDevice::UUIDSet service_class_ids = {
GetSerialPortProfileUUID()};
const std::u16string device_name(
base::UTF8ToUTF16(std::string(kTestDeviceName)));
enumerator.DeviceAddedOrChanged(kTestDeviceAddress, device_name,
service_class_ids, /*is_connected=*/true);
ASSERT_EQ(1u, enumerator.GetDevices().size());
// Remove device.
TestFuture<const mojom::SerialPortInfo&> disconnect_future;
EXPECT_CALL(observer, OnPortConnectedStateChanged).Times(0);
EXPECT_CALL(observer, OnPortRemoved)
.WillOnce(InvokeFuture(disconnect_future));
enumerator.DeviceRemoved(kTestDeviceAddress);
EXPECT_EQ(disconnect_future.Get().token, port_future.Get().token);
EXPECT_FALSE(disconnect_future.Get().connected);
// Remove again - now nonexistent.
enumerator.DeviceRemoved(kTestDeviceAddress);
// Prevent memory leak warning.
enumerator.SynchronouslyResetHelperForTesting();
}
TEST_F(BluetoothSerialDeviceEnumeratorTest, PortConnectedState) {
base::test::ScopedFeatureList scoped_feature_list(
features::kSerialPortConnected);
// Set a mock adapter and wait until it is initialized.
auto mock_adapter =
base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
device::BluetoothAdapterFactory::Get()->SetAdapterForTesting(mock_adapter);
base::RunLoop adapter_initialized_loop;
mock_adapter->Initialize(adapter_initialized_loop.QuitClosure());
adapter_initialized_loop.Run();
EXPECT_TRUE(mock_adapter->IsInitialized());
// Create the enumerator and wait until it has completed its initial
// enumeration.
EXPECT_CALL(*mock_adapter, GetDevices())
.WillOnce(Return(mock_adapter->GetConstMockDevices()));
BluetoothSerialDeviceEnumerator enumerator(adapter_runner());
TestFuture<std::vector<device::mojom::SerialPortInfoPtr>> get_devices_future;
enumerator.GetDevicesAfterInitialEnumeration(
get_devices_future.GetCallback());
ASSERT_TRUE(get_devices_future.Wait());
MockSerialPortEnumeratorObserver observer;
enumerator.AddObserver(&observer);
// Create a Bluetooth device with a serial port.
auto mock_device =
CreateDevice(mock_adapter.get(), kTestDeviceName, kTestDeviceAddress);
mock_device->AddUUID(GetSerialPortProfileUUID());
// Add the device. The observer is notified that a new port was added.
TestFuture<const mojom::SerialPortInfo&> port_future;
EXPECT_CALL(observer, OnPortAdded).WillOnce(InvokeFuture(port_future));
EXPECT_CALL(observer, OnPortConnectedStateChanged).Times(0);
enumerator.DeviceAddedForTesting(mock_adapter.get(), mock_device.get());
EXPECT_TRUE(port_future.Get().connected);
// Disconnect the Bluetooth device and wait for the observer to be notified.
TestFuture<const mojom::SerialPortInfo&> disconnect_future;
EXPECT_CALL(observer, OnPortConnectedStateChanged)
.WillOnce(InvokeFuture(disconnect_future));
mock_device->SetConnected(false);
enumerator.DeviceChangedForTesting(mock_adapter.get(), mock_device.get());
EXPECT_EQ(disconnect_future.Get().token, port_future.Get().token);
EXPECT_FALSE(disconnect_future.Get().connected);
// Reconnect the Bluetooth device and wait for the observer to be notified.
TestFuture<const mojom::SerialPortInfo&> reconnect_future;
EXPECT_CALL(observer, OnPortConnectedStateChanged)
.WillOnce(InvokeFuture(reconnect_future));
mock_device->SetConnected(true);
enumerator.DeviceChangedForTesting(mock_adapter.get(), mock_device.get());
EXPECT_EQ(reconnect_future.Get().token, port_future.Get().token);
EXPECT_TRUE(reconnect_future.Get().connected);
// Prevent memory leak warning.
enumerator.SynchronouslyResetHelperForTesting();
}
TEST_F(BluetoothSerialDeviceEnumeratorTest,
RemoveObserverIsCalledWhenAdapterHelperDestruct) {
auto mock_adapter =
base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
{
base::RunLoop run_loop;
mock_adapter->Initialize(run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(mock_adapter->IsInitialized());
}
device::BluetoothAdapterFactory::Get()->SetAdapterForTesting(mock_adapter);
std::unique_ptr<BluetoothSerialDeviceEnumerator> enumerator;
{
base::RunLoop run_loop;
EXPECT_CALL(*mock_adapter, AddObserver)
.WillOnce([&run_loop](BluetoothAdapter::Observer* observer) {
run_loop.Quit();
});
enumerator =
std::make_unique<BluetoothSerialDeviceEnumerator>(adapter_runner());
run_loop.Run();
}
{
base::RunLoop run_loop;
EXPECT_CALL(*mock_adapter, RemoveObserver)
.WillOnce([&run_loop](BluetoothAdapter::Observer* observer) {
run_loop.Quit();
});
enumerator->SynchronouslyResetHelperForTesting();
run_loop.Run();
}
}
TEST_F(BluetoothSerialDeviceEnumeratorTest,
PendingCallbacksInvokedOnDestruction) {
auto mock_adapter =
base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
{
base::RunLoop run_loop;
mock_adapter->Initialize(run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(mock_adapter->IsInitialized());
}
device::BluetoothAdapterFactory::Get()->SetAdapterForTesting(mock_adapter);
auto enumerator =
std::make_unique<BluetoothSerialDeviceEnumerator>(adapter_runner());
TestFuture<std::vector<mojom::SerialPortInfoPtr>> get_devices_future;
enumerator->GetDevicesAfterInitialEnumeration(
get_devices_future.GetCallback());
// Destroy the enumerator and make sure the callback is called.
enumerator->SynchronouslyResetHelperForTesting();
enumerator.reset();
EXPECT_TRUE(get_devices_future.Get().empty());
}
} // namespace device