mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 14:43:08 +08:00
5dd53ff9d0
Migrate all NodeManagers to IAsyncNodeManager Add more Tests ## Related Issues Issue linking is handled automatically by the system. ## Types of changes What types of changes does your code introduce? _Put an `x` in the boxes that apply. You can also fill these out after creating the PR._ - [x] Bugfix (non-breaking change which fixes an issue) - [ ] Enhancement (non-breaking change which adds functionality) - [ ] Test enhancement (non-breaking change to increase test coverage) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected, requires version increase of Nuget packages) - [ ] Documentation Update (if none of the other choices apply) ## Checklist _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ - [ ] I have read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf). - [x] I ran tests locally with my changes, all passed. - [x] I fixed all failing tests in the CI pipelines. - [x] I fixed all introduced issues with CodeQL and LGTM. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [ ] I have added necessary documentation (if appropriate). - [ ] Any dependent changes have been merged and published in downstream modules. ## Further comments --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <romanett98@gmail.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
265 lines
9.2 KiB
C#
265 lines
9.2 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using NUnit.Framework;
|
|
using Opc.Ua.Server.TestFramework;
|
|
using Opc.Ua.Tests;
|
|
using Quickstarts.ReferenceServer;
|
|
|
|
namespace Opc.Ua.Server.Tests
|
|
{
|
|
/// <summary>
|
|
/// Test <see cref="CustomNodeManager2"/>
|
|
/// </summary>
|
|
[TestFixture]
|
|
[Category("CustomNodeManager")]
|
|
[SetCulture("en-us")]
|
|
[SetUICulture("en-us")]
|
|
[Parallelizable]
|
|
public class CustomNodeManagerTests
|
|
{
|
|
/// <summary>
|
|
/// Tests the componentCache methods with multiple threads
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestComponentCacheAsync()
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
var fixture = new ServerFixture<StandardServer>(t => new ReferenceServer(t));
|
|
|
|
try
|
|
{
|
|
// Arrange
|
|
const string ns = "http://test.org/UA/Data/";
|
|
StandardServer server = await fixture.StartAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
using var nodeManager = new TestableCustomNodeManger2(server.CurrentInstance, ns);
|
|
|
|
var baseObject = new BaseObjectState(null);
|
|
var nodeHandle = new NodeHandle(
|
|
CommonTestWorkers.NodeIdTestSetStatic[0]
|
|
.WithNamespaceIndex(0)
|
|
.InnerNodeId,
|
|
baseObject);
|
|
|
|
//Act
|
|
await RunTaskInParallelAsync(
|
|
() => UseComponentCacheAsync(nodeManager, baseObject, nodeHandle),
|
|
100)
|
|
.ConfigureAwait(false);
|
|
|
|
//Assert, that entry was deleted from cache after parallel operations on the same node
|
|
NodeState handleFromCache = nodeManager.LookupNodeInComponentCache(
|
|
nodeManager.SystemContext,
|
|
nodeHandle);
|
|
|
|
Assert.That(handleFromCache, Is.Null);
|
|
}
|
|
finally
|
|
{
|
|
await fixture.StopAsync().ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests the Predefined Nodes methods with multiple threads
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestPredefinedNodesAsync()
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
var fixture = new ServerFixture<StandardServer>(t => new ReferenceServer(t));
|
|
|
|
try
|
|
{
|
|
// Arrange
|
|
const string ns = "http://test.org/UA/Data/";
|
|
StandardServer server = await fixture.StartAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
using var nodeManager = new TestableCustomNodeManger2(server.CurrentInstance, ns);
|
|
int index = server.CurrentInstance.NamespaceUris.GetIndex(ns);
|
|
|
|
var baseObject = new DataItemState(null);
|
|
NodeId nodeId =
|
|
CommonTestWorkers.NodeIdTestSetStatic[0]
|
|
.WithNamespaceIndex((ushort)index)
|
|
.InnerNodeId;
|
|
baseObject.NodeId = nodeId;
|
|
|
|
//single threaded test
|
|
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
|
|
|
|
Assert.That(nodeManager.PredefinedNodes.ContainsKey(nodeId), Is.True);
|
|
|
|
NodeState nodeState = nodeManager.Find(nodeId);
|
|
Assert.That(nodeState, Is.Not.Null);
|
|
|
|
var handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
|
|
Assert.That(handle, Is.Not.Null);
|
|
|
|
nodeManager.DeleteNode(nodeManager.SystemContext, nodeId);
|
|
|
|
Assert.That(nodeManager.PredefinedNodes, Is.Empty);
|
|
|
|
nodeState = nodeManager.Find(nodeId);
|
|
Assert.That(nodeState, Is.Null);
|
|
|
|
handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
|
|
Assert.That(handle, Is.Null);
|
|
|
|
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
|
|
|
|
nodeManager.DeleteAddressSpace();
|
|
|
|
Assert.That(nodeManager.PredefinedNodes, Is.Empty);
|
|
|
|
//Act
|
|
await RunTaskInParallelAsync(
|
|
() => UsePredefinedNodesAsync(nodeManager, baseObject, nodeId),
|
|
100)
|
|
.ConfigureAwait(false);
|
|
|
|
//last operation added the Node back into the dictionary
|
|
Assert.That(nodeManager.PredefinedNodes.ContainsKey(nodeId), Is.True);
|
|
|
|
//delete full adress space
|
|
nodeManager.DeleteAddressSpace();
|
|
Assert.That(nodeManager.PredefinedNodes, Is.Empty);
|
|
}
|
|
finally
|
|
{
|
|
await fixture.StopAsync().ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private static Task UsePredefinedNodesAsync(
|
|
TestableCustomNodeManger2 nodeManager,
|
|
DataItemState baseObject,
|
|
NodeId nodeId)
|
|
{
|
|
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
|
|
_ = nodeManager.Find(nodeId);
|
|
_ = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
|
|
|
|
nodeManager.DeleteNode(nodeManager.SystemContext, nodeId);
|
|
|
|
_ = nodeManager.Find(nodeId);
|
|
|
|
_ = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
|
|
|
|
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test Methods AddNodeToComponentCache, RemoveNodeFromComponentCache & LookupNodeInComponentCache & verify the node is added to the cache
|
|
/// </summary>
|
|
private static async Task UseComponentCacheAsync(
|
|
TestableCustomNodeManger2 nodeManager,
|
|
BaseObjectState baseObject,
|
|
NodeHandle nodeHandle)
|
|
{
|
|
//-- Act
|
|
nodeManager.AddNodeToComponentCache(nodeManager.SystemContext, nodeHandle, baseObject);
|
|
nodeManager.AddNodeToComponentCache(nodeManager.SystemContext, nodeHandle, baseObject);
|
|
|
|
NodeState handleFromCache = nodeManager.LookupNodeInComponentCache(
|
|
nodeManager.SystemContext,
|
|
nodeHandle);
|
|
|
|
//-- Assert
|
|
|
|
Assert.That(handleFromCache, Is.Not.Null);
|
|
|
|
nodeManager.RemoveNodeFromComponentCache(nodeManager.SystemContext, nodeHandle);
|
|
|
|
handleFromCache = nodeManager.LookupNodeInComponentCache(
|
|
nodeManager.SystemContext,
|
|
nodeHandle);
|
|
|
|
Assert.That(handleFromCache, Is.Not.Null);
|
|
|
|
nodeManager.RemoveNodeFromComponentCache(nodeManager.SystemContext, nodeHandle);
|
|
|
|
await Task.CompletedTask.ConfigureAwait(false);
|
|
}
|
|
|
|
private static async Task<(bool IsSuccess, Exception Error)> RunTaskInParallelAsync(
|
|
Func<Task> task,
|
|
int iterations)
|
|
{
|
|
using var cancellationTokenSource = new CancellationTokenSource();
|
|
Exception error = null;
|
|
int tasksCompletedCount = 0;
|
|
ParallelLoopResult result = Parallel.For(
|
|
0,
|
|
iterations,
|
|
new ParallelOptions(),
|
|
async _ =>
|
|
{
|
|
try
|
|
{
|
|
await task().ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex;
|
|
cancellationTokenSource.Cancel();
|
|
}
|
|
finally
|
|
{
|
|
tasksCompletedCount++;
|
|
}
|
|
});
|
|
|
|
int spinWaitCount = 0;
|
|
const int maxSpinWaitCount = 100;
|
|
while (iterations > tasksCompletedCount &&
|
|
error is null &&
|
|
spinWaitCount < maxSpinWaitCount)
|
|
{
|
|
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
|
|
spinWaitCount++;
|
|
}
|
|
|
|
return (error == null, error);
|
|
}
|
|
}
|
|
|
|
public class TestableCustomNodeManger2 : CustomNodeManager2
|
|
{
|
|
public TestableCustomNodeManger2(IServerInternal server, params string[] namespaceUris)
|
|
: base(server, namespaceUris)
|
|
{
|
|
}
|
|
|
|
public new NodeState AddNodeToComponentCache(
|
|
ISystemContext context,
|
|
NodeHandle handle,
|
|
NodeState node)
|
|
{
|
|
return base.AddNodeToComponentCache(context, handle, node);
|
|
}
|
|
|
|
public new void RemoveNodeFromComponentCache(ISystemContext context, NodeHandle handle)
|
|
{
|
|
base.RemoveNodeFromComponentCache(context, handle);
|
|
}
|
|
|
|
public new NodeState LookupNodeInComponentCache(ISystemContext context, NodeHandle handle)
|
|
{
|
|
return base.LookupNodeInComponentCache(context, handle);
|
|
}
|
|
|
|
public new NodeIdDictionary<NodeState> PredefinedNodes => base.PredefinedNodes;
|
|
|
|
public new virtual void AddPredefinedNode(ISystemContext context, NodeState node)
|
|
{
|
|
base.AddPredefinedNode(context, node);
|
|
}
|
|
}
|
|
}
|