Skip to content

Commit 064c151

Browse files
Copilotromanett
andauthored
Remove hardcoded Retain=true in ConditionState.UpdateStateAfterEnable (#3370)
* Initial plan * Add EvaluateRetainStateOnEnable virtual method for ConditionState - Removed hardcoded `Retain.Value = true` in UpdateStateAfterEnable - Added virtual EvaluateRetainStateOnEnable method that derived classes can override - Default implementation calls UpdateRetainState() which uses GetRetainState() - This allows concrete classes to customize Retain evaluation when enabling conditions - Added comprehensive tests for the new functionality - All existing tests pass (7846 tests) Co-authored-by: romanett <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: romanett <[email protected]>
1 parent e632da2 commit 064c151

File tree

2 files changed

+188
-1
lines changed

2 files changed

+188
-1
lines changed

Stack/Opc.Ua.Core/Stack/State/ConditionState.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,21 @@ protected virtual ServiceResult ProcessBeforeEnableDisable(
698698
return ServiceResult.Good;
699699
}
700700

701+
/// <summary>
702+
/// Evaluates and updates the Retain state when the condition is enabled.
703+
/// </summary>
704+
/// <param name="context">The system context.</param>
705+
/// <remarks>
706+
/// This method is called by UpdateStateAfterEnable to determine the Retain value.
707+
/// The default implementation calls UpdateRetainState() which uses GetRetainState().
708+
/// Derived classes can override this method to provide custom logic for determining
709+
/// the Retain value when the condition is enabled.
710+
/// </remarks>
711+
protected virtual void EvaluateRetainStateOnEnable(ISystemContext context)
712+
{
713+
UpdateRetainState();
714+
}
715+
701716
/// <summary>
702717
/// Updates the condition state after enabling.
703718
/// </summary>
@@ -709,7 +724,6 @@ protected virtual void UpdateStateAfterEnable(ISystemContext context)
709724
"en-US",
710725
ConditionStateNames.Enabled);
711726

712-
Retain.Value = true;
713727
EnabledState.Value = new LocalizedText(state);
714728
EnabledState.Id.Value = true;
715729

@@ -718,6 +732,8 @@ protected virtual void UpdateStateAfterEnable(ISystemContext context)
718732
EnabledState.TransitionTime.Value = DateTime.UtcNow;
719733
}
720734

735+
EvaluateRetainStateOnEnable(context);
736+
721737
UpdateEffectiveState(context);
722738
}
723739

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/* ========================================================================
2+
* Copyright (c) 2005-2018 The OPC Foundation, Inc. All rights reserved.
3+
*
4+
* OPC Foundation MIT License 1.00
5+
*
6+
* Permission is hereby granted, free of charge, to any person
7+
* obtaining a copy of this software and associated documentation
8+
* files (the "Software"), to deal in the Software without
9+
* restriction, including without limitation the rights to use,
10+
* copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the
12+
* Software is furnished to do so, subject to the following
13+
* conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be
16+
* included in all copies or substantial portions of the Software.
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19+
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24+
* OTHER DEALINGS IN THE SOFTWARE.
25+
*
26+
* The complete license agreement can be found here:
27+
* http://opcfoundation.org/License/MIT/1.00/
28+
* ======================================================================*/
29+
30+
using NUnit.Framework;
31+
using Opc.Ua.Tests;
32+
33+
namespace Opc.Ua.Core.Tests.Stack.State
34+
{
35+
/// <summary>
36+
/// Tests for the ConditionState class.
37+
/// </summary>
38+
[TestFixture]
39+
[Category("ConditionState")]
40+
[SetCulture("en-us")]
41+
[SetUICulture("en-us")]
42+
[Parallelizable]
43+
public class ConditionStateTests
44+
{
45+
private ISystemContext m_context;
46+
private ITelemetryContext m_telemetry;
47+
48+
[OneTimeSetUp]
49+
protected void OneTimeSetUp()
50+
{
51+
m_telemetry = NUnitTelemetryContext.Create();
52+
var messageContext = new ServiceMessageContext(m_telemetry);
53+
// Add OPC UA namespace
54+
messageContext.NamespaceUris.GetIndexOrAppend(Namespaces.OpcUa);
55+
m_context = new SystemContext(m_telemetry)
56+
{
57+
NamespaceUris = messageContext.NamespaceUris
58+
};
59+
}
60+
61+
[OneTimeTearDown]
62+
protected void OneTimeTearDown()
63+
{
64+
Utils.SilentDispose(m_context);
65+
}
66+
67+
/// <summary>
68+
/// Test that UpdateStateAfterEnable calls EvaluateRetainStateOnEnable
69+
/// and that the default implementation sets Retain based on GetRetainState.
70+
/// </summary>
71+
[Test]
72+
public void UpdateStateAfterEnableCallsEvaluateRetainStateOnEnable()
73+
{
74+
// Arrange
75+
var condition = new ConditionState(null);
76+
condition.Create(m_context, null, new QualifiedName("TestCondition"), null, true);
77+
78+
// Initially disabled
79+
condition.SetEnableState(m_context, false);
80+
Assert.That(condition.EnabledState.Id.Value, Is.False);
81+
Assert.That(condition.Retain.Value, Is.False);
82+
83+
// Act - Enable the condition
84+
condition.SetEnableState(m_context, true);
85+
86+
// Assert - Enabled state should be true
87+
Assert.That(condition.EnabledState.Id.Value, Is.True);
88+
89+
// The default implementation should call UpdateRetainState which uses GetRetainState
90+
// For base ConditionState with no branches, GetRetainState returns false when enabled
91+
Assert.That(condition.Retain.Value, Is.False);
92+
}
93+
94+
/// <summary>
95+
/// Test that UpdateStateAfterDisable sets Retain to false as per specification.
96+
/// </summary>
97+
[Test]
98+
public void UpdateStateAfterDisableSetsRetainToFalse()
99+
{
100+
// Arrange
101+
var condition = new TestConditionStateWithRetain(null);
102+
condition.Create(m_context, null, new QualifiedName("TestCondition"), null, true);
103+
104+
// Enable the condition and set retain to true
105+
condition.SetEnableState(m_context, true);
106+
condition.ForceRetain(true);
107+
Assert.That(condition.Retain.Value, Is.True);
108+
109+
// Act - Disable the condition
110+
condition.SetEnableState(m_context, false);
111+
112+
// Assert - Retain should be false per specification
113+
Assert.That(condition.EnabledState.Id.Value, Is.False);
114+
Assert.That(condition.Retain.Value, Is.False);
115+
}
116+
117+
/// <summary>
118+
/// Test that a derived class can override EvaluateRetainStateOnEnable
119+
/// to provide custom logic for determining the Retain value.
120+
/// </summary>
121+
[Test]
122+
public void DerivedClassCanOverrideEvaluateRetainStateOnEnable()
123+
{
124+
// Arrange
125+
var condition = new TestConditionStateWithCustomRetain(null);
126+
condition.Create(m_context, null, new QualifiedName("TestCondition"), null, true);
127+
128+
// Initially disabled
129+
condition.SetEnableState(m_context, false);
130+
Assert.That(condition.Retain.Value, Is.False);
131+
132+
// Act - Enable the condition
133+
condition.SetEnableState(m_context, true);
134+
135+
// Assert - The custom implementation should have been called
136+
Assert.That(condition.EnabledState.Id.Value, Is.True);
137+
Assert.That(condition.EvaluateCalled, Is.True);
138+
Assert.That(condition.Retain.Value, Is.True); // Custom logic always sets to true
139+
}
140+
141+
/// <summary>
142+
/// Test condition that exposes method to force Retain value for testing.
143+
/// </summary>
144+
private sealed class TestConditionStateWithRetain : ConditionState
145+
{
146+
public TestConditionStateWithRetain(NodeState parent) : base(parent) { }
147+
148+
public void ForceRetain(bool value)
149+
{
150+
Retain.Value = value;
151+
}
152+
}
153+
154+
/// <summary>
155+
/// Test condition that overrides EvaluateRetainStateOnEnable.
156+
/// </summary>
157+
private sealed class TestConditionStateWithCustomRetain : ConditionState
158+
{
159+
public bool EvaluateCalled { get; private set; }
160+
161+
public TestConditionStateWithCustomRetain(NodeState parent) : base(parent) { }
162+
163+
protected override void EvaluateRetainStateOnEnable(ISystemContext context)
164+
{
165+
EvaluateCalled = true;
166+
// Custom logic: always set Retain to true when enabled
167+
Retain.Value = true;
168+
}
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)