docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public
members surfaced by commentchecker — resolves 5,847 of 5,869 issues
(99.6%) across three /fixdocs passes.
This commit is contained in:
Joseph Doherty
2026-05-28 08:10:17 -04:00
parent f9fc7dd2e1
commit 64e3fbe035
756 changed files with 9876 additions and 96 deletions
@@ -8,6 +8,12 @@ public sealed class S7AddressParserTests
{
// --- Data blocks ---
/// <summary>Verifies that data block addresses are parsed correctly.</summary>
/// <param name="input">The address string to parse.</param>
/// <param name="db">The expected data block number.</param>
/// <param name="size">The expected data size.</param>
/// <param name="byteOff">The expected byte offset.</param>
/// <param name="bitOff">The expected bit offset.</param>
[Theory]
[InlineData("DB1.DBX0.0", 1, S7Size.Bit, 0, 0)]
[InlineData("DB1.DBX0.7", 1, S7Size.Bit, 0, 7)]
@@ -26,6 +32,11 @@ public sealed class S7AddressParserTests
r.BitOffset.ShouldBe(bitOff);
}
/// <summary>Verifies that parsing is case-insensitive and trims whitespace.</summary>
/// <param name="input">The address string to parse.</param>
/// <param name="db">The expected data block number.</param>
/// <param name="size">The expected data size.</param>
/// <param name="off">The expected byte offset.</param>
[Theory]
[InlineData("db1.dbw0", 1, S7Size.Word, 0)]
[InlineData(" DB1.DBW0 ", 1, S7Size.Word, 0)] // trim whitespace
@@ -40,6 +51,12 @@ public sealed class S7AddressParserTests
// --- M / I / Q ---
/// <summary>Verifies that Memory (M), Input (I), and Output (Q) addresses are parsed correctly.</summary>
/// <param name="input">The address string to parse.</param>
/// <param name="area">The expected address area.</param>
/// <param name="size">The expected data size.</param>
/// <param name="byteOff">The expected byte offset.</param>
/// <param name="bitOff">The expected bit offset.</param>
[Theory]
[InlineData("MB0", S7Area.Memory, S7Size.Byte, 0, 0)]
[InlineData("MW10", S7Area.Memory, S7Size.Word, 10, 0)]
@@ -65,6 +82,10 @@ public sealed class S7AddressParserTests
// --- Timers / counters ---
/// <summary>Verifies that timer and counter addresses are parsed correctly.</summary>
/// <param name="input">The address string to parse.</param>
/// <param name="area">The expected address area.</param>
/// <param name="number">The expected timer or counter number.</param>
[Theory]
[InlineData("T0", S7Area.Timer, 0)]
[InlineData("T15", S7Area.Timer, 15)]
@@ -80,6 +101,8 @@ public sealed class S7AddressParserTests
// --- Reject garbage ---
/// <summary>Verifies that invalid addresses are rejected with FormatException.</summary>
/// <param name="bad">The invalid address string to test.</param>
[Theory]
[InlineData("")]
[InlineData(" ")]
@@ -102,6 +125,7 @@ public sealed class S7AddressParserTests
public void Parse_rejects_invalid(string bad)
=> Should.Throw<FormatException>(() => S7AddressParser.Parse(bad));
/// <summary>Verifies that TryParse returns false for invalid input without throwing.</summary>
[Fact]
public void TryParse_returns_false_for_garbage_without_throwing()
{
@@ -109,6 +133,7 @@ public sealed class S7AddressParserTests
r.ShouldBe(default);
}
/// <summary>Verifies that TryParse returns true for a valid address.</summary>
[Fact]
public void TryParse_returns_true_for_valid_address()
{
@@ -18,27 +18,53 @@ public sealed class S7DiscoveryAndSubscribeTests
public readonly List<string> Folders = new();
public readonly List<(string Name, DriverAttributeInfo Attr)> Variables = new();
/// <summary>Adds a folder to the address space.</summary>
/// <param name="browseName">The browse name of the folder.</param>
/// <param name="displayName">The display name of the folder.</param>
/// <returns>This builder instance for method chaining.</returns>
public IAddressSpaceBuilder Folder(string browseName, string displayName)
{
Folders.Add(browseName);
return this;
}
/// <summary>Adds a variable to 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="attributeInfo">The attribute information for the variable.</param>
/// <returns>A handle to the created variable.</returns>
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
{
Variables.Add((browseName, attributeInfo));
return new StubHandle();
}
/// <summary>Adds a property to a variable.</summary>
/// <param name="browseName">The browse name of the property.</param>
/// <param name="dataType">The data type of the property.</param>
/// <param name="value">The initial value of the property.</param>
public void AddProperty(string browseName, DriverDataType dataType, object? value) { }
/// <summary>Attaches an alarm condition to a variable.</summary>
/// <param name="sourceVariable">The variable to attach the alarm to.</param>
/// <param name="alarmName">The name of the alarm.</param>
/// <param name="alarmInfo">The alarm information.</param>
public void AttachAlarmCondition(IVariableHandle sourceVariable, string alarmName, DriverAttributeInfo alarmInfo) { }
private sealed class StubHandle : IVariableHandle
{
/// <summary>Gets the full reference of the variable.</summary>
public string FullReference => "stub";
/// <summary>Marks this variable as an alarm condition.</summary>
/// <param name="info">The alarm condition information.</param>
/// <returns>An alarm condition sink.</returns>
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info)
=> throw new NotImplementedException("S7 driver never calls this — no alarm surfacing");
}
}
/// <summary>Verifies that DiscoverAsync projects every configured tag into the address space.</summary>
[Fact]
public async Task DiscoverAsync_projects_every_tag_into_the_address_space()
{
@@ -65,6 +91,7 @@ public sealed class S7DiscoveryAndSubscribeTests
builder.Variables[2].Attr.DriverDataType.ShouldBe(DriverDataType.Float32);
}
/// <summary>Verifies that DiscoverAsync propagates the WriteIdempotent flag from tag configuration to attribute info.</summary>
[Fact]
public async Task DiscoverAsync_propagates_WriteIdempotent_from_tag_to_attribute_info()
{
@@ -86,6 +113,7 @@ public sealed class S7DiscoveryAndSubscribeTests
builder.Variables.Single(v => v.Name == "StartBit").Attr.WriteIdempotent.ShouldBeFalse("default is opt-in per decision #44");
}
/// <summary>Verifies that GetHostStatuses returns one row with the host:port identity in pre-init state.</summary>
[Fact]
public void GetHostStatuses_returns_one_row_with_host_port_identity_pre_init()
{
@@ -98,6 +126,7 @@ public sealed class S7DiscoveryAndSubscribeTests
rows[0].State.ShouldBe(HostState.Unknown, "pre-init / pre-probe state is Unknown");
}
/// <summary>Verifies that SubscribeAsync returns unique handles and UnsubscribeAsync correctly accepts them.</summary>
[Fact]
public async Task SubscribeAsync_returns_unique_handles_and_UnsubscribeAsync_accepts_them()
{
@@ -121,6 +150,7 @@ public sealed class S7DiscoveryAndSubscribeTests
await drv.UnsubscribeAsync(h1, TestContext.Current.CancellationToken);
}
/// <summary>Verifies that Subscribe floors the publishing interval at 100ms.</summary>
[Fact]
public async Task Subscribe_publishing_interval_is_floored_at_100ms()
{
@@ -16,6 +16,8 @@ public sealed class S7DriverCodeReviewFixTests
{
// ---- Driver.S7-001 — Timer/Counter tags must be rejected at init ----
/// <summary>Verifies that initialize rejects timer or counter tags with NotSupportedException.</summary>
/// <param name="address">The S7 Timer or Counter address to test.</param>
[Theory]
[InlineData("T0")]
[InlineData("T15")]
@@ -45,6 +47,7 @@ public sealed class S7DriverCodeReviewFixTests
health.LastError.ShouldNotBeNull();
}
/// <summary>Verifies that initialize accepts DB and MIQ addresses without triggering the unsupported guard.</summary>
[Fact]
public async Task Initialize_accepts_DB_and_MIQ_addresses_without_the_unsupported_guard_tripping()
{
@@ -71,6 +74,7 @@ public sealed class S7DriverCodeReviewFixTests
// ---- Driver.S7-011 — driverConfigJson must be applied on Initialize ----
/// <summary>Verifies that initialize applies the supplied driverConfigJson over constructor options.</summary>
[Fact]
public async Task Initialize_applies_the_supplied_driverConfigJson_over_the_constructor_options()
{
@@ -96,6 +100,7 @@ public sealed class S7DriverCodeReviewFixTests
"InitializeAsync must apply the driverConfigJson timeout, not the constructor's");
}
/// <summary>Verifies that initialize rejects a timer tag supplied only through driverConfigJson.</summary>
[Fact]
public async Task Initialize_rejects_a_timer_tag_supplied_only_through_driverConfigJson()
{
@@ -114,6 +119,7 @@ public sealed class S7DriverCodeReviewFixTests
ex.Message.ShouldContain("T5");
}
/// <summary>Verifies that reinitialize applies a changed driverConfigJson.</summary>
[Fact]
public async Task Reinitialize_applies_a_changed_driverConfigJson()
{
@@ -135,6 +141,7 @@ public sealed class S7DriverCodeReviewFixTests
// ---- Driver.S7-006 — Shutdown drains probe/poll loops before disposing the gate ----
/// <summary>Verifies that shutdown completes cleanly with active subscriptions and no disposal race.</summary>
[Fact]
public async Task Shutdown_completes_cleanly_with_active_subscriptions_and_no_disposal_race()
{
@@ -159,6 +166,7 @@ public sealed class S7DriverCodeReviewFixTests
await Should.NotThrowAsync(async () => await drv.DisposeAsync());
}
/// <summary>Verifies that dispose after subscribe does not throw ObjectDisposedException.</summary>
[Fact]
public async Task Dispose_after_subscribe_does_not_throw_ObjectDisposedException()
{
@@ -15,6 +15,7 @@ public sealed class S7DriverCodeReviewFixTests2
{
// ── Driver.S7-003 — Read/WriteAsync must throw ArgumentNullException, not NRE ─────────
/// <summary>Verifies that ReadAsync throws ArgumentNullException for null references.</summary>
[Fact]
public async Task ReadAsync_with_null_fullReferences_throws_ArgumentNullException()
{
@@ -26,6 +27,7 @@ public sealed class S7DriverCodeReviewFixTests2
await drv.ReadAsync(null!, TestContext.Current.CancellationToken));
}
/// <summary>Verifies that WriteAsync throws ArgumentNullException for null writes.</summary>
[Fact]
public async Task WriteAsync_with_null_writes_throws_ArgumentNullException()
{
@@ -36,6 +38,7 @@ public sealed class S7DriverCodeReviewFixTests2
// ── Driver.S7-009 — Poll loop must update health on sustained failure ────────────────
/// <summary>Verifies that the poll loop degrades health when the driver is uninitialized.</summary>
[Fact]
public async Task PollLoop_against_uninitialized_driver_degrades_health()
{
@@ -64,6 +67,7 @@ public sealed class S7DriverCodeReviewFixTests2
await drv.DisposeAsync();
}
/// <summary>Verifies that the poll loop applies capped backoff after consecutive failures.</summary>
[Fact]
public async Task PollLoop_applies_capped_backoff_after_consecutive_failures()
{
@@ -103,6 +107,7 @@ public sealed class S7DriverCodeReviewFixTests2
// ── Driver.S7-010 — Dispose() must not deadlock via sync-over-async ──────────────────
/// <summary>Verifies that Dispose completes synchronously without sync-over-async.</summary>
[Fact]
public void Dispose_completes_synchronously_without_sync_over_async_round_trip()
{
@@ -120,6 +125,7 @@ public sealed class S7DriverCodeReviewFixTests2
"Dispose() must teardown directly — see Driver.S7-010");
}
/// <summary>Verifies that Dispose is idempotent.</summary>
[Fact]
public void Dispose_is_idempotent()
{
@@ -132,6 +138,8 @@ public sealed class S7DriverCodeReviewFixTests2
// ── Driver.S7-013 — Reject not-yet-implemented S7DataType values at init ─────────────
/// <summary>Verifies that Initialize rejects not-yet-implemented data types with NotSupportedException.</summary>
/// <param name="dt">The S7 data type that is not yet implemented.</param>
[Theory]
[InlineData(S7DataType.Int64)]
[InlineData(S7DataType.UInt64)]
@@ -162,6 +170,9 @@ public sealed class S7DriverCodeReviewFixTests2
health.State.ShouldBe(DriverState.Faulted);
}
/// <summary>Verifies that Initialize accepts implemented data types.</summary>
/// <param name="dt">The S7 data type to test.</param>
/// <param name="addr">The S7 address string corresponding to the data type.</param>
[Theory]
[InlineData(S7DataType.Bool, "DB1.DBX0.0")]
[InlineData(S7DataType.Byte, "DB1.DBB0")]
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
[Trait("Category", "Unit")]
public sealed class S7DriverReadWriteTests
{
/// <summary>Verifies that initialization rejects invalid tag addresses and fails fast.</summary>
[Fact]
public async Task Initialize_rejects_invalid_tag_address_and_fails_fast()
{
@@ -34,6 +35,7 @@ public sealed class S7DriverReadWriteTests
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken));
}
/// <summary>Verifies that ReadAsync without initialize throws InvalidOperationException.</summary>
[Fact]
public async Task ReadAsync_without_initialize_throws_InvalidOperationException()
{
@@ -42,6 +44,7 @@ public sealed class S7DriverReadWriteTests
await drv.ReadAsync(["Any"], TestContext.Current.CancellationToken));
}
/// <summary>Verifies that WriteAsync without initialize throws InvalidOperationException.</summary>
[Fact]
public async Task WriteAsync_without_initialize_throws_InvalidOperationException()
{
@@ -12,6 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
[Trait("Category", "Unit")]
public sealed class S7DriverScaffoldTests
{
/// <summary>Verifies that default options target S7-1500 slot 0 on port 102.</summary>
[Fact]
public void Default_options_target_S7_1500_slot_0_on_port_102()
{
@@ -22,6 +23,7 @@ public sealed class S7DriverScaffoldTests
opts.Slot.ShouldBe((short)0, "S7-1200/1500 onboard PN ports are slot 0 by convention");
}
/// <summary>Verifies that the default probe interval is reasonable for S7 scan cycles.</summary>
[Fact]
public void Default_probe_interval_is_reasonable_for_S7_scan_cycle()
{
@@ -30,6 +32,7 @@ public sealed class S7DriverScaffoldTests
new S7ProbeOptions().Interval.ShouldBe(TimeSpan.FromSeconds(5));
}
/// <summary>Verifies that tag definition defaults to writable with S7 max string length.</summary>
[Fact]
public void Tag_definition_defaults_to_writable_with_S7_max_string_length()
{
@@ -38,6 +41,7 @@ public sealed class S7DriverScaffoldTests
tag.StringLength.ShouldBe(254, "S7 STRING type max length is 254 chars");
}
/// <summary>Verifies that the driver instance reports type and ID before connect.</summary>
[Fact]
public void Driver_instance_reports_type_and_id_before_connect()
{
@@ -48,6 +52,7 @@ public sealed class S7DriverScaffoldTests
drv.GetHealth().State.ShouldBe(DriverState.Unknown, "health starts Unknown until InitializeAsync runs");
}
/// <summary>Verifies that Initialize against unreachable host transitions to Faulted and throws.</summary>
[Fact]
public async Task Initialize_against_unreachable_host_transitions_to_Faulted_and_throws()
{
@@ -22,6 +22,7 @@ public sealed class S7TypeMappingTests
// ── ReinterpretRawValue — implemented types ───────────────────────────────────────────
/// <summary>Verifies that bool values are returned unchanged.</summary>
[Fact]
public void ReinterpretRawValue_Bool_returns_bool()
{
@@ -31,6 +32,7 @@ public sealed class S7TypeMappingTests
S7Driver.ReinterpretRawValue(tag, addr, false).ShouldBe(false);
}
/// <summary>Verifies that byte values are returned unchanged.</summary>
[Fact]
public void ReinterpretRawValue_Byte_returns_byte()
{
@@ -39,6 +41,7 @@ public sealed class S7TypeMappingTests
S7Driver.ReinterpretRawValue(tag, addr, (byte)42).ShouldBe((byte)42);
}
/// <summary>Verifies that UInt16 values are returned as unsigned short.</summary>
[Fact]
public void ReinterpretRawValue_UInt16_returns_ushort()
{
@@ -46,6 +49,7 @@ public sealed class S7TypeMappingTests
.ShouldBe((ushort)1000);
}
/// <summary>Verifies that unsigned short bits are reinterpreted as signed short.</summary>
[Fact]
public void ReinterpretRawValue_Int16_reinterprets_ushort_as_signed()
{
@@ -54,6 +58,7 @@ public sealed class S7TypeMappingTests
result.ShouldBe((short)-1);
}
/// <summary>Verifies that UInt32 values are returned as unsigned integer.</summary>
[Fact]
public void ReinterpretRawValue_UInt32_returns_uint()
{
@@ -61,6 +66,7 @@ public sealed class S7TypeMappingTests
.ShouldBe(70_000u);
}
/// <summary>Verifies that unsigned integer bits are reinterpreted as signed integer.</summary>
[Fact]
public void ReinterpretRawValue_Int32_reinterprets_uint_as_signed()
{
@@ -69,6 +75,7 @@ public sealed class S7TypeMappingTests
result.ShouldBe(-1);
}
/// <summary>Verifies that unsigned integer bits are converted to IEEE 754 float.</summary>
[Fact]
public void ReinterpretRawValue_Float32_converts_uint_bits_to_float()
{
@@ -80,6 +87,8 @@ public sealed class S7TypeMappingTests
// ── ReinterpretRawValue — unsupported types throw NotSupportedException ───────────────
/// <summary>Verifies that unsupported data types throw NotSupportedException.</summary>
/// <param name="dt">The unsupported S7 data type.</param>
[Theory]
[InlineData(S7DataType.Int64)]
[InlineData(S7DataType.UInt64)]
@@ -94,6 +103,7 @@ public sealed class S7TypeMappingTests
// ── BoxValueForWrite — implemented types ─────────────────────────────────────────────
/// <summary>Verifies that values are converted to bool for write.</summary>
[Fact]
public void BoxValueForWrite_Bool_converts_value_to_bool()
{
@@ -102,18 +112,21 @@ public sealed class S7TypeMappingTests
S7Driver.BoxValueForWrite(S7DataType.Bool, 1).ShouldBe(true);
}
/// <summary>Verifies that values are converted to byte for write.</summary>
[Fact]
public void BoxValueForWrite_Byte_converts_to_byte()
{
S7Driver.BoxValueForWrite(S7DataType.Byte, (byte)200).ShouldBe((byte)200);
}
/// <summary>Verifies that values are converted to unsigned short for write.</summary>
[Fact]
public void BoxValueForWrite_UInt16_converts_to_ushort()
{
S7Driver.BoxValueForWrite(S7DataType.UInt16, (ushort)1234).ShouldBe((ushort)1234);
}
/// <summary>Verifies that signed short is reinterpreted as unsigned short using two's complement.</summary>
[Fact]
public void BoxValueForWrite_Int16_reinterprets_as_ushort_two_complement()
{
@@ -121,12 +134,14 @@ public sealed class S7TypeMappingTests
S7Driver.BoxValueForWrite(S7DataType.Int16, (short)-1).ShouldBe((ushort)0xFFFF);
}
/// <summary>Verifies that values are converted to unsigned integer for write.</summary>
[Fact]
public void BoxValueForWrite_UInt32_converts_to_uint()
{
S7Driver.BoxValueForWrite(S7DataType.UInt32, 70_000u).ShouldBe(70_000u);
}
/// <summary>Verifies that signed integer is reinterpreted as unsigned integer using two's complement.</summary>
[Fact]
public void BoxValueForWrite_Int32_reinterprets_as_uint_two_complement()
{
@@ -134,6 +149,7 @@ public sealed class S7TypeMappingTests
S7Driver.BoxValueForWrite(S7DataType.Int32, -1).ShouldBe(0xFFFF_FFFFu);
}
/// <summary>Verifies that float is encoded as IEEE 754 unsigned integer for write.</summary>
[Fact]
public void BoxValueForWrite_Float32_encodes_as_ieee754_uint()
{
@@ -144,6 +160,8 @@ public sealed class S7TypeMappingTests
// ── BoxValueForWrite — unsupported types throw NotSupportedException ─────────────────
/// <summary>Verifies that unsupported data types throw NotSupportedException on write.</summary>
/// <param name="dt">The unsupported S7 data type.</param>
[Theory]
[InlineData(S7DataType.Int64)]
[InlineData(S7DataType.UInt64)]
@@ -158,6 +176,7 @@ public sealed class S7TypeMappingTests
// ── BoxValueForWrite — overflow paths ────────────────────────────────────────────────
/// <summary>Verifies that out-of-range values throw OverflowException for byte write.</summary>
[Fact]
public void BoxValueForWrite_Byte_overflows_for_out_of_range_value()
{
@@ -165,6 +184,7 @@ public sealed class S7TypeMappingTests
Should.Throw<OverflowException>(() => S7Driver.BoxValueForWrite(S7DataType.Byte, 256));
}
/// <summary>Verifies that out-of-range values throw OverflowException for unsigned short write.</summary>
[Fact]
public void BoxValueForWrite_UInt16_overflows_for_out_of_range_value()
{