blob: 34a6761614c17ee8ba2ceab0f432a7a505d1b4c2 [file] [log] [blame]
// Copyright 2018 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 <fcntl.h>
#include <poll.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <set>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <base/barrier_closure.h>
#include <base/bind.h>
#include <base/callback.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <base/memory/ref_counted.h>
#include <base/run_loop.h>
#include <base/strings/stringprintf.h>
#include <base/test/task_environment.h>
#include <brillo/dbus/async_event_sequencer.h>
#include <dbus/message.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_exported_object.h>
#include <dbus/mock_object_proxy.h>
#include <dbus/property.h>
#include <dbus/wilco_dtc_supportd/dbus-constants.h>
#include <gmock/gmock.h>
#include <google/protobuf/util/message_differencer.h>
#include <gtest/gtest.h>
#include <mojo/core/embedder/embedder.h>
#include <mojo/public/cpp/bindings/binding.h>
#include <mojo/public/cpp/bindings/interface_ptr.h>
#include <mojo/public/cpp/bindings/interface_request.h>
#include "diagnostics/common/file_test_utils.h"
#include "diagnostics/common/mojo_test_utils.h"
#include "diagnostics/common/mojo_utils.h"
#include "diagnostics/common/protobuf_test_utils.h"
#include "diagnostics/common/system/fake_bluetooth_client.h"
#include "diagnostics/common/system/fake_powerd_adapter.h"
#include "diagnostics/common/system/mock_debugd_adapter.h"
#include "diagnostics/wilco_dtc_supportd/core.h"
#include "diagnostics/wilco_dtc_supportd/dbus_service.h"
#include "diagnostics/wilco_dtc_supportd/ec_constants.h"
#include "diagnostics/wilco_dtc_supportd/fake_browser.h"
#include "diagnostics/wilco_dtc_supportd/fake_diagnostics_service.h"
#include "diagnostics/wilco_dtc_supportd/fake_probe_service.h"
#include "diagnostics/wilco_dtc_supportd/fake_wilco_dtc.h"
#include "diagnostics/wilco_dtc_supportd/grpc_client_manager.h"
#include "diagnostics/wilco_dtc_supportd/mojo_grpc_adapter.h"
#include "diagnostics/wilco_dtc_supportd/mojo_service_factory.h"
#include "diagnostics/wilco_dtc_supportd/service_util.h"
#include "diagnostics/wilco_dtc_supportd/telemetry/ec_service.h"
#include "diagnostics/wilco_dtc_supportd/telemetry/ec_service_test_utils.h"
#include "diagnostics/wilco_dtc_supportd/telemetry/fake_bluetooth_event_service.h"
#include "diagnostics/wilco_dtc_supportd/telemetry/fake_ec_service.h"
#include "diagnostics/wilco_dtc_supportd/telemetry/fake_powerd_event_service.h"
#include "mojo/wilco_dtc_supportd.mojom.h"
#include "wilco_dtc_supportd.pb.h" // NOLINT(build/include)
using testing::_;
using testing::ElementsAreArray;
using testing::Invoke;
using testing::Mock;
using testing::Return;
using testing::SaveArg;
using testing::StrictMock;
using testing::WithArg;
namespace diagnostics {
// Templates for the gRPC URIs that should be used for testing. "%s" is
// substituted with a temporary directory.
const char kWilcoDtcSupportdGrpcUriTemplate[] =
"unix:%s/test_wilco_dtc_supportd_socket";
const char kWilcoDtcGrpcUriTemplate[] = "unix:%s/test_wilco_dtc_socket";
const char kUiMessageReceiverWilcoDtcGrpcUriTemplate[] =
"unix:%s/test_ui_message_receiver_wilco_dtc_socket";
using EcEvent = EcService::EcEvent;
using EcEventReason = EcService::EcEvent::Reason;
using MojoEvent = chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdEvent;
using MojomWilcoDtcSupportdService =
chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdService;
using MojomWilcoDtcSupportdServiceFactory =
chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdServiceFactory;
using MojomWilcoDtcSupportdWebRequestHttpMethod =
chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdWebRequestHttpMethod;
using MojomWilcoDtcSupportdWebRequestStatus =
chromeos::wilco_dtc_supportd::mojom::WilcoDtcSupportdWebRequestStatus;
namespace {
// Returns a callback that, once called, saves its parameter to |*response| and
// quits |*run_loop|.
template <typename ValueType>
base::Callback<void(grpc::Status, std::unique_ptr<ValueType>)>
MakeAsyncResponseWriter(const base::Closure& callback,
std::unique_ptr<ValueType>* response) {
return base::Bind(
[](const base::Closure& callback, std::unique_ptr<ValueType>* response,
grpc::Status status, std::unique_ptr<ValueType> received_response) {
ASSERT_TRUE(received_response);
ASSERT_FALSE(*response);
*response = std::move(received_response);
callback.Run();
},
callback, base::Unretained(response));
}
class FakeCoreDelegate : public Core::Delegate {
public:
FakeCoreDelegate()
: passed_bluetooth_client_(std::make_unique<FakeBluetoothClient>()),
passed_debugd_adapter_(
std::make_unique<StrictMock<MockDebugdAdapter>>()),
passed_powerd_adapter_(std::make_unique<FakePowerdAdapter>()),
passed_bluetooth_event_service_(
std::make_unique<FakeBluetoothEventService>()),
passed_ec_service_(std::make_unique<FakeEcService>()),
passed_powerd_event_service_(
std::make_unique<FakePowerdEventService>()),
passed_probe_service_(std::make_unique<FakeProbeService>()),
bluetooth_client_(passed_bluetooth_client_.get()),
debugd_adapter_(passed_debugd_adapter_.get()),
powerd_adapter_(passed_powerd_adapter_.get()),
bluetooth_event_service_(passed_bluetooth_event_service_.get()),
ec_service_(passed_ec_service_.get()),
powerd_event_service_(passed_powerd_event_service_.get()),
probe_service_(passed_probe_service_.get()) {}
// Must be called no more than once.
std::unique_ptr<BluetoothClient> CreateBluetoothClient(
const scoped_refptr<dbus::Bus>& bus) override {
DCHECK(bus);
DCHECK(passed_bluetooth_client_);
return std::move(passed_bluetooth_client_);
}
// Must be called no more than once.
std::unique_ptr<DebugdAdapter> CreateDebugdAdapter(
const scoped_refptr<dbus::Bus>& bus) override {
DCHECK(bus);
DCHECK(passed_debugd_adapter_);
return std::move(passed_debugd_adapter_);
}
// Must be called no more than once.
std::unique_ptr<PowerdAdapter> CreatePowerdAdapter(
const scoped_refptr<dbus::Bus>& bus) override {
DCHECK(bus);
DCHECK(passed_powerd_adapter_);
return std::move(passed_powerd_adapter_);
}
// Must be called no more than once.
std::unique_ptr<BluetoothEventService> CreateBluetoothEventService(
BluetoothClient* bluetooth_client) override {
DCHECK(bluetooth_client);
DCHECK(passed_bluetooth_event_service_);
DCHECK_EQ(bluetooth_client, bluetooth_client_);
return std::move(passed_bluetooth_event_service_);
}
// Must be called no more than once.
std::unique_ptr<EcService> CreateEcService() override {
DCHECK(passed_ec_service_);
return std::move(passed_ec_service_);
}
// Must be called no more than once.
std::unique_ptr<PowerdEventService> CreatePowerdEventService(
PowerdAdapter* powerd_adapter) override {
DCHECK(powerd_adapter);
DCHECK(passed_powerd_event_service_);
DCHECK_EQ(powerd_adapter, powerd_adapter_);
return std::move(passed_powerd_event_service_);
}
// Must be called no more than once.
std::unique_ptr<ProbeService> CreateProbeService(
ProbeService::Delegate* delegate) override {
DCHECK(delegate);
DCHECK(passed_probe_service_);
return std::move(passed_probe_service_);
}
StrictMock<MockDebugdAdapter>* debugd_adapter() const {
return debugd_adapter_;
}
FakeBluetoothEventService* bluetooth_event_service() const {
return bluetooth_event_service_;
}
FakeEcService* ec_service() const { return ec_service_; }
FakePowerdEventService* powerd_event_service() const {
return powerd_event_service_;
}
FakeProbeService* probe_service() const { return probe_service_; }
private:
// Mock objects to be transferred by Create* methods.
std::unique_ptr<FakeBluetoothClient> passed_bluetooth_client_;
std::unique_ptr<StrictMock<MockDebugdAdapter>> passed_debugd_adapter_;
std::unique_ptr<FakePowerdAdapter> passed_powerd_adapter_;
std::unique_ptr<FakeBluetoothEventService> passed_bluetooth_event_service_;
std::unique_ptr<FakeEcService> passed_ec_service_;
std::unique_ptr<FakePowerdEventService> passed_powerd_event_service_;
std::unique_ptr<FakeProbeService> passed_probe_service_;
// Pointers to objects originally stored in |passed_*| members. These allow
// continued access by tests even after the corresponding Create* method has
// been called and ownership has been transferred to |core_|.
FakeBluetoothClient* bluetooth_client_;
StrictMock<MockDebugdAdapter>* debugd_adapter_;
FakePowerdAdapter* powerd_adapter_;
FakeBluetoothEventService* bluetooth_event_service_;
FakeEcService* ec_service_;
FakePowerdEventService* powerd_event_service_;
FakeProbeService* probe_service_;
};
// Matches gRPC Bluetooth AdapterData and BluetoothEventService AdapterData.
MATCHER_P(BluetoothAdaptersEquals, expected_adapters, "") {
if (arg.adapters_size() != expected_adapters.size()) {
return false;
}
for (int i = 0; i < arg.adapters_size(); i++) {
auto expected_carrier_status =
(expected_adapters[i].powered)
? grpc_api::HandleBluetoothDataChangedRequest::AdapterData::
STATUS_UP
: grpc_api::HandleBluetoothDataChangedRequest::AdapterData::
STATUS_DOWN;
const auto& adapter = arg.adapters(i);
if (adapter.adapter_name() != expected_adapters[i].name ||
adapter.adapter_mac_address() != expected_adapters[i].address ||
adapter.carrier_status() != expected_carrier_status ||
adapter.connected_devices_count() !=
expected_adapters[i].connected_devices_count) {
return false;
}
}
return true;
}
class MockDaemon {
public:
MOCK_METHOD(void, ShutDown, ());
};
// Tests for the Core class.
class CoreTest : public testing::Test {
protected:
void CreateCore(const std::vector<std::string>& grpc_service_uris) {
core_ = std::make_unique<Core>(&core_delegate_, grpc_client_manager(),
grpc_service_uris, &mojo_service_factory_);
}
Core* core() {
DCHECK(core_);
return core_.get();
}
FakeCoreDelegate* core_delegate() { return &core_delegate_; }
// Fake for MojoServiceFactory::BindFactoryCallback that simulates
// successful Mojo service binding to the given file descriptor. After the
// mock gets triggered, |mojo_service_factory_interface_ptr_| becomes
// initialized to point to the tested Mojo service.
MojoServiceFactory::MojoBindingPtr FakeBindMojoFactory(
MojomWilcoDtcSupportdServiceFactory* mojo_service_factory,
base::ScopedFD mojo_pipe_fd) {
if (simulate_bind_failure_)
return nullptr;
// Initialize a Mojo binding that, instead of working through the
// given (fake) file descriptor, talks to the test endpoint
// |mojo_service_interface_ptr_|.
auto mojo_service_factory_binding =
std::make_unique<MojoServiceFactory::MojoBinding>(
mojo_service_factory,
mojo::MakeRequest(&mojo_service_factory_interface_ptr_));
DCHECK(mojo_service_factory_interface_ptr_);
return MojoServiceFactory::MojoBindingPtr(
mojo_service_factory_binding.release());
}
GrpcClientManager* grpc_client_manager() { return &grpc_client_manager_; }
StrictMock<MockDaemon>* daemon() { return &daemon_; }
MojoServiceFactory* mojo_service_factory() { return &mojo_service_factory_; }
mojo::InterfacePtr<MojomWilcoDtcSupportdServiceFactory>*
mojo_service_factory_interface_ptr() {
return &mojo_service_factory_interface_ptr_;
}
void SimulateBindFailure() { simulate_bind_failure_ = true; }
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY};
GrpcClientManager grpc_client_manager_;
MojoGrpcAdapter mojo_grpc_adapter_{&grpc_client_manager_};
// Mocked daemon (for calling ShutDown()).
StrictMock<MockDaemon> daemon_;
// Mojo interface to the service factory exposed by the tested code.
mojo::InterfacePtr<MojomWilcoDtcSupportdServiceFactory>
mojo_service_factory_interface_ptr_;
MojoServiceFactory mojo_service_factory_{
&mojo_grpc_adapter_,
base::Bind(&StrictMock<MockDaemon>::ShutDown, base::Unretained(&daemon_)),
base::BindOnce(&CoreTest::FakeBindMojoFactory, base::Unretained(this))};
bool simulate_bind_failure_ = false;
StrictMock<FakeCoreDelegate> core_delegate_;
std::unique_ptr<Core> core_;
};
// Test successful shutdown after failed start.
TEST_F(CoreTest, FailedStartAndSuccessfulShutdown) {
// Invalid gRPC service URI.
CreateCore({""});
EXPECT_FALSE(core()->Start());
ShutDownServicesInRunLoop(core());
}
// Tests for the Core class which started successfully.
class StartedCoreTest : public CoreTest {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(CoreTest::SetUp());
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
wilco_dtc_supportd_grpc_uri_ = base::StringPrintf(
kWilcoDtcSupportdGrpcUriTemplate, temp_dir_.GetPath().value().c_str());
ui_message_receiver_wilco_dtc_grpc_uri_ =
base::StringPrintf(kUiMessageReceiverWilcoDtcGrpcUriTemplate,
temp_dir_.GetPath().value().c_str());
wilco_dtc_grpc_uri_ = base::StringPrintf(
kWilcoDtcGrpcUriTemplate, temp_dir_.GetPath().value().c_str());
CreateCore({wilco_dtc_supportd_grpc_uri_});
core()->set_root_dir_for_testing(temp_dir_.GetPath());
SetUpEcService();
ASSERT_TRUE(core()->Start());
grpc_client_manager()->Start(ui_message_receiver_wilco_dtc_grpc_uri_,
{wilco_dtc_grpc_uri_});
SetUpEcServiceFifoWriteEnd();
SetUpDBus();
fake_browser_ =
std::make_unique<FakeBrowser>(mojo_service_factory_interface_ptr(),
bootstrap_mojo_connection_dbus_method_);
}
void TearDown() override {
SetDBusShutdownExpectations();
dbus_service_.ShutDown();
ShutDownServicesInRunLoop(core(), grpc_client_manager());
CoreTest::TearDown();
}
const base::FilePath& temp_dir_path() const {
DCHECK(temp_dir_.IsValid());
return temp_dir_.GetPath();
}
FakeBrowser* fake_browser() {
DCHECK(fake_browser_);
return fake_browser_.get();
}
dbus::ExportedObject::MethodCallCallback
bootstrap_mojo_connection_dbus_method() {
return bootstrap_mojo_connection_dbus_method_;
}
const std::string& wilco_dtc_supportd_grpc_uri() const {
DCHECK(!wilco_dtc_supportd_grpc_uri_.empty());
return wilco_dtc_supportd_grpc_uri_;
}
const std::string& ui_message_receiver_wilco_dtc_grpc_uri() const {
DCHECK(!ui_message_receiver_wilco_dtc_grpc_uri_.empty());
return ui_message_receiver_wilco_dtc_grpc_uri_;
}
const std::string& wilco_dtc_grpc_uri() const {
DCHECK(!wilco_dtc_grpc_uri_.empty());
return wilco_dtc_grpc_uri_;
}
void BootstrapMojoConnection(FakeMojoFdGenerator* fake_mojo_fd_generator) {
base::RunLoop run_loop;
ASSERT_TRUE(fake_browser()->BootstrapMojoConnection(
fake_mojo_fd_generator, run_loop.QuitClosure()));
run_loop.Run();
}
private:
// Perform initialization of the D-Bus object exposed by the tested code.
void SetUpDBus() {
const dbus::ObjectPath kDBusObjectPath(kWilcoDtcSupportdServicePath);
// Expect that the /org/chromium/WilcoDtcSupportd object is exported.
wilco_dtc_supportd_dbus_object_ = new StrictMock<dbus::MockExportedObject>(
dbus_bus_.get(), kDBusObjectPath);
EXPECT_CALL(*dbus_bus_, GetExportedObject(kDBusObjectPath))
.WillOnce(Return(wilco_dtc_supportd_dbus_object_.get()));
// Expect that standard methods on the org.freedesktop.DBus.Properties
// interface are exported.
EXPECT_CALL(
*wilco_dtc_supportd_dbus_object_,
ExportMethod(dbus::kPropertiesInterface, dbus::kPropertiesGet, _, _));
EXPECT_CALL(
*wilco_dtc_supportd_dbus_object_,
ExportMethod(dbus::kPropertiesInterface, dbus::kPropertiesSet, _, _));
EXPECT_CALL(*wilco_dtc_supportd_dbus_object_,
ExportMethod(dbus::kPropertiesInterface,
dbus::kPropertiesGetAll, _, _));
// Expect that methods on the org.chromium.WilcoDtcSupportdInterface
// interface are exported.
EXPECT_CALL(
*wilco_dtc_supportd_dbus_object_,
ExportMethod(kWilcoDtcSupportdServiceInterface,
kWilcoDtcSupportdBootstrapMojoConnectionMethod, _, _))
.WillOnce(Invoke(this, &StartedCoreTest::MockExportMethod));
// Run the tested code that exports D-Bus objects and methods.
scoped_refptr<brillo::dbus_utils::AsyncEventSequencer> dbus_sequencer(
new brillo::dbus_utils::AsyncEventSequencer());
dbus_service_.RegisterDBusObjectsAsync(dbus_bus_, dbus_sequencer.get());
core()->CreateDbusAdapters(dbus_bus_);
// Verify that required D-Bus methods are exported.
EXPECT_FALSE(bootstrap_mojo_connection_dbus_method_.is_null());
}
// Mock implementation of the `wilco_dtc_supportd_dbus_object_`'s
// `ExportMethod()` method.
void MockExportMethod(
const std::string& interface_name,
const std::string& method_name,
dbus::ExportedObject::MethodCallCallback method_callback,
dbus::ExportedObject::OnExportedCallback on_exported_callback) {
DCHECK(interface_name == kWilcoDtcSupportdServiceInterface);
DCHECK(method_name == kWilcoDtcSupportdBootstrapMojoConnectionMethod);
bootstrap_mojo_connection_dbus_method_ = method_callback;
}
// Set mock expectations for calls triggered during test destruction.
void SetDBusShutdownExpectations() {
EXPECT_CALL(*wilco_dtc_supportd_dbus_object_, Unregister());
}
// Creates FIFO to emulates the EC event file used by EC event service.
void SetUpEcService() {
core_delegate()->ec_service()->set_event_fd_events_for_testing(POLLIN);
ASSERT_TRUE(base::CreateDirectory(ec_event_file_path().DirName()));
ASSERT_EQ(mkfifo(ec_event_file_path().value().c_str(), 0600), 0);
}
// Setups |ec_service_fd_| FIFO file descriptor. Must be called only
// after |Core::Start()| call. Otherwise, it will block
// thread.
void SetUpEcServiceFifoWriteEnd() {
ASSERT_FALSE(ec_service_fd_.is_valid());
ec_service_fd_.reset(open(ec_event_file_path().value().c_str(), O_WRONLY));
ASSERT_TRUE(ec_service_fd_.is_valid());
}
base::FilePath ec_event_file_path() const {
return temp_dir_.GetPath().Append(kEcEventFilePath);
}
base::ScopedTempDir temp_dir_;
// gRPC URI on which the tested "WilcoDtcSupportd" gRPC service (owned by
// Core) is listening.
std::string wilco_dtc_supportd_grpc_uri_;
// gRPC URI on which the fake "WilcoDtc" gRPC service (owned by FakeWilcoDtc)
// is listening, eligible to receive UI messages.
std::string ui_message_receiver_wilco_dtc_grpc_uri_;
// gRPC URI on which the fake "WilcoDtc" gRPC service (owned by FakeWilcoDtc)
// is listening.
std::string wilco_dtc_grpc_uri_;
scoped_refptr<StrictMock<dbus::MockBus>> dbus_bus_ =
new StrictMock<dbus::MockBus>(dbus::Bus::Options());
DBusService dbus_service_{mojo_service_factory()};
// Mock D-Bus integration helper for the object exposed by the tested code.
scoped_refptr<StrictMock<dbus::MockExportedObject>>
wilco_dtc_supportd_dbus_object_;
// Write end of FIFO that emulates EC event file. EC service operates with
// read end of FIFO as with usual file. Must be initialized only after
// |Core::Start()| call.
base::ScopedFD ec_service_fd_;
// Callback that the tested code exposed as the BootstrapMojoConnection D-Bus
// method.
dbus::ExportedObject::MethodCallCallback
bootstrap_mojo_connection_dbus_method_;
std::unique_ptr<FakeBrowser> fake_browser_;
};
// Test that the Mojo service gets successfully bootstrapped after the
// BootstrapMojoConnection D-Bus method is called.
TEST_F(StartedCoreTest, MojoBootstrapSuccess) {
FakeMojoFdGenerator fake_mojo_fd_generator;
BootstrapMojoConnection(&fake_mojo_fd_generator);
EXPECT_TRUE(*mojo_service_factory_interface_ptr());
}
// Test failure to bootstrap the Mojo service due to an error returned by
// MojoServiceFactory::BootstrapMojoConnection().
TEST_F(StartedCoreTest, MojoBootstrapErrorToBind) {
FakeMojoFdGenerator fake_mojo_fd_generator;
EXPECT_CALL(*daemon(), ShutDown());
SimulateBindFailure();
base::RunLoop run_loop;
EXPECT_FALSE(fake_browser()->BootstrapMojoConnection(&fake_mojo_fd_generator,
run_loop.QuitClosure()));
run_loop.Run();
}
// Test that second attempt to bootstrap the Mojo service results in error and
// the daemon shutdown.
TEST_F(StartedCoreTest, MojoBootstrapErrorRepeated) {
FakeMojoFdGenerator first_fake_mojo_fd_generator;
BootstrapMojoConnection(&first_fake_mojo_fd_generator);
FakeMojoFdGenerator second_fake_mojo_fd_generator;
EXPECT_CALL(*daemon(), ShutDown());
base::RunLoop run_loop;
EXPECT_FALSE(fake_browser()->BootstrapMojoConnection(
&second_fake_mojo_fd_generator, run_loop.QuitClosure()));
run_loop.Run();
}
// Test that the daemon gets shut down when the previously bootstrapped Mojo
// connection aborts.
TEST_F(StartedCoreTest, MojoBootstrapSuccessThenAbort) {
FakeMojoFdGenerator fake_mojo_fd_generator;
BootstrapMojoConnection(&fake_mojo_fd_generator);
EXPECT_CALL(*daemon(), ShutDown());
// Abort the Mojo connection by closing the browser-side endpoint.
mojo_service_factory_interface_ptr()->reset();
base::RunLoop().RunUntilIdle();
}
// Test that the method |ProbeTelemetryInfo()| calls into
// ProbeService
TEST_F(StartedCoreTest, ProbeTelemetryInfo) {
using ProbeTelemetryInfoCallback =
base::OnceCallback<void(chromeos::cros_healthd::mojom::TelemetryInfoPtr)>;
const auto kCategories =
std::vector<chromeos::cros_healthd::mojom::ProbeCategoryEnum>{
chromeos::cros_healthd::mojom::ProbeCategoryEnum::kFan,
chromeos::cros_healthd::mojom::ProbeCategoryEnum::kCpu,
chromeos::cros_healthd::mojom::ProbeCategoryEnum::kStatefulPartition};
core_delegate()->probe_service()->SetProbeTelemetryInfoCallback(base::Bind(
[](std::vector<chromeos::cros_healthd::mojom::ProbeCategoryEnum>
expected_categories,
std::vector<chromeos::cros_healthd::mojom::ProbeCategoryEnum>
received_categories,
ProbeTelemetryInfoCallback received_callback) {
EXPECT_EQ(expected_categories, received_categories);
std::move(received_callback).Run(nullptr);
},
kCategories));
base::RunLoop run_loop;
static_cast<GrpcService::Delegate*>(core())->ProbeTelemetryInfo(
kCategories, base::Bind(
[](base::Closure loop_closure,
chromeos::cros_healthd::mojom::TelemetryInfoPtr) {
loop_closure.Run();
},
run_loop.QuitClosure()));
run_loop.Run();
}
// Test that the method |RequestBluetoothDataNotification()| exposed by
// wilco_dtc_supportd gRPC calls clients with the updated data
TEST_F(StartedCoreTest, HandleRequestBluetoothDataNotification) {
std::vector<BluetoothEventService::AdapterData> adapters(2);
adapters[0].name = "sarien-laptop";
adapters[0].address = "aa:bb:cc:dd:ee:ff";
adapters[0].powered = true;
adapters[0].connected_devices_count = 0;
adapters[1].name = "usb-bluetooth";
adapters[1].address = "00:11:22:33:44:55";
adapters[1].powered = false;
adapters[1].connected_devices_count = 2;
FakeWilcoDtc fake_wilco_dtc(wilco_dtc_grpc_uri(),
wilco_dtc_supportd_grpc_uri());
FakeWilcoDtc fake_ui_message_receiver_wilco_dtc(
ui_message_receiver_wilco_dtc_grpc_uri(), wilco_dtc_supportd_grpc_uri());
{
base::RunLoop run_loop;
auto barrier_closure = base::BarrierClosure(2, run_loop.QuitClosure());
auto update_callback = base::BindRepeating(
[](const base::Closure& callback,
const grpc_api::HandleBluetoothDataChangedRequest&) {
callback.Run();
},
barrier_closure);
fake_wilco_dtc.set_bluetooth_data_changed_callback(update_callback);
fake_ui_message_receiver_wilco_dtc.set_bluetooth_data_changed_callback(
update_callback);
core_delegate()->bluetooth_event_service()->EmitBluetoothAdapterDataChanged(
adapters);
run_loop.Run();
}
{
auto bluetooth_callback =
[](const base::Closure& callback,
grpc_api::HandleBluetoothDataChangedRequest* request_out,
const grpc_api::HandleBluetoothDataChangedRequest& request) {
DCHECK(request_out);
*request_out = request;
callback.Run();
};
base::RunLoop run_loop;
auto barrier_closure = base::BarrierClosure(3, run_loop.QuitClosure());
grpc_api::HandleBluetoothDataChangedRequest
fake_wilco_dtc_bluetooth_grpc_request;
grpc_api::HandleBluetoothDataChangedRequest
fake_ui_message_receiver_wilco_dtc_bluetooth_grpc_request;
fake_wilco_dtc.set_bluetooth_data_changed_callback(
base::BindRepeating(bluetooth_callback, barrier_closure,
&fake_wilco_dtc_bluetooth_grpc_request));
fake_ui_message_receiver_wilco_dtc.set_bluetooth_data_changed_callback(
base::BindRepeating(
bluetooth_callback, barrier_closure,
&fake_ui_message_receiver_wilco_dtc_bluetooth_grpc_request));
fake_wilco_dtc.RequestBluetoothDataNotification(
grpc_api::RequestBluetoothDataNotificationRequest{},
base::Bind(
[](base::Closure barrier_closure, grpc::Status status,
std::unique_ptr<
grpc_api::RequestBluetoothDataNotificationResponse>) {
barrier_closure.Run();
},
barrier_closure));
run_loop.Run();
EXPECT_THAT(fake_wilco_dtc_bluetooth_grpc_request,
BluetoothAdaptersEquals(adapters));
EXPECT_THAT(fake_ui_message_receiver_wilco_dtc_bluetooth_grpc_request,
BluetoothAdaptersEquals(adapters));
}
}
// Tests for the Core class with the already established Mojo
// connection to the fake browser and gRPC communication with the fake
// wilco_dtc.
class BootstrappedCoreTest : public StartedCoreTest {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(StartedCoreTest::SetUp());
FakeMojoFdGenerator fake_mojo_fd_generator;
BootstrapMojoConnection(&fake_mojo_fd_generator);
ASSERT_TRUE(*mojo_service_factory_interface_ptr());
fake_wilco_dtc_ = std::make_unique<FakeWilcoDtc>(
wilco_dtc_grpc_uri(), wilco_dtc_supportd_grpc_uri());
fake_ui_message_receiver_wilco_dtc_ =
std::make_unique<FakeWilcoDtc>(ui_message_receiver_wilco_dtc_grpc_uri(),
wilco_dtc_supportd_grpc_uri());
}
void TearDown() override {
fake_wilco_dtc_.reset();
fake_ui_message_receiver_wilco_dtc_.reset();
StartedCoreTest::TearDown();
}
FakeWilcoDtc* fake_ui_message_receiver_wilco_dtc() {
return fake_ui_message_receiver_wilco_dtc_.get();
}
FakeWilcoDtc* fake_wilco_dtc() { return fake_wilco_dtc_.get(); }
base::Callback<void(mojo::ScopedHandle)> fake_browser_valid_handle_callback(
const base::Closure& callback,
const std::string& expected_response_json_message) {
return base::Bind(
[](const base::Closure& callback,
const std::string& expected_response_json_message,
mojo::ScopedHandle response_json_message_handle) {
auto shm_mapping = GetReadOnlySharedMemoryMappingFromMojoHandle(
std::move(response_json_message_handle));
ASSERT_TRUE(shm_mapping.IsValid());
ASSERT_EQ(expected_response_json_message,
std::string(shm_mapping.GetMemoryAs<const char>(),
shm_mapping.mapped_size()));
callback.Run();
},
callback, expected_response_json_message);
}
base::Callback<void(mojo::ScopedHandle)> fake_browser_invalid_handle_callback(
const base::Closure& callback) {
return base::Bind(
[](const base::Closure& callback,
mojo::ScopedHandle response_json_message_handle) {
ASSERT_FALSE(response_json_message_handle.is_valid());
callback.Run();
},
callback);
}
MockMojoClient* wilco_dtc_supportd_client() {
return fake_browser()->wilco_dtc_supportd_client();
}
private:
std::unique_ptr<FakeWilcoDtc> fake_ui_message_receiver_wilco_dtc_;
std::unique_ptr<FakeWilcoDtc> fake_wilco_dtc_;
};
// Test that the UI message receiver wilco_dtc will receive message from
// browser.
TEST_F(BootstrappedCoreTest, SendGrpcUiMessageToWilcoDtc) {
constexpr char kJsonMessageRequest[] = "{\"message\": \"ping\"}";
constexpr char kJsonMessageResponse[] = "{\"message\": \"pong\"}";
base::RunLoop run_loop;
const auto barrier_closure = base::BarrierClosure(2, run_loop.QuitClosure());
fake_ui_message_receiver_wilco_dtc()->set_handle_message_from_ui_callback(
barrier_closure);
fake_ui_message_receiver_wilco_dtc()
->set_handle_message_from_ui_json_message_response(kJsonMessageResponse);
fake_wilco_dtc()->set_handle_message_from_ui_callback(base::Bind([]() {
// The wilco_dtc not eligible to receive messages from UI must not
// receive them.
FAIL();
}));
EXPECT_TRUE(fake_browser()->SendUiMessageToWilcoDtc(
kJsonMessageRequest, fake_browser_valid_handle_callback(
barrier_closure, kJsonMessageResponse)));
run_loop.Run();
EXPECT_EQ(kJsonMessageRequest,
fake_ui_message_receiver_wilco_dtc()
->handle_message_from_ui_actual_json_message());
}
// Test that the UI message receiver wilco_dtc will not receive message from
// browser if JSON message is invalid.
TEST_F(BootstrappedCoreTest, SendGrpcUiMessageToWilcoDtcInvalidJSON) {
constexpr char kJsonMessage[] = "{'some_key': 'some_value'}";
base::RunLoop run_loop_fake_browser;
auto callback =
fake_browser_invalid_handle_callback(run_loop_fake_browser.QuitClosure());
EXPECT_TRUE(fake_browser()->SendUiMessageToWilcoDtc(kJsonMessage, callback));
run_loop_fake_browser.Run();
// There's no reliable way to wait till the wrong HandleMessageFromUi(), if
// the tested code is buggy and calls it, gets executed. The RunUntilIdle() is
// used to make the test failing at least with some probability in case of
// such a bug.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(fake_ui_message_receiver_wilco_dtc()
->handle_message_from_ui_actual_json_message()
.has_value());
}
// Test that the UI message receiver wilco_dtc will receive message from
// browser.
TEST_F(BootstrappedCoreTest, SendGrpcUiMessageToWilcoDtcInvalidResponseJSON) {
constexpr char kJsonMessageRequest[] = "{\"some_key\": \"some_value\"}";
constexpr char kJsonMessageResponse[] = "{'key': 'value'}";
base::RunLoop run_loop;
const auto barrier_closure = base::BarrierClosure(2, run_loop.QuitClosure());
fake_ui_message_receiver_wilco_dtc()->set_handle_message_from_ui_callback(
barrier_closure);
fake_ui_message_receiver_wilco_dtc()
->set_handle_message_from_ui_json_message_response(kJsonMessageResponse);
EXPECT_TRUE(fake_browser()->SendUiMessageToWilcoDtc(
kJsonMessageRequest,
fake_browser_invalid_handle_callback(barrier_closure)));
run_loop.Run();
EXPECT_EQ(kJsonMessageRequest,
fake_ui_message_receiver_wilco_dtc()
->handle_message_from_ui_actual_json_message());
}
// Test that wilco_dtc_supportd can get a CrosHealthdDiagnosticsServicePtr from
// the browser and use it to fulfill a request from wilco_dtc.
TEST_F(BootstrappedCoreTest, GetCrosHealthdDiagnosticsService) {
FakeDiagnosticsService fake_diagnostics_service;
EXPECT_CALL(*wilco_dtc_supportd_client(), GetCrosHealthdDiagnosticsService(_))
.WillOnce(
WithArg<0>([&](chromeos::cros_healthd::mojom::
CrosHealthdDiagnosticsServiceRequest service) {
fake_diagnostics_service.GetCrosHealthdDiagnosticsService(
std::move(service));
}));
fake_diagnostics_service.SetGetAvailableRoutinesResponse(
std::vector<chromeos::cros_healthd::mojom::DiagnosticRoutineEnum>{
chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::
kBatteryCapacity});
std::vector<grpc_api::DiagnosticRoutine> received_routines;
base::RunLoop run_loop;
fake_wilco_dtc()->GetAvailableRoutines(base::Bind(
[](base::Closure quit_closure,
std::vector<grpc_api::DiagnosticRoutine>* unpacked_response_out,
grpc::Status status,
std::unique_ptr<grpc_api::GetAvailableRoutinesResponse> response) {
for (int i = 0; i < response->routines_size(); i++)
unpacked_response_out->push_back(response->routines(i));
quit_closure.Run();
},
run_loop.QuitClosure(), &received_routines));
run_loop.Run();
EXPECT_THAT(received_routines, ElementsAreArray({grpc_api::ROUTINE_BATTERY}));
}
// Test that wilco_dtc will be notified about configuration changes from
// browser.
TEST_F(BootstrappedCoreTest, NotifyConfigurationDataChanged) {
base::RunLoop run_loop;
const base::Closure barrier_closure =
base::BarrierClosure(2, run_loop.QuitClosure());
fake_ui_message_receiver_wilco_dtc()->set_configuration_data_changed_callback(
barrier_closure);
fake_wilco_dtc()->set_configuration_data_changed_callback(barrier_closure);
fake_browser()->NotifyConfigurationDataChanged();
run_loop.Run();
}
// Test that a message can be sent from wilco_dtc to browser and
// returns an expected response
TEST_F(BootstrappedCoreTest, SendWilcoDtcMessageToUi) {
constexpr char kFakeMessageToUi[] = "{\"message\": \"Fake JSON to UI\"}";
constexpr char kFakeMessageFromUi[] = "{\"message\": \"Fake JSON from UI\"}";
EXPECT_CALL(*wilco_dtc_supportd_client(),
SendWilcoDtcMessageToUiImpl(kFakeMessageToUi, _))
.WillOnce(WithArg<1>(
Invoke([kFakeMessageFromUi](
base::OnceCallback<void(mojo::ScopedHandle)> callback) {
std::move(callback).Run(
CreateReadOnlySharedMemoryRegionMojoHandle(kFakeMessageFromUi));
})));
std::unique_ptr<grpc_api::SendMessageToUiResponse> response;
{
base::RunLoop run_loop;
grpc_api::SendMessageToUiRequest request;
request.set_json_message(kFakeMessageToUi);
fake_wilco_dtc()->SendMessageToUi(
request, MakeAsyncResponseWriter(run_loop.QuitClosure(), &response));
run_loop.Run();
}
ASSERT_TRUE(response);
grpc_api::SendMessageToUiResponse expected_response;
expected_response.set_response_json_message(kFakeMessageFromUi);
EXPECT_THAT(*response, ProtobufEquals(expected_response))
<< "Actual: {" << response->ShortDebugString() << "}";
}
// Test that the GetProcData() method exposed by the daemon's gRPC server
// returns a dump of the corresponding file from the disk.
TEST_F(BootstrappedCoreTest, GetProcDataGrpcCall) {
constexpr char kFakeFileContents[] = "foo";
const base::FilePath file_path = temp_dir_path().Append("proc/uptime");
ASSERT_TRUE(WriteFileAndCreateParentDirs(file_path, kFakeFileContents));
grpc_api::GetProcDataRequest request;
request.set_type(grpc_api::GetProcDataRequest::FILE_UPTIME);
std::unique_ptr<grpc_api::GetProcDataResponse> response;
base::RunLoop run_loop;
fake_wilco_dtc()->GetProcData(
request, MakeAsyncResponseWriter(run_loop.QuitClosure(), &response));
run_loop.Run();
ASSERT_TRUE(response);
grpc_api::GetProcDataResponse expected_response;
expected_response.add_file_dump();
expected_response.mutable_file_dump(0)->set_path(file_path.value());
expected_response.mutable_file_dump(0)->set_canonical_path(file_path.value());
expected_response.mutable_file_dump(0)->set_contents(kFakeFileContents);
EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
*response, expected_response))
<< "Obtained: " << response->ShortDebugString()
<< ",\nExpected: " << expected_response.ShortDebugString();
}
// Test that the GetEcTelemetry() method exposed by the daemon's gRPC server
// writes payload to devfs file exposed by the EC driver and reads response
// using the same file.
TEST_F(BootstrappedCoreTest, GetEcTelemetryGrpcCall) {
const base::FilePath kFilePath =
temp_dir_path().Append(kEcGetTelemetryFilePath);
const std::string kRequestPayload = "12345";
const std::string kResponsePayload = "67890";
// Write request and response payload because EC telemetry char device is
// non-seekable.
ASSERT_TRUE(WriteFileAndCreateParentDirs(kFilePath,
kRequestPayload + kResponsePayload));
grpc_api::GetEcTelemetryRequest request;
request.set_payload(kRequestPayload);
std::unique_ptr<grpc_api::GetEcTelemetryResponse> response;
base::RunLoop run_loop;
fake_wilco_dtc()->GetEcTelemetry(
request, MakeAsyncResponseWriter(run_loop.QuitClosure(), &response));
run_loop.Run();
ASSERT_TRUE(response);
grpc_api::GetEcTelemetryResponse expected_response;
expected_response.set_status(grpc_api::GetEcTelemetryResponse::STATUS_OK);
expected_response.set_payload(kResponsePayload);
EXPECT_THAT(*response, ProtobufEquals(expected_response))
<< "Actual: {" << response->ShortDebugString() << "}";
}
// Test that PerformWebRequest() method exposed by the daemon's gRPC returns a
// Web request response from the browser.
TEST_F(BootstrappedCoreTest, PerformWebRequestToBrowser) {
constexpr char kHttpsUrl[] = "https://www.google.com";
constexpr char kHeader1[] = "Accept-Language: en-US";
constexpr char kHeader2[] = "Accept: text/html";
constexpr char kBodyRequest[] = "<html>Request</html>";
constexpr int kHttpStatusOk = 200;
constexpr char kBodyResponse[] = "<html>Response</html>";
grpc_api::PerformWebRequestParameter request;
request.set_http_method(
grpc_api::PerformWebRequestParameter::HTTP_METHOD_POST);
request.set_url(kHttpsUrl);
request.set_request_body(kBodyRequest);
*request.add_headers() = kHeader1;
*request.add_headers() = kHeader2;
std::unique_ptr<grpc_api::PerformWebRequestResponse> response;
{
base::RunLoop run_loop;
EXPECT_CALL(
*fake_browser()->wilco_dtc_supportd_client(),
PerformWebRequestImpl(
MojomWilcoDtcSupportdWebRequestHttpMethod::kPost, kHttpsUrl,
std::vector<std::string>{kHeader1, kHeader2}, kBodyRequest, _))
.WillOnce(WithArg<4>(
Invoke([kBodyResponse](
MockMojoClient::MojoPerformWebRequestCallback callback) {
std::move(callback).Run(
MojomWilcoDtcSupportdWebRequestStatus::kOk, kHttpStatusOk,
CreateReadOnlySharedMemoryRegionMojoHandle(kBodyResponse));
})));
fake_wilco_dtc()->PerformWebRequest(
request, MakeAsyncResponseWriter(run_loop.QuitClosure(), &response));
run_loop.Run();
}
ASSERT_TRUE(response);
grpc_api::PerformWebRequestResponse expected_response;
expected_response.set_status(grpc_api::PerformWebRequestResponse::STATUS_OK);
expected_response.set_http_status(kHttpStatusOk);
expected_response.set_response_body(kBodyResponse);
EXPECT_THAT(*response, ProtobufEquals(expected_response))
<< "Actual: {" << response->ShortDebugString() << "}";
}
// Test that GetConfigurationData() method exposed by the daemon's gRPC returns
// a response from the browser.
TEST_F(BootstrappedCoreTest, GetConfigurationDataFromBrowser) {
constexpr char kFakeJsonConfigurationData[] =
"{\"fake-message\": \"Fake JSON configuration data\"}";
EXPECT_CALL(*wilco_dtc_supportd_client(), GetConfigurationData(_))
.WillOnce(
Invoke([kFakeJsonConfigurationData](
base::OnceCallback<void(const std::string&)> callback) {
std::move(callback).Run(kFakeJsonConfigurationData);
}));
std::unique_ptr<grpc_api::GetConfigurationDataResponse> response;
{
base::RunLoop run_loop;
grpc_api::GetConfigurationDataRequest request;
fake_wilco_dtc()->GetConfigurationData(
request, MakeAsyncResponseWriter(run_loop.QuitClosure(), &response));
run_loop.Run();
}
ASSERT_TRUE(response);
grpc_api::GetConfigurationDataResponse expected_response;
expected_response.set_json_configuration_data(kFakeJsonConfigurationData);
EXPECT_THAT(*response, ProtobufEquals(expected_response))
<< "Actual: {" << response->ShortDebugString() << "}";
}
// Test that GetDriveSystemData() method exposed by the daemon's gRPC returns
// a response from the debugd.
TEST_F(BootstrappedCoreTest, GetDriveSystemData) {
constexpr char kFakeSmartctlData[] = "Fake smartctl data";
EXPECT_CALL(*core_delegate()->debugd_adapter(), GetSmartAttributes(_))
.WillOnce(WithArg<0>(
[kFakeSmartctlData](
const base::Callback<void(const std::string&, brillo::Error*)>&
callback) { callback.Run(kFakeSmartctlData, nullptr); }));
std::unique_ptr<grpc_api::GetDriveSystemDataResponse> response;
{
base::RunLoop run_loop;
grpc_api::GetDriveSystemDataRequest request;
request.set_type(grpc_api::GetDriveSystemDataRequest::SMART_ATTRIBUTES);
fake_wilco_dtc()->GetDriveSystemData(
request, MakeAsyncResponseWriter(run_loop.QuitClosure(), &response));
run_loop.Run();
}
ASSERT_TRUE(response);
grpc_api::GetDriveSystemDataResponse expected_response;
expected_response.set_status(grpc_api::GetDriveSystemDataResponse::STATUS_OK);
expected_response.set_payload(kFakeSmartctlData);
EXPECT_THAT(*response, ProtobufEquals(expected_response))
<< "Actual: {" << response->ShortDebugString() << "}";
}
// Test that the method |HandleBluetoothDataChanged()| exposed by wilco_dtc gRPC
// is called by wilco_dtc support daemon.
TEST_F(BootstrappedCoreTest, HandleBluetoothDataChanged) {
std::vector<BluetoothEventService::AdapterData> adapters(2);
adapters[0].name = "sarien-laptop";
adapters[0].address = "aa:bb:cc:dd:ee:ff";
adapters[0].powered = true;
adapters[0].connected_devices_count = 0;
adapters[1].name = "usb-bluetooth";
adapters[1].address = "00:11:22:33:44:55";
adapters[1].powered = false;
adapters[1].connected_devices_count = 2;
auto bluetooth_callback =
[](const base::Closure& callback,
grpc_api::HandleBluetoothDataChangedRequest* request_out,
const grpc_api::HandleBluetoothDataChangedRequest& request) {
DCHECK(request_out);
*request_out = request;
callback.Run();
};
base::RunLoop run_loop;
auto barrier_closure = base::BarrierClosure(2, run_loop.QuitClosure());
grpc_api::HandleBluetoothDataChangedRequest
fake_wilco_dtc_bluetooth_grpc_request;
grpc_api::HandleBluetoothDataChangedRequest
fake_ui_message_receiver_wilco_dtc_bluetooth_grpc_request;
fake_wilco_dtc()->set_bluetooth_data_changed_callback(
base::BindRepeating(bluetooth_callback, barrier_closure,
&fake_wilco_dtc_bluetooth_grpc_request));
fake_ui_message_receiver_wilco_dtc()->set_bluetooth_data_changed_callback(
base::BindRepeating(
bluetooth_callback, barrier_closure,
&fake_ui_message_receiver_wilco_dtc_bluetooth_grpc_request));
core_delegate()->bluetooth_event_service()->EmitBluetoothAdapterDataChanged(
adapters);
run_loop.Run();
EXPECT_THAT(fake_wilco_dtc_bluetooth_grpc_request,
BluetoothAdaptersEquals(adapters));
EXPECT_THAT(fake_ui_message_receiver_wilco_dtc_bluetooth_grpc_request,
BluetoothAdaptersEquals(adapters));
}
// Tests for EcService::Observer.
//
// This is a parametrized test with the following parameters:
// * |ec_event_reason| - the reason of the EcEvent
// * |expected_mojo_event| - the expected mojo event passed to the
// |wilco_dtc_supportd_client| over mojo
class EcServiceBootstrappedCoreTest
: public BootstrappedCoreTest,
public testing::WithParamInterface<
std::tuple<EcEventReason, base::Optional<MojoEvent>>> {
protected:
// Holds EC event type and payload of |grpc_api::HandleEcNotificationResponse|
using GrpcEvent = std::pair<uint16_t, std::string>;
void EmulateEcEvent(const EcEvent& ec_event) {
core_delegate()->ec_service()->EmitEcEvent(ec_event);
}
void ExpectAllFakeWilcoDtcReceivedEcEvents(
const std::multiset<GrpcEvent>& expected_ec_events) {
base::RunLoop run_loop;
auto barrier_closure = base::BarrierClosure(2 * expected_ec_events.size(),
run_loop.QuitClosure());
std::multiset<GrpcEvent> fake_wilco_dtc_ec_events;
std::multiset<GrpcEvent> fake_ui_message_receiver_wilco_dtc_ec_events;
SetupFakeWilcoDtcEcEventCallback(barrier_closure, fake_wilco_dtc(),
&fake_wilco_dtc_ec_events);
SetupFakeWilcoDtcEcEventCallback(
barrier_closure, fake_ui_message_receiver_wilco_dtc(),
&fake_ui_message_receiver_wilco_dtc_ec_events);
run_loop.Run();
EXPECT_EQ(fake_wilco_dtc_ec_events, expected_ec_events);
EXPECT_EQ(fake_ui_message_receiver_wilco_dtc_ec_events, expected_ec_events);
}
std::string GetPayload(const EcEvent& ec_event) const {
DCHECK_LE(ec_event.size - 1, 6);
uint16_t payload[6];
memcpy(&payload, &ec_event.payload, (ec_event.size - 1) * sizeof(uint16_t));
return ConvertDataInWordsToString(payload, ec_event.size - 1);
}
EcEventReason ec_event_reason() const { return std::get<0>(GetParam()); }
base::Optional<MojoEvent> expected_mojo_event() const {
return std::get<1>(GetParam());
}
private:
void SetupFakeWilcoDtcEcEventCallback(const base::Closure& callback,
FakeWilcoDtc* fake_wilco_dtc,
std::multiset<GrpcEvent>* events_out) {
DCHECK(fake_wilco_dtc);
DCHECK(events_out);
fake_wilco_dtc->set_handle_ec_event_request_callback(base::BindRepeating(
[](const base::Closure& callback, std::multiset<GrpcEvent>* events_out,
int32_t type, const std::string& payload) {
DCHECK(events_out);
events_out->insert({type, payload});
callback.Run();
},
callback, events_out));
}
};
// Test that the followings are called by the wilco_dtc support daemon:
// 1. |HandleEcNotification|, exposed by wilco_dtc gRPC, is called on valid
// EC events
// 2. |HandleEvent|, exposed by mojo_client, is called on any EcEvent::Reason
// values except |kSysNotification| and |kNonSysNotification|.
TEST_P(EcServiceBootstrappedCoreTest, SingleEvents) {
if (expected_mojo_event().has_value()) {
// Set HandleEvent expectations for the triggered mojo events
EXPECT_CALL(*wilco_dtc_supportd_client(),
HandleEvent(expected_mojo_event().value()));
}
const EcEvent& ec_event = GetEcEventWithReason(ec_event_reason());
EmulateEcEvent(ec_event);
ExpectAllFakeWilcoDtcReceivedEcEvents(
{{ec_event.type, GetPayload(ec_event)}});
}
// Test that both methods |HandleEcNotification()| and |HandleEvent()| exposed
// by wilco_dtc gRPC and mojo_client, respectively, are called multiple times
// by wilco_dtc support daemon.
TEST_F(EcServiceBootstrappedCoreTest, TriggerMultipleMojoEvents) {
// Set HandleEvent expectations for the triggered mojo events
EXPECT_CALL(*wilco_dtc_supportd_client(),
HandleEvent(MojoEvent::kBatteryAuth));
EXPECT_CALL(*wilco_dtc_supportd_client(),
HandleEvent(MojoEvent::kDockDisplay));
const EcEvent& first_ec_event =
GetEcEventWithReason(EcEventReason::kBatteryAuth);
const EcEvent& second_ec_event =
GetEcEventWithReason(EcEventReason::kDockDisplay);
EmulateEcEvent(first_ec_event);
EmulateEcEvent(second_ec_event);
ExpectAllFakeWilcoDtcReceivedEcEvents(
{{first_ec_event.type, GetPayload(first_ec_event)},
{second_ec_event.type, GetPayload(second_ec_event)}});
}
// Test that the method |HandleEcNotification()| exposed by wilco_dtc gRPC is
// not called by the wilco_dtc support daemon when |ec_event.size| exceeds
// allocated data array.
// TODO(mgawad): move size validation logic inside EcService and don't emit
// events when the size is invalid.
TEST_F(EcServiceBootstrappedCoreTest, SendGrpcEventToWilcoDtcInvalidSize) {
const EcEvent& valid_ec_event =
GetEcEventWithReason(EcEventReason::kNonSysNotification);
const EcEvent& invalid_ec_event = kEcEventInvalidPayloadSize;
EmulateEcEvent(valid_ec_event);
EmulateEcEvent(invalid_ec_event);
// Expect only EC event with valid payload size.
ExpectAllFakeWilcoDtcReceivedEcEvents(
{{valid_ec_event.type, GetPayload(valid_ec_event)}});
}
INSTANTIATE_TEST_SUITE_P(
_,
EcServiceBootstrappedCoreTest,
testing::Values(
std::make_tuple(
EcEventReason::kNonWilcoCharger,
base::make_optional<MojoEvent>(MojoEvent::kNonWilcoCharger)),
std::make_tuple(
EcEventReason::kLowPowerCharger,
base::make_optional<MojoEvent>(MojoEvent::kLowPowerCharger)),
std::make_tuple(
EcEventReason::kBatteryAuth,
base::make_optional<MojoEvent>(MojoEvent::kBatteryAuth)),
std::make_tuple(
EcEventReason::kDockDisplay,
base::make_optional<MojoEvent>(MojoEvent::kDockDisplay)),
std::make_tuple(
EcEventReason::kDockThunderbolt,
base::make_optional<MojoEvent>(MojoEvent::kDockThunderbolt)),
std::make_tuple(
EcEventReason::kIncompatibleDock,
base::make_optional<MojoEvent>(MojoEvent::kIncompatibleDock)),
std::make_tuple(EcEventReason::kDockError,
base::make_optional<MojoEvent>(MojoEvent::kDockError)),
std::make_tuple(EcEventReason::kNonSysNotification, base::nullopt),
std::make_tuple(EcEventReason::kSysNotification, base::nullopt)));
// Tests for powerd event service.
//
// This is a parametrized test with the following parameters:
// * |power_event| - the power event.
// * |expected_power_event| - the expected power event passed to fake_wilco_dtc
// over gRPC.
class PowerdEventServiceBootstrappedCoreTest
: public BootstrappedCoreTest,
public testing::WithParamInterface<std::tuple<
PowerdEventService::Observer::PowerEventType /* power_event */,
grpc_api::HandlePowerNotificationRequest::
PowerEvent /* expected_power_event */>> {
protected:
PowerdEventService::Observer::PowerEventType power_event() const {
return std::get<0>(GetParam());
}
grpc_api::HandlePowerNotificationRequest::PowerEvent expected_power_event()
const {
return std::get<1>(GetParam());
}
void SetupFakeWilcoDtcPowerEventCallback(
const base::Closure& callback,
FakeWilcoDtc* fake_wilco_dtc,
grpc_api::HandlePowerNotificationRequest::PowerEvent* event_out) {
DCHECK(fake_wilco_dtc);
DCHECK(event_out);
fake_wilco_dtc->set_handle_power_event_request_callback(base::BindRepeating(
[](const base::Closure& callback,
grpc_api::HandlePowerNotificationRequest::PowerEvent* event_out,
grpc_api::HandlePowerNotificationRequest::PowerEvent event) {
DCHECK(event_out);
*event_out = event;
callback.Run();
},
callback, event_out));
}
};
// Test that the method |HandlePowerNotification()| exposed by wilco_dtc gRPC is
// called by wilco_dtc support daemon.
TEST_P(PowerdEventServiceBootstrappedCoreTest, PowerEvent) {
core_delegate()->powerd_event_service()->EmitPowerEvent(power_event());
base::RunLoop run_loop;
auto barrier_closure = base::BarrierClosure(2, run_loop.QuitClosure());
grpc_api::HandlePowerNotificationRequest::PowerEvent
fake_wilco_dtc_power_event;
grpc_api::HandlePowerNotificationRequest::PowerEvent
fake_ui_message_receiver_wilco_dtc_power_event;
SetupFakeWilcoDtcPowerEventCallback(barrier_closure, fake_wilco_dtc(),
&fake_wilco_dtc_power_event);
SetupFakeWilcoDtcPowerEventCallback(
barrier_closure, fake_ui_message_receiver_wilco_dtc(),
&fake_ui_message_receiver_wilco_dtc_power_event);
run_loop.Run();
EXPECT_EQ(fake_wilco_dtc_power_event, expected_power_event());
EXPECT_EQ(fake_ui_message_receiver_wilco_dtc_power_event,
expected_power_event());
}
INSTANTIATE_TEST_SUITE_P(
,
PowerdEventServiceBootstrappedCoreTest,
testing::Values(
std::make_tuple(PowerdEventService::Observer::PowerEventType::kAcInsert,
grpc_api::HandlePowerNotificationRequest::AC_INSERT),
std::make_tuple(PowerdEventService::Observer::PowerEventType::kAcRemove,
grpc_api::HandlePowerNotificationRequest::AC_REMOVE),
std::make_tuple(
PowerdEventService::Observer::PowerEventType::kOsSuspend,
grpc_api::HandlePowerNotificationRequest::OS_SUSPEND),
std::make_tuple(PowerdEventService::Observer::PowerEventType::kOsResume,
grpc_api::HandlePowerNotificationRequest::OS_RESUME)));
} // namespace
} // namespace diagnostics