fix(driver-twincat-cli): resolve Low code-review findings (Driver.TwinCAT.Cli-001,002,003,004,005,006,007)
- Driver.TwinCAT.Cli-001: TwinCATCommandBase.Validate rejects non-positive TimeoutMs / IntervalMs and AmsPort outside 1..65535; ExecuteAsync calls it first. - Driver.TwinCAT.Cli-002: SubscribeCommand serialises every WriteLine through a writeLock to remove the notification-callback vs banner interleave risk. - Driver.TwinCAT.Cli-003: SubscribeCommand.DescribeMechanism derives the banner label from the returned ISubscriptionHandle.DiagnosticId so it can't disagree with what the driver actually did. - Driver.TwinCAT.Cli-004: introduced TwinCATTagCommandBase carrying --poll-only + BuildOptions; BrowseCommand stays on the slimmer TwinCATCommandBase so --poll-only no longer surfaces in browse --help. - Driver.TwinCAT.Cli-005: ProbeCommand --type now carries the 't' short alias to match the other commands. - Driver.TwinCAT.Cli-006: 35 new tests covering Gateway / AmsAddress parse / BuildOptions / PollOnly / browse-helpers / probe-alias / mechanism derivation. - Driver.TwinCAT.Cli-007: replaced the empty-init <inheritdoc/> with an explicit summary warning future maintainers about the no-op init. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Exceptions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli;
|
||||
|
||||
/// <summary>
|
||||
/// Base for every TwinCAT CLI command. Carries the AMS target options
|
||||
/// (<c>--ams-net-id</c> + <c>--ams-port</c>) + the notification-mode toggle that the
|
||||
/// driver itself takes. Exposes <see cref="BuildOptions"/> so each command can build a
|
||||
/// single-device / single-tag <see cref="TwinCATDriverOptions"/> from flag input.
|
||||
/// (<c>--ams-net-id</c> + <c>--ams-port</c>) + the per-call timeout. Commands that build
|
||||
/// a single-device / single-tag <see cref="TwinCATDriverOptions"/> from flag input inherit
|
||||
/// from <see cref="TwinCATTagCommandBase"/> instead — that intermediate adds the
|
||||
/// <c>--poll-only</c> flag and the <c>BuildOptions</c> helper.
|
||||
/// </summary>
|
||||
public abstract class TwinCATCommandBase : DriverCommandBase
|
||||
{
|
||||
@@ -23,16 +25,19 @@ public abstract class TwinCATCommandBase : DriverCommandBase
|
||||
[CommandOption("timeout-ms", Description = "Per-operation timeout in ms (default 5000).")]
|
||||
public int TimeoutMs { get; init; } = 5000;
|
||||
|
||||
[CommandOption("poll-only", Description =
|
||||
"Disable native ADS notifications and fall through to the shared PollGroupEngine " +
|
||||
"(same as setting UseNativeNotifications=false in a real driver config).")]
|
||||
public bool PollOnly { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The per-operation timeout, projected from <see cref="TimeoutMs"/>. The CliFx
|
||||
/// <c>init</c> accessor required by the abstract base property is intentionally a
|
||||
/// no-op: <see cref="TimeoutMs"/> is the only source of truth, so any value an
|
||||
/// `init` initialiser supplies to <see cref="Timeout"/> directly is silently
|
||||
/// dropped. Do NOT add a backing field "fixing" the empty body — it would diverge
|
||||
/// from <see cref="TimeoutMs"/> and the two would drift on every refactor
|
||||
/// (Driver.TwinCAT.Cli-007).
|
||||
/// </summary>
|
||||
public override TimeSpan Timeout
|
||||
{
|
||||
get => TimeSpan.FromMilliseconds(TimeoutMs);
|
||||
init { /* driven by TimeoutMs */ }
|
||||
init { /* see XML summary — driven by TimeoutMs */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,22 +46,29 @@ public abstract class TwinCATCommandBase : DriverCommandBase
|
||||
/// </summary>
|
||||
protected string Gateway => $"ads://{AmsNetId}:{AmsPort}";
|
||||
|
||||
/// <summary>
|
||||
/// Build a <see cref="TwinCATDriverOptions"/> with the AMS target this base collected +
|
||||
/// the tag list a subclass supplies. Probe disabled, controller-browse disabled,
|
||||
/// native notifications toggled by <see cref="PollOnly"/>.
|
||||
/// </summary>
|
||||
protected TwinCATDriverOptions BuildOptions(IReadOnlyList<TwinCATTagDefinition> tags) => new()
|
||||
{
|
||||
Devices = [new TwinCATDeviceOptions(
|
||||
HostAddress: Gateway,
|
||||
DeviceName: $"cli-{AmsNetId}:{AmsPort}")],
|
||||
Tags = tags,
|
||||
Timeout = Timeout,
|
||||
Probe = new TwinCATProbeOptions { Enabled = false },
|
||||
UseNativeNotifications = !PollOnly,
|
||||
EnableControllerBrowse = false,
|
||||
};
|
||||
|
||||
protected string DriverInstanceId => $"twincat-cli-{AmsNetId}:{AmsPort}";
|
||||
|
||||
/// <summary>
|
||||
/// Validates the numeric options every TwinCAT CLI command shares (timeout + AMS port).
|
||||
/// Subclasses override and call <c>base.Validate()</c> first to add their own range
|
||||
/// checks. Throwing here surfaces a clean CliFx one-line error before the driver gets
|
||||
/// a chance to fail with an opaque transport error (Driver.TwinCAT.Cli-001).
|
||||
/// </summary>
|
||||
protected virtual void Validate()
|
||||
{
|
||||
if (TimeoutMs <= 0)
|
||||
throw new CommandException(
|
||||
$"--timeout-ms must be greater than 0 (got {TimeoutMs}).");
|
||||
if (AmsPort is <= 0 or > 65535)
|
||||
throw new CommandException(
|
||||
$"--ams-port must be in the range 1..65535 (got {AmsPort}).");
|
||||
}
|
||||
|
||||
// ---- Test hooks ----
|
||||
// Protected members are exposed to the test assembly through these internal accessors so the
|
||||
// test project can cover Gateway / DriverInstanceId composition + range validation without
|
||||
// needing reflection on every assertion (Driver.TwinCAT.Cli-006).
|
||||
internal string GatewayForTest => Gateway;
|
||||
internal string DriverInstanceIdForTest => DriverInstanceId;
|
||||
internal void ValidateForTest() => Validate();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user