Skip to content

[实现] C1 / C5 端到端判据:设备错误 error→state 且不跳轨 #110

Closed

Description

@wt159

背景

#109 已重审「不新造接缝」并批准一个默认关闭、只在控制线程读一次的设备错误注入点。本票实现 doc/player-state-contract.md §10.4:213 登记的唯一残留判据缺口。

实现范围

  1. sdk/audio/device/AudioDevice.h:加 static void setForceOpenFailureForTest(bool enabled),注释写明「仅测试、默认关闭、不进公开 API」。
  2. sdk/audio/device/AudioDevice.cpp:文件级 std::atomic<bool> g_forceOpenFailure{false}(memory_order_relaxed);AudioDevice::open()(:302)在转 m_impl->open() 前读一次,命中则 return -1,不进 Impl::open()、不碰 SDL。sdk/MusicPlayer.cpp 播放逻辑零改动。
  3. sdk/test/unit/test_player_integration.cpp:在 TEST_SUITE("MusicPlayer Error Handling") 加一条用例,用带 pid 的 TempMediaDir 放一首完整 music.wav,addMusicDir → 等 getMusicCount()==1 与 playlist 回调 → setCurrentIndex(0) → listener.reset() 清序列 → RAII guard 打开注入 → play() → waitFor(error_count>0)。
    • C1:eventSequence() 里 firstEvent("error") < firstEvent("state");countEvents(events,"state") == 1(发且仅发一次);last_state == StoppedState;last_error_code == AudioDeviceOpenFailed。
    • C5:默认 autoSkipOnError() == true 时也不跳轨 —— last_current_index 不变、index_change_count == 0(reset 后计数从 0 起)。
    • 用例末尾 player.stop();RAII guard 保证 REQUIRE 抛异常时也恢复开关。
  4. doc/player-state-contract.md §8 / §10.3 / §10.4 与 doc/observability-contract.md §1 / §7.4:把「缺判据 / 不新造接缝 / 不可推翻前提」的时点措辞同步为「已由 [决策] 重审「不新造接缝」:批准一个默认关闭的设备错误注入点 #109 重审、[实现] C1 / C5 端到端判据:设备错误 error→state 且不跳轨 #110 落地」,保留历史原句作留痕;写明注入点形态、稳定性证据与反悔通道。

硬约束(承接 #109,不可推翻)

  • 不改任何既有判据数值 / 容差、不新增 add_test 分组、不改 14 个 sdk_* 目标 / 7 组 TIMEOUT / CI 配置 / BadInput 55 例 396 断言 / 覆盖度 15 行矩阵。
  • 不碰 soundbridge::Player 公开头与七个 void 控制入口、三态、回调契约;不新增同步解码入口、不改 positionMs 归属、不改 10 ms push 节奏。
  • 注入开关不得落在解码线程 / 消费主循环 / 判据主循环;后续 effort:L1/L2 观测指标与 drift 的数值阈值拍板 #104「不设任何数值阈值」与 §3.2 第 5 步永久生效不变。
  • 新用例不得靠裸 sleep / 绝对时长窗口 / 特定 demuxer 行为;素材缺失走既有 skipCase 约定。
  • 新用例写文件沿用带 pid 的 TempMediaDir(sdk/test/fixtures/TestProcess.hpp),避免 -j4 共享路径撞名。

验收判据

  • 新增用例在 sdk_interaction_tests 目标内稳定可红可绿;本地 ctest -j4 14/14 全绿。
  • 稳定性对照:master 基线 ≥10 轮 vs 集成分支 ≥10 轮 ctest -j4;若分支假红率高于基线,按 [决策] 重审「不新造接缝」:批准一个默认关闭的设备错误注入点 #109 闸门撤回全部 sdk/ 改动并裁不落地,留对照表与依据,不改判据 / 不放宽时序窗口。
  • 一次两轴 code-review 单提交修完;PR 合并后 Linux sdk-coverage 与 Windows sdk-windows 双绿(跨平台改动按 AGENTS.md / 规格 §8.4,本地全绿不算完成)。
  • git diff 中 sdk/MusicPlayer.cpp、sdk/include/soundbridge/、.github/、sdk/test/CMakeLists.txt 零改动。

依赖:原生阻塞于 #109 的条款重审结论。

Activity

  1. wt159 commented on Nov 8, 2026

    @wt159
    OwnerAuthor

    实现留痕(2026-11-08):本票正文有两处与最终落地不一致,在此更正并留完整证据,票正文不改(执行票是历史决策留痕)。

    1. 目标归属写错:正文写「新增用例在 sdk_interaction_tests 目标内」。实测 sdk/test/CMakeLists.txt:192-193 的 SB_TEST_INTERACTION_SUITES 只有 MusicPlayer Interactive Paths,PublicPlayer Interactive Paths;新用例在 TEST_SUITE("MusicPlayer Error Handling"),归 sdk_unit_tests(:196-198 的 -tse 排除集不含该 suite)。已用 -tse=<排除集> -tc=<用例名> 验证该过滤器下 12/12 断言绿;-ltc 显示该过滤器共 472 例。不新增 add_test 分组、不改任何 ctest 目标。
    2. C5 判据形态按实测调整:正文写「last_current_index 不变、index_change_count == 0」。实测 play() 自身就会上报一次当前索引(MusicPlayer.cpp:683),单曲列表下 skipToNextPlayable() 只会失败、跳轨与否几乎不可观测。最终改为双曲目列表 + 队列稳定窗口,判据是 last_current_index == 0 与 countEvents(events,"error") == 1;若设备错误被误判为可跳轨,会尝试第二首并产生第二次 error / 索引变化。队列屏障用「error/state/index 三个计数连续 4 次 25ms 采样不变」,属稳定后取数、不是绝对时长判据,且两个等待屏障都写成 CHECK(非 fatal),保证回归时后续断言也能给出具体红因。

    变异验证(判据可红可绿):

    • 去掉 GetErrorInfo(code).action == ErrorAction::SkipTrack → 6 条断言红,含 sequenceArrived、queueQuiet、状态只发一次、last_current_index == 0、countEvents(error) == 1。
    • 把 onError 挪到 updatePlayState 之后 → errAt < stAt 精确红。
    • 两次还原后均 12/12 断言绿;sdk/MusicPlayer.cpp 零改动(变异仅用于验证,未提交)。

    O1 稳定性闸门:ctest -j4(cwd sdk/test、三重 PATH)集成分支 10/10 轮全绿 14/14(56.65–59.17 s);master 基线 9/10 轮全绿(第 9 轮 sdk_interaction_tests 一次既有低概率 flake,LastTestsFailed.log 记录为 14:sdk_interaction_tests,与本用例无关)。分支假红率不高于基线,闸门通过。

    跨平台:本地 Windows 全绿不算完成,PR 合并后须等 Linux sdk-coverage 与 Windows sdk-windows 双绿。规格回填见 doc/player-state-contract.md §8 / §10.3 / §10.4 与 doc/observability-contract.md §1 / §7.4。

  2. wt159 commented on Nov 8, 2026

    @wt159
    OwnerAuthor

    收口决议(2026-11-08):本票已由 PR #111 的 Closes #110 自动 CLOSED,合并提交 8b364301a。C1 / C5 端到端判据缺口已补齐,这是 doc/player-state-contract.md §10.4 登记的唯一残留技术缺口的终态。

    交付

    • AudioDevice::setForceOpenFailureForTest():文件级 std::atomic<bool>(默认 false,release/acquire),只在 AudioDevice::open() 包装层短路返回 -1;播放链走生产里真实存在的设备失败分支,sdk/MusicPlayer.cpp 播放逻辑零改动。读点只在控制线程,soundbridge::Player 公开 API 与七个 void 控制入口一字未动。
    • 一条用例 audio device error reports error before state and does not skip track 同时覆盖 C1(error→state 顺序、发且仅发一次状态、Stopped、错误码 AudioDeviceOpenFailed)与 C5(默认 autoSkip 开启也不跳到第二首、不触发第二次播放尝试)。归属 sdk_unit_tests(票正文误写 sdk_interaction_tests,已在 comment 6722195681 更正)。
    • 规格 doc/player-state-contract.md §8 / §10.3 / §10.4 与 doc/observability-contract.md §1 / §7.4 已回填终态、证据与反悔通道。

    证据

    • 变异验证:去掉 SkipTrack 检查 → 6 条断言红(含 C5 两条核心);把 onError 挪到 state 之后 → errAt < stAt 精确红;还原后 12/12 绿。
    • ctest -j4 稳定性闸门:集成分支 10/10 轮全绿 14/14;master 基线 9/10 轮全绿(第 9 轮 sdk_interaction_tests 一次既有低概率 flake,与本用例无关)。分支假红率不高于基线,通过 [决策] 重审「不新造接缝」:批准一个默认关闭的设备错误注入点 #109 闸门。逐轮耗时见规格 §10.4。
    • 一次两轴 code-review(Standards 0 硬违规)提出的问题在 361fc8bf8 单提交修完:public 钩子注释自相矛盾、relaxed 内存序与线程注释不严谨、文档在 CI 未跑时宣称「终态」、反悔通道与逐轮对照表不全。
    • 跨平台双绿(AGENTS.md:本地 Windows 全绿不算完成):pull_request 阶段 Linux sdk-coverage 42487403007 success(14m15s)与 Windows sdk-windows 42487403112 success(4m34s),均 attempt=1;判定只认 gh run view --json status,conclusion,attempt。

    零改动:sdk/MusicPlayer.cpp、sdk/include/soundbridge/、.github/、sdk/test/CMakeLists.txt 零改动;不新增 add_test 分组,不改 14 个 sdk_* 目标 / TIMEOUT / BadInput 55 例 / 覆盖度 15 行矩阵 / 任何既有判据数值;#104「不设数值阈值」与 §3.2 第 5 步永久生效不变。

    反悔通道:将来若要撤掉注入点,须另开新票并同时处理 C1 / C5 两条判据的归属,不得只删判据留下「契约无门禁」;该条款同时适用于 doc/observability-contract.md §1 / §7.4 的界外例外。

  3. added 2 commits that reference this issue on Nov 8, 2026
  4. wt159 commented on Nov 8, 2026

    @wt159
    OwnerAuthor

    补记(2026-11-08,合并后 push CI 复核):本票 resolution 的「跨平台双绿」准确限定为 pull_request 阶段,此处补上合并后 master push run 的完整结果,避免未来读 resolution 时误以为全程无失败。

    • Windows sdk-windows push run 42488656337:success(attempt=1)。
    • Linux sdk-coverage push run 42488656322:attempt 1 failure —— sdk_interaction_tests 的既有 seek to middle moves position forward(test_player_integration.cpp:726)偶发 SIGSEGV,14 条断言全过、6.27s 进程崩溃;承载本票新增 C1/C5 用例的 sdk_unit_tests Passed 16.16 sec。
    • gh run rerun 42488656322 --failed → attempt 2 success:100% tests passed, 0 tests failed out of 14,同目标 Passed 12.24 sec,最终 conclusion=success。

    判偶发 flake 依据:merge commit 8b364301a 与 PR 分支头 361fc8bf8 的 tree 同为 1caeeae0…(git diff 为空),逐字节同代码在 PR run 与 rerun 两次全绿;失败用例属 MusicPlayer Interactive Paths suite,与本票新增用例(归 sdk_unit_tests)零交集;AudioDevice::open() 包装层默认 false 时行为等价、无新增崩溃路径。「失败目标不在改动文件」这一条本例不成立,已用上述替代证据补齐。

    未改任何判据 / TIMEOUT / 坏输入 55 例基线 / CI 配置,只做一次 rerun 取证。这是已知低概率 flake 的观测记录,不是待办;若反复出现须另开票重审。完整记录见 doc/player-state-contract.md §10.4(提交 cb9aa6ab3)。本票状态不变,票正文不改。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agent信息完备,agent 可直接开工

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions