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:
@@ -0,0 +1,123 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class FocasPmcBitRmwTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Fake client simulating PMC byte storage + exposing it as a sbyte so RMW callers can
|
||||
/// observe the read-modify-write round-trip. ReadAsync for a Bit with bitIndex surfaces
|
||||
/// the current bit; WriteAsync stores the full byte the driver issues.
|
||||
/// </summary>
|
||||
private sealed class PmcRmwFake : FakeFocasClient
|
||||
{
|
||||
public byte[] PmcBytes { get; } = new byte[1024];
|
||||
|
||||
public override Task<(object? value, uint status)> ReadAsync(
|
||||
FocasAddress address, FocasDataType type, CancellationToken ct)
|
||||
{
|
||||
if (address.Kind == FocasAreaKind.Pmc && type == FocasDataType.Byte)
|
||||
return Task.FromResult(((object?)(sbyte)PmcBytes[address.Number], FocasStatusMapper.Good));
|
||||
if (address.Kind == FocasAreaKind.Pmc && type == FocasDataType.Bit && address.BitIndex is int bit)
|
||||
return Task.FromResult(((object?)((PmcBytes[address.Number] & (1 << bit)) != 0), FocasStatusMapper.Good));
|
||||
return base.ReadAsync(address, type, ct);
|
||||
}
|
||||
|
||||
public override Task<uint> WriteAsync(
|
||||
FocasAddress address, FocasDataType type, object? value, CancellationToken ct)
|
||||
{
|
||||
// Driver writes the full byte after RMW (type==Byte with full byte value), OR a raw
|
||||
// bit write (type==Bit, bitIndex non-null) — depending on how the driver routes it.
|
||||
if (address.Kind == FocasAreaKind.Pmc && type == FocasDataType.Byte)
|
||||
{
|
||||
PmcBytes[address.Number] = (byte)Convert.ToSByte(value);
|
||||
return Task.FromResult(FocasStatusMapper.Good);
|
||||
}
|
||||
if (address.Kind == FocasAreaKind.Pmc && type == FocasDataType.Bit && address.BitIndex is int bit)
|
||||
{
|
||||
var current = PmcBytes[address.Number];
|
||||
PmcBytes[address.Number] = Convert.ToBoolean(value)
|
||||
? (byte)(current | (1 << bit))
|
||||
: (byte)(current & ~(1 << bit));
|
||||
return Task.FromResult(FocasStatusMapper.Good);
|
||||
}
|
||||
return base.WriteAsync(address, type, value, ct);
|
||||
}
|
||||
}
|
||||
|
||||
private static (FocasDriver drv, PmcRmwFake fake) NewDriver(params FocasTagDefinition[] tags)
|
||||
{
|
||||
var fake = new PmcRmwFake();
|
||||
var factory = new FakeFocasClientFactory { Customise = () => fake };
|
||||
var drv = new FocasDriver(new FocasDriverOptions
|
||||
{
|
||||
Devices = [new FocasDeviceOptions("focas://10.0.0.5:8193")],
|
||||
Tags = tags,
|
||||
Probe = new FocasProbeOptions { Enabled = false },
|
||||
}, "drv-1", factory);
|
||||
return (drv, fake);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bit_set_surfaces_as_Good_status_and_flips_bit()
|
||||
{
|
||||
var (drv, fake) = NewDriver(
|
||||
new FocasTagDefinition("Run", "focas://10.0.0.5:8193", "R100.3", FocasDataType.Bit));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
fake.PmcBytes[100] = 0b0000_0001;
|
||||
|
||||
var results = await drv.WriteAsync([new WriteRequest("Run", true)], CancellationToken.None);
|
||||
|
||||
results.Single().StatusCode.ShouldBe(FocasStatusMapper.Good);
|
||||
fake.PmcBytes[100].ShouldBe((byte)0b0000_1001);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bit_clear_preserves_other_bits()
|
||||
{
|
||||
var (drv, fake) = NewDriver(
|
||||
new FocasTagDefinition("Flag", "focas://10.0.0.5:8193", "R100.3", FocasDataType.Bit));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
fake.PmcBytes[100] = 0xFF;
|
||||
|
||||
await drv.WriteAsync([new WriteRequest("Flag", false)], CancellationToken.None);
|
||||
|
||||
fake.PmcBytes[100].ShouldBe((byte)0b1111_0111);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Subsequent_bit_sets_in_same_byte_compose_correctly()
|
||||
{
|
||||
var tags = Enumerable.Range(0, 8)
|
||||
.Select(b => new FocasTagDefinition($"Bit{b}", "focas://10.0.0.5:8193", $"R100.{b}", FocasDataType.Bit))
|
||||
.ToArray();
|
||||
var (drv, fake) = NewDriver(tags);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
fake.PmcBytes[100] = 0;
|
||||
|
||||
for (var b = 0; b < 8; b++)
|
||||
await drv.WriteAsync([new WriteRequest($"Bit{b}", true)], CancellationToken.None);
|
||||
|
||||
fake.PmcBytes[100].ShouldBe((byte)0xFF);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Bit_write_to_different_bytes_does_not_contend()
|
||||
{
|
||||
var tags = Enumerable.Range(0, 4)
|
||||
.Select(i => new FocasTagDefinition($"Bit{i}", "focas://10.0.0.5:8193", $"R{50 + i}.0", FocasDataType.Bit))
|
||||
.ToArray();
|
||||
var (drv, fake) = NewDriver(tags);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
await Task.WhenAll(Enumerable.Range(0, 4).Select(i =>
|
||||
drv.WriteAsync([new WriteRequest($"Bit{i}", true)], CancellationToken.None)));
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
fake.PmcBytes[50 + i].ShouldBe((byte)0x01);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user