Implemented qt port + e2e
This commit is contained in:
+370
-5
@@ -49,6 +49,13 @@ Scenario dict schema
|
||||
"oracle": "analytic" | "fed" | "both"
|
||||
"client_checks": subset of ["live","zoom","window","trigger"]
|
||||
"trig_signal": "src:sig" | None # which signal to trigger on
|
||||
"known_issue": str | None # if set, the scenario exercises a
|
||||
# documented, not-yet-fixed chain gap:
|
||||
# a FAIL is reclassified XFAIL (does not
|
||||
# break the baseline) and a PASS becomes
|
||||
# XPASS (the bug is unexpectedly fixed —
|
||||
# time to drop the marker). The string is
|
||||
# the human-readable reason.
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -75,6 +82,14 @@ NP_DTYPE = {
|
||||
"float32": "<f4", "float64": "<f8",
|
||||
}
|
||||
|
||||
# Wire constraints shared with the C++ UDPS layer. The UDPStreamer fragments the
|
||||
# serialised packet into chunks of at most MaxPayloadSize *payload* bytes and
|
||||
# prepends a UDPS_HEADER_SIZE-byte header, so the datagram it hands to sendto() is
|
||||
# MaxPayloadSize+UDPS_HEADER_SIZE bytes. That must not exceed the maximum UDP
|
||||
# payload or sendto() fails with EMSGSIZE.
|
||||
UDPS_HEADER_SIZE = 17 # bytes; mirrors Common/UDP/UDPSProtocol.h
|
||||
MAX_UDP_PAYLOAD = 65507 # 65535 - 20 (IP) - 8 (UDP)
|
||||
|
||||
FLOAT_TYPES = {"float32", "float64"}
|
||||
QUANT_TYPES = {"none", "uint8", "int8", "uint16", "int16"}
|
||||
QUANT_LEVELS = {"uint8": 255, "int8": 254, "uint16": 65535, "int16": 65534}
|
||||
@@ -91,6 +106,20 @@ ROW_DT = 1.0e-3 # seconds per producer cycle (row); 1 kHz producer
|
||||
LOOP_HZ = 1.0 / (NUM_ROWS * ROW_DT) # 5.0 Hz buffer fundamental
|
||||
|
||||
|
||||
def geometry(s):
|
||||
"""Resolve a scenario's producer geometry (per-scenario override or global).
|
||||
|
||||
Returns (row_dt, num_rows, producer_hz, loop_hz). Every data/config/validator
|
||||
consumer reads geometry through here so the override cannot drift apart from
|
||||
the seamless-loop fundamental used by the sine constraint.
|
||||
"""
|
||||
row_dt = s.get("row_dt") or ROW_DT
|
||||
num_rows = s.get("num_rows") or NUM_ROWS
|
||||
producer_hz = s.get("producer_hz") or int(round(1.0 / row_dt))
|
||||
loop_hz = 1.0 / (num_rows * row_dt)
|
||||
return row_dt, num_rows, producer_hz, loop_hz
|
||||
|
||||
|
||||
def _sig(name, type, elements=1, time_mode="PacketTime", time_signal=None,
|
||||
sampling_rate=None, quant="none", range_min=None, range_max=None,
|
||||
unit=None, formula="sine", freq=1.0, is_time=False):
|
||||
@@ -106,6 +135,7 @@ def _sig(name, type, elements=1, time_mode="PacketTime", time_signal=None,
|
||||
def validate_scenario(s):
|
||||
"""Return a list of validity error strings (empty == valid)."""
|
||||
errs = []
|
||||
row_dt, num_rows, _producer_hz, loop_hz = geometry(s)
|
||||
if s.get("network") not in ("unicast", "multicast"):
|
||||
errs.append("network must be unicast|multicast")
|
||||
if s.get("publishing") not in ("Strict", "Accumulate", "Decimate"):
|
||||
@@ -117,6 +147,12 @@ def validate_scenario(s):
|
||||
errs.append("Accumulate needs min_refresh_hz>0")
|
||||
if not s.get("sources"):
|
||||
errs.append("no sources")
|
||||
max_payload = s.get("max_payload")
|
||||
if max_payload is not None and max_payload + UDPS_HEADER_SIZE > MAX_UDP_PAYLOAD:
|
||||
errs.append(
|
||||
f"max_payload {max_payload} + {UDPS_HEADER_SIZE}B header exceeds the "
|
||||
f"{MAX_UDP_PAYLOAD}B UDP datagram limit (sendto EMSGSIZE); "
|
||||
f"cap at {MAX_UDP_PAYLOAD - UDPS_HEADER_SIZE}")
|
||||
for src in s.get("sources", []):
|
||||
if s["network"] == "multicast":
|
||||
if not src.get("multicast_group") or not src.get("data_port"):
|
||||
@@ -145,16 +181,77 @@ def validate_scenario(s):
|
||||
if sig["time_mode"] in ("FirstSample", "LastSample"):
|
||||
if not sig["sampling_rate"] or sig["sampling_rate"] <= 0:
|
||||
errs.append(f"{sig['name']}: First/LastSample needs sampling_rate>0")
|
||||
else:
|
||||
# The per-packet sample window must fit inside one producer
|
||||
# cycle, or successive packets' expanded timestamps overlap
|
||||
# and the hub's binary-search ring (which assumes a sorted
|
||||
# time axis) is corrupted — the s17/s18 failure class.
|
||||
window = (sig["elements"] - 1) / sig["sampling_rate"]
|
||||
if window > row_dt + 1e-12:
|
||||
errs.append(
|
||||
f"{sig['name']}: {sig['time_mode']} window "
|
||||
f"{window * 1e3:.4f} ms > row period {row_dt * 1e3:.4f} ms "
|
||||
f"(non-monotonic ring); raise producer rate or "
|
||||
f"sampling_rate, or lower elements")
|
||||
if sig["formula"] == "sine" and sig.get("freq"):
|
||||
ratio = sig["freq"] / LOOP_HZ
|
||||
ratio = sig["freq"] / loop_hz
|
||||
if abs(ratio - round(ratio)) > 1e-9 or round(ratio) < 1:
|
||||
errs.append(f"{sig['name']}: sine freq {sig['freq']} must be a "
|
||||
f"positive multiple of LOOP_HZ={LOOP_HZ} (seamless loop)")
|
||||
f"positive multiple of LOOP_HZ={loop_hz} (seamless loop)")
|
||||
return errs
|
||||
|
||||
|
||||
# ── Starter set (expanded to the full covering matrix in Task 8) ──────────────
|
||||
SCENARIOS = [
|
||||
# ── Curated covering matrix ───────────────────────────────────────────────────
|
||||
# The first three scenarios are referenced positionally by tests_py.py
|
||||
# (SCENARIOS[0..2]) and are kept verbatim. The remainder is built with the helpers
|
||||
# below, which auto-allocate unique WS/UDP/DATA ports so the matrix can grow
|
||||
# without manual bookkeeping. Coverage goal: every UDPStreamer option *value*
|
||||
# (all 10 types, scalar+array shapes, all four TimeModes, all five quant kinds,
|
||||
# the three publishing modes, unicast+multicast, fragmentation via small payload,
|
||||
# multi-source) appears at least once, plus deliberately chosen high-risk
|
||||
# interactions (decimate+quant+array, accumulate+fullarray, multicast+decimate,
|
||||
# fragmentation+decimate). Non-sine formulas (counter/ramp) are preferred for
|
||||
# pure type/shape coverage because only the fidelity oracle gates them; sine is
|
||||
# used where the shape metric should be tracked. MCAST_GROUP must have a route on
|
||||
# the test host or those scenarios report SKIP (the orchestrator probes it).
|
||||
import itertools
|
||||
|
||||
MCAST_GROUP = "239.0.7.7"
|
||||
_ws = itertools.count(8104)
|
||||
_udp = itertools.count(44616, 2)
|
||||
_data = itertools.count(45616, 2)
|
||||
|
||||
|
||||
def _src(sid, signals, multicast=False):
|
||||
return {
|
||||
"id": sid, "udp_port": next(_udp),
|
||||
"data_port": next(_data) if multicast else None,
|
||||
"multicast_group": MCAST_GROUP if multicast else None,
|
||||
"signals": signals,
|
||||
}
|
||||
|
||||
|
||||
def mk(sid, desc, sources, network="unicast", publishing="Strict",
|
||||
ratio=None, min_refresh_hz=None, max_payload=1400,
|
||||
oracle="analytic", checks=("live", "zoom"), trig=None,
|
||||
known_issue=None, row_dt=None, num_rows=None, producer_hz=None):
|
||||
# row_dt / num_rows / producer_hz override the global producer geometry for
|
||||
# this scenario only (default None == use the suite-wide NUM_ROWS / ROW_DT /
|
||||
# 1 kHz). They are kept together so the per-cycle wall gap (1/producer_hz),
|
||||
# the encoded time-step (row_dt) and the seamless-loop fundamental
|
||||
# (1/(num_rows*row_dt)) stay mutually consistent.
|
||||
return {
|
||||
"id": sid, "desc": desc, "network": network, "publishing": publishing,
|
||||
"ratio": ratio, "min_refresh_hz": min_refresh_hz,
|
||||
"max_payload": max_payload, "ws_port": next(_ws),
|
||||
"sources": sources, "oracle": oracle,
|
||||
"client_checks": list(checks), "trig_signal": trig,
|
||||
"known_issue": known_issue,
|
||||
"row_dt": row_dt, "num_rows": num_rows, "producer_hz": producer_hz,
|
||||
}
|
||||
|
||||
|
||||
_STARTERS = [
|
||||
{
|
||||
"id": "s01_scalar_uint32",
|
||||
"desc": "Single uint32 scalar counter, Strict unicast (type fidelity)",
|
||||
@@ -177,7 +274,8 @@ SCENARIOS = [
|
||||
"id": "s02_array_float32_fullarray",
|
||||
"desc": "100-elem float32 array, FullArray time mode, uint64 ns time array",
|
||||
"network": "unicast", "publishing": "Strict",
|
||||
"ratio": None, "min_refresh_hz": None, "max_payload": 65507,
|
||||
"ratio": None, "min_refresh_hz": None,
|
||||
"max_payload": MAX_UDP_PAYLOAD - UDPS_HEADER_SIZE,
|
||||
"ws_port": 8102,
|
||||
"sources": [{
|
||||
"id": "src", "udp_port": 44612, "data_port": None,
|
||||
@@ -214,11 +312,278 @@ SCENARIOS = [
|
||||
},
|
||||
]
|
||||
|
||||
# ── Type fidelity: one scalar per remaining MARTe type (fidelity-only) ────────
|
||||
_TYPES = [
|
||||
mk("s04_int8_scalar", "int8 scalar counter, type fidelity",
|
||||
[_src("src", [_sig("Cnt", "int8", 1, formula="counter")])]),
|
||||
mk("s05_uint8_scalar", "uint8 scalar counter, type fidelity",
|
||||
[_src("src", [_sig("Cnt", "uint8", 1, formula="counter")])]),
|
||||
mk("s06_int16_scalar", "int16 scalar ramp, type fidelity",
|
||||
[_src("src", [_sig("Ramp", "int16", 1, formula="ramp")])]),
|
||||
mk("s07_uint16_scalar", "uint16 scalar ramp, type fidelity",
|
||||
[_src("src", [_sig("Ramp", "uint16", 1, formula="ramp")])]),
|
||||
mk("s08_int32_scalar", "int32 scalar counter, type fidelity",
|
||||
[_src("src", [_sig("Cnt", "int32", 1, formula="counter")])]),
|
||||
mk("s09_int64_scalar", "int64 scalar counter, type fidelity",
|
||||
[_src("src", [_sig("Cnt", "int64", 1, formula="counter")])]),
|
||||
mk("s10_uint64_scalar", "uint64 scalar counter, type fidelity",
|
||||
[_src("src", [_sig("Cnt", "uint64", 1, formula="counter")])]),
|
||||
mk("s11_float64_scalar", "float64 scalar sine 5 Hz (double-precision path)",
|
||||
[_src("src", [_sig("Sine", "float64", 1, formula="sine", freq=5.0,
|
||||
unit="V")])],
|
||||
checks=("live", "zoom", "trigger"), trig="src:Sine"),
|
||||
]
|
||||
|
||||
# ── Array shapes (NumberOfElements) ──────────────────────────────────────────
|
||||
_ARRAYS = [
|
||||
mk("s12_f32_arr8", "float32 8-elem array sine 5 Hz",
|
||||
[_src("src", [_sig("Wave", "float32", 8, formula="sine", freq=5.0)])]),
|
||||
mk("s13_f32_arr32", "float32 32-elem array sine 10 Hz",
|
||||
[_src("src", [_sig("Wave", "float32", 32, formula="sine", freq=10.0)])]),
|
||||
mk("s14_f64_arr64", "float64 64-elem array ramp",
|
||||
[_src("src", [_sig("Ramp", "float64", 64, formula="ramp")])]),
|
||||
mk("s15_i16_arr16", "int16 16-elem array counter",
|
||||
[_src("src", [_sig("Cnt", "int16", 16, formula="counter")])]),
|
||||
mk("s16_f32_arr256", "float32 256-elem array sine 5 Hz (large frame)",
|
||||
[_src("src", [_sig("Wave", "float32", 256, formula="sine", freq=5.0)])],
|
||||
max_payload=MAX_UDP_PAYLOAD - UDPS_HEADER_SIZE),
|
||||
mk("s39_uint8_arr32", "uint8 32-elem array counter (wrap fidelity)",
|
||||
[_src("src", [_sig("Cnt", "uint8", 32, formula="counter")])]),
|
||||
mk("s40_int8_arr16", "int8 16-elem array counter (wrap fidelity)",
|
||||
[_src("src", [_sig("Cnt", "int8", 16, formula="counter")])]),
|
||||
]
|
||||
|
||||
# ── Time modes (each non-PacketTime mode needs a TimeSignal in the source) ────
|
||||
_TIMEMODES = [
|
||||
# sampling_rate = elements/ROW_DT (8/0.001 = 8000) so the per-cycle window
|
||||
# of 8 samples fills exactly one 1 kHz producer cycle. A smaller rate makes
|
||||
# each cycle's window wider than the inter-cycle gap, so successive windows
|
||||
# overlap and the published timestamps stop being monotonic — which corrupts
|
||||
# the hub's binary-search range/zoom queries (they assume a sorted ring).
|
||||
mk("s17_lastsample", "float32 8-elem LastSample, uint64 ns scalar anchor",
|
||||
[_src("src", [
|
||||
_sig("Tns", "uint64", 1, unit="ns", formula="time_ns", is_time=True),
|
||||
_sig("Data", "float32", 8, time_mode="LastSample",
|
||||
time_signal="Tns", sampling_rate=8000.0, formula="counter"),
|
||||
])]),
|
||||
mk("s18_firstsample", "float32 8-elem FirstSample, uint32 us scalar anchor",
|
||||
[_src("src", [
|
||||
_sig("Tus", "uint32", 1, unit="us", formula="time_us", is_time=True),
|
||||
_sig("Data", "float32", 8, time_mode="FirstSample",
|
||||
time_signal="Tus", sampling_rate=8000.0, formula="counter"),
|
||||
])]),
|
||||
mk("s19_fullarray_f64", "float64 50-elem FullArray sine 5 Hz, uint64 ns time",
|
||||
[_src("src", [
|
||||
_sig("TimeArr", "uint64", 50, unit="ns", formula="time_ns",
|
||||
is_time=True),
|
||||
_sig("Wave", "float64", 50, time_mode="FullArray",
|
||||
time_signal="TimeArr", formula="sine", freq=5.0, unit="V"),
|
||||
])],
|
||||
oracle="both"),
|
||||
mk("s43_fullarray_quant", "float32 16-elem FullArray quant uint16 sine 5 Hz",
|
||||
[_src("src", [
|
||||
_sig("TimeArr", "uint64", 16, unit="ns", formula="time_ns",
|
||||
is_time=True),
|
||||
_sig("Wave", "float32", 16, time_mode="FullArray",
|
||||
time_signal="TimeArr", quant="uint16", range_min=-1.0,
|
||||
range_max=1.0, formula="sine", freq=5.0, unit="V"),
|
||||
])]),
|
||||
]
|
||||
|
||||
# ── Quantization (each QuantizedType, on float signals) ──────────────────────
|
||||
_QUANT = [
|
||||
mk("s20_quant_uint8", "float32 scalar quant uint8 [-1,1] sine 5 Hz",
|
||||
[_src("src", [_sig("Sine", "float32", 1, quant="uint8", range_min=-1.0,
|
||||
range_max=1.0, formula="sine", freq=5.0)])]),
|
||||
mk("s21_quant_int8", "float32 scalar quant int8 [-10,10] sine 5 Hz",
|
||||
[_src("src", [_sig("Sine", "float32", 1, quant="int8", range_min=-10.0,
|
||||
range_max=10.0, formula="sine", freq=5.0)])]),
|
||||
mk("s22_quant_int16", "float32 scalar quant int16 [-100,100] ramp",
|
||||
[_src("src", [_sig("Ramp", "float32", 1, quant="int16", range_min=-100.0,
|
||||
range_max=100.0, formula="ramp")])]),
|
||||
mk("s23_quant_f64_arr", "float64 16-elem quant uint16 [-2,2] sine 5 Hz",
|
||||
[_src("src", [_sig("Wave", "float64", 16, quant="uint16", range_min=-2.0,
|
||||
range_max=2.0, formula="sine", freq=5.0)])]),
|
||||
]
|
||||
|
||||
# ── Publishing modes (Strict covered; Accumulate + Decimate here) ────────────
|
||||
_PUBLISH = [
|
||||
mk("s24_accumulate", "float32 scalar sine 5 Hz, Accumulate @50 Hz refresh",
|
||||
[_src("src", [_sig("Sine", "float32", 1, formula="sine", freq=5.0)])],
|
||||
publishing="Accumulate", min_refresh_hz=50.0),
|
||||
mk("s25_decimate4", "float32 scalar sine 5 Hz, Decimate ratio 4",
|
||||
[_src("src", [_sig("Sine", "float32", 1, formula="sine", freq=5.0)])],
|
||||
publishing="Decimate", ratio=4),
|
||||
mk("s26_decimate10_arr", "float32 8-elem counter, Decimate ratio 10",
|
||||
[_src("src", [_sig("Cnt", "float32", 8, formula="counter")])],
|
||||
publishing="Decimate", ratio=10),
|
||||
mk("s46_accumulate_arr",
|
||||
"Accumulate @200 Hz: accumulated scalar sine + 16-elem array passenger",
|
||||
[_src("src", [_sig("Sine", "float32", 1, formula="sine", freq=5.0),
|
||||
_sig("Wave", "float32", 16, formula="sine", freq=5.0)])],
|
||||
publishing="Accumulate", min_refresh_hz=200.0),
|
||||
mk("s45_decimate_multisig", "Decimate ratio 2 over a 2-signal source",
|
||||
[_src("src", [_sig("Sine", "float32", 1, formula="sine", freq=5.0),
|
||||
_sig("Cnt", "uint32", 1, formula="counter")])],
|
||||
publishing="Decimate", ratio=2),
|
||||
]
|
||||
|
||||
# ── Payload size / fragmentation (small MaxPayloadSize vs large array) ───────
|
||||
_PAYLOAD = [
|
||||
mk("s27_frag_f64_128", "float64 128-elem ramp, MaxPayload 512 (fragmented)",
|
||||
[_src("src", [_sig("Ramp", "float64", 128, formula="ramp")])],
|
||||
max_payload=512),
|
||||
mk("s28_frag_f32_100", "float32 100-elem sine 5 Hz, MaxPayload 256 (fragmented)",
|
||||
[_src("src", [_sig("Wave", "float32", 100, formula="sine", freq=5.0)])],
|
||||
max_payload=256),
|
||||
mk("s48_f64_arr_big_payload", "float64 100-elem ramp, MaxPayload 65490 (single frame)",
|
||||
[_src("src", [_sig("Ramp", "float64", 100, formula="ramp")])],
|
||||
max_payload=MAX_UDP_PAYLOAD - UDPS_HEADER_SIZE),
|
||||
]
|
||||
|
||||
# ── Multicast ────────────────────────────────────────────────────────────────
|
||||
_MCAST = [
|
||||
mk("s29_mcast_scalar", "multicast float32 scalar sine 5 Hz",
|
||||
[_src("src", [_sig("Sine", "float32", 1, formula="sine", freq=5.0)],
|
||||
multicast=True)],
|
||||
network="multicast"),
|
||||
mk("s30_mcast_arr_fullarray", "multicast float32 32-elem FullArray sine 5 Hz",
|
||||
[_src("src", [
|
||||
_sig("TimeArr", "uint64", 32, unit="ns", formula="time_ns",
|
||||
is_time=True),
|
||||
_sig("Wave", "float32", 32, time_mode="FullArray",
|
||||
time_signal="TimeArr", formula="sine", freq=5.0),
|
||||
], multicast=True)],
|
||||
network="multicast"),
|
||||
mk("s47_mcast_multisrc", "multicast, two sources (scalar each)",
|
||||
[_src("a", [_sig("Sine", "float32", 1, formula="sine", freq=5.0)],
|
||||
multicast=True),
|
||||
_src("b", [_sig("Cnt", "uint32", 1, formula="counter")],
|
||||
multicast=True)],
|
||||
network="multicast"),
|
||||
]
|
||||
|
||||
# ── Multiple sources (independent UDPStreamer blocks, one hub) ───────────────
|
||||
_MULTISRC = [
|
||||
mk("s31_two_src", "two unicast sources: float32 sine + uint32 counter",
|
||||
[_src("a", [_sig("Sine", "float32", 1, formula="sine", freq=5.0)]),
|
||||
_src("b", [_sig("Cnt", "uint32", 1, formula="counter")])]),
|
||||
mk("s32_three_src", "three unicast sources: int16 ramp / float64 sine / uint8 counter",
|
||||
[_src("a", [_sig("Ramp", "int16", 1, formula="ramp")]),
|
||||
_src("b", [_sig("Sine", "float64", 1, formula="sine", freq=5.0)]),
|
||||
_src("c", [_sig("Cnt", "uint8", 1, formula="counter")])]),
|
||||
]
|
||||
|
||||
# ── High-risk interactions ───────────────────────────────────────────────────
|
||||
_INTERACT = [
|
||||
mk("s33_dec_arr_quant", "Decimate 2 + 16-elem quant uint16 sine 5 Hz",
|
||||
[_src("src", [_sig("Wave", "float32", 16, quant="uint16", range_min=-1.0,
|
||||
range_max=1.0, formula="sine", freq=5.0)])],
|
||||
publishing="Decimate", ratio=2),
|
||||
mk("s34_acc_fullarray",
|
||||
"Accumulate @100 Hz: accumulated scalar + 32-elem FullArray sine passenger",
|
||||
[_src("src", [
|
||||
_sig("Sine", "float32", 1, formula="sine", freq=5.0),
|
||||
_sig("TimeArr", "uint64", 32, unit="ns", formula="time_ns",
|
||||
is_time=True),
|
||||
_sig("Wave", "float32", 32, time_mode="FullArray",
|
||||
time_signal="TimeArr", formula="sine", freq=5.0),
|
||||
])],
|
||||
publishing="Accumulate", min_refresh_hz=100.0),
|
||||
mk("s35_mcast_decimate", "multicast + Decimate ratio 5, float32 scalar sine 5 Hz",
|
||||
[_src("src", [_sig("Sine", "float32", 1, formula="sine", freq=5.0)],
|
||||
multicast=True)],
|
||||
network="multicast", publishing="Decimate", ratio=5),
|
||||
mk("s36_big_frag_dec", "float64 64-elem ramp, MaxPayload 256 + Decimate 4",
|
||||
[_src("src", [_sig("Ramp", "float64", 64, formula="ramp")])],
|
||||
max_payload=256, publishing="Decimate", ratio=4),
|
||||
mk("s49_mixed_quant_raw", "one source: quant uint8 sine + raw float32 sine",
|
||||
[_src("src", [
|
||||
_sig("Q", "float32", 1, quant="uint8", range_min=-1.0, range_max=1.0,
|
||||
formula="sine", freq=5.0),
|
||||
_sig("Raw", "float32", 1, formula="sine", freq=5.0),
|
||||
])]),
|
||||
]
|
||||
|
||||
# ── Trigger + client-check coverage (rising/falling/both swept by the client) ─
|
||||
_TRIGGER = [
|
||||
mk("s37_trig_ramp_i32", "trigger on int32 ramp scalar",
|
||||
[_src("src", [_sig("Ramp", "int32", 1, formula="ramp")])],
|
||||
checks=("live", "trigger"), trig="src:Ramp"),
|
||||
mk("s38_trig_f64_sine", "trigger on float64 sine 5 Hz scalar",
|
||||
[_src("src", [_sig("Sine", "float64", 1, formula="sine", freq=5.0)])],
|
||||
checks=("live", "zoom", "trigger"), trig="src:Sine"),
|
||||
mk("s44_window_check", "float32 sine 5 Hz scalar, window time-range check",
|
||||
[_src("src", [_sig("Sine", "float32", 1, formula="sine", freq=5.0)])],
|
||||
checks=("live", "zoom", "window")),
|
||||
mk("s50_trig_quant", "trigger on quantised uint16 sine 10 Hz",
|
||||
[_src("src", [_sig("Sine", "float32", 1, quant="uint16", range_min=-5.0,
|
||||
range_max=5.0, formula="sine", freq=10.0)])],
|
||||
checks=("live", "trigger"), trig="src:Sine"),
|
||||
]
|
||||
|
||||
# ── Misc scalar coverage (unit annotation, counter on float) ─────────────────
|
||||
_MISC = [
|
||||
mk("s41_f32_unit", "float32 scalar ramp with Unit=V",
|
||||
[_src("src", [_sig("Ramp", "float32", 1, formula="ramp", unit="V")])]),
|
||||
mk("s42_f64_counter", "float64 scalar counter (large integer values)",
|
||||
[_src("src", [_sig("Cnt", "float64", 1, formula="counter")])]),
|
||||
]
|
||||
|
||||
# ── High sample-rate / high-throughput stress ────────────────────────────────
|
||||
# Eight signals, each a 1000-element float32 array carrying 1 ms worth of 1 MSps
|
||||
# data, published once per 1 kHz producer cycle. Aggregate wire rate is
|
||||
# 8 x 1 MSps x 4 B = 32 MB/s. A single scalar uint64 ns anchor (FirstSample) is
|
||||
# shared by all eight data signals; the per-packet sample window
|
||||
# (1000 / 1 MHz = 1 ms) equals the 1 kHz inter-packet gap so the expanded
|
||||
# per-sample timestamps stay monotonic for the hub's binary-search ring. Ramp
|
||||
# formula keeps the gate fidelity-only (robust to the UDP loss expected at rate).
|
||||
#
|
||||
# Packet sizing — DATA payload is 8 (ns anchor) + 8 x 1000 x 4 = 32008 B, which
|
||||
# is deliberately kept under the 64 KB single-datagram ceiling. That ceiling is
|
||||
# the UDPSClient reassembly buffer (UDPSReassemblySlot::payload[65535]) plus its
|
||||
# offset overflow guard: a packet whose payload exceeds ~64 KB can never be
|
||||
# reassembled (fragment 1 lands past the buffer and is dropped), so high-rate
|
||||
# streaming must use single-datagram packets. max_payload is set to the maximum
|
||||
# valid value (UDP limit - 17 B header) so the 32 KB packet is sent unfragmented.
|
||||
_HIGHRATE = [
|
||||
mk("s51_8x1msps_100hz",
|
||||
"8x float32 10k-elem arrays @1 MSps, FirstSample, 100 Hz packets (~32 MB/s, "
|
||||
"320 KB/cycle fragmented)",
|
||||
[_src("src", [
|
||||
_sig("Tns", "uint64", 1, unit="ns", formula="time_ns", is_time=True),
|
||||
] + [
|
||||
_sig(f"S{i}", "float32", 10000, time_mode="FirstSample",
|
||||
time_signal="Tns", sampling_rate=1.0e6, formula="ramp")
|
||||
for i in range(8)
|
||||
])],
|
||||
max_payload=40000, row_dt=0.01, num_rows=50, producer_hz=100,
|
||||
checks=("live", "zoom")),
|
||||
]
|
||||
|
||||
SCENARIOS = (_STARTERS + _TYPES + _ARRAYS + _TIMEMODES + _QUANT + _PUBLISH +
|
||||
_PAYLOAD + _MCAST + _MULTISRC + _INTERACT + _TRIGGER + _MISC +
|
||||
_HIGHRATE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
ok = True
|
||||
seen_ids, seen_ws, seen_udp = set(), set(), set()
|
||||
for s in SCENARIOS:
|
||||
errs = validate_scenario(s)
|
||||
# uniqueness invariants the orchestrator relies on
|
||||
if s["id"] in seen_ids:
|
||||
errs.append("duplicate id")
|
||||
seen_ids.add(s["id"])
|
||||
if s["ws_port"] in seen_ws:
|
||||
errs.append(f"duplicate ws_port {s['ws_port']}")
|
||||
seen_ws.add(s["ws_port"])
|
||||
for src in s["sources"]:
|
||||
if src["udp_port"] in seen_udp:
|
||||
errs.append(f"duplicate udp_port {src['udp_port']}")
|
||||
seen_udp.add(src["udp_port"])
|
||||
print(f"{s['id']:32s} {'OK' if not errs else errs}")
|
||||
ok = ok and not errs
|
||||
print(f"\n{len(SCENARIOS)} scenarios, {'ALL VALID' if ok else 'INVALID PRESENT'}")
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
Reference in New Issue
Block a user