chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)

Group all 69 projects into category subfolders under src/ and tests/ so the
Rider Solution Explorer mirrors the module structure. Folders: Core, Server,
Drivers (with a nested Driver CLIs subfolder), Client, Tooling.

- Move every project folder on disk with git mv (history preserved as renames).
- Recompute relative paths in 57 .csproj files: cross-category ProjectReferences,
  the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external
  mxaccessgw refs in Driver.Galaxy and its test project.
- Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders.
- Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL,
  integration, install).

Build green (0 errors); unit tests pass. Docs left for a separate pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-17 01:55:28 -04:00
parent 69f02fed7f
commit a25593a9c6
1044 changed files with 365 additions and 343 deletions

View File

@@ -0,0 +1,139 @@
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests;
[Trait("Category", "Unit")]
public sealed class DirectLogicAddressTests
{
[Theory]
[InlineData("V0", (ushort)0x0000)]
[InlineData("V1", (ushort)0x0001)]
[InlineData("V7", (ushort)0x0007)]
[InlineData("V10", (ushort)0x0008)]
[InlineData("V2000", (ushort)0x0400)] // canonical DL205/DL260 user-memory start
[InlineData("V7777", (ushort)0x0FFF)]
[InlineData("V10000", (ushort)0x1000)]
[InlineData("V17777", (ushort)0x1FFF)]
public void UserVMemoryToPdu_converts_octal_V_prefix(string v, ushort expected)
=> DirectLogicAddress.UserVMemoryToPdu(v).ShouldBe(expected);
[Theory]
[InlineData("0", (ushort)0)]
[InlineData("2000", (ushort)0x0400)]
[InlineData("v2000", (ushort)0x0400)] // lowercase v
[InlineData(" V2000 ", (ushort)0x0400)] // surrounding whitespace
public void UserVMemoryToPdu_accepts_bare_or_prefixed_or_padded(string v, ushort expected)
=> DirectLogicAddress.UserVMemoryToPdu(v).ShouldBe(expected);
[Theory]
[InlineData("V8")] // 8 is not a valid octal digit
[InlineData("V19")]
[InlineData("V2009")]
public void UserVMemoryToPdu_rejects_non_octal_digits(string v)
{
Should.Throw<ArgumentException>(() => DirectLogicAddress.UserVMemoryToPdu(v))
.Message.ShouldContain("octal");
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("V")]
public void UserVMemoryToPdu_rejects_empty_input(string? v)
=> Should.Throw<ArgumentException>(() => DirectLogicAddress.UserVMemoryToPdu(v!));
[Fact]
public void UserVMemoryToPdu_overflow_rejected()
{
// 200000 octal = 0x10000 — one past ushort range.
Should.Throw<OverflowException>(() => DirectLogicAddress.UserVMemoryToPdu("V200000"));
}
[Fact]
public void SystemVMemoryBasePdu_is_0x2100_for_V40400()
{
// V40400 on DL260 / H2-ECOM100 absolute mode → PDU 0x2100 (decimal 8448), NOT 0x4100
// which a naive octal-to-decimal of 40400 octal would give (= 16640).
DirectLogicAddress.SystemVMemoryBasePdu.ShouldBe((ushort)0x2100);
DirectLogicAddress.SystemVMemoryToPdu(0).ShouldBe((ushort)0x2100);
}
[Fact]
public void SystemVMemoryToPdu_offsets_within_bank()
{
DirectLogicAddress.SystemVMemoryToPdu(1).ShouldBe((ushort)0x2101);
DirectLogicAddress.SystemVMemoryToPdu(0x100).ShouldBe((ushort)0x2200);
}
[Fact]
public void SystemVMemoryToPdu_rejects_overflow()
{
// ushort wrap: 0xFFFF - 0x2100 = 0xDEFF; anything above should throw.
Should.NotThrow(() => DirectLogicAddress.SystemVMemoryToPdu(0xDEFF));
Should.Throw<OverflowException>(() => DirectLogicAddress.SystemVMemoryToPdu(0xDF00));
}
// --- Bit memory: Y-output, C-relay, X-input, SP-special ---
[Theory]
[InlineData("Y0", (ushort)2048)]
[InlineData("Y1", (ushort)2049)]
[InlineData("Y7", (ushort)2055)]
[InlineData("Y10", (ushort)2056)] // octal 10 = decimal 8
[InlineData("Y17", (ushort)2063)] // octal 17 = decimal 15
[InlineData("Y777", (ushort)2559)] // top of DL260 Y range per doc table
public void YOutputToCoil_adds_octal_offset_to_2048(string y, ushort expected)
=> DirectLogicAddress.YOutputToCoil(y).ShouldBe(expected);
[Theory]
[InlineData("C0", (ushort)3072)]
[InlineData("C1", (ushort)3073)]
[InlineData("C10", (ushort)3080)]
[InlineData("C1777", (ushort)4095)] // top of DL260 C range
public void CRelayToCoil_adds_octal_offset_to_3072(string c, ushort expected)
=> DirectLogicAddress.CRelayToCoil(c).ShouldBe(expected);
[Theory]
[InlineData("X0", (ushort)0)]
[InlineData("X17", (ushort)15)]
[InlineData("X777", (ushort)511)] // top of DL260 X range
public void XInputToDiscrete_adds_octal_offset_to_0(string x, ushort expected)
=> DirectLogicAddress.XInputToDiscrete(x).ShouldBe(expected);
[Theory]
[InlineData("SP0", (ushort)1024)]
[InlineData("SP7", (ushort)1031)]
[InlineData("sp0", (ushort)1024)] // lowercase prefix
[InlineData("SP777", (ushort)1535)]
public void SpecialToDiscrete_adds_octal_offset_to_1024(string sp, ushort expected)
=> DirectLogicAddress.SpecialToDiscrete(sp).ShouldBe(expected);
[Theory]
[InlineData("Y8")]
[InlineData("C9")]
[InlineData("X18")]
public void Bit_address_rejects_non_octal_digits(string bad)
=> Should.Throw<ArgumentException>(() =>
{
if (bad[0] == 'Y') DirectLogicAddress.YOutputToCoil(bad);
else if (bad[0] == 'C') DirectLogicAddress.CRelayToCoil(bad);
else DirectLogicAddress.XInputToDiscrete(bad);
});
[Theory]
[InlineData("Y")]
[InlineData("C")]
[InlineData("")]
public void Bit_address_rejects_empty(string bad)
=> Should.Throw<ArgumentException>(() => DirectLogicAddress.YOutputToCoil(bad));
[Fact]
public void YOutputToCoil_accepts_lowercase_prefix()
=> DirectLogicAddress.YOutputToCoil("y0").ShouldBe((ushort)2048);
[Fact]
public void CRelayToCoil_accepts_bare_octal_without_C_prefix()
=> DirectLogicAddress.CRelayToCoil("0").ShouldBe((ushort)3072);
}