Skip to content

Commit d05536d

Browse files
authored
Add ISA95 JobControl and ManagedSession regression coverage (#3998)
## Summary - Adds the official ISA95 JobControl 2.0.0 NodeSet2 as a complete source-generator regression fixture. - Extends the lightweight generated-stack test stub with `NamespaceMetadataState`, required by the complete model. - Adds a deterministic ManagedSession cancellation regression proving that a started connect attempt is stopped. The ManagedSession cleanup implementation and Quickstarts utility coverage now come from `master`; this PR retains the additional deterministic cleanup regression. ## ISA95 fixture provenance and integrity The fixture is byte-for-byte the official file: - Source: `OPCFoundation/UA-Nodeset@e0b5d80cfff698f0276393e28df138a7fbc4b541` - Path: `ISA95-JOBCONTROL/opc.ua.isa95-jobcontrol.nodeset2.xml` - Git blob: `82f008cd126ade54e5f2c737bcc116621a8c3087` - SHA-256: `52327e736beb604c7253a5a5393d1ad525b9ecf35b71fbacb4f429cacb334064` The fixture test retains the OPC Foundation MIT header and verifies the 2.0.0 model/date, 258 nodes (including 134 variables), representative late variables, and that every `ns=1` reference target resolves locally. ## Validation - ManagedSession tests: 17 passing on net10.0; 15 passing and 2 framework-gated skips on net48. - Quickstarts server utility tests: 3 passing on net10.0 and net48. - ISA95 generator and fixture integrity regression: 5 passing on net10.0.
2 parents 007cfaf + ff8e9a9 commit d05536d

4 files changed

Lines changed: 4774 additions & 0 deletions

File tree

tests/Opc.Ua.Client.Tests/Session/ManagedSessionTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,48 @@ public async Task DisposeTransitionsToClosedState()
366366
Assert.That(sm.State, Is.EqualTo(ConnectionState.Closed));
367367
}
368368

369+
[Test]
370+
public async Task CreateAsyncCancellationStopsStartedConnectAttemptAsync()
371+
{
372+
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
373+
ApplicationConfiguration configuration = CreateClientConfiguration(telemetry);
374+
ConfiguredEndpoint endpoint = CreateEndpoint();
375+
var gate = new BlockingConnectGate();
376+
using var cancellation = new CancellationTokenSource();
377+
378+
Task<Client.ManagedSession> creation = Client.ManagedSession.CreateAsync(
379+
configuration,
380+
endpoint,
381+
new DefaultSessionFactory(telemetry),
382+
connectGate: gate,
383+
ct: cancellation.Token);
384+
385+
Task startedTask = await Task.WhenAny(
386+
gate.Started,
387+
Task.Delay(TimeSpan.FromSeconds(5)))
388+
.ConfigureAwait(false);
389+
Assert.That(startedTask, Is.SameAs(gate.Started));
390+
391+
cancellation.Cancel();
392+
393+
Task completedTask = await Task.WhenAny(
394+
creation,
395+
Task.Delay(TimeSpan.FromSeconds(5)))
396+
.ConfigureAwait(false);
397+
Assert.That(completedTask, Is.SameAs(creation));
398+
399+
OperationCanceledException exception =
400+
Assert.CatchAsync<OperationCanceledException>(async () =>
401+
await creation.ConfigureAwait(false))!;
402+
Assert.That(exception.CancellationToken, Is.EqualTo(cancellation.Token));
403+
404+
Task stoppedTask = await Task.WhenAny(
405+
gate.Stopped,
406+
Task.Delay(TimeSpan.FromSeconds(5)))
407+
.ConfigureAwait(false);
408+
Assert.That(stoppedTask, Is.SameAs(gate.Stopped));
409+
}
410+
369411
[Test]
370412
public void CreateAsyncThrowsOnNullConfiguration()
371413
{
@@ -660,6 +702,35 @@ private static Client.ManagedSession CreateManagedSessionWithInner(
660702
}
661703
#pragma warning restore IDE0051, RCS1213
662704

705+
private sealed class BlockingConnectGate : IClientConnectGate
706+
{
707+
public Task Started => m_started.Task;
708+
709+
public Task Stopped => m_stopped.Task;
710+
711+
public async ValueTask<IDisposable> AcquireAsync(
712+
CancellationToken ct = default)
713+
{
714+
m_started.TrySetResult(null);
715+
try
716+
{
717+
await Task.Delay(Timeout.Infinite, ct).ConfigureAwait(false);
718+
}
719+
finally
720+
{
721+
m_stopped.TrySetResult(null);
722+
}
723+
724+
throw new InvalidOperationException(
725+
"The blocking connect gate unexpectedly completed without cancellation.");
726+
}
727+
728+
private readonly TaskCompletionSource<object?> m_started =
729+
new(TaskCreationOptions.RunContinuationsAsynchronously);
730+
private readonly TaskCompletionSource<object?> m_stopped =
731+
new(TaskCreationOptions.RunContinuationsAsynchronously);
732+
}
733+
663734
#pragma warning disable IDE0051, RCS1213 // Test scaffold kept for ManagedSession construction tests.
664735
private static ApplicationConfiguration CreateClientConfiguration(
665736
ITelemetryContext telemetry)

tests/Opc.Ua.SourceGeneration.Core.Tests/CompilerUtils.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,24 @@ public class FolderState : BaseObjectState
768768
{
769769
public FolderState(NodeState? parent) : base(parent) { }
770770
}
771+
public class NamespaceMetadataState : BaseObjectState
772+
{
773+
public NamespaceMetadataState(NodeState? parent) : base(parent) { }
774+
public void CreateOrReplaceNamespaceUri(
775+
ISystemContext context, BaseInstanceState replacement) { }
776+
public void CreateOrReplaceNamespaceVersion(
777+
ISystemContext context, BaseInstanceState replacement) { }
778+
public void CreateOrReplaceNamespacePublicationDate(
779+
ISystemContext context, BaseInstanceState replacement) { }
780+
public void CreateOrReplaceIsNamespaceSubset(
781+
ISystemContext context, BaseInstanceState replacement) { }
782+
public void CreateOrReplaceStaticNodeIdTypes(
783+
ISystemContext context, BaseInstanceState replacement) { }
784+
public void CreateOrReplaceStaticNumericNodeIdRange(
785+
ISystemContext context, BaseInstanceState replacement) { }
786+
public void CreateOrReplaceStaticStringNodeIdPattern(
787+
ISystemContext context, BaseInstanceState replacement) { }
788+
}
771789
public class InstrumentDiagnosticAlarmState : BaseEventState
772790
{
773791
public InstrumentDiagnosticAlarmState(NodeState? parent) : base(parent) { }

0 commit comments

Comments
 (0)