Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/S7/S7_1500SmokeTests.cs
Joseph Doherty 10c724b5b6 Phase 3 PR 56 -- Siemens S7-1500 pymodbus profile + smoke integration test. Adds tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/s7_1500.json modelling the SIMATIC S7-1500 + MB_SERVER default deployment documented in docs/v2/s7.md: DB1.DBW0 = 0xABCD fingerprint marker (operators reserve this so clients can verify they're talking to the right DB), scratch HR range 200..209 for write-roundtrip tests mirroring dl205.json + standard.json, Float32 1.5f at HR[100..101] in ABCD word order (high word first -- OPPOSITE of DL260 CDAB), Int32 0x12345678 at HR[300..301] in ABCD. Also seeds a coil at bit-addr 400 (= cell 25 bit 0) and a discrete input at bit-addr 500 (= cell 31 bit 0) so future S7-specific tests for FC01/FC02 have stable markers. shared blocks=true to match the proven dl205.json pattern (pymodbus's bits/uint16 cells coexist cleanly when addresses don't collide). Write list references cells (0, 25, 100-101, 200-209, 300-301), not bit addresses -- pymodbus's write-range entries are cell-indexed, not bit-indexed. Adds tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/S7/ directory with S7_1500Profile.cs (mirrors DL205Profile pattern: SmokeHoldingRegister=200, SmokeHoldingValue=4321, BuildOptions tags + probe-disabled + 2s timeout) and S7_1500SmokeTests.cs (single fact S7_1500_roundtrip_write_then_read_of_holding_register that writes SmokeHoldingValue then reads it back, asserting both write status 0 and read status 0 + value equality). Gates on MODBUS_SIM_PROFILE=s7_1500 so the test skips cleanly against other profiles. csproj updated to copy S7/** to test output as PreserveNewest (pattern matching DL205/**). Pymodbus/serve.ps1 ValidateSet extended from {standard,dl205} to {standard,dl205,s7_1500,mitsubishi} -- mitsubishi.json lands in PR 58 but the validator slot is claimed now so the serve.ps1 diff is one line in this PR and zero lines in future PRs. Verified end-to-end: smoke test 1/1 passes against the running pymodbus s7_1500 profile (localhost:5020 FC06 write of 4321 at HR[200] + FC03 read back). 143/143 Modbus.Tests pass, no regression in driver code because this PR is purely test-asset. Per-quirk S7 integration tests (ABCD word order default, FC23 IllegalFunction, MB_SERVER STATUS 0x8383 behaviour, port-per-connection semantics) land in PR 57+.
2026-04-18 22:57:03 -04:00

55 lines
2.7 KiB
C#

using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.S7;
/// <summary>
/// End-to-end smoke against the S7-1500 <c>MB_SERVER</c> pymodbus profile (or a real
/// S7-1500 + MB_SERVER deployment when <c>MODBUS_SIM_ENDPOINT</c> points at one). Drives
/// the full <see cref="ModbusDriver"/> + real <see cref="ModbusTcpTransport"/> stack —
/// no fake transport. Success proves the driver initializes against the S7 simulator,
/// writes a known value, and reads it back with the correct status and value, which is
/// the baseline every S7-specific test (PR 57+) builds on.
/// </summary>
/// <remarks>
/// S7-specific quirk tests (MB_SERVER requires non-optimized DBs, ABCD word order
/// default, port-per-connection, FC23 Illegal Function, STOP-mode behaviour, etc.) land
/// as separate test classes in this directory as each quirk is validated in pymodbus.
/// Keep this smoke test deliberately narrow — filtering by device class
/// (<c>--filter DisplayName~S7</c>) should surface the quirk-specific failure mode when
/// something goes wrong, not a blanket smoke failure that could mean anything.
/// </remarks>
[Collection(ModbusSimulatorCollection.Name)]
[Trait("Category", "Integration")]
[Trait("Device", "S7")]
public sealed class S7_1500SmokeTests(ModbusSimulatorFixture sim)
{
[Fact]
public async Task S7_1500_roundtrip_write_then_read_of_holding_register()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "s7_1500",
StringComparison.OrdinalIgnoreCase))
{
Assert.Skip("MODBUS_SIM_PROFILE != s7_1500 — skipping (other profiles don't seed the S7 scratch range identically).");
}
var options = S7_1500Profile.BuildOptions(sim.Host, sim.Port);
await using var driver = new ModbusDriver(options, driverInstanceId: "s7-smoke");
await driver.InitializeAsync(driverConfigJson: "{}", TestContext.Current.CancellationToken);
var writeResults = await driver.WriteAsync(
[new(FullReference: "Smoke_HReg200", Value: (short)S7_1500Profile.SmokeHoldingValue)],
TestContext.Current.CancellationToken);
writeResults.Count.ShouldBe(1);
writeResults[0].StatusCode.ShouldBe(0u, "write must succeed against the S7-1500 MB_SERVER profile");
var readResults = await driver.ReadAsync(
["Smoke_HReg200"],
TestContext.Current.CancellationToken);
readResults.Count.ShouldBe(1);
readResults[0].StatusCode.ShouldBe(0u);
readResults[0].Value.ShouldBe((short)S7_1500Profile.SmokeHoldingValue);
}
}