64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
115 lines
5.1 KiB
C#
115 lines
5.1 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.Backend;
|
|
|
|
/// <summary>
|
|
/// Driver.Historian.Wonderware-010 regression. <see cref="HistorianConfiguration.RequestTimeoutSeconds"/>
|
|
/// was documented as the "outer safety timeout applied to sync-over-async Historian
|
|
/// operations" but was never read or enforced — a hung <c>StartQuery</c> or a slow
|
|
/// <c>MoveNext</c> could block the single pipe-server connection thread indefinitely.
|
|
/// The fix wires it into the read paths via a linked <see cref="CancellationTokenSource"/>
|
|
/// so the documented safety net actually exists.
|
|
///
|
|
/// The SDK-touching read methods cannot be unit-driven without a live AVEVA Historian.
|
|
/// This test pins the helper that derives the effective timeout from the config — the
|
|
/// read methods invoke that helper, so a regression in either the helper or the wiring
|
|
/// would break the test.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class HistorianDataSourceRequestTimeoutTests
|
|
{
|
|
/// <summary>Verifies default request timeout is 60 seconds.</summary>
|
|
[Fact]
|
|
public void Default_request_timeout_is_60_seconds()
|
|
{
|
|
new HistorianConfiguration().RequestTimeoutSeconds.ShouldBe(60);
|
|
}
|
|
|
|
/// <summary>Verifies positive request timeout values are applied correctly.</summary>
|
|
[Fact]
|
|
public void Positive_request_timeout_is_used_verbatim()
|
|
{
|
|
InvokeBuildLinkedTokenSource(
|
|
new HistorianConfiguration { RequestTimeoutSeconds = 30 },
|
|
CancellationToken.None,
|
|
out var cts);
|
|
cts.ShouldNotBeNull();
|
|
// The helper must wire CancelAfter — easiest cross-check is to observe that the
|
|
// returned CTS is NOT already cancelled, and that disposing it is safe.
|
|
cts!.IsCancellationRequested.ShouldBeFalse();
|
|
cts.Dispose();
|
|
}
|
|
|
|
/// <summary>Verifies zero or negative timeout values disable the outer safety timeout.</summary>
|
|
[Fact]
|
|
public void Zero_or_negative_request_timeout_is_treated_as_no_timeout()
|
|
{
|
|
// A zero/negative value means "no outer timeout" — the helper must still return a
|
|
// linked CTS so callers can use one code path, but it must not auto-cancel.
|
|
InvokeBuildLinkedTokenSource(
|
|
new HistorianConfiguration { RequestTimeoutSeconds = 0 },
|
|
CancellationToken.None,
|
|
out var cts);
|
|
cts.ShouldNotBeNull();
|
|
cts!.IsCancellationRequested.ShouldBeFalse();
|
|
// Give the runtime a moment — a misconfigured CancelAfter(0) would fire immediately.
|
|
Thread.Sleep(50);
|
|
cts.IsCancellationRequested.ShouldBeFalse("RequestTimeoutSeconds <= 0 must not auto-cancel");
|
|
cts.Dispose();
|
|
}
|
|
|
|
/// <summary>Verifies short timeout values correctly fire cancellation on the linked token.</summary>
|
|
[Fact]
|
|
public async Task Small_timeout_cancels_the_linked_token()
|
|
{
|
|
// 50 ms timeout — sleep 250 ms then assert the linked CTS has fired.
|
|
InvokeBuildLinkedTokenSource(
|
|
new HistorianConfiguration { RequestTimeoutSeconds = 1 }, // smallest non-zero whole-second value
|
|
CancellationToken.None,
|
|
out var cts);
|
|
cts.ShouldNotBeNull();
|
|
|
|
// The wall-clock cost of waiting a full second per test is acceptable — this
|
|
// pins the actual CancelAfter wiring rather than just the conditional logic.
|
|
await Task.Delay(1500);
|
|
cts!.IsCancellationRequested.ShouldBeTrue("RequestTimeoutSeconds=1 must cancel within 1.5s");
|
|
cts.Dispose();
|
|
}
|
|
|
|
/// <summary>Verifies caller's cancellation token propagates to the linked token.</summary>
|
|
[Fact]
|
|
public void Inbound_cancellation_propagates_into_the_linked_token()
|
|
{
|
|
using var outer = new CancellationTokenSource();
|
|
InvokeBuildLinkedTokenSource(
|
|
new HistorianConfiguration { RequestTimeoutSeconds = 60 },
|
|
outer.Token,
|
|
out var cts);
|
|
cts.ShouldNotBeNull();
|
|
cts!.IsCancellationRequested.ShouldBeFalse();
|
|
|
|
outer.Cancel();
|
|
cts.IsCancellationRequested.ShouldBeTrue("cancelling the caller's CT must cancel the linked CTS");
|
|
cts.Dispose();
|
|
}
|
|
|
|
private static void InvokeBuildLinkedTokenSource(
|
|
HistorianConfiguration cfg, CancellationToken ct, out CancellationTokenSource? cts)
|
|
{
|
|
// The helper is internal so the InternalsVisibleTo on the data-source project lets
|
|
// us bind to it directly. Reflection keeps the test resilient if the method name is
|
|
// ever shortened.
|
|
var method = typeof(HistorianDataSource)
|
|
.GetMethod("BuildRequestCts", BindingFlags.Static | BindingFlags.NonPublic);
|
|
method.ShouldNotBeNull(
|
|
"HistorianDataSource.BuildRequestCts must exist — wires RequestTimeoutSeconds into the read paths");
|
|
cts = (CancellationTokenSource?)method!.Invoke(null, new object[] { cfg, ct });
|
|
}
|
|
}
|