| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/exo/wayland/wayland_protocol_logger.h" |
| |
| #include <wayland-server-core.h> |
| #include <wayland-util.h> |
| #include <xdg-shell-client-protocol.h> |
| #include <xdg-shell-server-protocol.h> |
| |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "components/exo/wayland/test/client_util.h" |
| #include "components/exo/wayland/test/resource_key.h" |
| #include "components/exo/wayland/test/server_util.h" |
| #include "components/exo/wayland/test/wayland_server_test.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace exo::wayland { |
| namespace { |
| |
| class WaylandProtocolLoggerTest : public test::WaylandServerTest { |
| public: |
| WaylandProtocolLoggerTest() = default; |
| WaylandProtocolLoggerTest(const WaylandProtocolLoggerTest&) = delete; |
| WaylandProtocolLoggerTest& operator=(const WaylandProtocolLoggerTest&) = |
| delete; |
| ~WaylandProtocolLoggerTest() override = default; |
| |
| // test::WaylandServerTest: |
| void SetUp() override { |
| WaylandProtocolLogger::SetHandlerFuncForTesting( |
| [](void* user_data, wl_protocol_logger_type type, |
| const wl_protocol_logger_message* message) { |
| messages_.push_back( |
| WaylandProtocolLogger::FormatMessage(type, message)); |
| }); |
| test::WaylandServerTest::SetUp(); |
| |
| // Ignore messages from this test case's setup phase. |
| messages_.clear(); |
| } |
| |
| // test::WaylandServerTest: |
| void TearDown() override { |
| messages_.clear(); |
| test::WaylandServerTest::TearDown(); |
| } |
| |
| static std::vector<std::vector<std::string>> messages_; |
| }; |
| |
| std::vector<std::vector<std::string>> WaylandProtocolLoggerTest::messages_ = {}; |
| |
| class ClientData : public test::TestClient::CustomData { |
| public: |
| ClientData() |
| : callback(nullptr, &wl_callback_destroy), |
| data_source(nullptr, &wl_data_source_destroy), |
| pointer(nullptr, &wl_pointer_destroy), |
| surface(nullptr, &wl_surface_destroy), |
| xdg_surface(nullptr, &xdg_surface_destroy), |
| xdg_toplevel(nullptr, &xdg_toplevel_destroy) {} |
| ~ClientData() override = default; |
| |
| std::unique_ptr<wl_callback, decltype(&wl_callback_destroy)> callback; |
| std::unique_ptr<wl_data_source, decltype(&wl_data_source_destroy)> |
| data_source; |
| std::unique_ptr<wl_pointer, decltype(&wl_pointer_destroy)> pointer; |
| std::unique_ptr<wl_surface, decltype(&wl_surface_destroy)> surface; |
| std::unique_ptr<xdg_surface, decltype(&xdg_surface_destroy)> xdg_surface; |
| std::unique_ptr<xdg_toplevel, decltype(&xdg_toplevel_destroy)> xdg_toplevel; |
| }; |
| |
| std::string ProxyId(void* proxy) { |
| return base::NumberToString( |
| wl_proxy_get_id(reinterpret_cast<wl_proxy*>(proxy))); |
| } |
| |
| void AddState(wl_array* states, xdg_toplevel_state state) { |
| xdg_toplevel_state* value = static_cast<xdg_toplevel_state*>( |
| wl_array_add(states, sizeof(xdg_toplevel_state))); |
| DCHECK(value); |
| *value = state; |
| } |
| |
| } // namespace |
| |
| TEST_F(WaylandProtocolLoggerTest, LogsBasicRequest) { |
| std::string surface_id; |
| std::string compositor_id; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->set_data(std::make_unique<ClientData>()); |
| data->surface.reset(wl_compositor_create_surface(client->compositor())); |
| compositor_id = ProxyId(client->compositor()); |
| surface_id = ProxyId(data->surface.get()); |
| }); |
| |
| EXPECT_THAT(messages_, |
| ::testing::Contains(::testing::ElementsAre( |
| base::StrCat({"Received request: wl_compositor@", |
| compositor_id, ".create_surface"}), |
| base::StrCat({"new id wl_surface@", surface_id})))); |
| } |
| |
| TEST_F(WaylandProtocolLoggerTest, LogsBasicEvent) { |
| std::string callback_id; |
| |
| EXPECT_THAT(messages_, ::testing::IsEmpty()); |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->set_data(std::make_unique<ClientData>()); |
| data->callback.reset(wl_display_sync(client->display())); |
| callback_id = ProxyId(data->callback.get()); |
| }); |
| |
| EXPECT_THAT(messages_, |
| ::testing::Contains(::testing::Contains(base::StrCat( |
| {"Sent event: wl_callback@", callback_id, ".done"})))); |
| } |
| |
| TEST_F(WaylandProtocolLoggerTest, LogsObjects) { |
| std::string xdg_wm_base_id; |
| std::string xdg_surface_id; |
| std::string surface_id; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->set_data(std::make_unique<ClientData>()); |
| xdg_wm_base_id = ProxyId(client->xdg_wm_base()); |
| data->surface.reset(wl_compositor_create_surface(client->compositor())); |
| data->xdg_surface.reset(xdg_wm_base_get_xdg_surface(client->xdg_wm_base(), |
| data->surface.get())); |
| surface_id = ProxyId(data->surface.get()); |
| xdg_surface_id = ProxyId(data->xdg_surface.get()); |
| }); |
| |
| EXPECT_THAT(messages_, |
| ::testing::Contains(::testing::ElementsAre( |
| base::StrCat({"Received request: xdg_wm_base@", |
| xdg_wm_base_id, ".get_xdg_surface"}), |
| base::StrCat({"new id xdg_surface@", xdg_surface_id}), |
| base::StrCat({"wl_surface@", surface_id})))); |
| } |
| |
| TEST_F(WaylandProtocolLoggerTest, LogsStrings) { |
| constexpr const char* window_title = "🦄🌈 I ♥️ UTF-8 😋"; |
| std::string xdg_toplevel_id; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->set_data(std::make_unique<ClientData>()); |
| data->surface.reset(wl_compositor_create_surface(client->compositor())); |
| data->xdg_surface.reset(xdg_wm_base_get_xdg_surface(client->xdg_wm_base(), |
| data->surface.get())); |
| data->xdg_toplevel.reset(xdg_surface_get_toplevel(data->xdg_surface.get())); |
| xdg_toplevel_set_title(data->xdg_toplevel.get(), window_title); |
| xdg_toplevel_id = ProxyId(data->xdg_toplevel.get()); |
| }); |
| |
| EXPECT_THAT(messages_, ::testing::Contains(::testing::ElementsAre( |
| base::StrCat({"Received request: xdg_toplevel@", |
| xdg_toplevel_id, ".set_title"}), |
| window_title))); |
| } |
| |
| // TODO(b/335767378): triggers dangling pointer detection. |
| TEST_F(WaylandProtocolLoggerTest, DISABLED_LogsObjectsAndStrings) { |
| std::string display_id; |
| constexpr uint32_t fake_error_code = 0x12345678u; |
| constexpr const char* fake_error = "🦄🌈 Fake error 😋"; |
| EXPECT_THAT(messages_, ::testing::IsEmpty()); |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| wl_resource_post_error(test::server_util::LookUpResource( |
| server_.get(), test::client_util::GetResourceKey( |
| client->display())), |
| fake_error_code, "%s", fake_error); |
| display_id = ProxyId(client->display()); |
| }); |
| |
| EXPECT_THAT( |
| messages_, |
| ::testing::Contains(::testing::ElementsAre( |
| base::StrCat({"Sent event: wl_display@", display_id, ".error"}), |
| base::StrCat({"wl_display@", display_id}), |
| base::NumberToString(fake_error_code), fake_error))); |
| } |
| |
| TEST_F(WaylandProtocolLoggerTest, LogsFileDescriptors) { |
| std::string data_source_id; |
| test::ResourceKey data_source_resource_key; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->set_data(std::make_unique<ClientData>()); |
| data->data_source.reset(wl_data_device_manager_create_data_source( |
| client->data_device_manager())); |
| data_source_id = ProxyId(data->data_source.get()); |
| data_source_resource_key = |
| test::client_util::GetResourceKey(data->data_source.get()); |
| }); |
| |
| wl_resource* data_source_resource = test::server_util::LookUpResource( |
| server_.get(), data_source_resource_key); |
| EXPECT_NE(data_source_resource, nullptr); |
| |
| // Actually sending a wl_data_source::send event requires a valid file |
| // descriptor, which requires some ceremony to set up properly. |
| // Instead, call the message formatting utility directly. |
| const struct wl_interface* arg_types[2] = {nullptr, nullptr}; |
| wl_message message = { |
| .name = "send", .signature = "sh", .types = &arg_types[0]}; |
| wl_argument args[] = {{.s = "text/plain"}, {.h = 12345}}; |
| wl_protocol_logger_message logger_message = { |
| .resource = data_source_resource, |
| .message_opcode = WL_DATA_SOURCE_SEND, |
| .message = &message, |
| .arguments_count = 2, |
| .arguments = &args[0], |
| }; |
| |
| auto result = WaylandProtocolLogger::FormatMessage(WL_PROTOCOL_LOGGER_EVENT, |
| &logger_message); |
| EXPECT_THAT(result, ::testing::ElementsAre( |
| base::StrCat({"Sent event: wl_data_source@", |
| data_source_id, ".send"}), |
| "text/plain", "fd 12345")); |
| } |
| |
| TEST_F(WaylandProtocolLoggerTest, LogsFixedPointNumbers) { |
| // Arrange: Create a wl_pointer client object, wait for server to receive. |
| std::string pointer_id; |
| test::ResourceKey pointer_resource_key; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->set_data(std::make_unique<ClientData>()); |
| data->pointer.reset(wl_seat_get_pointer(client->seat())); |
| pointer_id = ProxyId(data->pointer.get()); |
| pointer_resource_key = |
| test::client_util::GetResourceKey(data->pointer.get()); |
| }); |
| |
| // Act: Send a motion event |
| wl_resource* pointer_resource = |
| test::server_util::LookUpResource(server_.get(), pointer_resource_key); |
| EXPECT_NE(pointer_resource, nullptr); |
| |
| wl_pointer_send_motion(pointer_resource, 123, wl_fixed_from_double(1234.5), |
| wl_fixed_from_double(0)); |
| |
| // Assert: Motion event correctly logged |
| EXPECT_THAT( |
| messages_, |
| ::testing::Contains(::testing::ElementsAre( |
| base::StrCat({"Sent event: wl_pointer@", pointer_id, ".motion"}), |
| "123", "1234.5", "0"))); |
| } |
| |
| TEST_F(WaylandProtocolLoggerTest, LogsArrays) { |
| std::string xdg_toplevel_id; |
| test::ResourceKey xdg_toplevel_resource_key; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->set_data(std::make_unique<ClientData>()); |
| data->surface.reset(wl_compositor_create_surface(client->compositor())); |
| data->xdg_surface.reset(xdg_wm_base_get_xdg_surface(client->xdg_wm_base(), |
| data->surface.get())); |
| data->xdg_toplevel.reset(xdg_surface_get_toplevel(data->xdg_surface.get())); |
| xdg_toplevel_id = ProxyId(data->xdg_toplevel.get()); |
| xdg_toplevel_resource_key = |
| test::client_util::GetResourceKey(data->xdg_toplevel.get()); |
| }); |
| |
| wl_resource* xdg_toplevel_resource = test::server_util::LookUpResource( |
| server_.get(), xdg_toplevel_resource_key); |
| EXPECT_NE(xdg_toplevel_resource, nullptr); |
| |
| // Act: Send a typical configure event. |
| { |
| messages_.clear(); |
| wl_array states; |
| wl_array_init(&states); |
| AddState(&states, XDG_TOPLEVEL_STATE_ACTIVATED); |
| AddState(&states, XDG_TOPLEVEL_STATE_FULLSCREEN); |
| xdg_toplevel_send_configure(xdg_toplevel_resource, 1024, 768, &states); |
| wl_array_release(&states); |
| } |
| |
| // Assert: Short arrays print correctly. |
| EXPECT_THAT(messages_, |
| ::testing::Contains(::testing::ElementsAre( |
| base::StrCat({"Sent event: xdg_toplevel@", xdg_toplevel_id, |
| ".configure"}), |
| "1024", "768", "array[8 bytes]{0400000002000000}"))); |
| |
| // Act: Send a very long array. |
| { |
| messages_.clear(); |
| wl_array states; |
| wl_array_init(&states); |
| for (int i = 0; i < 14; i++) { |
| AddState(&states, XDG_TOPLEVEL_STATE_ACTIVATED); |
| } |
| xdg_toplevel_send_configure(xdg_toplevel_resource, 1024, 768, &states); |
| wl_array_release(&states); |
| } |
| |
| // Assert: Long arrays truncate as expected. |
| EXPECT_THAT(messages_, |
| ::testing::Contains(::testing::ElementsAre( |
| base::StrCat({"Sent event: xdg_toplevel@", xdg_toplevel_id, |
| ".configure"}), |
| "1024", "768", |
| "array[56 bytes]" |
| "{04000000040000000400000004000000040000000400000004000000" |
| "0400000004000000040000000400000004000000...}"))); |
| } |
| |
| } // namespace exo::wayland |