QueueDropAccountingTests.cs 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. using System.Threading.Channels;
  2. using Xunit;
  3. using YZWater.Core.Services;
  4. namespace YZWater.Core.Tests.Services;
  5. /// <summary>
  6. /// 报警/审计写入队列"满了丢最旧"的可观测性回归。
  7. /// 背景:两个服务都用 BoundedChannelFullMode.DropOldest,并且忽略 TryWrite 的返回值 ——
  8. /// 队列塞满时记录被静默丢掉,界面和日志都不显示,事后看台账会以为"当时只发生过这些"。
  9. /// 现在丢弃条数被计数、周期性写日志,并在报警页/审计页底部提示。
  10. /// </summary>
  11. public class QueueDropAccountingTests
  12. {
  13. /// <summary>
  14. /// 这条测试盯的是整个改动的立足点:DropOldest 下 TryWrite 依然返回 true,
  15. /// 所以"写成功"不能当成"没丢数据",唯一的丢弃入口是 itemDropped 回调。
  16. /// </summary>
  17. [Fact]
  18. public void DropOldest_KeepsTryWriteTrue_And_ReportsDroppedItem()
  19. {
  20. var dropped = new List<int>();
  21. var channel = Channel.CreateBounded<int>(
  22. new BoundedChannelOptions(2) { FullMode = BoundedChannelFullMode.DropOldest },
  23. item => dropped.Add(item));
  24. Assert.True(channel.Writer.TryWrite(1));
  25. Assert.True(channel.Writer.TryWrite(2));
  26. // 队列已满:这一次写仍然"成功",但把最旧的 1 挤了出去
  27. Assert.True(channel.Writer.TryWrite(3));
  28. Assert.Equal(new[] { 1 }, dropped);
  29. Assert.Equal(new[] { 2, 3 }, ReadAll(channel));
  30. }
  31. /// <summary>
  32. /// 反向验证:只有 Wait 模式会在队满时让 TryWrite 返回 false,且不产生任何丢弃
  33. /// (实测 .NET 8:DropWrite/DropNewest/DropOldest 三种丢弃模式下 TryWrite 全部返回 true,
  34. /// 所以"写入成功"根本不能当成"没丢数据")
  35. /// </summary>
  36. [Fact]
  37. public void WaitMode_ReportsFailureOnTryWrite_And_DropsNothing()
  38. {
  39. var dropped = new List<int>();
  40. var channel = Channel.CreateBounded<int>(
  41. new BoundedChannelOptions(2) { FullMode = BoundedChannelFullMode.Wait },
  42. item => dropped.Add(item));
  43. Assert.True(channel.Writer.TryWrite(1));
  44. Assert.True(channel.Writer.TryWrite(2));
  45. Assert.False(channel.Writer.TryWrite(3));
  46. Assert.Empty(dropped);
  47. Assert.Equal(new[] { 1, 2 }, ReadAll(channel));
  48. }
  49. /// <summary>正常写入不该被算成丢弃:计数只应在队列真的满过时才涨</summary>
  50. [Fact]
  51. public void NewAlarmService_StartsWithNoDroppedRecords()
  52. {
  53. var service = new AlarmService(PlcPollingService.Instance);
  54. try
  55. {
  56. service.RecordJournalAlarm("测试用流水报警", 1);
  57. Assert.Equal(0, service.DroppedRecordCount);
  58. }
  59. finally
  60. {
  61. service.Dispose();
  62. }
  63. }
  64. private static int[] ReadAll(Channel<int> channel)
  65. {
  66. var items = new List<int>();
  67. while (channel.Reader.TryRead(out var item)) items.Add(item);
  68. return items.ToArray();
  69. }
  70. }