Merge B1-wp6integ

This commit is contained in:
Joseph Doherty
2026-07-15 21:26:51 -04:00
28 changed files with 249 additions and 104 deletions
@@ -0,0 +1,27 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests;
/// <summary>
/// Test helper bridging the pre-v3 pre-declared-tag authoring style (a typed
/// <see cref="AbCipTagDefinition"/>) to the v3 RawPath seam the driver now consumes
/// (<see cref="RawTagEntry"/>). Each typed def is serialised to its <c>TagConfig</c> blob via
/// <see cref="AbCipTagDefinitionFactory.ToTagConfig"/>, and the def's identity (<c>Name</c> → RawPath),
/// device routing key (<c>DeviceHostAddress</c> → <see cref="RawTagEntry.DeviceName"/>) and
/// write-idempotence travel on the entry — exactly the shape the deploy artifact delivers. Lets the
/// driver's integration smoke tests keep expressing fixtures as typed definitions while exercising the
/// v3 <c>RawTags</c> → <c>FromTagConfig</c> table-build path. Mirrors the unit-test project's helper of
/// the same name.
/// </summary>
internal static class AbCipRawTags
{
/// <summary>Projects typed definitions to the driver's <c>RawTags</c> option shape.</summary>
/// <param name="defs">The typed definitions to project.</param>
/// <returns>The equivalent <see cref="RawTagEntry"/> list.</returns>
public static IReadOnlyList<RawTagEntry> From(params AbCipTagDefinition[] defs) =>
[.. defs.Select(d => new RawTagEntry(
RawPath: d.Name,
TagConfig: AbCipTagDefinitionFactory.ToTagConfig(d),
WriteIdempotent: d.WriteIdempotent,
DeviceName: d.DeviceHostAddress))];
}
@@ -37,7 +37,7 @@ public sealed class AbCipReadSmokeTests
var drv = new AbCipDriver(new AbCipDriverOptions var drv = new AbCipDriver(new AbCipDriverOptions
{ {
Devices = [new AbCipDeviceOptions(deviceUri, profile.Family)], Devices = [new AbCipDeviceOptions(deviceUri, profile.Family)],
Tags = [new AbCipTagDefinition("Counter", deviceUri, "TestDINT", AbCipDataType.DInt)], RawTags = AbCipRawTags.From(new AbCipTagDefinition("Counter", deviceUri, "TestDINT", AbCipDataType.DInt)),
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
}, $"drv-smoke-{profile.Family}"); }, $"drv-smoke-{profile.Family}");
@@ -54,7 +54,7 @@ public sealed class AbCipEmulateAlmdTests
Devices = [new AbCipDeviceOptions($"ab://{endpoint}/1,0")], Devices = [new AbCipDeviceOptions($"ab://{endpoint}/1,0")],
EnableAlarmProjection = true, EnableAlarmProjection = true,
AlarmPollInterval = TimeSpan.FromMilliseconds(200), AlarmPollInterval = TimeSpan.FromMilliseconds(200),
Tags = [ RawTags = AbCipRawTags.From(
new AbCipTagDefinition( new AbCipTagDefinition(
Name: "HighTempAlarm", Name: "HighTempAlarm",
DeviceHostAddress: $"ab://{endpoint}/1,0", DeviceHostAddress: $"ab://{endpoint}/1,0",
@@ -72,8 +72,7 @@ public sealed class AbCipEmulateAlmdTests
DeviceHostAddress: $"ab://{endpoint}/1,0", DeviceHostAddress: $"ab://{endpoint}/1,0",
TagPath: "SimulateAlarm", TagPath: "SimulateAlarm",
DataType: AbCipDataType.Bool, DataType: AbCipDataType.Bool,
Writable: true), Writable: true)),
],
}; };
await using var drv = new AbCipDriver(options, driverInstanceId: "emulate-almd"); await using var drv = new AbCipDriver(options, driverInstanceId: "emulate-almd");
@@ -51,7 +51,7 @@ public sealed class AbCipEmulateUdtReadTests
var options = new AbCipDriverOptions var options = new AbCipDriverOptions
{ {
Devices = [new AbCipDeviceOptions($"ab://{endpoint}/1,0")], Devices = [new AbCipDeviceOptions($"ab://{endpoint}/1,0")],
Tags = [ RawTags = AbCipRawTags.From(
new AbCipTagDefinition( new AbCipTagDefinition(
Name: "Motor1", Name: "Motor1",
DeviceHostAddress: $"ab://{endpoint}/1,0", DeviceHostAddress: $"ab://{endpoint}/1,0",
@@ -61,8 +61,7 @@ public sealed class AbCipEmulateUdtReadTests
new AbCipStructureMember("Speed", AbCipDataType.DInt), new AbCipStructureMember("Speed", AbCipDataType.DInt),
new AbCipStructureMember("Torque", AbCipDataType.Real), new AbCipStructureMember("Torque", AbCipDataType.Real),
new AbCipStructureMember("Status", AbCipDataType.DInt), new AbCipStructureMember("Status", AbCipDataType.DInt),
]), ])),
],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
}; };
@@ -0,0 +1,32 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests;
/// <summary>
/// Test helper bridging the v2 authoring shape (a typed <see cref="AbLegacyTagDefinition"/>) to the v3
/// delivery shape (<see cref="RawTagEntry"/>). Under v3 the driver no longer takes pre-declared
/// definitions — the deploy artifact hands it authored raw tags (RawPath + TagConfig blob + owning
/// DeviceName), and the driver rebuilds the typed definitions via
/// <see cref="AbLegacyTagDefinitionFactory"/>. These integration smoke tests keep expressing their
/// intent as typed definitions; this helper serialises each back to its TagConfig blob (via
/// <see cref="AbLegacyTagDefinitionFactory.ToTagConfig"/>) and packages it as a <see cref="RawTagEntry"/>
/// whose RawPath is the def's <c>Name</c> and whose <see cref="RawTagEntry.DeviceName"/> is the def's
/// <c>DeviceHostAddress</c> — so the round-trip through the real mapper reproduces the same definition
/// the test authored, keyed by the same name the read/write/subscribe call sites use and routed to the
/// same device (the driver matches DeviceName against a configured device's Name or HostAddress).
/// Mirrors the unit-test project's helper of the same name.
/// </summary>
internal static class AbLegacyRawTags
{
/// <summary>Serialises one typed definition into its <see cref="RawTagEntry"/> (RawPath = def.Name, DeviceName = def.DeviceHostAddress).</summary>
/// <param name="def">The typed definition to convert.</param>
/// <returns>The equivalent <see cref="RawTagEntry"/>.</returns>
public static RawTagEntry Entry(AbLegacyTagDefinition def)
=> new(def.Name, AbLegacyTagDefinitionFactory.ToTagConfig(def), def.WriteIdempotent, DeviceName: def.DeviceHostAddress);
/// <summary>Serialises typed definitions into <see cref="RawTagEntry"/> records.</summary>
/// <param name="defs">The typed definitions to convert (array, params, or a collection expression).</param>
/// <returns>The equivalent raw-tag entries.</returns>
public static IReadOnlyList<RawTagEntry> Entries(params AbLegacyTagDefinition[] defs)
=> [.. defs.Select(Entry)];
}
@@ -51,13 +51,12 @@ public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim)
await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions
{ {
Devices = [new AbLegacyDeviceOptions(deviceUri, profile.Family)], Devices = [new AbLegacyDeviceOptions(deviceUri, profile.Family)],
Tags = [ RawTags = AbLegacyRawTags.Entries(
new AbLegacyTagDefinition( new AbLegacyTagDefinition(
Name: "IntCounter", Name: "IntCounter",
DeviceHostAddress: deviceUri, DeviceHostAddress: deviceUri,
Address: "N7:0", Address: "N7:0",
DataType: AbLegacyDataType.Int), DataType: AbLegacyDataType.Int)),
],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
Probe = new AbLegacyProbeOptions { Enabled = false }, Probe = new AbLegacyProbeOptions { Enabled = false },
}, driverInstanceId: $"ablegacy-smoke-{profile.Family}"); }, driverInstanceId: $"ablegacy-smoke-{profile.Family}");
@@ -96,14 +95,13 @@ public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim)
await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions
{ {
Devices = [new AbLegacyDeviceOptions(deviceUri, AbLegacyPlcFamily.Slc500)], Devices = [new AbLegacyDeviceOptions(deviceUri, AbLegacyPlcFamily.Slc500)],
Tags = [ RawTags = AbLegacyRawTags.Entries(
new AbLegacyTagDefinition( new AbLegacyTagDefinition(
Name: "Scratch", Name: "Scratch",
DeviceHostAddress: deviceUri, DeviceHostAddress: deviceUri,
Address: "N7:5", Address: "N7:5",
DataType: AbLegacyDataType.Int, DataType: AbLegacyDataType.Int,
Writable: true), Writable: true)),
],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
Probe = new AbLegacyProbeOptions { Enabled = false }, Probe = new AbLegacyProbeOptions { Enabled = false },
}, driverInstanceId: "ablegacy-smoke-rw"); }, driverInstanceId: "ablegacy-smoke-rw");
@@ -0,0 +1,31 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests;
/// <summary>
/// Test helper bridging the v2 authoring shape (a typed <see cref="FocasTagDefinition"/>) to the v3
/// delivery shape (<see cref="RawTagEntry"/>). Under v3 the driver no longer takes pre-declared
/// definitions — the deploy artifact hands it authored raw tags (RawPath + TagConfig blob + DeviceName),
/// and the driver rebuilds the typed definitions via <see cref="FocasTagDefinitionFactory"/> and routes
/// each to its device by <see cref="RawTagEntry.DeviceName"/>. These integration wire tests keep
/// expressing their intent as typed definitions; this helper serialises each back to its TagConfig blob
/// (via <see cref="FocasTagDefinitionFactory.ToTagConfig"/>) and packages it as a <see cref="RawTagEntry"/>
/// whose RawPath is the def's <c>Name</c> and whose DeviceName is the def's <c>DeviceHostAddress</c> — so
/// the round-trip through the real mapper + device match reproduces the same definition the test authored,
/// keyed by the same name the read/write/subscribe call sites use. Mirrors the unit-test project's helper
/// of the same name.
/// </summary>
internal static class FocasRawTags
{
/// <summary>Serialises one typed definition into its <see cref="RawTagEntry"/> (RawPath = def.Name, DeviceName = def.DeviceHostAddress).</summary>
/// <param name="def">The typed definition to convert.</param>
/// <returns>The equivalent <see cref="RawTagEntry"/>.</returns>
public static RawTagEntry Entry(FocasTagDefinition def)
=> new(def.Name, FocasTagDefinitionFactory.ToTagConfig(def), def.WriteIdempotent, def.DeviceHostAddress);
/// <summary>Serialises typed definitions into <see cref="RawTagEntry"/> records.</summary>
/// <param name="defs">The typed definitions to convert (array, params, or a collection expression).</param>
/// <returns>The equivalent raw-tag entries.</returns>
public static IReadOnlyList<RawTagEntry> Entries(params FocasTagDefinition[] defs)
=> [.. defs.Select(Entry)];
}
@@ -54,12 +54,10 @@ public sealed class WireBackendCoverageTests
var drv = new FocasDriver(new FocasDriverOptions var drv = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost)], Devices = [new FocasDeviceOptions(DeviceHost)],
Tags = RawTags = FocasRawTags.Entries(
[
new FocasTagDefinition("Param6711", DeviceHost, "PARAM:6711", FocasDataType.Int32, Writable: false), new FocasTagDefinition("Param6711", DeviceHost, "PARAM:6711", FocasDataType.Int32, Writable: false),
new FocasTagDefinition("Macro500", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false), new FocasTagDefinition("Macro500", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false),
new FocasTagDefinition("R100", DeviceHost, "R100", FocasDataType.Byte, Writable: false), new FocasTagDefinition("R100", DeviceHost, "R100", FocasDataType.Byte, Writable: false)),
],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
}, driverInstanceId: "wire-usertags", clientFactory: new WireFocasClientFactory()); }, driverInstanceId: "wire-usertags", clientFactory: new WireFocasClientFactory());
@@ -86,11 +84,9 @@ public sealed class WireBackendCoverageTests
var drv = new FocasDriver(new FocasDriverOptions var drv = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost, DeviceName: "Lathe-1")], Devices = [new FocasDeviceOptions(DeviceHost, DeviceName: "Lathe-1")],
Tags = RawTags = FocasRawTags.Entries(
[
new FocasTagDefinition("Run", DeviceHost, "R100", FocasDataType.Byte, Writable: false), new FocasTagDefinition("Run", DeviceHost, "R100", FocasDataType.Byte, Writable: false),
new FocasTagDefinition("Speed", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false), new FocasTagDefinition("Speed", DeviceHost, "MACRO:500", FocasDataType.Float64, Writable: false)),
],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
}, driverInstanceId: "wire-discover", clientFactory: new WireFocasClientFactory()); }, driverInstanceId: "wire-discover", clientFactory: new WireFocasClientFactory());
@@ -127,7 +123,7 @@ public sealed class WireBackendCoverageTests
var drv = new FocasDriver(new FocasDriverOptions var drv = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost)], Devices = [new FocasDeviceOptions(DeviceHost)],
Tags = [new FocasTagDefinition("Run", DeviceHost, "R100", FocasDataType.Byte, Writable: false)], RawTags = FocasRawTags.Entries(new FocasTagDefinition("Run", DeviceHost, "R100", FocasDataType.Byte, Writable: false)),
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
}, driverInstanceId: "wire-subscribe", clientFactory: new WireFocasClientFactory()); }, driverInstanceId: "wire-subscribe", clientFactory: new WireFocasClientFactory());
@@ -172,7 +168,7 @@ public sealed class WireBackendCoverageTests
var drv = new FocasDriver(new FocasDriverOptions var drv = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost)], Devices = [new FocasDeviceOptions(DeviceHost)],
Tags = [], RawTags = [],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
AlarmProjection = new FocasAlarmProjectionOptions AlarmProjection = new FocasAlarmProjectionOptions
{ {
@@ -275,7 +271,7 @@ public sealed class WireBackendCoverageTests
var driver = new FocasDriver(new FocasDriverOptions var driver = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost, PositionDecimalPlaces: 1)], Devices = [new FocasDeviceOptions(DeviceHost, PositionDecimalPlaces: 1)],
Tags = [], RawTags = [],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
FixedTree = new FocasFixedTreeOptions FixedTree = new FocasFixedTreeOptions
{ {
@@ -57,7 +57,7 @@ public sealed class WireBackendTests
var driver = new FocasDriver(new FocasDriverOptions var driver = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost)], Devices = [new FocasDeviceOptions(DeviceHost)],
Tags = [], RawTags = [],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
FixedTree = new FocasFixedTreeOptions FixedTree = new FocasFixedTreeOptions
{ {
@@ -120,7 +120,7 @@ public sealed class WireBackendTests
var driver = new FocasDriver(new FocasDriverOptions var driver = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost)], Devices = [new FocasDeviceOptions(DeviceHost)],
Tags = [], RawTags = [],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
FixedTree = new FocasFixedTreeOptions FixedTree = new FocasFixedTreeOptions
{ {
@@ -175,7 +175,7 @@ public sealed class WireBackendTests
var driver = new FocasDriver(new FocasDriverOptions var driver = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost)], Devices = [new FocasDeviceOptions(DeviceHost)],
Tags = [], RawTags = [],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
FixedTree = new FocasFixedTreeOptions FixedTree = new FocasFixedTreeOptions
{ {
@@ -242,7 +242,7 @@ public sealed class WireBackendTests
var driver = new FocasDriver(new FocasDriverOptions var driver = new FocasDriver(new FocasDriverOptions
{ {
Devices = [new FocasDeviceOptions(DeviceHost)], Devices = [new FocasDeviceOptions(DeviceHost)],
Tags = [], RawTags = [],
Probe = new FocasProbeOptions { Enabled = false }, Probe = new FocasProbeOptions { Enabled = false },
FixedTree = new FocasFixedTreeOptions FixedTree = new FocasFixedTreeOptions
{ {
@@ -27,7 +27,7 @@ public sealed class AddressingGrammarTests
{ {
var opts = new ModbusDriverOptions var opts = new ModbusDriverOptions
{ {
Host = _sim.Host, Port = _sim.Port, Tags = tags, Host = _sim.Host, Port = _sim.Port, RawTags = ModbusRawTags.Entries(tags),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
var drv = new ModbusDriver(opts, "addressing-e2e"); var drv = new ModbusDriver(opts, "addressing-e2e");
@@ -108,7 +108,7 @@ public sealed class AddressingGrammarTests
var t3 = new ModbusTagDefinition("T3", ModbusRegion.HoldingRegisters, 204, ModbusDataType.Int16); var t3 = new ModbusTagDefinition("T3", ModbusRegion.HoldingRegisters, 204, ModbusDataType.Int16);
var opts = new ModbusDriverOptions var opts = new ModbusDriverOptions
{ {
Host = _sim.Host, Port = _sim.Port, Tags = [t1, t2, t3], MaxReadGap = 5, Host = _sim.Host, Port = _sim.Port, RawTags = ModbusRawTags.Entries([t1, t2, t3]), MaxReadGap = 5,
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
var drv = new ModbusDriver(opts, "addressing-e2e"); var drv = new ModbusDriver(opts, "addressing-e2e");
@@ -31,15 +31,14 @@ public sealed class DL205BcdQuirkTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("DL205_Count_Bcd", new ModbusTagDefinition("DL205_Count_Bcd",
ModbusRegion.HoldingRegisters, Address: 1072, ModbusRegion.HoldingRegisters, Address: 1072,
DataType: ModbusDataType.Bcd16, Writable: false), DataType: ModbusDataType.Bcd16, Writable: false),
new ModbusTagDefinition("DL205_Count_Int16", new ModbusTagDefinition("DL205_Count_Int16",
ModbusRegion.HoldingRegisters, Address: 1072, ModbusRegion.HoldingRegisters, Address: 1072,
DataType: ModbusDataType.Int16, Writable: false), DataType: ModbusDataType.Int16, Writable: false),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-bcd"); await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-bcd");
@@ -106,7 +106,7 @@ public sealed class DL205CoilMappingTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = tags, RawTags = ModbusRawTags.Entries(tags),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
} }
@@ -36,12 +36,11 @@ public sealed class DL205ExceptionCodeTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("Unmapped", new ModbusTagDefinition("Unmapped",
ModbusRegion.HoldingRegisters, Address: 16383, ModbusRegion.HoldingRegisters, Address: 16383,
DataType: ModbusDataType.UInt16, Writable: false), DataType: ModbusDataType.UInt16, Writable: false),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-exc"); await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-exc");
@@ -33,8 +33,7 @@ public sealed class DL205FloatCdabQuirkTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("DL205_Float_CDAB", new ModbusTagDefinition("DL205_Float_CDAB",
ModbusRegion.HoldingRegisters, Address: 1056, ModbusRegion.HoldingRegisters, Address: 1056,
DataType: ModbusDataType.Float32, Writable: false, DataType: ModbusDataType.Float32, Writable: false,
@@ -44,7 +43,7 @@ public sealed class DL205FloatCdabQuirkTests(ModbusSimulatorFixture sim)
ModbusRegion.HoldingRegisters, Address: 1056, ModbusRegion.HoldingRegisters, Address: 1056,
DataType: ModbusDataType.Float32, Writable: false, DataType: ModbusDataType.Float32, Writable: false,
ByteOrder: ModbusByteOrder.BigEndian), ByteOrder: ModbusByteOrder.BigEndian),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-cdab"); await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-cdab");
@@ -37,15 +37,14 @@ public static class DL205Profile
Port = port, Port = port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition( new ModbusTagDefinition(
Name: "Smoke_HReg200", Name: "Smoke_HReg200",
Region: ModbusRegion.HoldingRegisters, Region: ModbusRegion.HoldingRegisters,
Address: SmokeHoldingRegister, Address: SmokeHoldingRegister,
DataType: ModbusDataType.Int16, DataType: ModbusDataType.Int16,
Writable: true), Writable: true),
], ]),
// Disable the background probe loop — integration tests drive reads explicitly and // Disable the background probe loop — integration tests drive reads explicitly and
// the probe would race with assertions. // the probe would race with assertions.
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
@@ -40,8 +40,7 @@ public sealed class DL205StringQuirkTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition( new ModbusTagDefinition(
Name: "DL205_Hello_Low", Name: "DL205_Hello_Low",
Region: ModbusRegion.HoldingRegisters, Region: ModbusRegion.HoldingRegisters,
@@ -60,7 +59,7 @@ public sealed class DL205StringQuirkTests(ModbusSimulatorFixture sim)
Writable: false, Writable: false,
StringLength: 5, StringLength: 5,
StringByteOrder: ModbusStringByteOrder.HighByteFirst), StringByteOrder: ModbusStringByteOrder.HighByteFirst),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-string"); await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-string");
@@ -36,12 +36,11 @@ public sealed class DL205VMemoryQuirkTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("DL205_V2000", new ModbusTagDefinition("DL205_V2000",
ModbusRegion.HoldingRegisters, Address: pdu, ModbusRegion.HoldingRegisters, Address: pdu,
DataType: ModbusDataType.UInt16, Writable: false), DataType: ModbusDataType.UInt16, Writable: false),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-vmem"); await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-vmem");
@@ -75,12 +74,11 @@ public sealed class DL205VMemoryQuirkTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("DL205_V40400", new ModbusTagDefinition("DL205_V40400",
ModbusRegion.HoldingRegisters, Address: pdu, ModbusRegion.HoldingRegisters, Address: pdu,
DataType: ModbusDataType.UInt16, Writable: false), DataType: ModbusDataType.UInt16, Writable: false),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-sysv"); await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-sysv");
@@ -66,7 +66,7 @@ public sealed class DL205XInputTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = tags, RawTags = ModbusRawTags.Entries(tags),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
} }
@@ -47,12 +47,11 @@ public sealed class ExceptionInjectionTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition(tagName, new ModbusTagDefinition(tagName,
ModbusRegion.HoldingRegisters, Address: (ushort)address, ModbusRegion.HoldingRegisters, Address: (ushort)address,
DataType: ModbusDataType.UInt16, Writable: false), DataType: ModbusDataType.UInt16, Writable: false),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(opts, driverInstanceId: "modbus-exc"); await using var driver = new ModbusDriver(opts, driverInstanceId: "modbus-exc");
@@ -112,12 +111,11 @@ public sealed class ExceptionInjectionTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition(tag, new ModbusTagDefinition(tag,
ModbusRegion.HoldingRegisters, Address: (ushort)address, ModbusRegion.HoldingRegisters, Address: (ushort)address,
DataType: ModbusDataType.UInt16, Writable: true), DataType: ModbusDataType.UInt16, Writable: true),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(opts, driverInstanceId: "modbus-exc-write"); await using var driver = new ModbusDriver(opts, driverInstanceId: "modbus-exc-write");
@@ -32,15 +32,14 @@ public static class MitsubishiProfile
Port = port, Port = port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition( new ModbusTagDefinition(
Name: "Smoke_HReg200", Name: "Smoke_HReg200",
Region: ModbusRegion.HoldingRegisters, Region: ModbusRegion.HoldingRegisters,
Address: SmokeHoldingRegister, Address: SmokeHoldingRegister,
DataType: ModbusDataType.Int16, DataType: ModbusDataType.Int16,
Writable: true), Writable: true),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
} }
@@ -176,7 +176,7 @@ public sealed class MitsubishiQuirkTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = tags, RawTags = ModbusRawTags.Entries(tags),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}, },
driverInstanceId: "melsec-quirk"); driverInstanceId: "melsec-quirk");
@@ -0,0 +1,29 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests;
/// <summary>
/// Test helper bridging the v2 authoring shape (a typed <see cref="ModbusTagDefinition"/>) to the v3
/// delivery shape (<see cref="RawTagEntry"/>). Under v3 the driver no longer takes pre-declared
/// definitions — the deploy artifact hands it authored raw tags (RawPath + TagConfig blob), and the
/// driver rebuilds the typed definitions via <see cref="ModbusTagDefinitionFactory"/>. These integration
/// profiles + quirk tests keep expressing their intent as typed definitions; this helper serialises each
/// back to its TagConfig blob (via <see cref="ModbusTagDefinitionFactory.ToTagConfig"/>) and packages it
/// as a <see cref="RawTagEntry"/> whose RawPath is the def's <c>Name</c> — so the round-trip through the
/// real mapper reproduces the same definition the test authored, keyed by the same name the
/// read/write/subscribe call sites use. Mirrors the unit-test project's helper of the same name.
/// </summary>
internal static class ModbusRawTags
{
/// <summary>Serialises one typed definition into its <see cref="RawTagEntry"/> (RawPath = def.Name).</summary>
/// <param name="def">The typed definition to convert.</param>
/// <returns>The equivalent <see cref="RawTagEntry"/>.</returns>
public static RawTagEntry Entry(ModbusTagDefinition def)
=> new(def.Name, ModbusTagDefinitionFactory.ToTagConfig(def), def.WriteIdempotent);
/// <summary>Serialises a sequence of typed definitions into <see cref="RawTagEntry"/> records.</summary>
/// <param name="defs">The typed definitions to convert.</param>
/// <returns>The equivalent raw-tag entries.</returns>
public static IReadOnlyList<RawTagEntry> Entries(IEnumerable<ModbusTagDefinition> defs)
=> [.. defs.Select(Entry)];
}
@@ -32,15 +32,14 @@ public static class S7_1500Profile
Port = port, Port = port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition( new ModbusTagDefinition(
Name: "Smoke_HReg200", Name: "Smoke_HReg200",
Region: ModbusRegion.HoldingRegisters, Region: ModbusRegion.HoldingRegisters,
Address: SmokeHoldingRegister, Address: SmokeHoldingRegister,
DataType: ModbusDataType.Int16, DataType: ModbusDataType.Int16,
Writable: true), Writable: true),
], ]),
// Disable the background probe loop — integration tests drive reads explicitly and // Disable the background probe loop — integration tests drive reads explicitly and
// the probe would race with assertions. // the probe would race with assertions.
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
@@ -33,8 +33,7 @@ public sealed class S7_ByteOrderTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("S7_Float_ABCD", new ModbusTagDefinition("S7_Float_ABCD",
ModbusRegion.HoldingRegisters, Address: 100, ModbusRegion.HoldingRegisters, Address: 100,
DataType: ModbusDataType.Float32, Writable: false, DataType: ModbusDataType.Float32, Writable: false,
@@ -45,7 +44,7 @@ public sealed class S7_ByteOrderTests(ModbusSimulatorFixture sim)
ModbusRegion.HoldingRegisters, Address: 100, ModbusRegion.HoldingRegisters, Address: 100,
DataType: ModbusDataType.Float32, Writable: false, DataType: ModbusDataType.Float32, Writable: false,
ByteOrder: ModbusByteOrder.WordSwap), ByteOrder: ModbusByteOrder.WordSwap),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "s7-float-abcd"); await using var driver = new ModbusDriver(options, driverInstanceId: "s7-float-abcd");
@@ -79,13 +78,12 @@ public sealed class S7_ByteOrderTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("S7_Int32_ABCD", new ModbusTagDefinition("S7_Int32_ABCD",
ModbusRegion.HoldingRegisters, Address: 300, ModbusRegion.HoldingRegisters, Address: 300,
DataType: ModbusDataType.Int32, Writable: false, DataType: ModbusDataType.Int32, Writable: false,
ByteOrder: ModbusByteOrder.BigEndian), ByteOrder: ModbusByteOrder.BigEndian),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "s7-int-abcd"); await using var driver = new ModbusDriver(options, driverInstanceId: "s7-int-abcd");
@@ -117,12 +115,11 @@ public sealed class S7_ByteOrderTests(ModbusSimulatorFixture sim)
Port = sim.Port, Port = sim.Port,
UnitId = 1, UnitId = 1,
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
Tags = RawTags = ModbusRawTags.Entries([
[
new ModbusTagDefinition("S7_Fingerprint", new ModbusTagDefinition("S7_Fingerprint",
ModbusRegion.HoldingRegisters, Address: 0, ModbusRegion.HoldingRegisters, Address: 0,
DataType: ModbusDataType.UInt16, Writable: false), DataType: ModbusDataType.UInt16, Writable: false),
], ]),
Probe = new ModbusProbeOptions { Enabled = false }, Probe = new ModbusProbeOptions { Enabled = false },
}; };
await using var driver = new ModbusDriver(options, driverInstanceId: "s7-fingerprint"); await using var driver = new ModbusDriver(options, driverInstanceId: "s7-fingerprint");
@@ -0,0 +1,29 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests;
/// <summary>
/// Test helper bridging the v2 authoring shape (a typed <see cref="S7TagDefinition"/>) to the v3
/// delivery shape (<see cref="RawTagEntry"/>). Under v3 the driver no longer takes pre-declared
/// definitions — the deploy artifact hands it authored raw tags (RawPath + TagConfig blob), and the
/// driver rebuilds the typed definitions via <see cref="S7TagDefinitionFactory"/>. These integration
/// profiles keep expressing their intent as typed definitions; this helper serialises each back to its
/// TagConfig blob (via <see cref="S7TagDefinitionFactory.ToTagConfig"/>) and packages it as a
/// <see cref="RawTagEntry"/> whose RawPath is the def's <c>Name</c> — so the round-trip through the real
/// mapper reproduces the same definition the profile authored, keyed by the same name the
/// read/write/subscribe call sites use. Mirrors the unit-test project's helper of the same name.
/// </summary>
internal static class S7RawTags
{
/// <summary>Serialises one typed definition into its <see cref="RawTagEntry"/> (RawPath = def.Name).</summary>
/// <param name="def">The typed definition to convert.</param>
/// <returns>The equivalent <see cref="RawTagEntry"/>.</returns>
public static RawTagEntry Entry(S7TagDefinition def)
=> new(def.Name, S7TagDefinitionFactory.ToTagConfig(def), def.WriteIdempotent);
/// <summary>Serialises a sequence of typed definitions into <see cref="RawTagEntry"/> records.</summary>
/// <param name="defs">The typed definitions to convert.</param>
/// <returns>The equivalent raw-tag entries.</returns>
public static IReadOnlyList<RawTagEntry> Entries(params S7TagDefinition[] defs)
=> [.. defs.Select(Entry)];
}
@@ -41,14 +41,12 @@ public static class S7_1500Profile
// gate, injecting flakiness that has nothing to do with the code // gate, injecting flakiness that has nothing to do with the code
// under test. // under test.
Probe = new S7ProbeOptions { Enabled = false }, Probe = new S7ProbeOptions { Enabled = false },
Tags = RawTags = S7RawTags.Entries(
[
new S7TagDefinition(ProbeTag, "DB1.DBW0", S7DataType.UInt16), new S7TagDefinition(ProbeTag, "DB1.DBW0", S7DataType.UInt16),
new S7TagDefinition(SmokeI16Tag, "DB1.DBW10", S7DataType.Int16), new S7TagDefinition(SmokeI16Tag, "DB1.DBW10", S7DataType.Int16),
new S7TagDefinition(SmokeI32Tag, "DB1.DBD20", S7DataType.Int32), new S7TagDefinition(SmokeI32Tag, "DB1.DBD20", S7DataType.Int32),
new S7TagDefinition(SmokeF32Tag, "DB1.DBD30", S7DataType.Float32), new S7TagDefinition(SmokeF32Tag, "DB1.DBD30", S7DataType.Float32),
new S7TagDefinition(SmokeBoolTag, "DB1.DBX50.3", S7DataType.Bool), new S7TagDefinition(SmokeBoolTag, "DB1.DBX50.3", S7DataType.Bool),
new S7TagDefinition(WriteScratchTag, "DB1.DBW100", S7DataType.UInt16), new S7TagDefinition(WriteScratchTag, "DB1.DBW100", S7DataType.UInt16)),
],
}; };
} }
@@ -246,14 +246,12 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
var options = new TwinCATDriverOptions var options = new TwinCATDriverOptions
{ {
Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")], Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")],
Tags = RawTags = TwinCATRawTags.Entries(
[
new TwinCATTagDefinition( new TwinCATTagDefinition(
Name: "Primitive", Name: "Primitive",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
SymbolPath: symbolPath, SymbolPath: symbolPath,
DataType: type), DataType: type)),
],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
Probe = new TwinCATProbeOptions { Enabled = false }, Probe = new TwinCATProbeOptions { Enabled = false },
}; };
@@ -307,8 +305,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
var options = new TwinCATDriverOptions var options = new TwinCATDriverOptions
{ {
Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")], Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")],
Tags = RawTags = TwinCATRawTags.Entries(
[
new TwinCATTagDefinition( new TwinCATTagDefinition(
Name: "WordBit3", Name: "WordBit3",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
@@ -318,8 +315,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
Name: "WordBit4", Name: "WordBit4",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
SymbolPath: "GVL_Primitives.vWord.4", SymbolPath: "GVL_Primitives.vWord.4",
DataType: TwinCATDataType.Bool), DataType: TwinCATDataType.Bool)),
],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
Probe = new TwinCATProbeOptions { Enabled = false }, Probe = new TwinCATProbeOptions { Enabled = false },
}; };
@@ -345,8 +341,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
var options = new TwinCATDriverOptions var options = new TwinCATDriverOptions
{ {
Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")], Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")],
Tags = RawTags = TwinCATRawTags.Entries(
[
new TwinCATTagDefinition( new TwinCATTagDefinition(
Name: "MotorTemp", Name: "MotorTemp",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
@@ -356,8 +351,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
Name: "MotorRunning", Name: "MotorRunning",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
SymbolPath: "GVL_Plant.Line1.Stations[1].Axes[1].Motor.Running", SymbolPath: "GVL_Plant.Line1.Stations[1].Axes[1].Motor.Running",
DataType: TwinCATDataType.Bool), DataType: TwinCATDataType.Bool)),
],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
Probe = new TwinCATProbeOptions { Enabled = false }, Probe = new TwinCATProbeOptions { Enabled = false },
}; };
@@ -386,8 +380,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
var options = new TwinCATDriverOptions var options = new TwinCATDriverOptions
{ {
Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")], Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")],
Tags = RawTags = TwinCATRawTags.Entries(
[
new TwinCATTagDefinition( new TwinCATTagDefinition(
Name: "NonexistentSymbol", Name: "NonexistentSymbol",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
@@ -398,8 +391,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
SymbolPath: "GVL_Fixture.nCounter", SymbolPath: "GVL_Fixture.nCounter",
DataType: TwinCATDataType.DInt, DataType: TwinCATDataType.DInt,
Writable: false), Writable: false)),
],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
Probe = new TwinCATProbeOptions { Enabled = false }, Probe = new TwinCATProbeOptions { Enabled = false },
}; };
@@ -442,8 +434,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
new TwinCATDeviceOptions(realHost, "Real-VM"), new TwinCATDeviceOptions(realHost, "Real-VM"),
new TwinCATDeviceOptions(ghostHost, "Ghost-VM"), new TwinCATDeviceOptions(ghostHost, "Ghost-VM"),
], ],
Tags = RawTags = TwinCATRawTags.Entries(
[
new TwinCATTagDefinition( new TwinCATTagDefinition(
Name: "RealCounter", Name: "RealCounter",
DeviceHostAddress: realHost, DeviceHostAddress: realHost,
@@ -453,8 +444,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
Name: "GhostCounter", Name: "GhostCounter",
DeviceHostAddress: ghostHost, DeviceHostAddress: ghostHost,
SymbolPath: "GVL_Fixture.nCounter", SymbolPath: "GVL_Fixture.nCounter",
DataType: TwinCATDataType.DInt), DataType: TwinCATDataType.DInt)),
],
// Shorter timeout so the bogus device fails fast rather than dragging the whole // Shorter timeout so the bogus device fails fast rather than dragging the whole
// test; the healthy read shouldn't be slowed down by a peer timeout. // test; the healthy read shouldn't be slowed down by a peer timeout.
Timeout = TimeSpan.FromSeconds(2), Timeout = TimeSpan.FromSeconds(2),
@@ -488,7 +478,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
var options = new TwinCATDriverOptions var options = new TwinCATDriverOptions
{ {
Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")], Devices = [new TwinCATDeviceOptions($"ads://{sim.TargetNetId}:{sim.AmsPort}", "XAR-VM")],
Tags = [], RawTags = [],
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
Probe = new TwinCATProbeOptions Probe = new TwinCATProbeOptions
{ {
@@ -532,7 +522,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
var options = new TwinCATDriverOptions var options = new TwinCATDriverOptions
{ {
Devices = baseOptions.Devices, Devices = baseOptions.Devices,
Tags = baseOptions.Tags, RawTags = baseOptions.RawTags,
UseNativeNotifications = baseOptions.UseNativeNotifications, UseNativeNotifications = baseOptions.UseNativeNotifications,
Timeout = baseOptions.Timeout, Timeout = baseOptions.Timeout,
Probe = baseOptions.Probe, Probe = baseOptions.Probe,
@@ -565,7 +555,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
HostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", HostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
DeviceName: "XAR-VM"), DeviceName: "XAR-VM"),
], ],
Tags = [ RawTags = TwinCATRawTags.Entries(
new TwinCATTagDefinition( new TwinCATTagDefinition(
Name: "Counter", Name: "Counter",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
@@ -582,8 +572,7 @@ public sealed class TwinCAT3SmokeTests(TwinCATXarFixture sim)
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}", DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
SymbolPath: "GVL_Arrays.aReal1D[5]", SymbolPath: "GVL_Arrays.aReal1D[5]",
DataType: TwinCATDataType.Real, DataType: TwinCATDataType.Real,
Writable: true), Writable: true)),
],
UseNativeNotifications = true, UseNativeNotifications = true,
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
// Disable the probe loop — the smoke tests run their own reads; a background // Disable the probe loop — the smoke tests run their own reads; a background
@@ -0,0 +1,32 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests;
/// <summary>
/// Test helper bridging the v2 authoring shape (a typed <see cref="TwinCATTagDefinition"/>) to the v3
/// delivery shape (<see cref="RawTagEntry"/>). Under v3 the driver no longer takes pre-declared
/// definitions — the deploy artifact hands it authored raw tags (RawPath + TagConfig blob + owning
/// DeviceName), and the driver rebuilds the typed definitions via <see cref="TwinCATTagDefinitionFactory"/>.
/// These integration smoke tests keep expressing their intent as typed definitions; this helper serialises
/// each back to its TagConfig blob (via <see cref="TwinCATTagDefinitionFactory.ToTagConfig"/>) and packages
/// it as a <see cref="RawTagEntry"/> whose RawPath is the def's <c>Name</c> and whose
/// <see cref="RawTagEntry.DeviceName"/> is the def's <c>DeviceHostAddress</c> — so the round-trip through
/// the real mapper + the driver's device resolution (which matches DeviceName against the configured
/// devices by host) reproduces the same definition + device routing the test authored, keyed by the same
/// name the read/write/subscribe call sites use. Mirrors the unit-test project's helper of the same name.
/// </summary>
internal static class TwinCATRawTags
{
/// <summary>Serialises one typed definition into its <see cref="RawTagEntry"/>
/// (RawPath = def.Name, DeviceName = def.DeviceHostAddress).</summary>
/// <param name="def">The typed definition to convert.</param>
/// <returns>The equivalent <see cref="RawTagEntry"/>.</returns>
public static RawTagEntry Entry(TwinCATTagDefinition def)
=> new(def.Name, TwinCATTagDefinitionFactory.ToTagConfig(def), def.WriteIdempotent, def.DeviceHostAddress);
/// <summary>Serialises typed definitions into <see cref="RawTagEntry"/> records.</summary>
/// <param name="defs">The typed definitions to convert (array, params, or a collection expression).</param>
/// <returns>The equivalent raw-tag entries.</returns>
public static IReadOnlyList<RawTagEntry> Entries(params TwinCATTagDefinition[] defs)
=> [.. defs.Select(Entry)];
}