using System.Threading.Channels; using Xunit; using YZWater.Core.Services; namespace YZWater.Core.Tests.Services; /// /// 报警/审计写入队列"满了丢最旧"的可观测性回归。 /// 背景:两个服务都用 BoundedChannelFullMode.DropOldest,并且忽略 TryWrite 的返回值 —— /// 队列塞满时记录被静默丢掉,界面和日志都不显示,事后看台账会以为"当时只发生过这些"。 /// 现在丢弃条数被计数、周期性写日志,并在报警页/审计页底部提示。 /// public class QueueDropAccountingTests { /// /// 这条测试盯的是整个改动的立足点:DropOldest 下 TryWrite 依然返回 true, /// 所以"写成功"不能当成"没丢数据",唯一的丢弃入口是 itemDropped 回调。 /// [Fact] public void DropOldest_KeepsTryWriteTrue_And_ReportsDroppedItem() { var dropped = new List(); var channel = Channel.CreateBounded( new BoundedChannelOptions(2) { FullMode = BoundedChannelFullMode.DropOldest }, item => dropped.Add(item)); Assert.True(channel.Writer.TryWrite(1)); Assert.True(channel.Writer.TryWrite(2)); // 队列已满:这一次写仍然"成功",但把最旧的 1 挤了出去 Assert.True(channel.Writer.TryWrite(3)); Assert.Equal(new[] { 1 }, dropped); Assert.Equal(new[] { 2, 3 }, ReadAll(channel)); } /// /// 反向验证:只有 Wait 模式会在队满时让 TryWrite 返回 false,且不产生任何丢弃 /// (实测 .NET 8:DropWrite/DropNewest/DropOldest 三种丢弃模式下 TryWrite 全部返回 true, /// 所以"写入成功"根本不能当成"没丢数据") /// [Fact] public void WaitMode_ReportsFailureOnTryWrite_And_DropsNothing() { var dropped = new List(); var channel = Channel.CreateBounded( new BoundedChannelOptions(2) { FullMode = BoundedChannelFullMode.Wait }, item => dropped.Add(item)); Assert.True(channel.Writer.TryWrite(1)); Assert.True(channel.Writer.TryWrite(2)); Assert.False(channel.Writer.TryWrite(3)); Assert.Empty(dropped); Assert.Equal(new[] { 1, 2 }, ReadAll(channel)); } /// 正常写入不该被算成丢弃:计数只应在队列真的满过时才涨 [Fact] public void NewAlarmService_StartsWithNoDroppedRecords() { var service = new AlarmService(PlcPollingService.Instance); try { service.RecordJournalAlarm("测试用流水报警", 1); Assert.Equal(0, service.DroppedRecordCount); } finally { service.Dispose(); } } private static int[] ReadAll(Channel channel) { var items = new List(); while (channel.Reader.TryRead(out var item)) items.Add(item); return items.ToArray(); } }