WLCS Development Guide

Architecture Overview

What is WLCS: A protocol-conformance test suite for Wayland compositors. Unlike previous test suites that use protocol extensions, WLCS uses compositor-provided integration modules loaded as shared libraries (.so files). Tests run in-process with the compositor, enabling easier debugging and consistent timing.

Core Components:

  • include/wlcs/display_server.h: Integration API that compositors must implement (WlcsServerIntegration, WlcsDisplayServer structs)
  • src/: Test infrastructure (test runner, client/server helpers, protocol wrappers)
  • tests/: Protocol conformance tests for wl_compositor, xdg_shell, layer_shell, input methods, etc.
  • example/mir_integration.cpp: Reference integration showing how Mir implements the WLCS API

Key Pattern: Compositors provide a wlcs_server_integration symbol in their integration .so. WLCS loads this and calls hooks to start/stop the compositor, create client sockets, inject input, etc.

Test Infrastructure

Base Classes (see include/in_process_server.h):

  • InProcessServer: GTest fixture that creates a Server instance. Use SetUp()/TearDown() explicitly
  • StartedInProcessServer: Auto-starts the server in constructor. Most tests inherit from this

Creating Test Clients:

wlcs::Client client{the_server()};   // Creates Wayland client connection
wlcs::Surface surface{client};       // Creates wl_surface
client.roundtrip();                  // Block until server processes requests

Simulating Input (see include/wlcs/pointer.h, include/wlcs/touch.h, include/wlcs/keyboard.h):

auto pointer = the_server().create_pointer();
pointer.move_to(100, 200);  // Absolute coordinates
pointer.left_click();

auto touch = the_server().create_touch();
touch.down_at(50, 50);
touch.move_to(60, 60);
touch.up();

auto keyboard = the_server().create_keyboard();
keyboard.key_down(KEY_A);  // Linux input event codes
keyboard.key_up(KEY_A);
keyboard.key(KEY_ENTER);   // Press and release

Protocol Wrappers: Headers like layer_shell_v1.h, xdg_shell_stable.h provide RAII wrappers around Wayland protocols with helper methods (e.g., dispatch_until_configure()).

Parameterized Tests: Many tests use TEST_P to run across multiple shell types or input devices. See tests/subsurfaces.cpp for the pattern using AbstractInputDevice abstraction.

Code Style

Follow the Canonical Mir C++ Guide.

Requirements:

  • C++20 required (CMAKE_CXX_STANDARD 20)
  • Strict warnings: -Werror enabled by default (toggle with WLCS_FATAL_COMPILE_WARNINGS)

Key Conventions:

  • Headers use include guards (#ifndef WLCS_*_H_)
  • Wayland objects wrapped in RAII types (see WlHandle<T> in wl_handle.h)
  • Helper timeouts: wlcs::helpers::a_short_time() (for negative tests), wlcs::helpers::a_long_time() (for operation timeouts)
  • Test structure: Arrange (create surfaces/clients), Act (inject input/commit), Assert (check window focus/position)

Testing Protocol Errors: Use expect_protocol_error.h to verify compositor rejects invalid client requests:

EXPECT_PROTOCOL_ERROR(client, &interface_name, error_code, {
    // code that should trigger error
});

Common Tasks

Adding Protocol Tests:

  1. Add .xml to src/protocol/ if not present
  2. Create wrapper class in include/ (follow pattern from layer_shell_v1.h)
  3. Implement wrapper in src/ (generate protocol bindings in CMake)
  4. Write tests in tests/ inheriting from StartedInProcessServer

Protocol Version Handling: Use VersionSpecifier (see version_specifier.h) to bind interfaces:

auto shell = client.bind_if_supported<xdg_wm_base>(wlcs::AtLeast(2));