| // Copyright 2020 The Fuchsia 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 <lib/async-loop/cpp/loop.h> |
| #include <lib/fidl/llcpp/server.h> |
| #include <lib/input_report_reader/reader.h> |
| |
| #include <zxtest/zxtest.h> |
| |
| struct MouseReport { |
| int64_t movement_x; |
| int64_t movement_y; |
| void ToFidlInputReport( |
| fidl::WireTableBuilder<::fuchsia_input_report::wire::InputReport>& input_report, |
| fidl::AnyArena& allocator) { |
| auto mouse = fuchsia_input_report::wire::MouseInputReport::Builder(allocator); |
| mouse.movement_x(this->movement_x); |
| mouse.movement_y(this->movement_y); |
| |
| input_report.mouse(mouse.Build()); |
| } |
| }; |
| |
| class MouseDevice : public fidl::WireServer<fuchsia_input_report::InputDevice> { |
| public: |
| zx_status_t Start(); |
| |
| void SendReport(const MouseReport& report); |
| // Function for testing that blocks until a new reader is connected. |
| zx_status_t WaitForNextReader(zx::duration timeout) { |
| zx_status_t status = sync_completion_wait(&next_reader_wait_, timeout.get()); |
| if (status == ZX_OK) { |
| sync_completion_reset(&next_reader_wait_); |
| } |
| return ZX_OK; |
| } |
| |
| // The FIDL methods for InputDevice. |
| void GetInputReportsReader(GetInputReportsReaderRequestView request, |
| GetInputReportsReaderCompleter::Sync& completer) override; |
| void GetDescriptor(GetDescriptorRequestView request, |
| GetDescriptorCompleter::Sync& completer) override; |
| void SendOutputReport(SendOutputReportRequestView request, |
| SendOutputReportCompleter::Sync& completer) override; |
| void GetFeatureReport(GetFeatureReportRequestView request, |
| GetFeatureReportCompleter::Sync& completer) override; |
| void SetFeatureReport(SetFeatureReportRequestView request, |
| SetFeatureReportCompleter::Sync& completer) override; |
| void GetInputReport(GetInputReportRequestView request, |
| GetInputReportCompleter::Sync& completer) override; |
| |
| private: |
| sync_completion_t next_reader_wait_; |
| input_report_reader::InputReportReaderManager<MouseReport> input_report_readers_; |
| async::Loop loop_ = async::Loop(&kAsyncLoopConfigNeverAttachToThread); |
| }; |
| |
| zx_status_t MouseDevice::Start() { |
| zx_status_t status = ZX_OK; |
| if ((status = loop_.StartThread("MouseDeviceReaderThread")) != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| void MouseDevice::SendReport(const MouseReport& report) { |
| input_report_readers_.SendReportToAllReaders(report); |
| } |
| |
| void MouseDevice::GetInputReportsReader(GetInputReportsReaderRequestView request, |
| GetInputReportsReaderCompleter::Sync& completer) { |
| zx_status_t status = |
| input_report_readers_.CreateReader(loop_.dispatcher(), std::move(request->reader)); |
| if (status == ZX_OK) { |
| // Signal to a test framework (if it exists) that we are connected to a reader. |
| sync_completion_signal(&next_reader_wait_); |
| } |
| } |
| |
| void MouseDevice::GetDescriptor(GetDescriptorRequestView request, |
| GetDescriptorCompleter::Sync& completer) { |
| fidl::Arena allocator; |
| |
| completer.Reply(fuchsia_input_report::wire::DeviceDescriptor::Builder(allocator).Build()); |
| } |
| |
| void MouseDevice::SendOutputReport(SendOutputReportRequestView request, |
| SendOutputReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void MouseDevice::GetFeatureReport(GetFeatureReportRequestView request, |
| GetFeatureReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void MouseDevice::SetFeatureReport(SetFeatureReportRequestView request, |
| SetFeatureReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void MouseDevice::GetInputReport(GetInputReportRequestView request, |
| GetInputReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| class InputReportReaderTests : public zxtest::Test { |
| void SetUp() override { |
| ASSERT_EQ(mouse_.Start(), ZX_OK); |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputDevice>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| auto result = fidl::BindServer(loop_.dispatcher(), std::move(server), &mouse_); |
| input_device_ = fidl::WireSyncClient<fuchsia_input_report::InputDevice>(std::move(client)); |
| ASSERT_EQ(loop_.StartThread("MouseDeviceThread"), ZX_OK); |
| } |
| |
| void TearDown() override {} |
| |
| protected: |
| MouseDevice mouse_; |
| async::Loop loop_ = async::Loop(&kAsyncLoopConfigNeverAttachToThread); |
| fidl::WireSyncClient<fuchsia_input_report::InputDevice> input_device_; |
| }; |
| |
| TEST_F(InputReportReaderTests, LifeTimeTest) { |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputReportsReader>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)input_device_->GetInputReportsReader(std::move(server)); |
| reader = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(client)); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| } |
| |
| TEST_F(InputReportReaderTests, ReadInputReportsTest) { |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputReportsReader>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)input_device_->GetInputReportsReader(std::move(server)); |
| reader = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(client)); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| |
| // Send a report. |
| MouseReport report; |
| report.movement_x = 0x100; |
| report.movement_y = 0x200; |
| mouse_.SendReport(report); |
| |
| // Get the report. |
| auto result = reader->ReadInputReports(); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto& reports = result->value()->reports; |
| |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_event_time()); |
| ASSERT_TRUE(reports[0].has_mouse()); |
| auto& mouse_report = reports[0].mouse(); |
| |
| ASSERT_TRUE(mouse_report.has_movement_x()); |
| ASSERT_EQ(0x100, mouse_report.movement_x()); |
| |
| ASSERT_TRUE(mouse_report.has_movement_y()); |
| ASSERT_EQ(0x200, mouse_report.movement_y()); |
| |
| ASSERT_FALSE(mouse_report.has_pressed_buttons()); |
| } |
| |
| TEST_F(InputReportReaderTests, ReaderAddsRequiredFields) { |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputReportsReader>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)input_device_->GetInputReportsReader(std::move(server)); |
| reader = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(client)); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| |
| // Send a report. |
| MouseReport report; |
| report.movement_x = 0x100; |
| report.movement_y = 0x200; |
| mouse_.SendReport(report); |
| |
| // Get the report. |
| auto result = reader->ReadInputReports(); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto& reports = result->value()->reports; |
| |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_event_time()); |
| ASSERT_TRUE(reports[0].has_trace_id()); |
| } |
| |
| TEST_F(InputReportReaderTests, TwoReaders) { |
| // Get the first reader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader_one; |
| { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputReportsReader>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)input_device_->GetInputReportsReader(std::move(server)); |
| reader_one = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(client)); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| |
| // Get the second reader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader_two; |
| { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputReportsReader>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)input_device_->GetInputReportsReader(std::move(server)); |
| reader_two = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(client)); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| |
| // Send a report. |
| MouseReport report; |
| report.movement_x = 0x100; |
| report.movement_y = 0x200; |
| mouse_.SendReport(report); |
| |
| // Get the first report. |
| { |
| auto result = reader_one->ReadInputReports(); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto& reports = result->value()->reports; |
| |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_event_time()); |
| ASSERT_TRUE(reports[0].has_mouse()); |
| auto& mouse_report = reports[0].mouse(); |
| |
| ASSERT_TRUE(mouse_report.has_movement_x()); |
| ASSERT_EQ(0x100, mouse_report.movement_x()); |
| |
| ASSERT_TRUE(mouse_report.has_movement_y()); |
| ASSERT_EQ(0x200, mouse_report.movement_y()); |
| |
| ASSERT_FALSE(mouse_report.has_pressed_buttons()); |
| } |
| |
| // Get the second report. |
| { |
| auto result = reader_two->ReadInputReports(); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto& reports = result->value()->reports; |
| |
| ASSERT_EQ(1, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_event_time()); |
| ASSERT_TRUE(reports[0].has_mouse()); |
| auto& mouse_report = reports[0].mouse(); |
| |
| ASSERT_TRUE(mouse_report.has_movement_x()); |
| ASSERT_EQ(0x100, mouse_report.movement_x()); |
| |
| ASSERT_TRUE(mouse_report.has_movement_y()); |
| ASSERT_EQ(0x200, mouse_report.movement_y()); |
| |
| ASSERT_FALSE(mouse_report.has_pressed_buttons()); |
| } |
| } |
| |
| TEST_F(InputReportReaderTests, ReadInputReportsHangingGetTest) { |
| async::Loop loop = async::Loop(&kAsyncLoopConfigNeverAttachToThread); |
| |
| // Get an async InputReportsReader. |
| fidl::WireClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputReportsReader>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)input_device_->GetInputReportsReader(std::move(server)); |
| reader.Bind(std::move(client), loop.dispatcher()); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| |
| // Read the report. This will hang until a report is sent. |
| reader->ReadInputReports().ThenExactlyOnce( |
| [&](fidl::WireUnownedResult<fuchsia_input_report::InputReportsReader::ReadInputReports>& |
| result) { |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto& reports = result->value()->reports; |
| ASSERT_EQ(1, reports.count()); |
| |
| auto& report = reports[0]; |
| ASSERT_TRUE(report.has_event_time()); |
| ASSERT_TRUE(report.has_mouse()); |
| auto& mouse = report.mouse(); |
| |
| ASSERT_TRUE(mouse.has_movement_x()); |
| ASSERT_EQ(0x50, mouse.movement_x()); |
| |
| ASSERT_TRUE(mouse.has_movement_y()); |
| ASSERT_EQ(0x70, mouse.movement_y()); |
| loop.Quit(); |
| }); |
| loop.RunUntilIdle(); |
| |
| // Send the report. |
| MouseReport report; |
| report.movement_x = 0x50; |
| report.movement_y = 0x70; |
| mouse_.SendReport(report); |
| |
| loop.Run(); |
| } |
| |
| TEST_F(InputReportReaderTests, CloseReaderWithOutstandingRead) { |
| async::Loop loop = async::Loop(&kAsyncLoopConfigNeverAttachToThread); |
| |
| // Get an async InputReportsReader. |
| fidl::WireClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_input_report::InputReportsReader>(); |
| ASSERT_TRUE(endpoints.is_ok()); |
| auto [client, server] = std::move(endpoints.value()); |
| // TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it. |
| (void)input_device_->GetInputReportsReader(std::move(server)); |
| reader.Bind(std::move(client), loop.dispatcher()); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| |
| // Queue a read. |
| reader->ReadInputReports().ThenExactlyOnce( |
| [&](fidl::WireUnownedResult<fuchsia_input_report::InputReportsReader::ReadInputReports>& |
| result) { ASSERT_TRUE(result.is_canceled()); }); |
| |
| loop.RunUntilIdle(); |
| |
| // Unbind the reader now that the report is waiting. |
| reader = {}; |
| } |