Auto: twincat-2.1 — ADS Sum-read / Sum-write

Closes #310
This commit is contained in:
Joseph Doherty
2026-04-25 21:43:32 -04:00
parent fa2fbb404d
commit 931049b5a7
11 changed files with 875 additions and 26 deletions

View File

@@ -72,6 +72,64 @@ internal class FakeTwinCATClient : ITwinCATClient
return Task.FromResult(ProbeResult);
}
// ---- Bulk surface (PR 2.1: SumSymbolRead / SumSymbolWrite) ----
public List<IReadOnlyList<TwinCATBulkReadItem>> BulkReadInvocations { get; } = new();
public List<IReadOnlyList<TwinCATBulkWriteItem>> BulkWriteInvocations { get; } = new();
public bool ThrowOnBulkRead { get; set; }
public bool ThrowOnBulkWrite { get; set; }
/// <summary>Per-symbol read failure injection — overlay onto <see cref="ReadStatuses"/>.</summary>
public Dictionary<string, uint> BulkReadStatuses { get; } = new(StringComparer.OrdinalIgnoreCase);
public virtual Task<IReadOnlyList<(object? value, uint status)>> ReadValuesAsync(
IReadOnlyList<TwinCATBulkReadItem> reads, CancellationToken ct)
{
// ThrowOnRead applies to both per-tag + bulk paths so legacy tests that toggled
// ThrowOnRead before bulk existed still surface BadCommunicationError correctly.
if (ThrowOnRead || ThrowOnBulkRead) throw Exception ?? new InvalidOperationException();
BulkReadInvocations.Add(reads);
// Preserve request order — the production sum-read returns one entry per request slot
// even on partial failure; so does this fake.
var output = new (object? value, uint status)[reads.Count];
for (var i = 0; i < reads.Count; i++)
{
var r = reads[i];
ReadLog.Add((r.SymbolPath, r.Type, null, null));
if (BulkReadStatuses.TryGetValue(r.SymbolPath, out var bulkStatus))
{
output[i] = (null, bulkStatus);
continue;
}
if (ReadStatuses.TryGetValue(r.SymbolPath, out var status) && status != TwinCATStatusMapper.Good)
{
output[i] = (null, status);
continue;
}
var value = Values.TryGetValue(r.SymbolPath, out var v) ? v : null;
output[i] = (value, TwinCATStatusMapper.Good);
}
return Task.FromResult<IReadOnlyList<(object? value, uint status)>>(output);
}
public virtual Task<IReadOnlyList<uint>> WriteValuesAsync(
IReadOnlyList<TwinCATBulkWriteItem> writes, CancellationToken ct)
{
if (ThrowOnWrite || ThrowOnBulkWrite) throw Exception ?? new InvalidOperationException();
BulkWriteInvocations.Add(writes);
var output = new uint[writes.Count];
for (var i = 0; i < writes.Count; i++)
{
var w = writes[i];
WriteLog.Add((w.SymbolPath, w.Type, null, w.Value));
Values[w.SymbolPath] = w.Value;
output[i] = WriteStatuses.TryGetValue(w.SymbolPath, out var s) ? s : TwinCATStatusMapper.Good;
}
return Task.FromResult<IReadOnlyList<uint>>(output);
}
public virtual void Dispose()
{
DisposeCount++;