using System; using IvfTl.ControlHost.Debug; using IvfTl.ControlHost.Tests.Fakes; using Xunit; namespace IvfTl.ControlHost.Tests { /// /// D2-02 Task3.2a:CalibrationManager 的 sid 校验路径(不触真实硬件标定)。 /// 真实标定要硬件,这里只断言会话校验/字典管理的纯逻辑分支: /// · 不存在的 sid → StartCalibrate / GetProgress / Recalibrate 返回 SESSION_EXPIRED; /// · Stop 未知 sid 幂等返回 Okay; /// · houseBinOf 委托在 SESSION_EXPIRED 路径上绝不被调用(closure 未进入)。 /// public class CalibrationManagerTests { private DateTime _now = new DateTime(2026, 6, 25, 0, 0, 0); private (CalibrationManager calib, DebugSessionManager debug, bool[] houseBinTouched) New() { var serial = new FakeSerial(); var gate = new FakeGate(7, serial) { CanAcquire = true }; var debug = new DebugSessionManager(sn => gate, () => _now, ttlMs: 10000, log: _ => { }); var touched = new bool[1]; // houseBinOf:SESSION_EXPIRED 路径下绝不应被调用;被调用则置标志,便于断言。 var calib = new CalibrationManager(debug, sn => { touched[0] = true; return null; }, log: _ => { }); return (calib, debug, touched); } [Fact] public void StartCalibrate_Unknown_Sid_Returns_SESSION_EXPIRED_Without_Touching_HouseBin() { var (calib, _, touched) = New(); var r = calib.StartCalibrate("not-a-session", null); Assert.False(r.Ok); Assert.Equal("SESSION_EXPIRED", r.Code); Assert.False(touched[0]); // 会话校验先于取 HouseBin,委托不被触发 } [Fact] public void GetProgress_Unknown_Sid_Returns_SESSION_EXPIRED() { var (calib, _, _) = New(); var r = calib.GetProgress("nope"); Assert.False(r.Ok); Assert.Equal("SESSION_EXPIRED", r.Code); } [Fact] public void Recalibrate_Unknown_Sid_Returns_SESSION_EXPIRED() { var (calib, _, _) = New(); var r = calib.Recalibrate("nope", 3); Assert.False(r.Ok); Assert.Equal("SESSION_EXPIRED", r.Code); } [Fact] public void Stop_Unknown_Sid_Is_Idempotent_Ok() { var (calib, _, _) = New(); Assert.True(calib.Stop("nope").Ok); Assert.True(calib.Stop(null).Ok); } // Critical 并发修复:StopAndWait 对未起标定的 sid 是 no-op 且返回 true, // 让 DebugSessionManager 的"会话即将关闭"回调即便该会话没在标定也能畅通(不阻塞 Dispose)。 [Fact] public void StopAndWait_Unknown_Sid_Is_NoOp_ReturnsTrue() { var (calib, _, _) = New(); Assert.True(calib.StopAndWait("nope", 1000)); Assert.True(calib.StopAndWait(null, 1000)); } // ── Task3.2b:标定预览帧缓冲的「非标定/无帧」安全分支(推流线程据此决定走 GrabStable) ── [Fact] public void IsCalibrating_NoSession_Returns_False() { // 无活跃协作器(未起标定)→ IsCalibrating=false → 推流线程走原 GrabStable 分支(行为零变化)。 var (calib, _, _) = New(); Assert.False(calib.IsCalibrating("nope")); Assert.False(calib.IsCalibrating(null)); } [Fact] public void TryGetLatestFrame_NoFrame_Returns_False_And_Empty_Out() { // 未标定/无缓冲帧 → TryGetLatestFrame=false 且 out 全空; // 推流线程标定中遇此会跳过等下一帧、绝不 GrabStable(不争相机锁)。 var (calib, _, _) = New(); Assert.False(calib.TryGetLatestFrame("nope", out var bgr, out var w, out var h)); Assert.Null(bgr); Assert.Equal(0, w); Assert.Equal(0, h); Assert.False(calib.TryGetLatestFrame(null, out _, out _, out _)); } } }