Align Machinery transfer regressions with retained-handle cleanup

Retain all 101 original transfer scenarios and add eight missing or malformed handle cases for non-Good generation statuses. Require declaration-based Close without discovery, retain primary errors and cleanup ownership checks, and adjust the two affected browse counts. Both target frameworks pass 109 focused transfer cases and 443 full Machinery cases.
This commit is contained in:
Marc
2026-10-15 10:59:03 +02:00
parent af7a5ce41c
commit ba8b392a68
4 changed files with 40 additions and 21 deletions
@@ -82,12 +82,19 @@ namespace Opc.Ua.Machinery.Tests
public void NonGoodServiceOrMethodStatusIsRejected(
string phase,
StatusCode status,
bool serviceStatus)
bool serviceStatus,
string handleKind)
{
CallResponse response = TransferHarness.Response(
phase == "generate"
? [Variant.From(TransferHarness.FileId), Variant.From(37u), Variant.From(NodeId.Null)]
: phase == "read" ? [Variant.From(ByteString.Empty)] : []);
if (phase == "generate" && handleKind != "valid")
{
response.Results[0].OutputArguments = handleKind == "missing"
? [Variant.From(TransferHarness.FileId)]
: [Variant.From(TransferHarness.FileId), Variant.From(37ul), Variant.From(NodeId.Null)];
}
if (serviceStatus)
{
response.ResponseHeader.ServiceResult = status;
@@ -117,10 +124,15 @@ namespace Opc.Ua.Machinery.Tests
Assert.That(error.StatusCode, Is.EqualTo(status));
Assert.That(destination.Length, Is.Zero);
Assert.That(m_transfer.GenerateCount, Is.EqualTo(1));
Assert.That(m_transfer.CloseCount, Is.EqualTo(phase == "generate" ? 0 : 1));
bool acquiredHandle = phase != "generate" || handleKind == "valid";
Assert.That(m_transfer.CloseCount, Is.EqualTo(acquiredHandle ? 1 : 0));
Assert.That(m_transfer.ReadLengths, Has.Count.EqualTo(phase == "generate" ? 0 : 1));
Assert.That(m_transfer.OwnsHandle, Is.EqualTo(phase == "close"));
m_transfer.Session.Verify(session => session.Dispose(), Times.Never);
if (acquiredHandle && phase != "close")
{
AssertSuccessfulCleanup();
}
}
[Test]
@@ -136,10 +148,11 @@ namespace Opc.Ua.Machinery.Tests
TransferHarness.MachineId, "selected", destination, 8))!;
Assert.That(error.StatusCode, Is.EqualTo(StatusCodes.BadMethodInvalid));
Assert.That(m_transfer.Calls, Has.Count.EqualTo(1));
Assert.That(m_transfer.Calls, Has.Count.EqualTo(2));
Assert.That(m_transfer.Paths, Has.Count.EqualTo(2));
Assert.That(m_transfer.CloseCount, Is.Zero);
Assert.That(m_transfer.OwnsHandle, Is.False);
Assert.That(m_transfer.ReadLengths, Is.Empty);
Assert.That(destination.Length, Is.Zero);
AssertSuccessfulCleanup();
}
[TestCase("missing")]
@@ -502,14 +515,18 @@ namespace Opc.Ua.Machinery.Tests
{
foreach (string phase in new[] { "generate", "read", "close" })
{
string[] handleKinds = phase == "generate" ? ["valid", "missing", "type"] : ["valid"];
foreach (StatusCode status in new StatusCode[]
{
StatusCodes.Uncertain,
StatusCodes.BadCommunicationError
})
{
yield return new TestCaseData(phase, status, true);
yield return new TestCaseData(phase, status, false);
foreach (string handleKind in handleKinds)
{
yield return new TestCaseData(phase, status, true, handleKind);
yield return new TestCaseData(phase, status, false, handleKind);
}
}
}
}
@@ -195,13 +195,11 @@ namespace Opc.Ua.Machinery.Tests
GenerateCount++;
CallResponse response = GenerateResponse ?? Response(
[Variant.From(FileId), Variant.From(Handle), Variant.From(NodeId.Null)]);
if (response.ResponseHeader is not null &&
StatusCode.IsGood(response.ResponseHeader.ServiceResult) &&
response.Results.Count == 1 &&
StatusCode.IsGood(response.Results[0].StatusCode) &&
response.Results[0].OutputArguments.Count >= 2 &&
response.Results[0].OutputArguments[0].TryGetValue(out NodeId node) && node == FileId &&
response.Results[0].OutputArguments[1].TryGetValue(out uint returnedHandle))
if (response.Results.Count > 0 &&
response.Results[0] is { } generated &&
generated.OutputArguments.Count >= 2 &&
generated.OutputArguments[0].TryGetValue(out NodeId node) && node == FileId &&
generated.OutputArguments[1].TryGetValue(out uint returnedHandle))
{
Handle = returnedHandle;
OwnsHandle = true;
@@ -146,7 +146,7 @@ namespace Opc.Ua.Machinery.Tests
}
[Test]
public void CloseMethodResolutionFailureStillAttemptsTheDeclarationClose()
public async Task CloseMethodResolutionFailureStillAttemptsTheDeclarationClose()
{
m_transfer.Translate = path =>
{
@@ -160,11 +160,11 @@ namespace Opc.Ua.Machinery.Tests
};
using var destination = new MemoryStream();
ServiceResultException error = Assert.ThrowsAsync<ServiceResultException>(async () =>
await m_transfer.Client.DownloadResultAsync(
TransferHarness.MachineId, "selected", destination, 0))!;
long copied = await m_transfer.Client.DownloadResultAsync(
TransferHarness.MachineId, "selected", destination, 0);
Assert.That(error.StatusCode, Is.EqualTo(StatusCodes.Uncertain));
Assert.That(copied, Is.Zero);
Assert.That(destination.Length, Is.Zero);
Assert.That(m_transfer.Calls[^1].MethodId, Is.EqualTo(MethodIds.FileType_Close));
AssertSuccessfulCleanup();
}
@@ -186,7 +186,7 @@ namespace Opc.Ua.Machinery.Tests
Assert.That(copied, Is.EqualTo(2));
Assert.That(destination.ToArray(), Is.EqualTo(m_transfer.Payload.ToArray()));
Assert.That(m_transfer.AttributeReads, Is.EqualTo(2));
Assert.That(m_transfer.Paths, Has.Count.EqualTo(5));
Assert.That(m_transfer.Paths, Has.Count.EqualTo(4));
Assert.That(m_transfer.Paths[2].StartingNode, Is.EqualTo(TransferHarness.StateMachineId));
Assert.That(m_transfer.Paths[2].RelativePath.Elements[0].TargetName.Name, Is.EqualTo("CurrentState"));
Assert.That(m_transfer.Paths[2].RelativePath.Elements[1].TargetName.Name, Is.EqualTo("Id"));
@@ -193,7 +193,7 @@ namespace Opc.Ua.Machinery.Tests
await client.DownloadResultAsync(TransferHarness.MachineId, selected, destination, 2);
Assert.That(m_transfer.SelectedResultId, Is.EqualTo(selected));
Assert.That(m_transfer.Paths, Has.Count.EqualTo(4));
Assert.That(m_transfer.Paths, Has.Count.EqualTo(3));
BrowsePath path = m_transfer.Paths[0];
Assert.That(path.StartingNode, Is.EqualTo(TransferHarness.MachineId));
Assert.That(path.RelativePath.Elements, Has.Count.EqualTo(2));
@@ -418,8 +418,12 @@ namespace Opc.Ua.Machinery.Tests
Assert.That(m_transfer.CloseToken.CanBeCanceled, Is.True);
Assert.That(m_transfer.CloseWasCancelledOnEntry, Is.False);
Assert.That(m_transfer.Calls[^1].ObjectId, Is.EqualTo(TransferHarness.FileId));
Assert.That(m_transfer.Calls[^1].MethodId, Is.EqualTo(MethodIds.FileType_Close));
Assert.That(m_transfer.Calls[^1].InputArguments[0].TryGetValue(out uint handle), Is.True);
Assert.That(handle, Is.EqualTo(m_transfer.Handle));
Assert.That(m_transfer.Paths, Has.None.Matches<BrowsePath>(static path =>
path.StartingNode == TransferHarness.FileId &&
path.RelativePath.Elements[0].TargetName.Name == "Close"));
m_transfer.Session.Verify(session => session.Dispose(), Times.Never);
}