140 lines
6.3 KiB
C#
140 lines
6.3 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.S7.Szl;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.S7_1500;
|
|
|
|
/// <summary>
|
|
/// PR-S7-E1 / #302 — integration scaffold for SZL (System Status List) reads against
|
|
/// a real S7-1500 CPU. snap7 (the simulator that backs <see cref="Snap7ServerFixture"/>)
|
|
/// does not implement SZL — every <c>@System.*</c> read returns <c>BadNotSupported</c>
|
|
/// against the simulator — so the asserts here verify the not-supported semantics
|
|
/// when running against snap7, and the live-firmware tests are gated on a real-PLC
|
|
/// env-var (<c>S7_LIVE_HOST</c>) the same way other PR-S7-* live tests are.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Why scaffolding rather than full live verification?</b> The plan section
|
|
/// calls for a "live-firmware test against dev-box S7-1500"; that's a hardware-
|
|
/// gated test and it is parked behind an env-var so the CI pipeline + a fresh
|
|
/// developer checkout both stay green. The not-supported assertion against snap7
|
|
/// is the "always-runs" piece — proves the dispatch path lights up + surfaces
|
|
/// the right StatusCode without a live CPU.
|
|
/// </para>
|
|
/// </remarks>
|
|
[Collection(Snap7ServerCollection.Name)]
|
|
[Trait("Category", "Integration")]
|
|
[Trait("Device", "S7_1500")]
|
|
public sealed class S7_1500SzlTests(Snap7ServerFixture sim)
|
|
{
|
|
/// <summary>OPC UA <c>BadNotSupported</c> status code — same constant the driver uses.</summary>
|
|
private const uint StatusBadNotSupported = 0x803D0000u;
|
|
|
|
/// <summary>
|
|
/// Re-build the simulator profile with <c>ExposeSystemTags = true</c>. <see cref="S7DriverOptions"/>
|
|
/// is a class (not a record), so we copy fields manually rather than using a <c>with</c> expression.
|
|
/// </summary>
|
|
private static S7DriverOptions BuildOptionsWithSystemTags(string host, int port, int diagBufferDepth = 10)
|
|
{
|
|
var baseOpts = S7_1500Profile.BuildOptions(host, port);
|
|
return new S7DriverOptions
|
|
{
|
|
Host = baseOpts.Host,
|
|
Port = baseOpts.Port,
|
|
CpuType = baseOpts.CpuType,
|
|
Rack = baseOpts.Rack,
|
|
Slot = baseOpts.Slot,
|
|
Timeout = baseOpts.Timeout,
|
|
Probe = baseOpts.Probe,
|
|
Tags = baseOpts.Tags,
|
|
ExposeSystemTags = true,
|
|
DiagBufferDepth = diagBufferDepth,
|
|
};
|
|
}
|
|
|
|
[Fact]
|
|
public async Task System_CpuType_returns_BadNotSupported_against_snap7_simulator()
|
|
{
|
|
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
|
|
|
|
var options = BuildOptionsWithSystemTags(sim.Host, sim.Port);
|
|
await using var drv = new S7Driver(options, driverInstanceId: "s7-szl-cputype");
|
|
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
|
|
|
var snaps = await drv.ReadAsync(["@System.CpuType"], TestContext.Current.CancellationToken);
|
|
snaps.Count.ShouldBe(1);
|
|
// snap7 doesn't implement SZL; the production S7NetSzlReader returns null too
|
|
// (S7netplus 0.20 has no public ReadSzlAsync surface). Both paths converge on
|
|
// BadNotSupported.
|
|
snaps[0].StatusCode.ShouldBe(StatusBadNotSupported);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DiscoverAsync_emits_diagnostics_folder_against_real_simulator_when_opted_in()
|
|
{
|
|
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
|
|
|
|
var options = BuildOptionsWithSystemTags(sim.Host, sim.Port, diagBufferDepth: 5);
|
|
await using var drv = new S7Driver(options, driverInstanceId: "s7-szl-discover");
|
|
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
|
|
|
var builder = new TestAddressSpaceBuilder();
|
|
await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken);
|
|
|
|
builder.Folders.ShouldContain(S7SystemTags.FolderName);
|
|
builder.Folders.ShouldContain("DiagBuffer");
|
|
// 6 scalars + 5 buffer entries = 11 system variables.
|
|
builder.Variables
|
|
.Count(v => v.FullName.StartsWith(S7SystemTags.Prefix, StringComparison.Ordinal))
|
|
.ShouldBe(11);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Live-firmware gate: when the env-var <c>S7_LIVE_HOST</c> points at a real
|
|
/// S7-1500, this test runs end-to-end against the live CPU and expects a
|
|
/// non-empty CpuType / Firmware / OrderNo. Hardware-gated; CI skips it.
|
|
/// Currently parked at <see cref="Assert.Skip"/> because S7netplus 0.20 doesn't
|
|
/// expose a public SZL surface — even against a real CPU the production
|
|
/// <see cref="S7NetSzlReader"/> returns null. Flip this back on once the
|
|
/// S7netplus PR for ReadSzlAsync lands or we ship a raw-PDU helper.
|
|
/// </summary>
|
|
[Fact(Skip = "Requires real S7-1500 + S7netplus public ReadSzlAsync surface; see PR-S7-E1 docs.")]
|
|
public Task System_CpuType_against_live_S7_1500_returns_non_empty_string()
|
|
{
|
|
// var liveHost = Environment.GetEnvironmentVariable("S7_LIVE_HOST");
|
|
// if (string.IsNullOrWhiteSpace(liveHost))
|
|
// Assert.Skip("S7_LIVE_HOST not set — skipping live-firmware SZL test");
|
|
// var options = new S7DriverOptions { Host = liveHost, ExposeSystemTags = true, ... };
|
|
// ...
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private sealed class TestAddressSpaceBuilder : Core.Abstractions.IAddressSpaceBuilder
|
|
{
|
|
public List<string> Folders { get; } = [];
|
|
public List<Core.Abstractions.DriverAttributeInfo> Variables { get; } = [];
|
|
|
|
public Core.Abstractions.IAddressSpaceBuilder Folder(string browseName, string displayName)
|
|
{
|
|
Folders.Add(browseName);
|
|
return this;
|
|
}
|
|
|
|
public Core.Abstractions.IVariableHandle Variable(
|
|
string browseName, string displayName, Core.Abstractions.DriverAttributeInfo info)
|
|
{
|
|
Variables.Add(info);
|
|
return new StubHandle();
|
|
}
|
|
|
|
public void AddProperty(string browseName, Core.Abstractions.DriverDataType dataType, object? value) { }
|
|
|
|
private sealed class StubHandle : Core.Abstractions.IVariableHandle
|
|
{
|
|
public string FullReference => "stub";
|
|
public Core.Abstractions.IAlarmConditionSink MarkAsAlarmCondition(Core.Abstractions.AlarmConditionInfo info)
|
|
=> throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|