docs: complete XML doc comments via fixdocs (2757 to 131 findings)
Add missing <returns>/<param>/<summary>/<typeparam> tags and clean up misused inheritdoc across 481 files so the documented API surface is complete. Documentation-only (zero code lines changed). The 131 remaining findings are inheritdoc-style warnings deliberately left to preserve hand-written implementation rationale (plan-decision notes, race-condition explanations).
This commit is contained in:
@@ -49,6 +49,7 @@ public sealed class AbCipAlarmProjectionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disabled alarm projection returns a valid handle but does not poll.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task FeatureFlag_Off_SubscribeAlarms_Returns_Handle_But_Never_Polls()
|
||||
{
|
||||
@@ -76,6 +77,7 @@ public sealed class AbCipAlarmProjectionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that enabled alarm projection starts polling and fires raise event on 0-to-1 transition.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task FeatureFlag_On_Subscribe_Starts_Polling_And_Fires_Raise_On_0_to_1()
|
||||
{
|
||||
@@ -120,6 +122,7 @@ public sealed class AbCipAlarmProjectionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that alarm clear event fires on 1-to-0 transition.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Clear_Event_Fires_On_1_to_0_Transition()
|
||||
{
|
||||
@@ -161,6 +164,7 @@ public sealed class AbCipAlarmProjectionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that unsubscribing stops the alarm poll loop.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Unsubscribe_Stops_The_Poll_Loop()
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ public sealed class AbCipBoolInDIntRmwTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that bit set reads parent, ORs bit, and writes back.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Bit_set_reads_parent_ORs_bit_writes_back()
|
||||
{
|
||||
@@ -50,6 +51,7 @@ public sealed class AbCipBoolInDIntRmwTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that bit clear preserves other bits.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Bit_clear_preserves_other_bits()
|
||||
{
|
||||
@@ -73,6 +75,7 @@ public sealed class AbCipBoolInDIntRmwTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that concurrent bit writes to same parent compose correctly.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Concurrent_bit_writes_to_same_parent_compose_correctly()
|
||||
{
|
||||
@@ -98,6 +101,7 @@ public sealed class AbCipBoolInDIntRmwTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that bit writes to different parents each get their own runtime.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Bit_writes_to_different_parents_each_get_own_runtime()
|
||||
{
|
||||
@@ -125,6 +129,7 @@ public sealed class AbCipBoolInDIntRmwTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that repeat bit writes reuse one parent runtime.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Repeat_bit_writes_reuse_one_parent_runtime()
|
||||
{
|
||||
|
||||
+10
@@ -19,6 +19,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
// ---- Driver.AbCip-001 — ReinitializeAsync must apply a changed config JSON ----
|
||||
|
||||
/// <summary>Tests that InitializeAsync applies devices and tags from the config JSON.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAsync_applies_devices_and_tags_from_the_config_json()
|
||||
{
|
||||
@@ -40,6 +41,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
}
|
||||
|
||||
/// <summary>Tests that ReinitializeAsync with changed config JSON picks up the new device.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReinitializeAsync_with_a_changed_config_json_picks_up_the_new_device()
|
||||
{
|
||||
@@ -63,6 +65,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
}
|
||||
|
||||
/// <summary>Tests that InitializeAsync with blank JSON keeps construction-time options.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAsync_with_blank_json_keeps_construction_time_options()
|
||||
{
|
||||
@@ -82,6 +85,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
// ---- Driver.AbCip-003 — declaration-only whole-UDT grouping is opt-in ----
|
||||
|
||||
/// <summary>Tests that whole UDT grouping is off by default so members read per tag.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Whole_udt_grouping_is_off_by_default_so_members_read_per_tag()
|
||||
{
|
||||
@@ -137,6 +141,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
// ---- Driver.AbCip-008 — ShutdownAsync awaits probe loops; reads are concurrency-safe ----
|
||||
|
||||
/// <summary>Tests that ShutdownAsync awaits the probe loop before returning.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ShutdownAsync_awaits_the_probe_loop_before_returning()
|
||||
{
|
||||
@@ -162,6 +167,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
}
|
||||
|
||||
/// <summary>Tests that ShutdownAsync is idempotent.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ShutdownAsync_is_idempotent()
|
||||
{
|
||||
@@ -176,6 +182,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
}
|
||||
|
||||
/// <summary>Tests that concurrent first reads of the same tag do not corrupt the runtime cache.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Concurrent_first_reads_of_the_same_tag_do_not_corrupt_the_runtime_cache()
|
||||
{
|
||||
@@ -217,6 +224,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
}
|
||||
|
||||
/// <summary>Tests that read UDInt tag returns uint value not negative-wrapped int.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Read_UDInt_tag_returns_uint_value_not_negative_wrapped_int()
|
||||
{
|
||||
@@ -243,6 +251,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
// ---- Driver.AbCip-005 — Structure parent not registered; duplicate key check ----
|
||||
|
||||
/// <summary>Tests that structure parent tag read returns BadNotSupported not Good null.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Structure_parent_tag_read_returns_BadNotSupported_not_Good_null()
|
||||
{
|
||||
@@ -318,6 +327,7 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
// ---- Driver.AbCip-010 — stale runtime evicted on failure ----
|
||||
|
||||
/// <summary>Tests that read failure evicts runtime so next read creates fresh handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Read_failure_evicts_runtime_so_next_read_creates_fresh_handle()
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
||||
public sealed class AbCipDriverDiscoveryTests
|
||||
{
|
||||
/// <summary>Verifies that pre-declared tags emit as variables under device folder.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task PreDeclared_tags_emit_as_variables_under_device_folder()
|
||||
{
|
||||
@@ -35,6 +36,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that device folder display name falls back to host address when not provided.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Device_folder_displayname_falls_back_to_host_address()
|
||||
{
|
||||
@@ -52,6 +54,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that pre-declared system tags are filtered out.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task PreDeclared_system_tags_are_filtered_out()
|
||||
{
|
||||
@@ -74,6 +77,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that tags for mismatched devices are ignored.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Tags_for_mismatched_device_are_ignored()
|
||||
{
|
||||
@@ -91,6 +95,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that controller enumeration adds tags under Discovered folder.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Controller_enumeration_adds_tags_under_Discovered_folder()
|
||||
{
|
||||
@@ -114,6 +119,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that controller enumeration honours system tag hint and filter.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Controller_enumeration_honours_system_tag_hint_and_filter()
|
||||
{
|
||||
@@ -136,6 +142,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that controller enumeration ReadOnly flag surfaces ViewOnly classification.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Controller_enumeration_ReadOnly_surfaces_ViewOnly_classification()
|
||||
{
|
||||
@@ -156,6 +163,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that controller enumeration receives correct device parameters.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Controller_enumeration_receives_correct_device_params()
|
||||
{
|
||||
@@ -236,6 +244,7 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that FlushOptionalCachesAsync clears the template cache.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task FlushOptionalCachesAsync_clears_template_cache()
|
||||
{
|
||||
@@ -257,39 +266,29 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
/// <summary>Gets the list of recorded variables.</summary>
|
||||
public List<(string BrowseName, DriverAttributeInfo Info)> Variables { get; } = new();
|
||||
|
||||
/// <summary>Records a folder node.</summary>
|
||||
/// <param name="browseName">The browse name of the folder.</param>
|
||||
/// <param name="displayName">The display name of the folder.</param>
|
||||
/// <inheritdoc />
|
||||
public IAddressSpaceBuilder Folder(string browseName, string displayName)
|
||||
{ Folders.Add((browseName, displayName)); return this; }
|
||||
|
||||
/// <summary>Records a variable node.</summary>
|
||||
/// <param name="browseName">The browse name of the variable.</param>
|
||||
/// <param name="displayName">The display name of the variable.</param>
|
||||
/// <param name="info">The attribute information for the variable.</param>
|
||||
/// <inheritdoc />
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info)
|
||||
{ Variables.Add((browseName, info)); return new Handle(info.FullName); }
|
||||
|
||||
/// <summary>Adds a property (no-op in test).</summary>
|
||||
/// <param name="_">Property name (unused in test).</param>
|
||||
/// <param name="__">Property data type (unused in test).</param>
|
||||
/// <param name="___">Property value (unused in test).</param>
|
||||
/// <inheritdoc />
|
||||
public void AddProperty(string _, DriverDataType __, object? ___) { }
|
||||
|
||||
/// <summary>Test variable handle.</summary>
|
||||
private sealed class Handle(string fullRef) : IVariableHandle
|
||||
{
|
||||
/// <summary>Gets the full reference of the variable.</summary>
|
||||
/// <inheritdoc />
|
||||
public string FullReference => fullRef;
|
||||
/// <summary>Marks the variable as an alarm condition.</summary>
|
||||
/// <param name="info">The alarm condition information.</param>
|
||||
/// <inheritdoc />
|
||||
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink();
|
||||
}
|
||||
/// <summary>Null sink for alarm conditions.</summary>
|
||||
private sealed class NullSink : IAlarmConditionSink
|
||||
{
|
||||
/// <summary>Handles alarm transition (no-op).</summary>
|
||||
/// <param name="args">The alarm event arguments.</param>
|
||||
/// <inheritdoc />
|
||||
public void OnTransition(AlarmEventArgs args) { }
|
||||
}
|
||||
}
|
||||
@@ -303,15 +302,13 @@ public sealed class AbCipDriverDiscoveryTests
|
||||
/// <summary>Initializes a new instance of the FakeEnumeratorFactory.</summary>
|
||||
/// <param name="tags">The tags to enumerate.</param>
|
||||
public FakeEnumeratorFactory(params AbCipDiscoveredTag[] tags) => _tags = tags;
|
||||
/// <summary>Creates a new fake enumerator.</summary>
|
||||
/// <inheritdoc />
|
||||
public IAbCipTagEnumerator Create() => new FakeEnumerator(this);
|
||||
|
||||
/// <summary>Fake tag enumerator for testing.</summary>
|
||||
private sealed class FakeEnumerator(FakeEnumeratorFactory outer) : IAbCipTagEnumerator
|
||||
{
|
||||
/// <summary>Enumerates discovered tags asynchronously.</summary>
|
||||
/// <param name="deviceParams">The device parameters for enumeration.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<AbCipDiscoveredTag> EnumerateAsync(
|
||||
AbCipTagCreateParams deviceParams,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
|
||||
@@ -22,6 +22,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unknown reference maps to BadNodeIdUnknown status.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Unknown_reference_maps_to_BadNodeIdUnknown()
|
||||
{
|
||||
@@ -35,6 +36,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a tag on an unknown device maps to BadNodeIdUnknown status.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Tag_on_unknown_device_maps_to_BadNodeIdUnknown()
|
||||
{
|
||||
@@ -52,6 +54,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a successful DInt read returns Good status with the correct value.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Successful_DInt_read_returns_Good_with_value()
|
||||
{
|
||||
@@ -71,6 +74,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that repeated reads reuse the runtime without reinitializing.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Repeat_read_reuses_runtime_without_reinitialise()
|
||||
{
|
||||
@@ -88,6 +92,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that non-zero libplctag status is mapped via AbCipStatusMapper.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task NonZero_libplctag_status_maps_via_AbCipStatusMapper()
|
||||
{
|
||||
@@ -103,6 +108,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an exception during read surfaces BadCommunicationError status.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Exception_during_read_surfaces_BadCommunicationError()
|
||||
{
|
||||
@@ -119,6 +125,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that batched reads preserve order and per-tag status.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Batched_reads_preserve_order_and_per_tag_status()
|
||||
{
|
||||
@@ -144,6 +151,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a successful read marks health as Healthy.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Successful_read_marks_health_Healthy()
|
||||
{
|
||||
@@ -158,6 +166,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that tag creation parameters are built correctly from device and profile.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task TagCreateParams_are_built_from_device_and_profile()
|
||||
{
|
||||
@@ -176,6 +185,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that cancellation propagates from read operations.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Cancellation_propagates_from_read()
|
||||
{
|
||||
@@ -194,6 +204,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ShutdownAsync disposes each tag runtime.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ShutdownAsync_disposes_each_tag_runtime()
|
||||
{
|
||||
@@ -211,6 +222,7 @@ public sealed class AbCipDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that initialization failure disposes the tag and surfaces communication error.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Initialize_failure_disposes_tag_and_surfaces_communication_error()
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ public sealed class AbCipDriverTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies InitializeAsync with no devices succeeds and marks driver healthy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAsync_with_empty_devices_succeeds_and_marks_healthy()
|
||||
{
|
||||
@@ -28,6 +29,7 @@ public sealed class AbCipDriverTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies InitializeAsync registers devices with their respective PLC family profiles.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAsync_registers_each_device_with_its_family_profile()
|
||||
{
|
||||
@@ -48,6 +50,7 @@ public sealed class AbCipDriverTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies InitializeAsync rejects malformed host addresses and faults the driver.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAsync_with_malformed_host_address_faults()
|
||||
{
|
||||
@@ -62,6 +65,7 @@ public sealed class AbCipDriverTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies ShutdownAsync clears devices and marks driver state unknown.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ShutdownAsync_clears_devices_and_marks_unknown()
|
||||
{
|
||||
@@ -79,6 +83,7 @@ public sealed class AbCipDriverTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies ReinitializeAsync stops and restarts all devices.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReinitializeAsync_cycles_devices()
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ public sealed class AbCipDriverWholeUdtReadTests
|
||||
]);
|
||||
|
||||
/// <summary>Verifies that multiple members of the same UDT trigger only one parent read.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Two_members_of_same_udt_trigger_one_parent_read()
|
||||
{
|
||||
@@ -55,6 +56,7 @@ public sealed class AbCipDriverWholeUdtReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that each UDT member is decoded at its correct offset.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Each_member_decodes_at_its_own_offset()
|
||||
{
|
||||
@@ -81,6 +83,7 @@ public sealed class AbCipDriverWholeUdtReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that parent read failure marks all grouped members as Bad.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Parent_read_failure_stamps_every_grouped_member_Bad()
|
||||
{
|
||||
@@ -101,6 +104,7 @@ public sealed class AbCipDriverWholeUdtReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that mixed batches group UDT members and fall back to atomic reads.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Mixed_batch_groups_udt_and_falls_back_atomics()
|
||||
{
|
||||
@@ -121,6 +125,7 @@ public sealed class AbCipDriverWholeUdtReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a single UDT member uses the per-tag read path rather than grouping.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Single_member_of_Udt_uses_per_tag_read_path()
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that unknown reference maps to BadNodeIdUnknown status.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Unknown_reference_maps_to_BadNodeIdUnknown()
|
||||
{
|
||||
@@ -34,6 +35,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that non-writable tags map to BadNotWritable status.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Non_writable_tag_maps_to_BadNotWritable()
|
||||
{
|
||||
@@ -48,6 +50,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that successful DInt writes encode and flush values.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Successful_DInt_write_encodes_and_flushes()
|
||||
{
|
||||
@@ -64,6 +67,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that bit-in-DInt writes succeed via read-modify-write.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Bit_in_dint_write_now_succeeds_via_RMW()
|
||||
{
|
||||
@@ -85,6 +89,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that non-zero libplctag status after write maps correctly.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Non_zero_libplctag_status_after_write_maps_via_AbCipStatusMapper()
|
||||
{
|
||||
@@ -100,6 +105,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that type mismatch surfaces BadTypeMismatch status.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Type_mismatch_surfaces_BadTypeMismatch()
|
||||
{
|
||||
@@ -126,6 +132,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overflow surfaces BadOutOfRange status.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Overflow_surfaces_BadOutOfRange()
|
||||
{
|
||||
@@ -144,6 +151,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that exceptions during write surface BadCommunicationError.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Exception_during_write_surfaces_BadCommunicationError()
|
||||
{
|
||||
@@ -160,6 +168,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that batch write preserves order across success and failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Batch_preserves_order_across_success_and_failure()
|
||||
{
|
||||
@@ -192,6 +201,7 @@ public sealed class AbCipDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that cancellation propagates from write operations.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Cancellation_propagates_from_write()
|
||||
{
|
||||
|
||||
@@ -25,11 +25,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
/// <summary>Gets the last template ID read.</summary>
|
||||
public uint LastTemplateId { get; private set; }
|
||||
|
||||
/// <summary>Reads the template data for the specified device and template ID.</summary>
|
||||
/// <param name="deviceParams">The device parameters.</param>
|
||||
/// <param name="templateInstanceId">The template instance ID.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task that returns the template response bytes.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<byte[]> ReadAsync(AbCipTagCreateParams deviceParams, uint templateInstanceId, CancellationToken ct)
|
||||
{
|
||||
ReadCount++;
|
||||
@@ -50,8 +46,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
/// <summary>Gets or sets an optional customization function for reader creation.</summary>
|
||||
public Func<IAbCipTemplateReader>? Customise { get; set; }
|
||||
|
||||
/// <summary>Creates a new template reader.</summary>
|
||||
/// <returns>The created reader.</returns>
|
||||
/// <inheritdoc />
|
||||
public IAbCipTemplateReader Create()
|
||||
{
|
||||
var r = Customise?.Invoke() ?? new FakeTemplateReader();
|
||||
@@ -93,6 +88,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that FetchUdtShapeAsync decodes a blob and caches the result.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task FetchUdtShapeAsync_decodes_blob_and_caches_result()
|
||||
{
|
||||
@@ -123,6 +119,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that different template IDs result in separate fetch operations.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task FetchUdtShapeAsync_different_templateIds_each_fetch()
|
||||
{
|
||||
@@ -154,6 +151,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that FetchUdtShapeAsync returns null for an unknown device.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task FetchUdtShapeAsync_unknown_device_returns_null()
|
||||
{
|
||||
@@ -170,6 +168,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a decode failure returns null and does not cache the result.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task FetchUdtShapeAsync_decode_failure_returns_null_and_does_not_cache()
|
||||
{
|
||||
@@ -193,6 +192,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a reader exception returns null.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task FetchUdtShapeAsync_reader_exception_returns_null()
|
||||
{
|
||||
@@ -211,6 +211,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that FlushOptionalCachesAsync empties the template cache.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task FlushOptionalCachesAsync_empties_template_cache()
|
||||
{
|
||||
@@ -241,11 +242,7 @@ public sealed class AbCipFetchUdtShapeTests
|
||||
/// <summary>Test implementation of IAbCipTemplateReader that throws on read.</summary>
|
||||
private sealed class ThrowingTemplateReader : IAbCipTemplateReader
|
||||
{
|
||||
/// <summary>Throws an exception when read is attempted.</summary>
|
||||
/// <param name="p">The device parameters.</param>
|
||||
/// <param name="id">The template ID.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>Never returns; throws instead.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<byte[]> ReadAsync(AbCipTagCreateParams p, uint id, CancellationToken ct) =>
|
||||
throw new InvalidOperationException("fake read failure");
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
||||
public sealed class AbCipHostProbeTests
|
||||
{
|
||||
/// <summary>Verifies that GetHostStatuses returns one entry per configured device.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetHostStatuses_returns_one_entry_per_device()
|
||||
{
|
||||
@@ -31,6 +32,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a successful probe read transitions the host state to Running.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Probe_with_successful_read_transitions_to_Running()
|
||||
{
|
||||
@@ -58,6 +60,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a failed probe read transitions the host state to Stopped.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Probe_with_read_failure_transitions_to_Stopped()
|
||||
{
|
||||
@@ -87,6 +90,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the probe is disabled when the Enabled option is false.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Probe_disabled_when_Enabled_is_false()
|
||||
{
|
||||
@@ -108,6 +112,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the probe is skipped when ProbeTagPath is null.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Probe_skipped_when_ProbeTagPath_is_null()
|
||||
{
|
||||
@@ -125,6 +130,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the probe loops across multiple devices independently.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Probe_loops_across_multiple_devices_independently()
|
||||
{
|
||||
@@ -162,6 +168,7 @@ public sealed class AbCipHostProbeTests
|
||||
// ---- IPerCallHostResolver ----
|
||||
|
||||
/// <summary>Verifies that ResolveHost returns the declared device for a known tag.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ResolveHost_returns_declared_device_for_known_tag()
|
||||
{
|
||||
@@ -186,6 +193,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ResolveHost falls back to the first device for an unknown tag reference.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ResolveHost_falls_back_to_first_device_for_unknown_reference()
|
||||
{
|
||||
@@ -200,6 +208,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ResolveHost falls back to the driver instance ID when no devices are configured.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ResolveHost_falls_back_to_DriverInstanceId_when_no_devices()
|
||||
{
|
||||
@@ -210,6 +219,7 @@ public sealed class AbCipHostProbeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ResolveHost for a UDT member walks to the synthesized definition.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ResolveHost_for_UDT_member_walks_to_synthesised_definition()
|
||||
{
|
||||
|
||||
@@ -36,6 +36,7 @@ public sealed class AbCipLoggingTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ProbeLoop logs when an exception is swallowed.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ProbeLoop_logs_when_an_exception_is_swallowed()
|
||||
{
|
||||
@@ -79,6 +80,7 @@ public sealed class AbCipLoggingTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadFailure logs at warning level.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadFailure_logs_at_warning_level()
|
||||
{
|
||||
@@ -106,6 +108,7 @@ public sealed class AbCipLoggingTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadException logs at warning level.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadException_logs_at_warning_level()
|
||||
{
|
||||
@@ -137,6 +140,7 @@ public sealed class AbCipLoggingTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that InitializeAsync warns when probe is enabled but ProbeTagPath is blank.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAsync_warns_when_probe_is_enabled_but_ProbeTagPath_is_blank()
|
||||
{
|
||||
@@ -164,6 +168,7 @@ public sealed class AbCipLoggingTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that InitializeAsync does not warn when probe is disabled.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAsync_does_not_warn_when_probe_is_disabled()
|
||||
{
|
||||
@@ -191,9 +196,11 @@ public sealed class AbCipLoggingTests
|
||||
/// <summary>Begins a scope (stub implementation).</summary>
|
||||
/// <typeparam name="TState">The type of the scope state.</typeparam>
|
||||
/// <param name="state">The scope state.</param>
|
||||
/// <returns>A no-op disposable scope.</returns>
|
||||
public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;
|
||||
/// <summary>Checks if logging is enabled (always true).</summary>
|
||||
/// <param name="logLevel">The log level to check.</param>
|
||||
/// <returns><c>true</c> always — this capturing logger accepts every log level.</returns>
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
/// <summary>Logs an entry and captures it.</summary>
|
||||
/// <typeparam name="TState">The type of the log state.</typeparam>
|
||||
|
||||
+5
@@ -21,6 +21,7 @@ public sealed class AbCipPerDeviceConnectionOptionsTests
|
||||
private const string Device = "ab://10.0.0.5/1,0";
|
||||
|
||||
/// <summary>Verifies that per-device AllowPacking override is forwarded to tag creation parameters.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Device_AllowPacking_override_is_forwarded_to_tag_create_params()
|
||||
{
|
||||
@@ -40,6 +41,7 @@ public sealed class AbCipPerDeviceConnectionOptionsTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AllowPacking defaults inherit from the family profile when not overridden.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Device_AllowPacking_default_inherits_from_family_profile()
|
||||
{
|
||||
@@ -61,6 +63,7 @@ public sealed class AbCipPerDeviceConnectionOptionsTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Micro800 devices have AllowPacking defaulting to false from the family profile.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Micro800_default_AllowPacking_is_false_from_family_profile()
|
||||
{
|
||||
@@ -81,6 +84,7 @@ public sealed class AbCipPerDeviceConnectionOptionsTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that per-device ConnectionSize override is forwarded to tag creation parameters.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Device_ConnectionSize_override_is_forwarded_to_tag_create_params()
|
||||
{
|
||||
@@ -99,6 +103,7 @@ public sealed class AbCipPerDeviceConnectionOptionsTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ConnectionSize defaults inherit from the family profile when not overridden.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Device_ConnectionSize_default_inherits_from_family_profile()
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ public sealed class AbCipPlcFamilyTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a ControlLogix device initializes with the correct profile.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ControlLogix_device_initialises_with_correct_profile()
|
||||
{
|
||||
@@ -54,6 +55,7 @@ public sealed class AbCipPlcFamilyTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a CompactLogix device initializes with a narrow connection size.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task CompactLogix_device_initialises_with_narrow_ConnectionSize()
|
||||
{
|
||||
@@ -85,6 +87,7 @@ public sealed class AbCipPlcFamilyTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a Micro800 device with an empty CIP path parses correctly.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Micro800_device_with_empty_cip_path_parses_correctly()
|
||||
{
|
||||
@@ -102,6 +105,7 @@ public sealed class AbCipPlcFamilyTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Micro800 read operations forward the empty path to tag creation parameters.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Micro800_read_forwards_empty_path_to_tag_create_params()
|
||||
{
|
||||
@@ -134,6 +138,7 @@ public sealed class AbCipPlcFamilyTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GuardLogix safety tags surface as ViewOnly in discovery.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task GuardLogix_safety_tag_surfaces_as_ViewOnly_in_discovery()
|
||||
{
|
||||
@@ -160,6 +165,7 @@ public sealed class AbCipPlcFamilyTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GuardLogix safety tag writes are rejected even when the tag is marked Writable.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task GuardLogix_safety_tag_writes_rejected_even_when_Writable_is_true()
|
||||
{
|
||||
@@ -206,37 +212,27 @@ public sealed class AbCipPlcFamilyTests
|
||||
/// <summary>Gets the list of variables recorded by this builder.</summary>
|
||||
public List<(string BrowseName, DriverAttributeInfo Info)> Variables { get; } = new();
|
||||
|
||||
/// <summary>Adds a folder to the recorded list and returns this builder for chaining.</summary>
|
||||
/// <param name="browseName">The browse name of the folder.</param>
|
||||
/// <param name="displayName">The display name of the folder.</param>
|
||||
/// <inheritdoc />
|
||||
public IAddressSpaceBuilder Folder(string browseName, string displayName)
|
||||
{ Folders.Add((browseName, displayName)); return this; }
|
||||
|
||||
/// <summary>Adds a variable to the recorded list and returns a handle.</summary>
|
||||
/// <param name="browseName">The browse name of the variable.</param>
|
||||
/// <param name="displayName">The display name of the variable.</param>
|
||||
/// <param name="info">The driver attribute information.</param>
|
||||
/// <inheritdoc />
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info)
|
||||
{ Variables.Add((browseName, info)); return new Handle(info.FullName); }
|
||||
|
||||
/// <summary>No-op property adding operation for test compatibility.</summary>
|
||||
/// <param name="_">The property name.</param>
|
||||
/// <param name="__">The property data type.</param>
|
||||
/// <param name="___">The property value.</param>
|
||||
/// <inheritdoc />
|
||||
public void AddProperty(string _, DriverDataType __, object? ___) { }
|
||||
|
||||
private sealed class Handle(string fullRef) : IVariableHandle
|
||||
{
|
||||
/// <summary>Gets the full reference for this variable handle.</summary>
|
||||
/// <inheritdoc />
|
||||
public string FullReference => fullRef;
|
||||
/// <summary>Marks this variable as an alarm condition and returns a null sink.</summary>
|
||||
/// <param name="info">The alarm condition information.</param>
|
||||
/// <inheritdoc />
|
||||
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink();
|
||||
}
|
||||
private sealed class NullSink : IAlarmConditionSink
|
||||
{
|
||||
/// <summary>Called when an alarm state transitions.</summary>
|
||||
/// <param name="args">The alarm event arguments.</param>
|
||||
/// <inheritdoc />
|
||||
public void OnTransition(AlarmEventArgs args) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class AbCipSubscriptionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the initial poll raises OnDataChange events for every subscribed tag.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Initial_poll_raises_OnDataChange_for_every_tag()
|
||||
{
|
||||
@@ -47,6 +48,7 @@ public sealed class AbCipSubscriptionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that unchanged values raise OnDataChange only once (on initial poll).</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Unchanged_value_raises_only_once()
|
||||
{
|
||||
@@ -66,6 +68,7 @@ public sealed class AbCipSubscriptionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that value changes between polls raise OnDataChange events.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Value_change_between_polls_raises_OnDataChange()
|
||||
{
|
||||
@@ -89,6 +92,7 @@ public sealed class AbCipSubscriptionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that unsubscribe halts polling and no further events are raised.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Unsubscribe_halts_polling()
|
||||
{
|
||||
@@ -112,6 +116,7 @@ public sealed class AbCipSubscriptionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that polling intervals below 100ms are floored to the minimum.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Interval_below_100ms_is_floored()
|
||||
{
|
||||
@@ -133,6 +138,7 @@ public sealed class AbCipSubscriptionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ShutdownAsync cancels all active subscriptions.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task ShutdownAsync_cancels_active_subscriptions()
|
||||
{
|
||||
@@ -154,6 +160,7 @@ public sealed class AbCipSubscriptionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that subscriptions on UDT members use the synthesized full reference.</summary>
|
||||
/// <returns>A task that represents the asynchronous test.</returns>
|
||||
[Fact]
|
||||
public async Task Subscription_on_UDT_member_uses_synthesised_full_reference()
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
||||
public sealed class AbCipUdtMemberTests
|
||||
{
|
||||
/// <summary>Verifies that UDT with declared members expands to individual member variables.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UDT_with_declared_members_fans_out_to_member_variables()
|
||||
{
|
||||
@@ -48,6 +49,7 @@ public sealed class AbCipUdtMemberTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UDT members can be read via synthesised full reference paths.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UDT_members_resolvable_for_read_via_synthesised_full_reference()
|
||||
{
|
||||
@@ -84,6 +86,7 @@ public sealed class AbCipUdtMemberTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UDT member writes route through synthesised tag paths.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UDT_member_write_routes_through_synthesised_tagpath()
|
||||
{
|
||||
@@ -110,6 +113,7 @@ public sealed class AbCipUdtMemberTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UDT member read/write operations respect the Writable flag.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UDT_member_read_write_honours_member_Writable_flag()
|
||||
{
|
||||
@@ -135,6 +139,7 @@ public sealed class AbCipUdtMemberTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that structure tags without declared members appear as single variables.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Structure_tag_without_members_is_emitted_as_single_variable()
|
||||
{
|
||||
@@ -156,6 +161,7 @@ public sealed class AbCipUdtMemberTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that empty member lists are treated the same as null.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Empty_Members_list_is_treated_like_null()
|
||||
{
|
||||
@@ -174,6 +180,7 @@ public sealed class AbCipUdtMemberTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UDT members and flat tags can coexist in the address space.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UDT_members_mixed_with_flat_tags_coexist()
|
||||
{
|
||||
@@ -209,39 +216,29 @@ public sealed class AbCipUdtMemberTests
|
||||
/// <summary>Gets the collected variables.</summary>
|
||||
public List<(string BrowseName, DriverAttributeInfo Info)> Variables { get; } = new();
|
||||
|
||||
/// <summary>Records a folder in the address space.</summary>
|
||||
/// <param name="browseName">The browse name of the folder.</param>
|
||||
/// <param name="displayName">The display name of the folder.</param>
|
||||
/// <inheritdoc />
|
||||
public IAddressSpaceBuilder Folder(string browseName, string displayName)
|
||||
{ Folders.Add((browseName, displayName)); return this; }
|
||||
|
||||
/// <summary>Records a variable in the address space.</summary>
|
||||
/// <param name="browseName">The browse name of the variable.</param>
|
||||
/// <param name="displayName">The display name of the variable.</param>
|
||||
/// <param name="info">The driver attribute information for the variable.</param>
|
||||
/// <inheritdoc />
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info)
|
||||
{ Variables.Add((browseName, info)); return new Handle(info.FullName); }
|
||||
|
||||
/// <summary>Records a property (stub implementation for testing).</summary>
|
||||
/// <param name="_">The property name (unused in this stub).</param>
|
||||
/// <param name="__">The property data type (unused in this stub).</param>
|
||||
/// <param name="___">The property value (unused in this stub).</param>
|
||||
/// <inheritdoc />
|
||||
public void AddProperty(string _, DriverDataType __, object? ___) { }
|
||||
|
||||
/// <summary>Variable handle implementation for testing.</summary>
|
||||
private sealed class Handle(string fullRef) : IVariableHandle
|
||||
{
|
||||
/// <summary>Gets the full reference path.</summary>
|
||||
/// <inheritdoc />
|
||||
public string FullReference => fullRef;
|
||||
/// <summary>Marks this handle as an alarm condition.</summary>
|
||||
/// <param name="info">The alarm condition information.</param>
|
||||
/// <inheritdoc />
|
||||
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink();
|
||||
}
|
||||
/// <summary>Null alarm condition sink for testing.</summary>
|
||||
private sealed class NullSink : IAlarmConditionSink
|
||||
{
|
||||
/// <summary>Handles alarm transitions (stub).</summary>
|
||||
/// <param name="args">The alarm event arguments.</param>
|
||||
/// <inheritdoc />
|
||||
public void OnTransition(AlarmEventArgs args) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ internal class FakeAbCipTag : IAbCipTagRuntime
|
||||
/// <param name="createParams">The tag creation parameters.</param>
|
||||
public FakeAbCipTag(AbCipTagCreateParams createParams) => CreationParams = createParams;
|
||||
|
||||
/// <summary>Increments the initialize count and simulates initialization.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token for the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public virtual Task InitializeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
InitializeCount++;
|
||||
@@ -44,8 +43,7 @@ internal class FakeAbCipTag : IAbCipTagRuntime
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Increments the read count and simulates a read operation.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token for the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public virtual Task ReadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ReadCount++;
|
||||
@@ -53,20 +51,17 @@ internal class FakeAbCipTag : IAbCipTagRuntime
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Increments the write count and simulates a write operation.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token for the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public virtual Task WriteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
WriteCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Returns the simulated status code.</summary>
|
||||
/// <inheritdoc />
|
||||
public virtual int GetStatus() => Status;
|
||||
|
||||
/// <summary>Returns the mock tag value.</summary>
|
||||
/// <param name="type">The data type being decoded.</param>
|
||||
/// <param name="bitIndex">The optional bit index for bit operations.</param>
|
||||
/// <inheritdoc />
|
||||
public virtual object? DecodeValue(AbCipDataType type, int? bitIndex) => Value;
|
||||
|
||||
/// <summary>
|
||||
@@ -78,20 +73,14 @@ internal class FakeAbCipTag : IAbCipTagRuntime
|
||||
/// </summary>
|
||||
public Dictionary<int, object?> ValuesByOffset { get; } = new();
|
||||
|
||||
/// <summary>Returns the mock value at the specified offset.</summary>
|
||||
/// <param name="type">The data type being decoded.</param>
|
||||
/// <param name="offset">The byte offset into the tag storage.</param>
|
||||
/// <param name="bitIndex">The optional bit index for bit operations.</param>
|
||||
/// <inheritdoc />
|
||||
public virtual object? DecodeValueAt(AbCipDataType type, int offset, int? bitIndex)
|
||||
{
|
||||
if (ValuesByOffset.TryGetValue(offset, out var v)) return v;
|
||||
return offset == 0 ? Value : null;
|
||||
}
|
||||
|
||||
/// <summary>Encodes a value into the mock tag storage.</summary>
|
||||
/// <param name="type">The data type being encoded.</param>
|
||||
/// <param name="bitIndex">The optional bit index for bit operations.</param>
|
||||
/// <param name="value">The value to encode.</param>
|
||||
/// <inheritdoc />
|
||||
public virtual void EncodeValue(AbCipDataType type, int? bitIndex, object? value) => Value = value;
|
||||
|
||||
/// <summary>Marks the tag as disposed.</summary>
|
||||
@@ -106,8 +95,7 @@ internal sealed class FakeAbCipTagFactory : IAbCipTagFactory
|
||||
/// <summary>Gets or sets an optional customization function to override the tag creation.</summary>
|
||||
public Func<AbCipTagCreateParams, FakeAbCipTag>? Customise { get; set; }
|
||||
|
||||
/// <summary>Creates a new fake tag and indexes it by name.</summary>
|
||||
/// <param name="createParams">The tag creation parameters.</param>
|
||||
/// <inheritdoc />
|
||||
public IAbCipTagRuntime Create(AbCipTagCreateParams createParams)
|
||||
{
|
||||
var fake = Customise?.Invoke(createParams) ?? new FakeAbCipTag(createParams);
|
||||
|
||||
Reference in New Issue
Block a user