fix(mtconnect): deterministic concurrency test + DATA_SET -> BadNotSupported
The concurrency test asserted a post-Apply post-condition from inside the race window: dev1_pos is authored, so a reader beating the first Apply correctly observes BadWaitingForInitialData, which the test's two-member "legal" set omitted. Consistently red (10/10) rather than intermittent. Rewritten to assert only properties that hold at every instant — the whole (status, value, source timestamp) triple against the three legal snapshots, which is the real torn-snapshot check — plus a read counter guarding a vacuous pass and one deterministic closing Apply for an exact end state. The index is unchanged here; BadWaitingForInitialData is a designed state. Also codes structured (DATA_SET / TABLE) observations BadNotSupported now that the parser supplies MTConnectObservation.IsStructured, so a two-entry data set no longer surfaces as the Good string "12".
This commit is contained in:
@@ -48,20 +48,18 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
|
||||
/// would serialize the pump against every read for no semantic gain.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>KNOWN LIMITATION — <c>DATA_SET</c> / <c>TABLE</c> observations.</b> Their real content
|
||||
/// is <c><Entry key=…></c> child elements, but the parser reads an observation's value
|
||||
/// with <c>XElement.Value</c>, which concatenates all descendant text. Such an observation
|
||||
/// therefore reaches this index as a run-together string with the keys discarded, and —
|
||||
/// because the type inference correctly demotes <c>DATA_SET</c>/<c>TABLE</c> to
|
||||
/// <see cref="DriverDataType.String"/> — it coerces "successfully" into a <b>Good-coded
|
||||
/// snapshot carrying noise</b>. This index <b>cannot</b> detect the shape: neither
|
||||
/// <see cref="MTConnectObservation"/> nor <see cref="MTConnectTagDefinition"/> carries the
|
||||
/// DataItem's <c>representation</c>, and no value-shape heuristic can separate a
|
||||
/// concatenated entry list from a legitimate space-bearing EVENT (a <c>Message</c> reads
|
||||
/// "Coolant level low"). The discriminating signal — the observation element having child
|
||||
/// elements — exists only in <see cref="MTConnectStreamsParser"/>, so the fix belongs there
|
||||
/// (flag structured observations at parse time; this index then codes them
|
||||
/// <c>BadNotSupported</c> alongside the TIME_SERIES deferral below).
|
||||
/// <b>Shapes this build does not represent</b> are coded <c>BadNotSupported</c> with a null
|
||||
/// value rather than approximated: a <c>TIME_SERIES</c> vector (array materialization is
|
||||
/// deferred to P1.5) and a <c>DATA_SET</c> / <c>TABLE</c> observation. The latter carries its
|
||||
/// real content in <c><Entry key=…></c> child elements, which reach a text-valued
|
||||
/// observation concatenated with the keys discarded — a two-entry set reads as the single
|
||||
/// token <c>"12"</c> — and because the type inference correctly demotes those
|
||||
/// representations to <see cref="DriverDataType.String"/>, coercion would otherwise
|
||||
/// "succeed" into a Good-coded snapshot carrying noise. This index cannot detect that shape
|
||||
/// itself (no value heuristic can separate concatenated entries from a legitimate
|
||||
/// space-bearing EVENT such as a <c>Message</c> reading "Coolant level low"), so it relies on
|
||||
/// <see cref="MTConnectObservation.IsStructured"/>, which
|
||||
/// <see cref="MTConnectStreamsParser"/> sets while the XML is still XML.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal sealed class MTConnectObservationIndex
|
||||
@@ -257,6 +255,17 @@ internal sealed class MTConnectObservationIndex
|
||||
return new DataValueSnapshot(null, BadNoCommunication, sourceTimestamp, serverTimestamp);
|
||||
}
|
||||
|
||||
// DATA_SET / TABLE: the Agent carried the content in <Entry key="…"> child elements, so
|
||||
// Value is their concatenated text with the keys discarded (a two-entry set reads "12").
|
||||
// Publishing that as a Good string would be noise an operator would trust. Checked before
|
||||
// the tag lookup because it is a fact about the wire shape, true whether or not the data
|
||||
// item is authored — and after the sentinel, so an unavailable data set still reports the
|
||||
// truthful no-comms.
|
||||
if (observation.IsStructured)
|
||||
{
|
||||
return new DataValueSnapshot(null, BadNotSupported, sourceTimestamp, serverTimestamp);
|
||||
}
|
||||
|
||||
_tags.TryGetValue(observation.DataItemId, out var tag);
|
||||
|
||||
// TIME_SERIES vectors arrive as one space-separated string. P1 does not materialize OPC UA
|
||||
|
||||
Reference in New Issue
Block a user