chromium/chrome/services/sharing/nearby/platform/bluetooth_server_socket_unittest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/services/sharing/nearby/platform/bluetooth_server_socket.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/services/sharing/nearby/platform/bluetooth_device.h"
#include "chrome/services/sharing/nearby/platform/bluetooth_socket.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace nearby {
namespace chrome {

namespace {

const char kDeviceAddress[] = "DeviceAddress";
const char kDeviceName[] = "DeviceName";

class FakeSocket : public bluetooth::mojom::Socket {
 public:
  FakeSocket(mojo::ScopedDataPipeProducerHandle receive_stream,
             mojo::ScopedDataPipeConsumerHandle send_stream)
      : receive_stream_(std::move(receive_stream)),
        send_stream_(std::move(send_stream)) {}
  ~FakeSocket() override = default;

 private:
  // bluetooth::mojom::Socket:
  void Disconnect(DisconnectCallback callback) override {
    std::move(callback).Run();
  }

  mojo::ScopedDataPipeProducerHandle receive_stream_;
  mojo::ScopedDataPipeConsumerHandle send_stream_;
};

class FakeServerSocket : public bluetooth::mojom::ServerSocket {
 public:
  FakeServerSocket() = default;
  ~FakeServerSocket() override {
    if (on_destroy_callback_)
      std::move(on_destroy_callback_).Run();
  }

  void SetAcceptConnectionResult(
      bluetooth::mojom::AcceptConnectionResultPtr result) {
    accept_connection_result_ = std::move(result);
  }

  void SetOnDestroyCallback(base::OnceClosure callback) {
    on_destroy_callback_ = std::move(callback);
  }

  void SetAcceptShouldBlock(bool block) { accept_should_block_ = block; }

 private:
  // bluetooth::mojom::ServerSocket:
  void Accept(AcceptCallback callback) override {
    if (accept_should_block_) {
      accept_callback_ = std::move(callback);
      return;
    }
    std::move(callback).Run(std::move(accept_connection_result_));
  }
  void Disconnect(DisconnectCallback callback) override {
    if (accept_callback_) {
      std::move(accept_callback_).Run(nullptr);
    }
    std::move(callback).Run();
  }

  AcceptCallback accept_callback_;
  bluetooth::mojom::AcceptConnectionResultPtr accept_connection_result_;
  base::OnceClosure on_destroy_callback_;
  bool accept_should_block_;
};

}  // namespace

class BluetoothServerSocketTest : public testing::Test {
 public:
  BluetoothServerSocketTest() = default;
  ~BluetoothServerSocketTest() override = default;
  BluetoothServerSocketTest(const BluetoothServerSocketTest&) = delete;
  BluetoothServerSocketTest& operator=(const BluetoothServerSocketTest&) =
      delete;

  void SetUp() override {
    auto fake_server_socket = std::make_unique<FakeServerSocket>();
    fake_server_socket_ = fake_server_socket.get();

    mojo::PendingRemote<bluetooth::mojom::ServerSocket> pending_server_socket;

    mojo::MakeSelfOwnedReceiver(
        std::move(fake_server_socket),
        pending_server_socket.InitWithNewPipeAndPassReceiver());

    // In production Nearby Connections code, BluetoothServerSocket is created
    // on a thread separate from the thread it is later used on. Replicate this
    // behavior by creating |bluetooth_server_socket_| on a dedicated thread.
    base::RunLoop run_loop;
    auto creation_task_runner = base::ThreadPool::CreateSingleThreadTaskRunner(
        {base::MayBlock()}, base::SingleThreadTaskRunnerThreadMode::DEDICATED);
    creation_task_runner->PostTask(
        FROM_HERE, base::BindLambdaForTesting([&] {
          bluetooth_server_socket_ = std::make_unique<BluetoothServerSocket>(
              std::move(pending_server_socket));
          run_loop.Quit();
        }));
    run_loop.Run();
  }

 protected:
  raw_ptr<FakeServerSocket, DanglingUntriaged> fake_server_socket_ = nullptr;

  std::unique_ptr<BluetoothServerSocket> bluetooth_server_socket_;

 private:
  base::test::TaskEnvironment task_environment_;
};

TEST_F(BluetoothServerSocketTest, TestAccept_Success) {
  auto accept_connection_result =
      bluetooth::mojom::AcceptConnectionResult::New();

  accept_connection_result->device = bluetooth::mojom::DeviceInfo::New();
  accept_connection_result->device->address = kDeviceAddress;
  accept_connection_result->device->name_for_display = kDeviceName;

  mojo::ScopedDataPipeProducerHandle receive_pipe_producer_handle;
  mojo::ScopedDataPipeConsumerHandle receive_pipe_consumer_handle;
  ASSERT_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(/*options=*/nullptr,
                                                 receive_pipe_producer_handle,
                                                 receive_pipe_consumer_handle));

  mojo::ScopedDataPipeProducerHandle send_pipe_producer_handle;
  mojo::ScopedDataPipeConsumerHandle send_pipe_consumer_handle;
  ASSERT_EQ(MOJO_RESULT_OK,
            mojo::CreateDataPipe(/*options=*/nullptr, send_pipe_producer_handle,
                                 send_pipe_consumer_handle));

  mojo::PendingRemote<bluetooth::mojom::Socket> pending_socket;

  mojo::MakeSelfOwnedReceiver(
      std::make_unique<FakeSocket>(std::move(receive_pipe_producer_handle),
                                   std::move(send_pipe_consumer_handle)),
      pending_socket.InitWithNewPipeAndPassReceiver());

  accept_connection_result->socket = std::move(pending_socket);
  accept_connection_result->receive_stream =
      std::move(receive_pipe_consumer_handle);
  accept_connection_result->send_stream = std::move(send_pipe_producer_handle);

  fake_server_socket_->SetAcceptConnectionResult(
      std::move(accept_connection_result));

  auto bluetooth_socket = bluetooth_server_socket_->Accept();
  ASSERT_TRUE(bluetooth_socket);
  EXPECT_EQ(kDeviceName, bluetooth_socket->GetRemoteDevice()->GetName());
}

TEST_F(BluetoothServerSocketTest, TestAccept_NoConnection) {
  EXPECT_FALSE(bluetooth_server_socket_->Accept());
}

TEST_F(BluetoothServerSocketTest, TestCloseInterruptsBlockingAccept) {
  base::RunLoop run_loop;
  fake_server_socket_->SetOnDestroyCallback(run_loop.QuitClosure());
  fake_server_socket_->SetAcceptShouldBlock(true);
  auto close_socket_task_runner =
      base::ThreadPool::CreateSingleThreadTaskRunner(
          {base::MayBlock()},
          base::SingleThreadTaskRunnerThreadMode::DEDICATED);

  close_socket_task_runner->PostDelayedTask(
      FROM_HERE, base::BindLambdaForTesting([&] {
        base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
        bluetooth_server_socket_->Close();
      }),
      base::Seconds(1));

  base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
  EXPECT_FALSE(bluetooth_server_socket_->Accept());
  run_loop.Run();
}

TEST_F(BluetoothServerSocketTest, TestClose) {
  base::RunLoop run_loop;
  fake_server_socket_->SetOnDestroyCallback(run_loop.QuitClosure());
  bluetooth_server_socket_->Close();
  run_loop.Run();
}

TEST_F(BluetoothServerSocketTest, TestCloseThenAccept) {
  base::RunLoop run_loop;
  fake_server_socket_->SetOnDestroyCallback(run_loop.QuitClosure());
  bluetooth_server_socket_->Close();
  run_loop.Run();
  ASSERT_FALSE(bluetooth_server_socket_->Accept());
}

TEST_F(BluetoothServerSocketTest, TestDestroy) {
  base::RunLoop run_loop;
  fake_server_socket_->SetOnDestroyCallback(run_loop.QuitClosure());
  bluetooth_server_socket_.reset();
  run_loop.Run();
}

}  // namespace chrome
}  // namespace nearby