| // 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/async/cpp/task.h> |
| #include <lib/fidl/cpp/wire/server.h> |
| #include <lib/input_report_reader/reader.h> |
| |
| #include <zxtest/zxtest.h> |
| |
| #include "zircon/system/public/zircon/time.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(); |
| |
| size_t 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; |
| } |
| void SetInitialReport(MouseReport report) { initial_report_ = report; } |
| |
| // The FIDL methods for InputDevice. |
| void GetInputReportsReader(GetInputReportsReaderRequestView request, |
| GetInputReportsReaderCompleter::Sync& completer) override; |
| void GetDescriptor(GetDescriptorCompleter::Sync& completer) override; |
| void SendOutputReport(SendOutputReportRequestView request, |
| SendOutputReportCompleter::Sync& completer) override; |
| void GetFeatureReport(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, 10> input_report_readers_; |
| async::Loop loop_ = async::Loop(&kAsyncLoopConfigNeverAttachToThread); |
| std::optional<MouseReport> initial_report_ = std::nullopt; |
| }; |
| |
| zx_status_t MouseDevice::Start() { |
| zx_status_t status = ZX_OK; |
| if ((status = loop_.StartThread("MouseDeviceReaderThread")) != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| size_t MouseDevice::SendReport(const MouseReport& report) { |
| return input_report_readers_.SendReportToAllReaders(report); |
| } |
| |
| void MouseDevice::GetInputReportsReader(GetInputReportsReaderRequestView request, |
| GetInputReportsReaderCompleter::Sync& completer) { |
| sync_completion_t wait; |
| async::PostTask(loop_.dispatcher(), [&]() { |
| zx_status_t status = input_report_readers_.CreateReader( |
| loop_.dispatcher(), std::move(request->reader), initial_report_); |
| 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_); |
| } |
| sync_completion_signal(&wait); |
| }); |
| sync_completion_wait(&wait, ZX_TIME_INFINITE); |
| } |
| |
| void MouseDevice::GetDescriptor(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(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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputDevice>::Create(); |
| 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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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 [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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 = {}; |
| } |
| |
| TEST_F(InputReportReaderTests, MaxUnreadReports) { |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| ASSERT_TRUE(input_device_->GetInputReportsReader(std::move(server)).ok()); |
| reader = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(client)); |
| mouse_.WaitForNextReader(zx::duration::infinite()); |
| } |
| |
| // Send 15 reports, and store the counter value in movement_x. Our InputReportReaderManager has |
| // kMaxUnreadReports set to 10, so the first five reports should be dropped. |
| for (int64_t i = 1; i <= 10; i++) { |
| // The first 10 reports should be accepted without causing others to be dropped. |
| EXPECT_EQ(mouse_.SendReport({.movement_x = i}), 0); |
| } |
| for (int64_t i = 11; i <= 15; i++) { |
| // With the report queue full, SendReport should now result in one report getting dropped. |
| EXPECT_EQ(mouse_.SendReport({.movement_x = i}), 1); |
| } |
| |
| auto result = reader->ReadInputReports(); |
| ASSERT_OK(result.status()); |
| ASSERT_FALSE(result->is_error()); |
| auto& reports = result->value()->reports; |
| |
| ASSERT_EQ(10, reports.count()); |
| |
| ASSERT_TRUE(reports[0].has_mouse()); |
| ASSERT_TRUE(reports[0].mouse().has_movement_x()); |
| EXPECT_EQ(reports[0].mouse().movement_x(), 6); |
| |
| ASSERT_TRUE(reports[9].has_mouse()); |
| ASSERT_TRUE(reports[9].mouse().has_movement_x()); |
| EXPECT_EQ(reports[9].mouse().movement_x(), 15); |
| } |
| |
| TEST_F(InputReportReaderTests, InitialReportTest) { |
| mouse_.SetInitialReport({ |
| .movement_x = 0x50, |
| .movement_y = 0x100, |
| }); |
| |
| // Get an InputReportsReader. |
| fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader; |
| { |
| auto [client, server] = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create(); |
| // TODO(https://fxbug.dev/42180237) 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()); |
| } |
| |
| // 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(0x50, mouse_report.movement_x()); |
| |
| ASSERT_TRUE(mouse_report.has_movement_y()); |
| ASSERT_EQ(0x100, mouse_report.movement_y()); |
| |
| ASSERT_FALSE(mouse_report.has_pressed_buttons()); |
| } |