Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/AbLegacyIndirectAddressingTests.cs
T

132 lines
5.3 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
/// <summary>#646: indirect addresses (N7:[N7:0], N[N7:0]:5) resolve at read/write time.</summary>
[Trait("Category", "Unit")]
public sealed class AbLegacyIndirectAddressingTests
{
private const string Host = "ab://10.0.0.5/1,0";
private static async Task<(AbLegacyDriver drv, FakeAbLegacyTagFactory factory)> NewDriverAsync(
params AbLegacyTagDefinition[] tags)
{
var factory = new FakeAbLegacyTagFactory();
var drv = new AbLegacyDriver(new AbLegacyDriverOptions
{
Devices = [new AbLegacyDeviceOptions(Host, AbLegacyPlcFamily.Slc500)],
RawTags = AbLegacyRawTags.Entries(tags),
Probe = new AbLegacyProbeOptions { Enabled = false },
}, "drv-1", factory);
await drv.InitializeAsync("{}", CancellationToken.None);
return (drv, factory);
}
private static void Seed(FakeAbLegacyTagFactory f, params (string name, object value)[] values)
{
var map = values.ToDictionary(v => v.name, v => v.value, StringComparer.OrdinalIgnoreCase);
f.Customise = p => new FakeAbLegacyTag(p) { Value = map.TryGetValue(p.TagName, out var v) ? v : 0 };
}
[Fact]
public async Task Word_indirect_reads_index_then_composed_address()
{
var (drv, f) = await NewDriverAsync(new AbLegacyTagDefinition("Ind", Host, "N7:[N7:0]", AbLegacyDataType.Int));
Seed(f, ("N7:0", (short)5), ("N7:5", (short)42));
var r = (await drv.ReadAsync(["Ind"], CancellationToken.None)).Single();
r.StatusCode.ShouldBe(AbLegacyStatusMapper.Good);
r.Value.ShouldBe((short)42);
f.Tags["N7:0"].ReadCount.ShouldBe(1);
f.Tags["N7:5"].ReadCount.ShouldBe(1);
f.Tags.Keys.ShouldNotContain(k => k.Contains('['));
}
[Fact]
public async Task File_indirect_resolves_the_file_number()
{
var (drv, f) = await NewDriverAsync(new AbLegacyTagDefinition("Ind", Host, "N[N7:0]:5", AbLegacyDataType.Int));
Seed(f, ("N7:0", (short)9), ("N9:5", (short)77));
var r = (await drv.ReadAsync(["Ind"], CancellationToken.None)).Single();
r.StatusCode.ShouldBe(AbLegacyStatusMapper.Good);
r.Value.ShouldBe((short)77);
f.Tags.ContainsKey("N9:5").ShouldBeTrue();
}
[Fact]
public async Task Index_is_re_read_every_poll_never_cached()
{
var (drv, f) = await NewDriverAsync(new AbLegacyTagDefinition("Ind", Host, "N7:[N7:0]", AbLegacyDataType.Int));
Seed(f, ("N7:0", (short)5), ("N7:5", (short)42), ("N7:6", (short)43));
(await drv.ReadAsync(["Ind"], CancellationToken.None)).Single().Value.ShouldBe((short)42);
f.Tags["N7:0"].Value = (short)6;
(await drv.ReadAsync(["Ind"], CancellationToken.None)).Single().Value.ShouldBe((short)43);
f.Tags["N7:0"].ReadCount.ShouldBe(2);
// the stale resolution's handle is released when the index moves
f.Tags["N7:5"].Disposed.ShouldBeTrue();
}
[Theory]
[InlineData(-1)]
[InlineData(256)]
[InlineData(30000)]
public async Task Out_of_range_index_is_BadOutOfRange_without_health_impact(int index)
{
var (drv, f) = await NewDriverAsync(new AbLegacyTagDefinition("Ind", Host, "N7:[N7:0]", AbLegacyDataType.Int));
Seed(f, ("N7:0", (short)index));
var r = (await drv.ReadAsync(["Ind"], CancellationToken.None)).Single();
r.StatusCode.ShouldBe(AbLegacyStatusMapper.BadOutOfRange);
drv.GetHealth().State.ShouldNotBe(DriverState.Degraded);
f.Tags.Keys.ShouldNotContain(k => k.StartsWith("N7:") && k != "N7:0");
}
[Fact]
public async Task Inner_read_failure_propagates_its_status()
{
var (drv, f) = await NewDriverAsync(new AbLegacyTagDefinition("Ind", Host, "N7:[N7:0]", AbLegacyDataType.Int));
f.Customise = p => new FakeAbLegacyTag(p) { Value = (short)5, Status = p.TagName == "N7:0" ? -14 : 0 };
var r = (await drv.ReadAsync(["Ind"], CancellationToken.None)).Single();
r.StatusCode.ShouldNotBe(AbLegacyStatusMapper.Good);
r.StatusCode.ShouldNotBe(AbLegacyStatusMapper.BadOutOfRange);
f.Tags.ContainsKey("N7:5").ShouldBeFalse();
}
[Fact]
public async Task Write_resolves_index_then_writes_the_composed_address()
{
var (drv, f) = await NewDriverAsync(new AbLegacyTagDefinition("Ind", Host, "N7:[N7:0]", AbLegacyDataType.Int, Writable: true));
Seed(f, ("N7:0", (short)5));
var w = (await drv.WriteAsync([new WriteRequest("Ind", 99)], CancellationToken.None)).Single();
w.StatusCode.ShouldBe(AbLegacyStatusMapper.Good);
f.Tags["N7:5"].WriteCount.ShouldBe(1);
f.Tags["N7:5"].Value.ShouldBe((short)99);
f.Tags["N7:0"].WriteCount.ShouldBe(0);
}
[Fact]
public async Task Write_with_bad_index_is_BadOutOfRange_and_writes_nothing()
{
var (drv, f) = await NewDriverAsync(new AbLegacyTagDefinition("Ind", Host, "N7:[N7:0]", AbLegacyDataType.Int, Writable: true));
Seed(f, ("N7:0", (short)-3));
var w = (await drv.WriteAsync([new WriteRequest("Ind", 1)], CancellationToken.None)).Single();
w.StatusCode.ShouldBe(AbLegacyStatusMapper.BadOutOfRange);
f.Tags.Values.ShouldAllBe(t => t.WriteCount == 0);
}
}