blob: 10382f7016ac7536be0d5a43477913226d1c9bb4 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/bluetooth/floss/floss_advertiser_client.h"
#include <utility>
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_exported_object.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "device/bluetooth/floss/floss_dbus_client.h"
#include "device/bluetooth/floss/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using testing::_;
using testing::DoAll;
constexpr char kTestSender[] = ":0.1";
const int kTestSerial = 1;
const int32_t kRegId1 = 1;
const int32_t kAdvId1 = 2;
const uint32_t kCallbackId1 = 3;
void FakeExportMethod(
const std::string& interface_name,
const std::string& method_name,
const dbus::ExportedObject::MethodCallCallback& method_call_callback,
dbus::ExportedObject::OnExportedCallback on_exported_callback) {
std::move(on_exported_callback)
.Run(interface_name, method_name, /*success=*/true);
}
} // namespace
namespace floss {
class FlossAdvertiserClientTest : public testing::Test,
public FlossAdvertiserClientObserver {
public:
FlossAdvertiserClientTest() = default;
base::Version GetCurrVersion() {
return floss::version::GetMaximalSupportedVersion();
}
void SetUp() override {
::dbus::Bus::Options options;
options.bus_type = ::dbus::Bus::BusType::SYSTEM;
bus_ = base::MakeRefCounted<::dbus::MockBus>(std::move(options));
advclient_ = FlossAdvertiserClient::Create();
callback_path_ = ::dbus::ObjectPath(kAdvertisingSetCallbackPath);
gatt_adapter_path_ = FlossDBusClient::GenerateGattPath(adapter_index_);
advclient_proxy_ = base::MakeRefCounted<::dbus::MockObjectProxy>(
bus_.get(), kGattInterface, gatt_adapter_path_);
}
void TearDown() override {
// Clean up the advertiser client first to get rid of all references to
// various buses, object proxies, etc.
advclient_.reset();
}
void DoOnAdvertisingSetStarted(
dbus::ExportedObject::MethodCallCallback method_handler,
RegId reg_id,
AdvertiserId adv_id,
int32_t tx_power,
AdvertisingStatus status) {
dbus::MethodCall method_call("some.interface",
advertiser::kOnAdvertisingSetStarted);
method_call.SetPath(callback_path_);
method_call.SetSender(kTestSender);
method_call.SetSerial(kTestSerial);
dbus::MessageWriter writer(&method_call);
writer.AppendInt32(reg_id);
writer.AppendInt32(adv_id);
writer.AppendInt32(tx_power);
writer.AppendUint32(static_cast<uint32_t>(status));
method_handler.Run(
&method_call,
base::BindOnce([](std::unique_ptr<dbus::Response> response) {
ASSERT_TRUE(!!response);
EXPECT_NE(response->GetMessageType(),
dbus::Message::MessageType::MESSAGE_ERROR);
}));
}
void DoOnAdvertisingSetStopped(
dbus::ExportedObject::MethodCallCallback method_handler,
AdvertiserId adv_id) {
dbus::MethodCall method_call("some.interface",
advertiser::kOnAdvertisingSetStopped);
method_call.SetPath(callback_path_);
method_call.SetSender(kTestSender);
method_call.SetSerial(kTestSerial);
dbus::MessageWriter writer(&method_call);
writer.AppendInt32(adv_id);
method_handler.Run(
&method_call,
base::BindOnce([](std::unique_ptr<dbus::Response> response) {
ASSERT_TRUE(!!response);
EXPECT_NE(response->GetMessageType(),
dbus::Message::MessageType::MESSAGE_ERROR);
}));
}
void DoOnAdvertisingParametersUpdated(
dbus::ExportedObject::MethodCallCallback method_handler,
AdvertiserId adv_id,
int32_t tx_power,
AdvertisingStatus status) {
dbus::MethodCall method_call("some.interface",
advertiser::kOnAdvertisingParametersUpdated);
method_call.SetPath(callback_path_);
method_call.SetSender(kTestSender);
method_call.SetSerial(kTestSerial);
dbus::MessageWriter writer(&method_call);
writer.AppendInt32(adv_id);
writer.AppendInt32(tx_power);
writer.AppendUint32(static_cast<uint32_t>(status));
method_handler.Run(
&method_call,
base::BindOnce([](std::unique_ptr<dbus::Response> response) {
ASSERT_TRUE(!!response);
EXPECT_NE(response->GetMessageType(),
dbus::Message::MessageType::MESSAGE_ERROR);
}));
}
scoped_refptr<::dbus::MockBus> bus_;
scoped_refptr<::dbus::MockExportedObject> exported_callbacks_;
scoped_refptr<::dbus::MockObjectProxy> advclient_proxy_;
std::unique_ptr<FlossAdvertiserClient> advclient_;
dbus::ObjectPath callback_path_;
dbus::ObjectPath gatt_adapter_path_;
int adapter_index_ = 2;
base::test::TaskEnvironment task_environment_;
base::WeakPtrFactory<FlossAdvertiserClientTest> weak_ptr_factory_{this};
};
TEST_F(FlossAdvertiserClientTest, StartAndStopAdvertisingSet) {
exported_callbacks_ = base::MakeRefCounted<::dbus::MockExportedObject>(
bus_.get(), callback_path_);
EXPECT_CALL(*bus_.get(), GetExportedObject)
.WillRepeatedly(::testing::Return(exported_callbacks_.get()));
EXPECT_CALL(*bus_.get(), GetObjectProxy(kGattInterface, gatt_adapter_path_))
.WillRepeatedly(::testing::Return(advclient_proxy_.get()));
// There are 7 exported methods that we don't interested in it.
EXPECT_CALL(*exported_callbacks_.get(), ExportMethod)
.Times(7)
.WillRepeatedly(&FakeExportMethod);
// Handle OnAdvertisingSetStarted, OnAdvertisingParametersUpdated,
// OnAdvertisingSetStopped
dbus::ExportedObject::MethodCallCallback
method_handler_on_advertising_set_started;
dbus::ExportedObject::MethodCallCallback
method_handler_on_advertising_parameters_updated;
dbus::ExportedObject::MethodCallCallback
method_handler_on_advertising_set_stopped;
EXPECT_CALL(*exported_callbacks_.get(),
ExportMethod(advertiser::kCallbackInterface,
advertiser::kOnAdvertisingSetStarted, _, _))
.WillOnce(
DoAll(testing::SaveArg<2>(&method_handler_on_advertising_set_started),
&FakeExportMethod));
EXPECT_CALL(*exported_callbacks_.get(),
ExportMethod(advertiser::kCallbackInterface,
advertiser::kOnAdvertisingParametersUpdated, _, _))
.WillOnce(DoAll(testing::SaveArg<2>(
&method_handler_on_advertising_parameters_updated),
&FakeExportMethod));
EXPECT_CALL(*exported_callbacks_.get(),
ExportMethod(advertiser::kCallbackInterface,
advertiser::kOnAdvertisingSetStopped, _, _))
.WillOnce(
DoAll(testing::SaveArg<2>(&method_handler_on_advertising_set_stopped),
&FakeExportMethod));
EXPECT_CALL(*advclient_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(advertiser::kRegisterCallback), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
auto response = ::dbus::Response::CreateEmpty();
::dbus::MessageWriter msg(response.get());
FlossDBusClient::WriteAllDBusParams(&msg, kCallbackId1);
std::move(cb).Run(response.get(), nullptr);
});
advclient_->Init(bus_.get(), kGattInterface, adapter_index_, GetCurrVersion(),
base::DoNothing());
ASSERT_TRUE(!!method_handler_on_advertising_set_started);
ASSERT_TRUE(!!method_handler_on_advertising_parameters_updated);
ASSERT_TRUE(!!method_handler_on_advertising_set_stopped);
// Do StartAdvertisingSet
EXPECT_CALL(*advclient_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(advertiser::kStartAdvertisingSet), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
auto response = ::dbus::Response::CreateEmpty();
::dbus::MessageWriter msg(response.get());
FlossDBusClient::WriteAllDBusParams(&msg,
static_cast<int32_t>(kRegId1));
std::move(cb).Run(response.get(), nullptr);
});
AdvertisingSetParameters params = {};
AdvertiseData adv_data = {};
adv_data.include_tx_power_level = false;
adv_data.include_device_name = false;
base::RunLoop run_loop0;
advclient_->StartAdvertisingSet(
params, adv_data, /*scan_rsp=*/std::nullopt,
/*periodic_params=*/std::nullopt, /*periodic_data=*/std::nullopt,
/*duration=*/1,
/*max_ext_adv_events=*/1,
base::BindLambdaForTesting([&run_loop0](AdvertiserId adv_id) {
EXPECT_EQ(kAdvId1, adv_id);
run_loop0.Quit();
}),
base::BindOnce([](device::BluetoothAdvertisement::ErrorCode error_code) {
FAIL();
}));
DoOnAdvertisingSetStarted(method_handler_on_advertising_set_started, kRegId1,
kAdvId1, /*tx_power=*/0,
AdvertisingStatus::kSuccess);
run_loop0.Run();
// Do SetAdvertisingParameters
EXPECT_CALL(*advclient_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(advertiser::kSetAdvertisingParameters), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
auto response = ::dbus::Response::CreateEmpty();
std::move(cb).Run(response.get(), nullptr);
});
base::RunLoop run_loop1;
advclient_->SetAdvertisingParameters(
kAdvId1, params,
base::BindLambdaForTesting([&run_loop1]() { run_loop1.Quit(); }),
base::BindOnce([](device::BluetoothAdvertisement::ErrorCode error_code) {
FAIL();
}));
DoOnAdvertisingParametersUpdated(
method_handler_on_advertising_parameters_updated, kAdvId1, 0,
AdvertisingStatus::kSuccess);
run_loop1.Run();
// Do StopAdvertisingSet
EXPECT_CALL(*advclient_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(advertiser::kStopAdvertisingSet), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
auto response = ::dbus::Response::CreateEmpty();
std::move(cb).Run(response.get(), nullptr);
});
base::RunLoop run_loop2;
advclient_->StopAdvertisingSet(
kAdvId1, base::BindLambdaForTesting([&run_loop2]() { run_loop2.Quit(); }),
base::BindOnce([](device::BluetoothAdvertisement::ErrorCode error_code) {
FAIL();
}));
DoOnAdvertisingSetStopped(method_handler_on_advertising_set_stopped, kAdvId1);
run_loop2.Run();
// Expected call to UnregisterCallback when client is destroyed
EXPECT_CALL(*advclient_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(advertiser::kUnregisterCallback), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
dbus::MessageReader msg(method_call);
// D-Bus method call should have 1 parameter.
uint32_t param1;
ASSERT_TRUE(FlossDBusClient::ReadAllDBusParams(&msg, &param1));
EXPECT_EQ(kCallbackId1, param1);
EXPECT_FALSE(msg.HasMoreData());
});
}
} // namespace floss