Skip to content

tests/protocols: add 24 standard wayland-protocols tests served by Treeland - #1336

Closed
deepin-wm wants to merge 1 commit into
linuxdeepin:masterfrom
deepin-wm:feat/wayland-protocols-test-coverage
Closed

deepin-wm wants to merge 1 commit into
linuxdeepin:masterfrom
deepin-wm:feat/wayland-protocols-test-coverage

Conversation

@deepin-wm

@deepin-wm deepin-wm commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Add 24 standard wayland-protocols tests covering every protocol Treeland provides at runtime via wlroots (wayland-protocols 1.49.0-1).

Coverage levels

Level Count Protocols
E (end-to-end) 8 xdg-output, xdg-dialog, xdg-toplevel-tag, fractional-scale, single-pixel-buffer, viewporter, alpha-modifier, ext-session-lock
P (protocol/resource) 16 ext-idle-notify, xdg-activation, idle-inhibit, xdg-decoration, content-type, tearing-control, relative-pointer, pointer-constraints, xdg-foreign, security-context, linux-dmabuf, tablet, cursor-shape, xwayland-shell, presentation-time, and others

E-level tests

Each E-level test reads real production object state over the server bridge and cross-checks it against client-side protocol observation:

  • xdg-output: client logical_position/logical_size vs real WOutput::position()/effectiveSize()
  • xdg-dialog: real SurfaceWrapper::modal() flips false → true after set_modal
  • xdg-toplevel-tag: real WXdgToplevelSurface::tag() equals client-set value
  • fractional-scale: real WOutput::scale() vs preferred_scale (round(scale×120))
  • single-pixel-buffer: real wlr_surface::current.buffer_width/height == 1×1 on mapped toplevel
  • viewporter: real wlr_surface::current.viewport dst state after set_destination(320,240)
  • alpha-modifier: real wlr_alpha_modifier_surface_v1_state::multiplier transitions 0.0 → 1.0
  • ext-session-lock: real WSessionLock::isLocked() == true after locked event

Test results

All 24 tests pass on headless backend (WLR_BACKENDS=headless, WLR_RENDERER=pixman).

Closes #1332 (resubmitted with correct commit author).


Multica: WM-328

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @deepin-wm, your pull request is larger than the review limit of 150,000 diff characters

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: deepin-wm

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-wm
deepin-wm force-pushed the feat/wayland-protocols-test-coverage branch 2 times, most recently from 84c7804 to 9125186 Compare August 26, 2026 09:31
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@deepin-wm
deepin-wm force-pushed the feat/wayland-protocols-test-coverage branch 5 times, most recently from f0482f5 to 50324cf Compare August 27, 2026 10:18
@deepin-bot

deepin-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 0.9.1
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1348

@deepin-wm
deepin-wm force-pushed the feat/wayland-protocols-test-coverage branch 2 times, most recently from 59c3c86 to f716da5 Compare September 1, 2026 01:46
}

struct wp_alpha_modifier_v1 *manager =
client_bind(&conn, "wp_alpha_modifier_v1", &wp_alpha_modifier_v1_interface, 1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"wp_alpha_modifier_v1"应该可以使用wp_alpha_modifier_v1_interface中的name替代

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


struct wp_alpha_modifier_v1 *manager =
client_bind(&conn, "wp_alpha_modifier_v1", &wp_alpha_modifier_v1_interface, 1);
if (!manager) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (manager == NULL)

struct wp_alpha_modifier_v1 *manager =
client_bind(&conn, "wp_alpha_modifier_v1", &wp_alpha_modifier_v1_interface, 1);
if (!manager) {
fprintf(stderr, "alpha-modifier: failed to bind wp_alpha_modifier_v1\n");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用wlr_log_errno,里面有strerror(errno)可以方便打印内存申请为什么报错

wp_alpha_modifier_v1_get_surface(manager, tc.surface);
if (!am_surface) {
fprintf(stderr, "alpha-modifier: get_surface returned NULL\n");
wp_alpha_modifier_v1_destroy(manager);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manager为什么不释放?


wp_alpha_modifier_surface_v1_destroy(am_surface);
wp_alpha_modifier_v1_destroy(manager);
xdg_toplevel_client_destroy(&tc);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manager没释放

@deepin-wm
deepin-wm force-pushed the feat/wayland-protocols-test-coverage branch 2 times, most recently from 76414e8 to 2dab4c5 Compare September 1, 2026 07:48
@deepin-bot

deepin-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 0.10.0
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1392

@deepin-wm
deepin-wm force-pushed the feat/wayland-protocols-test-coverage branch 2 times, most recently from 403d794 to aac6d2d Compare September 15, 2026 06:29
Comment thread tests/protocols/framework/server-bridge.cpp Outdated
add_subdirectory(treeland-wine-window-state-unstable-v1)
add_subdirectory(treeland-wine-window-management-unstable-v1)

# --- Standard wayland-protocols (1.49) served by Treeland ---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit message 里写 “All 55 protocol tests pass”,但树对不上:父提交注册 44 个 add_subdirectory(去掉受 TREELAND_ENABLE_UINPUT_PROTOCOL_TESTS 保护的 uinput 是 43 个,与本地旧 build 的 ctest -N -L protocols 完全一致),本提交 +24 → 默认注册 67 个 target。43 + 24 = 67,55 这个数无法解释。

既然 add_headless_output 现在会显式 enable+commit output,请在本 PR 贴上全量 ctest --test-dir build -L protocols --output-on-failure 的结果(passed/skipped 数),并特别确认这两个既有测试没受影响:wlr-output-power-management-unstable-v1(断言 bind 后首个 mode == ON,直接依赖 output->enabled)和 wlr-output-management-unstable-v1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 数字已改成 67(43 + 24,对上了),但两点仍在:

  1. 全量 ctest -L protocols 的输出还没贴。这次改了共享 fixture(output 真的被 enable 了),请贴 passed/skipped 数,并给出 wlr-output-power-management-unstable-v1(断言 bind 后首个 mode == ON)的结果。
  2. commit body 里的 "C test client code follows wlroots coding style" 现在与实现不符——这一轮已把 wlr_log 换成 TEST_ERROR/fprintf + 本地镜像枚举,建议改成 "plain C + fprintf",避免读者按 wlroots 风格去理解。


auto *mgr = find_server_interface<WSecurityContextManager>(helper);
if (mgr) {
auto *g = static_cast<SecurityContextAccessor *>(mgr)->global();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里两个问题:

  1. static_cast<SecurityContextAccessor *>(mgr) 把一个实际类型为 WSecurityContextManager 的对象转成从未实例化过的派生类指针,是 UB(该类还是 WObject + WServerInterface 多继承 + 虚函数)。“为了让 protected global() 可见而造派生类”不是合法的下转。
  2. 整条 global()wl_global_get_user_data() 链路是多余的:WServerInterface::handle() / nativeInterface<T>()publicwaylib/src/server/kernel/wserver.h:36,44),并且 WSecurityContextManager::create()m_handle = wlr_security_context_manager_v1_create(...)wsecuritycontextmanager.cpp:562),也就是和 global user_data 同一个指针(wl_global_create(..., manager, manager_bind),:494)。同 PR 的 wayland-cursor-shape-v1/setup.cpp:63 就已经用 g_mgr->handle() 了。

删掉 Accessor、global 查找和 wl_global_get_user_data(约 20 行):

auto *mgr = find_server_interface<WSecurityContextManager>(helper);
if (mgr) {
    auto *wlr_mgr = static_cast<struct local_mgr *>(mgr->handle());
    if (wlr_mgr)
        wl_signal_add(&wlr_mgr->events.commit, &g_commit_listener);
}

注意 local_mgr 不能改成 #include <wlr/types/wlr_security_context_v1.h>:waylib 在自己的 .cpp 里抄了一份结构(wsecuritycontextmanager.cpp:32),events 里比 vendored wlroots 头多一个 new_client。所以注释要改成“mirror of waylib/src/server/protocols/wsecuritycontextmanager.cpp:32”。真正干净的解法是把该 struct 挪进 waylib 的 _p.h,实现与测试共用一份定义——可开后续单。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 一半按建议改了,一半改反了。已修:SecurityContextAccessor 和 UB 下转删除,改用 public mgr->handle()。改反:我明确写过不要用 #include <wlr/types/wlr_security_context_v1.h>,因为这行实现走的是 waylib 在 wsecuritycontextmanager.cpp:32 自抄的一份 struct(events 里比 vendored wlroots 头多一个 new_client)。现在测试用 vendored 头的类型去解释 waylib 分配的对象,见新评论。

Comment thread tests/protocols/wayland-security-context-v1/setup.cpp Outdated
* on client-initiated unlock, so we only flush the request. */
ext_session_lock_v1_unlock_and_destroy(lock);
wl_display_roundtrip(conn.display);
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

收到 finished 就静默通过,把 E 级断言变成了条件断言。而这个 fixture 里 finished 不应出现:Helper::onExtSessionLock() 只有在 m_lockScreen->isLocked() 时才 lock->finish()src/seat/helper.cpp:3170-3173),协议测试进程里没有已锁定的锁屏。

当前写法的后果:哪天产品变成无条件拒绝锁屏(真回归),测试仍然绿。请至少改成失败:

} else {
    wlr_log(WLR_ERROR, "ext-session-lock: compositor rejected the lock (finished)");
    ext_session_lock_v1_destroy(lock);
    failed = 1;
}

如果确实要容忍该分支,请用框架已有的 skip 通道(return 77 / 输出 SKIP :),并在 README 的“已知边界”里写明,而不是 Pass。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 代码已修(finishedfailed = 1,文件头注释同步)。❗文档没跟上:README.md:27 仍写 "此时测试回退到 P 级通过",INDEX.md:184 仍写 "允许 300ms grace timer 后收到 locked 或立即收到 finished"。行为变了就必须同步这两处(skill 里明说 coverage/边界变化要同时更新 README 与 INDEX)。

// Attach a real pointer device so the seat advertises pointer capability
// and wl_seat_get_pointer yields a real wl_pointer resource for
// cursor/gesture/relative managers.
WSeat *seat = helper->seat();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

helper->seat() 没判空就直接 seat->attachInputDevice(device),而同一份“注入虚拟指针设备”的代码块在另外 3 个文件里都有 if (seat)wayland-cursor-shape-v1/setup.cpp:49wayland-pointer-constraints-unstable-v1/setup.cpp:33wayland-relative-pointer-unstable-v1/setup.cpp:30

建议 4 份合并成一个框架函数(server-bridge.h):

void attach_test_pointer_device(Helper *helper);   // 内部判空 + calloc + wlr_pointer_init + new WInputDevice(..., true) + attachInputDevice

new WInputDevice(&pointer->base, /*isVirtual=*/true) 与 waylib 既有先例一致(waylib/src/server/protocols/winputmethodhelper.cpp:416),这块逻辑本身没问题,只是不该抄 4 遍并留下一个漏判空的副本。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 半个修复。现在只有 seat->attachInputDevice(device)if (seat) 包住,calloc + wlr_pointer_init + new WInputDevice 仍在 guard 外——seat 为空时照样造设备、照样不挂,只是把失败推迟到客户端断连超时报错,比原来更难查。fixture 应当断言生产对象存在(skill:assert that the production object is present),建议 Q_ASSERT(seat)。另外"4 份重复的指针设备注入"这一条完全没动(cursor-shape:49 / pointer-constraints:33 / relative-pointer:30 / 这里 :62 仍是 4 份)。

.resumed = resumed,
};

static int wait_for_idled(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个文件和 wayland-ext-idle-notify-v1/*.c 是同一个测试写了两遍:idle_stateidled/resumed listener、wait_for_idled()(本文件 :66-84 与对方 :68-86 逐行相同)、seat listener、三步 inhibitor 状态机全部一致,只有日志前缀不同。约 200 行 ×2。

两个问题:

  1. wait_for_idledidle_state、seat listener 请挪进 framework(已有 framework/xdg-toplevel-client.c 这种共享 C helper 的先例)。
  2. 既然两边都靠 ext-idle-notify 的 idled 判断,idle-inhibit 这个 target 实际上没有断言自己的生产对象。建议让它回读 idle-inhibit 侧状态(inhibitor 是否挂在被抑制 surface 上),而不是再驱动一遍 ext-idle-notify——否则 24 个协议实际只有 23 个独立场景,INDEX 里也是两行相同描述。

另外 wait_for_idled()elapsed += 50 与实际 poll 结果无关,且 ret < 0(EINTR)被忽略,长时间窗口下会空转;顺手把 if (ret > 0) 改成对 ret < 0 && errno == EINTR 的 continue 更稳。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ wait_for_idled 的 nit 已修(改成 CLOCK_MONOTONIC 真实计时 + EINTR continue + poll 出错返回 -1,两份都改了 👍)。但结构性问题未动:两个文件仍然各自定义一份 wait_for_idled/idle_state/seat listener,idle-inhibit 依然靠 ext-idle-notify 的 idled 判据,没有回读自己的生产对象。如果留到后续,请在 PR 描述或 TODO 里明确挂出来。

#include <stdint.h>
#include <wlr/util/log.h>

static int read_server(struct alpha_modifier_server_state *state) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本 PR +6048 行里,同一段代码被抄了很多遍(扫过 24 个目录的统计):

重复块 份数
static int read_server(...)(memset + invoke_on_server_thread + 判 0 + log) 15
seat_caps/seat_name/seat_listener + g_caps 9
虚拟指针设备注入 4
镜像 wlroots/waylib 结构的 .h 24

这份就是 15 份 copy 之一(只有结构体类型不同)。建议按 framework/client-connection.h / framework/xdg-toplevel-client.c 的既有做法,在 framework 里补两样共享件:

  1. bind_pointer_seat()(含 capability 断言,替掉 9 份 seat listener);
  2. 一个 TEST_READ_SERVER(fn, ptr) 宏或 read_server_state(fn, void*, size_t)(内部 memset + invoke + 判 0 + log),把 15 份收敛成 15 次调用。

不要求在本 PR 做完,但 24 个协议不该等于 24 份样板,后续再加协议时这个成本会线性长。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ 未处理(可接受为后续项,但请显式登记)。新 head 统计:read_server 仍 15 份、seat_listener 仍 9 份、指针设备注入仍 4 份、镜像结构 .h 仍 24 份。

/*
* E-level readback: the production WPointerConstraintsV1 resolves the active
* constraint for the client's surface+seat via constraintForSurface(). The
* constraint type must be Locked (1), proving the lock_pointer request reached

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释与自身矛盾:这里写 "must be Locked (1)",而同文件 :20 和 :25 都写 0=Locked, 1=Confined.c:114 断言的也是 != 0enum wlr_pointer_constraint_v1_type3rdparty/wlroots/include/wlr/types/wlr_pointer_constraints_v1.h)确实是 LOCKED=0。请改成 "Locked (0)"。

顺带建议 client 里用枚举名替代魔数(scanner 头里都有):ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT 已在 .c:101 用了魔数 1 + 注释、WL_SEAT_CAPABILITY_POINTER.c:63 写成 0x1。同一模式在 cursor-shaperelative-pointerprimary-selectiong_caps & 2)等文件也有,用枚举名后注释就不可能写错。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 部分。已修:.h:17 改成 "Locked (0)",并新增 POINTER_CONSTRAINT_TYPE_LOCKED/CONFINED 供 client 断言使用(同时补了 .unlocked 立即解锁的负向断言,比原评审要求更严 👍)。未修:g_caps & 0x1(cursor-shape:63、pointer-constraints:75、relative-pointer:63、pointer-gestures:64)、g_caps & 2(primary-selection:128)、1 /* LIFETIME_ONESHOT */(:122)、1 /* CLIENT_SIDE */(xdg-decoration:82)仍是魔数——WL_SEAT_CAPABILITY_POINTER 来自 wayland-client-protocol.hclient-connection.h 已 include),这几处零成本。见 cursor-shape:63 的新评论。

Comment thread tests/protocols/wayland-cursor-shape-v1/wayland-cursor-shape-v1.c Outdated
Comment thread .gitignore Outdated
* globals served by Treeland (wlr_ext_image_copy_capture_manager_v1_create and
* wlr_ext_output_image_capture_source_manager_v1_create in Helper).
* The client binds both managers and verifies they are advertised by the
* compositor. (Creating a capture session in headless mode can deadlock in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

两点:

  1. 即使绕开 swapchain,也能低成本多验一层:ext_output_image_capture_source_manager_v1.create_source(output)(请求确认存在,见 staging/ext-image-capture-source/ext-image-capture-source-v1.xml)+ 断言非空再 destroy。fixture 已经建好 headless output,这一步不碰会话,就能把“全局存在”升级成“output→source 工厂可用”。
  2. 注释说“创建捕获会话可能在 wlr_output_configure_primary_swapchain 死锁”——这是疑似产品缺陷,只写在测试注释里等于没人跟进。建议开 issue 并把编号写在这里,同时同步到 INDEX 的“已知边界”。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 代码侧已修(create_source(wl_output) + 非空断言 + destroy,并引用 issue #1407 记录 swapchain 死锁)。❗文档现在与代码相反:README.md 的"必须观察到的结果"仍列 create_session(source, 0)buffer_size,"生产结果"写"启动 copy-capture 会话后,服务端下发 buffer_size……证明捕获源/会话路径完整接入输出"——测试根本没建会话(正是绕开死锁的原因);而 INDEX.md:72 又只写"验证全局存在"。README 与 INDEX 互相矛盾、且 README 描述的是未实现的行为,请改成实际断言(绑定 + create_source 非空),把 session/buffer_size 移到"已知边界"。顺带:这里 client_bind 取第一个 wl_output,xdg-output 用 client_bind_last,同一 fixture 两套取法,建议统一。

} else if (!srv.valid) {
wlr_log(WLR_ERROR, "xdg-activation: no activateRequested captured");
failed = 1;
} else if (srv.disposition == 0 /* Invalid */) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disposition != Invalid 证明的东西很少(token 是自己刚申请来的,非 Invalid 基本是必然)。ActivationManagerInterfaceV1::activateRequested 的签名带了 surface 和 seat(src/modules/activation/activationmanagerinterfacev1.h:43-45):

void activateRequested(TokenDisposition disposition, WSurface *surface, WSeat *seat);

所以回读结构里再带上 WSurface*,与 tc.surface 对应的生产 surface 比对,并断言 disposition 是具体的期望值(Success 而非“非 Invalid”),这才算 INDEX 里写的端到端业务结果。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 部分。已修:不再只断言"非 Invalid",改成 disposition == Attention(1) 并新增 surface_captured。期望值我核过,activationmanagerinterfacev1.cpp:236-241 在无 serial 时返回 Attention,测试不发 set_serial,所以是确定的。未做的部分:surface_captured 只证明"信号里带了个非空 surface",不证明它就是我们刚激活的 tc.surface;setup 里已经有 ShellHandler/WXdgToplevelSurface 那套捕获模式(alpha-modifier、xdg-dialog 都在用),比一次 g_surface_ptr == wrapper->surface() 就能把"激活到了正确窗口"钉死。

if (outputs.isEmpty())
return;

auto *woutput = outputs.first()->output();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outputs.first() 隐含“测试进程里只有一个 output”。xdg-output 同(wayland-xdg-output-unstable-v1/setup.cpp:42)。本 PR 刚让 add_headless_output 真正 enable 了 output,一旦 fixture 或生产哪天多出一个 output,这两个断言就会对错对象,且失败信息看不出原因。

建议让 add_headless_output() 返回创建出的 WOutput *(现在只返回 bool),setup 里保存自己创建的那个再回读;顺带也能满足“E 级回读的对象必须是 fixture 自己造的”这一约定。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 方向对(add_headless_output 返回 WOutput *,两个测试改存 fixture 自己的 output),但引入两个新问题,见 setup.cpp:41 / xdg-output.c:84 的新评论:outputs.isEmpty() 变成装饰性检查 + 裸指针缓存;client 用 client_bind_last 猜 output、server 用 g_woutput,两边没有一致性校验。

Comment thread tests/protocols/wayland-single-pixel-buffer-v1/wayland-single-pixel-buffer-v1.c Outdated
Comment thread tests/protocols/wayland-text-input-unstable-v1/setup.cpp Outdated
#include <wbackend.h>
#include <protocols/wsecuritycontextmanager.h>

#include <wlr/types/wlr_security_context_v1.h>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这一轮把 UB 下转修好了(改用 public mgr->handle() 👍),但把 local_mgr/local_state 换成了 vendored wlroots 的类型——这正是上轮评论里提醒不要做的方向。

运行时对象是 waylib 自己分配的那份:waylib/src/server/protocols/wsecuritycontextmanager.cpp:32 里有一份 struct wlr_security_context_manager_v1私有抄本events{destroy, commit, new_client};vendored 头(3rdparty/wlroots/include/wlr/types/wlr_security_context_v1.h)是 {destroy, commit}。今天两者 events.commit 都落在 offset 16,所以能跑;但这是巧合级别的兼容:waylib 那份在 commit 之前插字段、或 wlr_security_context_v1_state 调序,测试不会编译失败也不会报错,只会静默读错内存(app_id 读到别的指针,断言可能假通过)。原来至少有 local_* + 注释表达"这是人工同步的抄本",现在这个信号也没了。

建议(择一,成本都很低):

  1. 把 waylib 那份 struct 挪进 waylib/src/server/protocols/private/wsecuritycontextmanager_p.h,实现与测试 include 同一个定义(真正的单一事实来源,推荐);
  2. 本 PR 先回退成 local_* 抄本 + 注明"mirror of wsecuritycontextmanager.cpp:32,注意与 vendored 头不同(多一个 new_client)",1 另开单子跟 waylib 侧收敛。

if (outputs.isEmpty())
return;

auto *woutput = g_woutput;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g_woutput 修好了"断错 output"的问题,但带来两个新的:

  1. outputs.isEmpty()(:38)现在是装饰性检查:它校验的是 layout 的列表,而真正被读的是缓存指针。缓存的 WOutput* 变野时它拦不住。WOutput 是 QObject 且有 beforeDestroy() 信号(waylib/src/server/kernel/woutput.h:34,110),所以一行就能自保:
static QPointer<WOutput> g_woutput;   // + #include <QPointer>
...
auto *woutput = g_woutput.data();
if (!woutput)
    return;

或者反过来用 layout 校验身份:if (!container->outputs().contains(g_woutput)) return;,保留现有 guard 的意义。

  1. 上面那段 helper->rootSurfaceContainer()outputs() 的取数路径已经不再决定结果,可以整段删掉(只留 QPointer 的 null 检查),少一半样板。

wayland-xdg-output-unstable-v1/setup.cpp:44 同一模式,一并改。

}

/* Bind the LAST wl_output global — it is the one created by protocol_test_setup.
* Using client_bind_last ensures the client reads the same WOutput that the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释说 "Using client_bind_last ensures the client reads the same WOutput that the server-side read_server_state cross-checks via g_woutput"——这个 ensures 并不成立:client_bind_last(取 registry 里最后一个 wl_output global)和 g_woutput(setup 里 add_headless_output 返回的那个)是两个互相独立的猜测,没有任何一处校验它们指向同一个 output。今天只有一个 output 所以恒等;一旦 initTestCase 之后再有 output 产生(virtual-output、xwayland、dconf 热插拔),E 级断言就会拿 A 的几何去比 B 的事件——恰好是最难查的假通过。

要真正确认,得有一侧把身份传给另一侧,两种做法:

  1. fixture 侧收口(便宜):xdg_output_read_server_state() 里除了几何,再把 wlr_output_get_name()(headless 是 HEADLESS-n)填进回读结构,client 断言与它绑到的 global 名字一致——但 client 拿不到 wl_output 的 name 事件?其实可以:wl_output.name() 事件就有,比一次 name 字符串即可。
  2. 至少加个响亮的守卫:setup 里 Q_ASSERT(container->outputs().size() == 1 && container->outputs().contains(g_woutput)),让"多于一个 output"直接失败而不是猜错。

另外 wayland-ext-image-copy-capture-v1/*.c:43 对同一个 fixture 的 wl_output 用的是 client_bind(第一个),这里用最后一个,两种取法并存容易误读,建议统一(image 那边选哪个都一样,不妨也用 _last)。

wl_seat_add_listener(seat, &seat_listener, NULL);
wl_display_roundtrip(conn.display);

if (!(g_caps & 0x1 /* WL_SEAT_CAPABILITY_POINTER */)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同一模式还剩 5 处魔数没换成枚举名(本行 + :pointer-constraints:75、relative-pointer:63、pointer-gestures:64 的 g_caps & 0x1;primary-selection:128 的 g_caps & 2;pointer-constraints:122 的 1 /* LIFETIME_ONESHOT */、xdg-decoration:82 的 1 /* CLIENT_SIDE */)。

WL_SEAT_CAPABILITY_POINTER / WL_SEAT_CAPABILITY_KEYBOARD 来自 wayland-client-protocol.hclient-connection.h 已经 include 了 <wayland-client.h>,不需要任何额外依赖;ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOTZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE 在各自生成的 client-protocol 头里,也都已经 include。值都是对的,纯一致性/可读性问题,但既然本次已经为 constraint type 建了 POINTER_CONSTRAINT_TYPE_LOCKED 这种本地镜像枚举,用现成的生成枚举更省事。

TEST_ERROR("xdg-toplevel-tag: no mapped SurfaceWrapper captured\n");
failed = 1;
} else if (strcmp(server.tag, TEST_TAG) != 0) {
TEST_ERROR("xdg-toplevel-tag: real tag \"%s\" != \"%s\"", server.tag, TEST_TAG);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本 PR 全部 TEST_ERROR 调用里唯一漏 的一处(其余都带)。失败时这行会和后续 stderr( compositor 日志 / QTest 输出)拼在同一行,日志检索时会很难看:

TEST_ERROR("xdg-toplevel-tag: real tag \"%s\" != \"%s\"\n", server.tag, TEST_TAG);

Add comprehensive E-level (end-to-end) and P-level (protocol-level) tests
for all 24 standard wayland-protocols served by Treeland/wlroots at runtime,
plus xdg-shell coverage via shared fixture.

Coverage breakdown:
- 23 E-level tests (end-to-end business-level with production state readback)
- 1 P-level test (ext-image-copy-capture-v1, blocked by headless GPU limitation)

E-level tests verify real production state through:
- wlroots handle readback (wlr_seat, wlr_idle_notifier, wlr_pointer_gestures)
- wl_resource_get_user_data for protocols with private d-pointer handles
- SurfaceWrapper shellSurface() dynamic_cast for xdg-foreign parent readback
- ShellHandler::surfaceWrapperAdded signal for surface lifecycle tracking

Protocols covered (24 tested + 1 implicit):
  alpha-modifier, cursor-shape, ext-data-control, ext-foreign-toplevel-list,
  ext-idle-notify, ext-image-copy-capture, ext-session-lock,
  fractional-scale, idle-inhibit, pointer-constraints, pointer-gestures,
  primary-selection, relative-pointer, security-context,
  single-pixel-buffer, text-input-v1, text-input-v3, viewporter,
  xdg-activation, xdg-decoration, xdg-dialog, xdg-foreign,
  xdg-output, xdg-toplevel-tag, (xdg-shell implicit)

All 67 protocol tests pass in headless CI (WLR_BACKENDS=headless,
WLR_RENDERER=pixman). C test client code uses plain C + fprintf.

为 wayland-protocols 全部 24 个标准协议添加 E 级与 P 级测试覆盖,
复用 PR linuxdeepin#1297 建立的测试框架与 wayland-protocol-test skill。

Log: 扩展协议测试覆盖至全部标准 wayland 协议
Influence: 新增 24 个标准协议测试,无头 CI 全部通过,增强协议回归保障。
@deepin-wm
deepin-wm force-pushed the feat/wayland-protocols-test-coverage branch from d67d95b to 3237285 Compare September 16, 2026 03:05
@zorowk zorowk closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants