Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasPmcCoalescerTests.cs
2026-04-25 20:02:10 -04:00

177 lines
5.6 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
[Trait("Category", "Unit")]
public sealed class FocasPmcCoalescerTests
{
[Fact]
public void Empty_input_yields_no_groups()
{
var groups = FocasPmcCoalescer.Plan(Array.Empty<PmcAddressRequest>());
groups.ShouldBeEmpty();
}
[Fact]
public void Contiguous_same_letter_same_path_coalesces_into_one_group()
{
// 100 contiguous R-letter byte reads at byte 0..99
var requests = new List<PmcAddressRequest>();
for (var i = 0; i < 100; i++)
requests.Add(new PmcAddressRequest("R", PathId: 1, ByteNumber: i, ByteWidth: 1, OriginalIndex: i));
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(1);
var g = groups[0];
g.Letter.ShouldBe("R");
g.PathId.ShouldBe(1);
g.StartByte.ShouldBe(0);
g.ByteCount.ShouldBe(100);
g.Members.Count.ShouldBe(100);
g.Members[42].Offset.ShouldBe(42);
g.Members[42].OriginalIndex.ShouldBe(42);
}
[Fact]
public void Range_cap_splits_oversized_runs_into_multiple_groups()
{
// 300 contiguous bytes — must split (cap = 256)
var requests = new List<PmcAddressRequest>();
for (var i = 0; i < 300; i++)
requests.Add(new PmcAddressRequest("R", 1, i, 1, i));
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(2);
groups[0].ByteCount.ShouldBe(FocasPmcCoalescer.MaxRangeBytes);
groups[0].StartByte.ShouldBe(0);
groups[1].StartByte.ShouldBe(FocasPmcCoalescer.MaxRangeBytes);
groups[1].ByteCount.ShouldBe(300 - FocasPmcCoalescer.MaxRangeBytes);
}
[Fact]
public void Gap_within_bridge_threshold_is_bridged()
{
// Two runs: R0..R9 then R20..R29 — gap = 10 bytes, within bridge cap of 16.
var requests = new List<PmcAddressRequest>
{
new("R", 1, 0, 1, 0),
new("R", 1, 9, 1, 1),
new("R", 1, 20, 1, 2),
new("R", 1, 29, 1, 3),
};
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(1);
groups[0].StartByte.ShouldBe(0);
groups[0].ByteCount.ShouldBe(30);
}
[Fact]
public void Gap_larger_than_bridge_threshold_splits()
{
// Two runs: R0 then R100 — gap of 99 bytes >> 16, must split.
var requests = new List<PmcAddressRequest>
{
new("R", 1, 0, 1, 0),
new("R", 1, 100, 1, 1),
};
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(2);
groups[0].StartByte.ShouldBe(0);
groups[1].StartByte.ShouldBe(100);
}
[Fact]
public void Different_letters_split_into_separate_groups()
{
var requests = new List<PmcAddressRequest>
{
new("R", 1, 0, 1, 0),
new("R", 1, 1, 1, 1),
new("D", 1, 0, 1, 2),
new("D", 1, 1, 1, 3),
};
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(2);
groups.ShouldContain(g => g.Letter == "R" && g.ByteCount == 2);
groups.ShouldContain(g => g.Letter == "D" && g.ByteCount == 2);
}
[Fact]
public void Different_paths_split_into_separate_groups()
{
var requests = new List<PmcAddressRequest>
{
new("R", 1, 0, 1, 0),
new("R", 1, 1, 1, 1),
new("R", 2, 0, 1, 2),
new("R", 2, 1, 1, 3),
};
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(2);
groups.ShouldContain(g => g.Letter == "R" && g.PathId == 1);
groups.ShouldContain(g => g.Letter == "R" && g.PathId == 2);
}
[Fact]
public void Wider_data_types_extend_range_correctly()
{
// R0 is Int32 (4 bytes covers R0..R3), R4 is Byte → contiguous, one group of 5 bytes.
var requests = new List<PmcAddressRequest>
{
new("R", 1, 0, ByteWidth: 4, 0),
new("R", 1, 4, ByteWidth: 1, 1),
};
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(1);
groups[0].ByteCount.ShouldBe(5);
groups[0].Members[0].ByteWidth.ShouldBe(4);
groups[0].Members[0].Offset.ShouldBe(0);
groups[0].Members[1].ByteWidth.ShouldBe(1);
groups[0].Members[1].Offset.ShouldBe(4);
}
[Fact]
public void Overlapping_requests_do_not_grow_range_beyond_their_union()
{
// R10 Int32 (R10..R13) + R12 Byte — overlap; range should still be 4 bytes from 10.
var requests = new List<PmcAddressRequest>
{
new("R", 1, 10, 4, 0),
new("R", 1, 12, 1, 1),
};
var groups = FocasPmcCoalescer.Plan(requests);
groups.Count.ShouldBe(1);
groups[0].StartByte.ShouldBe(10);
groups[0].ByteCount.ShouldBe(4);
groups[0].Members[1].Offset.ShouldBe(2); // member at byte 12, offset within range = 2
}
[Fact]
public void ByteWidth_helper_matches_data_type_sizes()
{
FocasPmcCoalescer.ByteWidth(FocasDataType.Bit).ShouldBe(1);
FocasPmcCoalescer.ByteWidth(FocasDataType.Byte).ShouldBe(1);
FocasPmcCoalescer.ByteWidth(FocasDataType.Int16).ShouldBe(2);
FocasPmcCoalescer.ByteWidth(FocasDataType.Int32).ShouldBe(4);
FocasPmcCoalescer.ByteWidth(FocasDataType.Float32).ShouldBe(4);
FocasPmcCoalescer.ByteWidth(FocasDataType.Float64).ShouldBe(8);
}
}