using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Runtime.Drivers; namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers; /// /// lmxopcua#777: every write bound derives from the one write timeout, and each outer bound covers the worst case /// of everything inside it — whatever the timeout. (The fixed 8 s Ask failed this from a 7 s timeout up.) /// public sealed class DriverWriteBoundsTests { [Theory] [InlineData(0.5)] [InlineData(2)] [InlineData(5)] [InlineData(9)] [InlineData(10)] [InlineData(30)] [InlineData(300)] public void Each_outer_bound_covers_everything_inside_it(double timeoutSeconds) { var b = new DriverWriteBounds(TimeSpan.FromSeconds(timeoutSeconds)); b.AbandonGrace.ShouldBeGreaterThan(TimeSpan.Zero); b.RunBound.ShouldBe(b.Timeout + b.AbandonGrace); // The child answers a write by queue budget + run bound after it was issued; the Asks sit strictly outside. b.ReplyBound.ShouldBe(b.QueueBudget + b.RunBound); b.HostAsk.ShouldBeGreaterThanOrEqualTo(b.ReplyBound + DriverWriteBounds.ReplyMargin); b.GatewayAsk.ShouldBeGreaterThanOrEqualTo(b.HostAsk + DriverWriteBounds.ReplyMargin); } [Fact] public void The_default_is_10s_and_the_grace_is_half_the_timeout_capped_at_2s() { DriverWriteBounds.Default.Timeout.ShouldBe(TimeSpan.FromSeconds(10)); new DriverWriteBounds(TimeSpan.FromSeconds(3)).AbandonGrace.ShouldBe(TimeSpan.FromSeconds(1.5)); new DriverWriteBounds(TimeSpan.FromSeconds(9)).AbandonGrace.ShouldBe(TimeSpan.FromSeconds(2)); DriverInstanceActor.DefaultWriteTimeout.ShouldBe(DriverWriteBounds.Default.Timeout); DriverInstanceActor.DefaultQueuedWriteBudget.ShouldBe(DriverWriteBounds.Default.QueueBudget); } [Theory] [InlineData(0)] [InlineData(-1)] public void A_non_positive_timeout_is_rejected(double seconds) => Should.Throw(() => new DriverWriteBounds(TimeSpan.FromSeconds(seconds))); }