blob: e1a58b9314f865c42906b1b32d7be48e835879db [file] [log] [blame]
// Copyright 2020 The Chromium 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 "services/device/serial/bluetooth_serial_port_impl.h"
#include "base/command_line.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "device/bluetooth/test/mock_bluetooth_socket.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/io_buffer.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/public/cpp/test/fake_serial_port_client.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "services/device/serial/buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::WithArgs;
constexpr char kBuffer[] = "test";
constexpr char kDeviceAddress[] = "00:00:00:00:00:00";
constexpr uint32_t kElementNumBytes = 1;
constexpr uint32_t kCapacityNumBytes = 64;
class BluetoothSerialPortImplTest : public testing::Test {
public:
BluetoothSerialPortImplTest() {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableBluetoothSerialPortProfileInSerialApi);
}
BluetoothSerialPortImplTest(const BluetoothSerialPortImplTest&) = delete;
BluetoothSerialPortImplTest& operator=(const BluetoothSerialPortImplTest&) =
delete;
~BluetoothSerialPortImplTest() override = default;
void CreatePort(
mojo::Remote<mojom::SerialPort>* port,
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher>* watcher) {
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher_remote;
*watcher = mojo::MakeSelfOwnedReceiver(
std::make_unique<mojom::SerialPortConnectionWatcher>(),
watcher_remote.InitWithNewPipeAndPassReceiver());
scoped_refptr<MockBluetoothAdapter> adapter =
base::MakeRefCounted<MockBluetoothAdapter>();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
mock_device_ = std::make_unique<MockBluetoothDevice>(
adapter.get(), 0, "Test Device", kDeviceAddress, false, false);
mock_device_->AddUUID(GetSerialPortProfileUUID());
EXPECT_CALL(*adapter, GetDevice(kDeviceAddress))
.WillOnce(Return(mock_device_.get()));
EXPECT_CALL(*mock_device_,
ConnectToService(GetSerialPortProfileUUID(), _, _))
.WillOnce(RunOnceCallback<1>(mock_socket_));
base::RunLoop loop;
BluetoothSerialPortImpl::Open(
std::move(adapter), kDeviceAddress,
mojom::SerialConnectionOptions::New(), FakeSerialPortClient::Create(),
std::move(watcher_remote),
base::BindLambdaForTesting(
[&](mojo::PendingRemote<mojom::SerialPort> remote) {
EXPECT_TRUE(remote.is_valid());
port->Bind(std::move(remote));
loop.Quit();
}));
loop.Run();
}
void CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer,
mojo::ScopedDataPipeConsumerHandle* consumer) {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(options);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = kElementNumBytes;
options.capacity_num_bytes = kCapacityNumBytes;
MojoResult result = mojo::CreateDataPipe(&options, producer, consumer);
DCHECK_EQ(result, MOJO_RESULT_OK);
}
MockBluetoothSocket& mock_socket() { return *mock_socket_; }
private:
scoped_refptr<MockBluetoothSocket> mock_socket_ =
base::MakeRefCounted<MockBluetoothSocket>();
std::unique_ptr<MockBluetoothDevice> mock_device_;
base::test::SingleThreadTaskEnvironment task_environment_;
};
} // namespace
TEST_F(BluetoothSerialPortImplTest, OpenFailure) {
scoped_refptr<MockBluetoothAdapter> adapter =
base::MakeRefCounted<MockBluetoothAdapter>();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
auto mock_device = std::make_unique<MockBluetoothDevice>(
adapter.get(), 0, "Test Device", kDeviceAddress, false, false);
mock_device->AddUUID(GetSerialPortProfileUUID());
EXPECT_CALL(*adapter, GetDevice(kDeviceAddress))
.WillOnce(Return(mock_device.get()));
EXPECT_CALL(*mock_device, ConnectToService(GetSerialPortProfileUUID(), _, _))
.WillOnce(RunOnceCallback<2>("Error"));
EXPECT_CALL(mock_socket(), Receive(_, _, _)).Times(0);
EXPECT_CALL(mock_socket(), Disconnect(_)).Times(0);
base::RunLoop loop;
BluetoothSerialPortImpl::Open(
std::move(adapter), kDeviceAddress, mojom::SerialConnectionOptions::New(),
FakeSerialPortClient::Create(), mojo::NullRemote(),
base::BindLambdaForTesting(
[&](mojo::PendingRemote<mojom::SerialPort> remote) {
EXPECT_FALSE(remote.is_valid());
loop.Quit();
}));
loop.Run();
}
TEST_F(BluetoothSerialPortImplTest, StartWritingTest) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePort(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
uint32_t bytes_read = std::char_traits<char>::length(kBuffer);
auto write_buffer = base::MakeRefCounted<net::StringIOBuffer>(kBuffer);
MojoResult result =
producer->WriteData(&kBuffer, &bytes_read, MOJO_WRITE_DATA_FLAG_NONE);
EXPECT_EQ(result, MOJO_RESULT_OK);
EXPECT_CALL(mock_socket(), Send)
.WillOnce(WithArgs<0, 1, 2>(Invoke(
[&](scoped_refptr<net::IOBuffer> buf, int buffer_size,
MockBluetoothSocket::SendCompletionCallback success_callback) {
ASSERT_EQ(buffer_size, int{bytes_read});
// EXPECT_EQ only does a shallow comparison, so it's necessary to
// iterate through both objects and compare each character.
for (int i = 0; i < buffer_size; i++) {
EXPECT_EQ(buf->data()[i], kBuffer[i])
<< "buffer comparison failed at index " << i;
}
std::move(success_callback).Run(buffer_size);
})));
EXPECT_CALL(mock_socket(), Disconnect(_)).WillOnce(RunOnceCallback<0>());
serial_port->StartWriting(std::move(consumer));
EXPECT_EQ(write_buffer->size(), int{bytes_read});
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(disconnect_loop.QuitClosure());
serial_port.reset();
disconnect_loop.Run();
}
TEST_F(BluetoothSerialPortImplTest, StartReadingTest) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePort(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
uint32_t bytes_read = std::char_traits<char>::length(kBuffer);
auto write_buffer = base::MakeRefCounted<net::StringIOBuffer>(kBuffer);
MojoResult result =
producer->WriteData(&kBuffer, &bytes_read, MOJO_WRITE_DATA_FLAG_NONE);
EXPECT_EQ(result, MOJO_RESULT_OK);
EXPECT_CALL(mock_socket(), Receive(_, _, _))
.WillOnce(RunOnceCallback<1>(write_buffer->size(), write_buffer))
.WillOnce(RunOnceCallback<2>(BluetoothSocket::kSystemError, "Error"));
EXPECT_CALL(mock_socket(), Disconnect(_)).WillOnce(RunOnceCallback<0>());
serial_port->StartReading(std::move(producer));
ASSERT_EQ(write_buffer->size(), int{bytes_read});
int size = write_buffer->size();
// EXPECT_EQ only does a shallow comparison, so it's necessary to iterate
// through both objects and compare each character.
for (int i = 0; i < size; i++) {
EXPECT_EQ(write_buffer->data()[i], kBuffer[i])
<< "buffer comparison failed at index " << i;
}
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(disconnect_loop.QuitClosure());
serial_port.reset();
disconnect_loop.Run();
}
TEST_F(BluetoothSerialPortImplTest, Drain) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePort(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
serial_port->StartWriting(std::move(consumer));
producer.reset();
base::RunLoop drain_loop;
serial_port->Drain(drain_loop.QuitClosure());
drain_loop.Run();
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(disconnect_loop.QuitClosure());
serial_port.reset();
disconnect_loop.Run();
}
TEST_F(BluetoothSerialPortImplTest, Close) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePort(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
EXPECT_CALL(mock_socket(), Disconnect(_)).WillOnce(RunOnceCallback<0>());
base::RunLoop close_loop;
serial_port->Close(close_loop.QuitClosure());
close_loop.Run();
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(
base::BindLambdaForTesting([&]() { disconnect_loop.Quit(); }));
serial_port.reset();
disconnect_loop.Run();
}
} // namespace device