| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- using System;
- using IvfTl.ControlHost.Debug;
- using IvfTl.ControlHost.Tests.Fakes;
- using Xunit;
- namespace IvfTl.ControlHost.Tests
- {
- public class DebugSessionManagerTests
- {
- private DateTime _now = new DateTime(2026, 6, 23, 0, 0, 0);
- private (DebugSessionManager mgr, FakeGate gate) New(bool canAcquire = true)
- {
- var serial = new FakeSerial();
- var gate = new FakeGate(5, serial) { CanAcquire = canAcquire };
- var mgr = new DebugSessionManager(sn => gate, () => _now, ttlMs: 10000, log: _ => { });
- return (mgr, gate);
- }
- [Fact]
- public void Acquire_Returns_SessionId_And_Pauses()
- {
- var (mgr, gate) = New();
- var r = mgr.Acquire(5);
- Assert.True(r.Ok);
- Assert.False(string.IsNullOrEmpty((string)r.Result));
- Assert.True(gate.IsCapturePaused);
- }
- [Fact]
- public void Acquire_Busy_Returns_BUSY()
- {
- var (mgr, _) = New(canAcquire: false);
- var r = mgr.Acquire(5);
- Assert.False(r.Ok);
- Assert.Equal("BUSY", r.Code);
- }
- [Fact]
- public void Release_Disposes_Lease_And_Resumes()
- {
- var (mgr, gate) = New();
- string sid = (string)mgr.Acquire(5).Result;
- var r = mgr.Release(sid);
- Assert.True(r.Ok);
- Assert.True(gate.LastLease.Disposed);
- }
- [Fact]
- public void Release_Unknown_Session_Is_Idempotent_Ok()
- {
- var (mgr, _) = New();
- Assert.True(mgr.Release("not-a-session").Ok);
- }
- [Fact]
- public void Sweep_Reclaims_After_Ttl_And_Resumes()
- {
- var (mgr, gate) = New();
- string sid = (string)mgr.Acquire(5).Result;
- _now = _now.AddMilliseconds(11000);
- int reclaimed = mgr.SweepExpired();
- Assert.Equal(1, reclaimed);
- Assert.True(gate.LastLease.Disposed);
- Assert.False(gate.IsCapturePaused);
- Assert.Equal("SESSION_EXPIRED", mgr.Heartbeat(sid).Code);
- }
- [Fact]
- public void Heartbeat_Keeps_Session_Alive()
- {
- var (mgr, gate) = New();
- string sid = (string)mgr.Acquire(5).Result;
- _now = _now.AddMilliseconds(8000); mgr.Heartbeat(sid);
- _now = _now.AddMilliseconds(8000);
- Assert.Equal(0, mgr.SweepExpired());
- Assert.False(gate.LastLease.Disposed);
- }
- // ── Critical 并发修复:会话关闭(Release/Sweep)在 lease.Dispose 之前先触发 onSessionClosing 回调 ──
- // 装配时回调接到 calibMgr.StopAndWait(sid),先停标定并等当前孔跑完,之后才 Dispose lease、恢复采集,
- // 消除"标定后台线程操作已 Dispose 的 lease + 与已恢复的采集线程争同一硬件"的窗口。
- // 验证两点:① 回调被调且传入正确 sid;② 回调发生在 lease.Dispose 之前(用 FakeLease.Disposed 在回调里取快照)。
- private (DebugSessionManager mgr, FakeGate gate, System.Collections.Generic.List<(string sid, bool disposedAtCallback)> log)
- NewWithClosing()
- {
- var serial = new FakeSerial();
- var gate = new FakeGate(5, serial) { CanAcquire = true };
- var log = new System.Collections.Generic.List<(string, bool)>();
- // 回调里读 LastLease.Disposed:若回调真在 Dispose 之前调,这里应为 false。
- var mgr = new DebugSessionManager(sn => gate, () => _now, ttlMs: 10000, log: _ => { },
- onSessionClosing: sid => log.Add((sid, gate.LastLease.Disposed)));
- return (mgr, gate, log);
- }
- [Fact]
- public void Release_Invokes_OnSessionClosing_Before_Dispose()
- {
- var (mgr, gate, log) = NewWithClosing();
- string sid = (string)mgr.Acquire(5).Result;
- mgr.Release(sid);
- Assert.Single(log);
- Assert.Equal(sid, log[0].sid);
- Assert.False(log[0].disposedAtCallback); // 回调时 lease 尚未 Dispose
- Assert.True(gate.LastLease.Disposed); // 之后已 Dispose
- }
- [Fact]
- public void Sweep_Invokes_OnSessionClosing_Before_Dispose()
- {
- var (mgr, gate, log) = NewWithClosing();
- string sid = (string)mgr.Acquire(5).Result;
- _now = _now.AddMilliseconds(11000);
- int n = mgr.SweepExpired();
- Assert.Equal(1, n);
- Assert.Single(log);
- Assert.Equal(sid, log[0].sid);
- Assert.False(log[0].disposedAtCallback); // 超时回收路径同样:回调先于 Dispose
- Assert.True(gate.LastLease.Disposed);
- }
- }
- }
|