Skip to content

Lua API 测试用例设计 ​

本文定义 Lua API 契约的测试矩阵。examples/hello_world/ 是用户参考示例,不作为 API 正确性的唯一验收;API 正确性必须由独立测试覆盖。

测试目标:

  • 每个 shield.* API 都有成功、失败和边界行为测试。
  • coroutine 挂起语义必须通过运行时测试验证。
  • 错误返回必须验证 ok=false 和 Error.code。
  • 旧 API 必须有负向测试,确保不会被重新引入。

测试目录建议 ​

txt
tests/lua_api/
├── CMakeLists.txt
├── lua_api_test_harness.cpp
├── scripts/
│   ├── lifecycle_service.lua
│   ├── messaging_service.lua
│   ├── timer_service.lua
│   ├── data_service.lua
│   ├── gateway_service.lua
│   └── legacy_api_service.lua
└── test_*.cpp

Harness 要求:

  • 能启动最小 shield_core + shield_lua runtime。
  • 能加载 Lua service module。
  • 能模拟 service 间 send/call。
  • 能推进 fake clock 或使用可控 timer scheduler。
  • 能注入 fake DB/Redis backend。
  • 能捕获 Lua log、error、panic 和 dead letter。

LAPI-001 Module Loader ​

Case设置操作断言
LAPI-001-01Lua 文件返回 tablespawn servicespawn 成功
LAPI-001-02Lua 文件返回 nilspawn serviceinvalid_service_module
LAPI-001-03Lua 文件语法错误spawn servicescript_load_failed
LAPI-001-04module 顶层抛错spawn servicescript_load_failed

LAPI-002 Lifecycle ​

Case设置操作断言
LAPI-002-01on_init 无返回值spawn成功
LAPI-002-02on_init 返回 truespawn成功
LAPI-002-03on_init 返回 false, errspawninit_failed,name rollback
LAPI-002-04on_init 抛错spawninit_failed,触发 panic 记录
LAPI-002-05service exitshield.exit("normal")调用 on_exit("normal")
LAPI-002-06on_exit 调用 shield.callexit返回/记录 api_not_allowed_in_exit
LAPI-002-07runtime shutdownshutdown先调用 on_shutdown(ctx),再调用 on_exit("stopping")
LAPI-002-08on_shutdown 超时shutdown记录 timeout,继续调用 on_exit 并释放服务
LAPI-002-09on_shutdown 调用 shield.callshutdown draindeadline 内允许挂起并恢复

LAPI-003 Registry ​

Case设置操作断言
LAPI-003-01spawn with nameshield.query(name)返回等价 handle
LAPI-003-02重复 namespawn secondname_conflict
LAPI-003-03init 失败占名query name不可见
LAPI-003-04shield.register 新 namequery可见
LAPI-003-05service exitquery old namesservice_not_found
LAPI-003-06invalid nameregisterinvalid_name

LAPI-004 Message Send ​

Case设置操作断言
LAPI-004-01target by namesend method argsreceiver handler 收到业务参数
LAPI-004-02target by handlesendreceiver 收到
LAPI-004-03target missingsendfalse, service_not_found
LAPI-004-04method missingsenddead letter 记录
LAPI-004-05mailbox fullsendlegacy Shield Mailbox 已删除,CAF actor mailbox 无界,此用例移除
LAPI-004-06self-sendsend self不 reentrant,下一调度点执行

LAPI-005 Message Call ​

Case设置操作断言
LAPI-005-01callee returns one valuecalltrue, value
LAPI-005-02callee returns false, reasoncalltrue, false, reason
LAPI-005-03callee returns trailing nilcallargc 保留
LAPI-005-04callee throwscallfalse, handler_error
LAPI-005-05method missingcallfalse, method_not_found
LAPI-005-06timeoutcall_timeoutfalse, timeout
LAPI-005-07late responsecallee returns after timeoutresponse discarded
LAPI-005-08nested callhandler calls another serviceruntime thread 不阻塞
LAPI-005-09主线程/module-level 调 shield.call/call_timeout非 coroutine 上下文false, {code="call_not_allowed_off_coroutine"}
LAPI-005-10协程内 shield.call 挂起期间同 service 第二消息actor 不阻塞,第二消息被处理
LAPI-005-11on_init 内 shield.callspawnon_init 协程挂起至响应到达,spawn 成功完成
LAPI-005-12timer/fork callback 内 call + sleepcallback 挂起期间actor 不阻塞,回调完成后恢复

LAPI-005A Lua-driven Spawn / Exit / Panic ​

tests/lua_api/test_lua_api_spawn.cpp(scripts: spawn_parent.lua、 spawn_child.lua、spawn_fail_child.lua、spawn_in_init_parent.lua、 spawn_panic_service.lua、spawn_watcher.lua、spawn_opts_shape.lua; 其中 spawn_opts_shape.lua 由 SpawnOptsTableWithMixedKeysStillSpawns 使用,覆盖 opts table 混合键形状,无矩阵编号)。

Case设置操作断言
LAPI-005A-01parent on_initshield.spawn(VM 主线程)同步路径成功,child published,handle node()==0
LAPI-005A-02handler 协程 spawn 慢 init childspawn 进行中 call parent pingparent 不阻塞(<400ms),spawn 完成返回正确 handle
LAPI-005A-03异步 spawn 持有 name reservation同名 spawnservice name already reserved 快速失败,原 spawn 正常完成
LAPI-005A-04child on_init 返回 nil, err协程 spawnnil, {code="init_failed"},name 回滚可再次 spawn
LAPI-005A-05handler 调 shield.exitsend exit_nowservice 停止,name 注销
LAPI-005A-06shield.self():node()call self_node0(本地)
LAPI-005A-07handler 调 shield.panic("test panic")watcher 观察on_panic 收到 {type="explicit"},service 退出

LAPI-006 Context ​

Case设置操作断言
LAPI-006-01send from A to BB receives ctx.senderequals A handle
LAPI-006-02handler returnssaved callback reads sendernil or context_expired
LAPI-006-03timer callbackctx.sendernil
LAPI-006-04trace enabledcall chaintrace id propagated
LAPI-006-05deadline enabledcall_timeoutremaining deadline visible

LAPI-007 Timers And Tasks ​

Case设置操作断言
LAPI-007-01timer_onceadvance clockcallback once
LAPI-007-02timer fixed-delaycallback takes timenext run after callback completes
LAPI-007-03cancel timeradvance clockcallback not called
LAPI-007-04timer errorcallback throwson_error called,timer stops
LAPI-007-05sleepcoroutine sleepsruntime can process other message
LAPI-007-06forktask runsowner service tracked
LAPI-007-07service exitowned timers/taskscanceled

LAPI-008 Plugin Data API ​

Case设置操作断言
LAPI-008-01database.default -> db.main binding configuredshield.database.mysql("database.default"):query(...)true, rows or plugin-specific SQL result
LAPI-008-02binding missingshield.database.mysql("database.default")nil, module_unavailable
LAPI-008-03target instance failed/unavailabledata plugin namespace callnil, module_unavailable
LAPI-008-04SQL plugin returns query errordb:query(...)false, db_query_failed or plugin-mapped database error
LAPI-008-05cache.session -> cache.redis binding configuredshield.cache.redis("cache.session"):get(key)true, value or true, nil for miss
LAPI-008-06cache binding missingshield.cache.redis("cache.session")nil, module_unavailable
LAPI-008-07queue binding configuredshield.queue.redis("queue.events"):subscribe(...); service exitsubscription canceled
LAPI-008-08SQL transaction supporteddb:transaction(fn)commit forwards payload; rollback returns reason; closed tx rejected
LAPI-008-09document binding configuredshield.database.mongodb("document.default"):find(...)true, cursor/docs
LAPI-008-10leaderboard binding configuredshield.leaderboard.redis("leaderboard.default"):top_n(...)true, entries

LAPI-009 Gateway API ​

Case设置操作断言
LAPI-009-01gateway servicetyped Boundon_client_bound(ctx, client) called
LAPI-009-02ingress request formsdeliver decoded/raw body编译绑定 handler(ctx, client, request) called;request 按 descriptor 契约成形
LAPI-009-03disconnecttyped Disconnectedon_disconnect(ctx, client, reason) called
LAPI-009-04kicktyped Unboundon_client_unbound(ctx, client, reason) called
LAPI-009-05unknown clientingress before bindhandler 正常执行,无 session 记录

LAPI-010 Legacy API Rejection ​

Case操作断言
LAPI-010-01shield.service("x")nil or legacy_api_removed
LAPI-010-02shield.plugin.on(...)nil or legacy_api_removed
LAPI-010-03shield.db.query(...) / shield.redis.get(...)unavailable or legacy_api_removed; use plugin namespace
LAPI-010-04shield.db:query(...) / shield.redis:get(...)unavailable or legacy_api_removed; use plugin namespace
LAPI-010-05service only defines on_message(src, type, data)send method does not dispatch through legacy entrypoint
LAPI-010-06DI injection APIunavailable

LAPI-010A Local Event API ​

未实现(目标契约):shield.event 当前源码尚未实现(全仓无注册点,无对应测试),本组用例为目标契约预留,实现后按本表补齐。

Case设置操作断言
LAPI-010A-01当前 service 注册 listenershield.event.emit(name, payload)listener 在同一 VM 内同步收到 payload
LAPI-010A-02listener unsubscribe调用返回的 off() 后 emitlistener 不再触发
LAPI-010A-03listener 抛错emit触发 on_error(err, {type="event"}),其他 listener 继续
LAPI-010A-04另一个 service 注册同名 listener从当前 service emit另一个 service 不收到,证明不跨 VM
LAPI-010A-05lifecycle event nameemit application_ready / shutdown只作为普通本地事件名处理,不触发 runtime lifecycle

LAPI-011 Player Lifecycle ​

适用前提:shield_player 已启用。未启用时整个 LAPI-011 矩阵跳过,且 shield.player.* 调用应返回 module_unavailable。

编号口径:本文矩阵编号与测试源码的 BOOST_AUTO_TEST_CASE 编号两套自成体系,不一一对应,以源码为准(tests/lua_api/test_lua_api_player.cpp)。例如源码 LAPI_011_18/19/20 是 shield.player.Base 用例(对应本文 -32/-33),并非本文 -18~-20 的 resolve 用例(resolve 错误矩阵由 LAPI_011_08_ResolveErrorMatrix 覆盖),且源码无 LAPI_011_11~14、LAPI_011_21~33 同号用例。legacy 矩阵(LAPI-010)同理:源码 LAPI_010_04_OldOnMessageEntryIsNotDispatched / LAPI_010_05_OldDiApiUnavailable 对应本文 -05/-06(错位一号),tests/lua_api/test_lua_api_config.cpp 另复用 LAPI_010_01~03 前缀(shield.config 子系统)。

Case设置操作断言
LAPI-011-01setup 缺 auth 字段spawn player servicenil, setup_invalid
LAPI-011-02setup 缺 login 字段spawn player servicenil, setup_invalid
LAPI-011-03setup 缺 client_message 字段spawn player servicenil, setup_invalid
LAPI-011-04setup 完整且 ready 缺省登录成功默认 ready 标记 player.state="ready",开始分发 client_message
LAPI-011-05setup 提供 ready 字段登录成功调用业务 ready 后进入 ready;不是 service/application on_ready
LAPI-011-06ready 前收到客户端业务消息deliver payload排队或拒绝,但不得调用 client_message
LAPI-011-07setup 缺 disconnect 字段触发断线进入重连窗口,默认实现被调用
LAPI-011-08setup 缺 logout 字段玩家离线PlayerManager.unregister 被调用
LAPI-011-09setup 缺 save 字段且未配置 persistence触发定时保存no-op,无数据插件调用
LAPI-011-10setup 覆盖 disconnect触发断线业务实现被调用,默认实现不执行
LAPI-011-11setup 完整 + persistence 启用触发 on_save 默认实现adapter 通过配置的插件 binding 持久化白名单字段
LAPI-011-12setup 完整 + persistence 未启用触发 on_save 默认实现no-op,无数据插件调用
LAPI-011-13persistence save 失败(on_save_error="log")adapter 返回错误错误码 persistence_save_failed,service 继续运行
LAPI-011-14persistence save 失败(on_save_error="panic")adapter 返回错误触发 on_panic
LAPI-011-15persistence 字段含 functionsetupnil, setup_invalid
LAPI-011-16PlayerRef LuaPack 编码encode独立 type tag,字段完整
LAPI-011-17shield.player.resolve 本地 ready 玩家解析 ref返回 PlayerSession
LAPI-011-18shield.player.resolve 本地已下线玩家解析 refnil, player_not_found
LAPI-011-19shield.player.resolve 远端 ref(remote resolve 未启用)解析 refnil, remote_resolve_unimplemented
LAPI-011-20shield.player.resolve 字段非法解析 refnil, invalid_player_ref
LAPI-011-21cross-service 传 PlayerRefsend payloadreceiver 拿到等价 ref
LAPI-011-22cross-service 传裸连接句柄(非 ClientContext/ClientRef)send payloadruntime 拒绝并返回错误
LAPI-011-23cross-service 传完整 PlayerSessionsend payloadruntime 拒绝并返回错误
LAPI-011-24auth 返回 anonymous=true 且未开启 anonymous认证nil, anonymous_disabled
LAPI-011-25anonymous 已开启且 auth 返回 anonymous=true认证状态进入 anonymous,默认不触发 persistence
LAPI-011-26auth 返回 spectator=true 且未开启 spectator认证nil, spectator_disabled
LAPI-011-27spectator 已开启调用 player:set_data 或 player:save拒绝并返回 spectator_readonly
LAPI-011-28multi_device single 且 UID 已在线新连接认证false, already_online
LAPI-011-29multi_device kick_old 且 UID 已在线新连接认证旧会话 logout reason 为 replaced
LAPI-011-30multi_device multi 且超过 max_devices新连接认证false, too_many_devices
LAPI-011-31player_pool 模式同一 uid 多条消息路由到同一 shard 且单玩家 handler 串行
LAPI-011-32shield.player.Base.setup(M, opts)(P2 语法糖,OD-014:不引入第二套 lifecycle、不做继承、钩子名无 on_* 前缀)spawn player service,模块上同名钩子齐备行为等价于 shield.player.setup(M, opts);缺必填钩子 nil, setup_invalid;opts 显式钩子优先;模块未启用 module_unavailable
LAPI-011-33Base 覆盖可选 hook触发 hook默认实现不自动执行

验收要求 ​

  • 当前 CTest 已有 shield_runtime_lua_smoke 覆盖 YAML actors 启动、 shield.self/now/spawn/send/call/sender/names/exit/on_exit 的单节点同步路径; shield_runtime_registry_smoke 覆盖 query/register/unregister/names 的本地 registry 路径; 插件系统相关 smoke 覆盖 manifest scan、instance、binding、register_lua 分发和缺失 binding 的 module_unavailable 返回形态。 它们不是完整 LAPI 矩阵的替代品。
  • Lua API 契约变更必须先更新本文。
  • 每个新增 API 至少补充一个成功用例和一个失败用例。
  • 每个错误码必须在测试中至少出现一次。
  • examples/hello_world/ 可以更贴近用户体验,但不能替代上述测试。

剩余延迟用例 ​

以下用例仍依赖后续实现或专用 mock harness,当前尚未完整验证。已完成的历史延迟项保留删除线,作为状态记录。

Case延迟原因
LAPI-005-06call timeout 未实现 已实现:call timeout 由 CAF delayed call_timeout_atom 驱动,LAPI-005-06 覆盖协程 timeout + 同步调用
LAPI-005-07late response 丢弃 call timeout 已实现,超时后 caller 已 resume;callee 返回时 resume_caller 在 pending_calls 中找不到 session,静默丢弃。行为正确
LAPI-005-08nested call 协程路径支持:caller yield 后 CAF actor 处理 callee 消息,CallApiFromLuaWrapsRuntimeResult 测试覆盖嵌套 call
LAPI-006-04trace id 传播:消息字段携带通道已实现(send/call 会把 current_trace_id 写入消息),但 runtime 当前不生成 trace id,ctx.trace 恒返回 nil。trace id 生成与完整链路传播属于 Phase 2+
LAPI-006-05deadline 可见性 读取/传播通道已实现:ctx.deadline 从 dispatch context 读取,字段随消息传播;但 runtime 当前不为 call 赋值 deadline,ctx.deadline 恒返回 nil。deadline 赋值策略属于 Phase 2+
LAPI-007-04on_error hook 调用 已实现:invoke_hook 调用 service table 上的 on_error,OnErrorHookCalledOnHandlerThrow 测试覆盖
LAPI-007-05shield.sleep coroutine 语义 已由 LAPI-007-08 覆盖
LAPI-008-02/03/06缺失 binding / 目标 instance unavailable 的 module_unavailable 需要插件 mock harness 覆盖 已覆盖:tests/plugin/test_plugin_lua_facade.cpp 用 fake host resolver(binding_instance_id 恒报 absent)逐插件固定软失败形状——shield.<ns>("ghost.binding") → nil, {code="module_unavailable"}(含 no-arg 调用);namespace 侧 resolver 解析不到 instance 即返回该形状
LAPI-008-04SQL error 路径需要由 SQL 插件测试或统一 mock 插件覆盖
LAPI-008-07subscribe then exit:需要 queue plugin mock/集成测试覆盖订阅生命周期
LAPI-009-01~05Gateway session 模拟 已覆盖:connect/message/disconnect/queue_full/stale_send 共 6 个测试
LAPI-009-real-session真实 TCP session → ClientContext/ClientRef 的闭环
LAPI-002-06on_exit 中调用 shield.call 返回 api_not_allowed_in_exit 已实现:_is_in_exit() + Lua wrapper guard,OnExitCallGuard 测试覆盖

Apache License 2.0