| // 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/scoped_file.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/logging.h> |
| #include <base/memory/ref_counted.h> |
| #include <base/memory/shared_memory.h> |
| #include <base/message_loop/message_loop.h> |
| #include <base/run_loop.h> |
| #include <base/strings/stringprintf.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: |
| CoreTest() { InitializeMojo(); } |
| |
| 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: |
| // Initialize the Mojo subsystem. |
| void InitializeMojo() { mojo::core::Init(); } |
| |
| base::MessageLoop message_loop_; |
| |
| 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(SaveArg<2 /* method_call_callback */>( |
| &bootstrap_mojo_connection_dbus_method_)); |
| |
| // 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()); |
| } |
| |
| // 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 |