From 1d78b4596378c279746a0f2c84019e4a65af3a13 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Wed, 1 Jul 2026 19:06:30 +0200 Subject: [PATCH] feat(e2e): dispatch direct/recorder scenario kinds in run_e2e.sh Extends the scenario-list builder and main loop to actually execute s52_direct_unicast/s53_direct_multicast (self-contained single-MARTeApp FileReader->UDPStreamer->UDPStreamerClient->FileWriter round trip) and s54_recorder (StreamHub BinaryRecorder round trip, ported from run_recorder_e2e.sh) alongside the existing chain scenarios, and tags each results.json record with its scenario kind. Co-Authored-By: Claude Opus 4.6 --- Test/E2E/suite/run_e2e.sh | 126 +++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 10 deletions(-) diff --git a/Test/E2E/suite/run_e2e.sh b/Test/E2E/suite/run_e2e.sh index 9add131..1267768 100755 --- a/Test/E2E/suite/run_e2e.sh +++ b/Test/E2E/suite/run_e2e.sh @@ -45,6 +45,7 @@ source "${ENV_SCRIPT}" COMP="${MARTe2_Components_DIR}/Build/${TARGET}/Components" export LD_LIBRARY_PATH="\ ${BUILD_DIR}/Components/DataSources/UDPStreamer:\ +${BUILD_DIR}/Components/DataSources/UDPStreamerClient:\ ${BUILD_DIR}/Components/Interfaces/UDPStream:\ ${MARTe2_DIR}/Build/${TARGET}/Core:\ ${COMP}/DataSources/LinuxTimer:\ @@ -76,6 +77,7 @@ if [ "${SKIP_BUILD}" -eq 0 ]; then echo "── Building components ──" make -C "${REPO_ROOT}/Source/Components/Interfaces/UDPStream" -f Makefile.gcc TARGET="${TARGET}" 2>&1 | tail -1 make -C "${REPO_ROOT}/Source/Components/DataSources/UDPStreamer" -f Makefile.gcc TARGET="${TARGET}" 2>&1 | tail -1 + make -C "${REPO_ROOT}/Source/Components/DataSources/UDPStreamerClient" -f Makefile.gcc TARGET="${TARGET}" 2>&1 | tail -1 make -C "${REPO_ROOT}/Source/Applications/StreamHub" -f Makefile.gcc TARGET="${TARGET}" 2>&1 | tail -1 fi if [ ! -x "${CLIENT}" ]; then @@ -85,7 +87,10 @@ fi [ -x "${MARTE_APP}" ] || { echo "ERROR: MARTeApp.ex not found at ${MARTE_APP}" >&2; exit 1; } [ -x "${STREAMHUB_EX}" ] || { echo "ERROR: StreamHub.ex not found" >&2; exit 1; } -# ── Scenario list (id|ws_port|udp_port0|network|oracle|trig|checks) ────────── +# ── Scenario list (kind|id|f2|f3|f4|f5|f6|f7) ──────────────────────────────── +# chain: kind|id|ws_port|udp_port0|network|oracle|trig|checks +# direct: kind|id|cfg|-|-|-|-|- +# recorder: kind|id|marte_cfg|hub_cfg|-|-|-|- LIST="$(${PY} - "${ONLY}" <<'PY' import sys, os sys.path.insert(0, os.path.dirname(os.path.abspath("Test/E2E/suite/scenarios.py"))) @@ -95,12 +100,18 @@ only = sys.argv[1] if len(sys.argv) > 1 else "" for s in S.SCENARIOS: if only and s["id"] != only: continue - trig = s.get("trig_signal") or "" - checks = ",".join(s.get("client_checks", [])) - if not trig: - checks = ",".join(c for c in s.get("client_checks", []) if c != "trigger") - print("|".join([s["id"], str(s["ws_port"]), str(s["sources"][0]["udp_port"]), - s["network"], s["oracle"], trig, checks])) + kind = s["kind"] + if kind == "chain": + trig = s.get("trig_signal") or "" + checks = ",".join(s.get("client_checks", [])) + if not trig: + checks = ",".join(c for c in s.get("client_checks", []) if c != "trigger") + print("|".join([kind, s["id"], str(s["ws_port"]), str(s["sources"][0]["udp_port"]), + s["network"], s["oracle"], trig, checks])) + elif kind == "direct": + print("|".join([kind, s["id"], s["cfg"], "", "", "", "", ""])) + elif kind == "recorder": + print("|".join([kind, s["id"], s["marte_cfg"], s["hub_cfg"], "", "", "", ""])) PY )" @@ -116,12 +127,16 @@ cleanup() { } trap cleanup EXIT -while IFS='|' read -r ID WSPORT UDPPORT NET ORACLE TRIG CHECKS; do +while IFS='|' read -r KIND ID F2 F3 F4 F5 F6 F7; do [ -z "${ID}" ] && continue SCEN_IDS="${SCEN_IDS} ${ID}" + : > "${WORK}/status_${ID}.txt" + + case "${KIND}" in + chain) + WSPORT="${F2}"; UDPPORT="${F3}"; NET="${F4}"; ORACLE="${F5}"; TRIG="${F6}"; CHECKS="${F7}" echo "" echo "══ scenario ${ID} (net=${NET} oracle=${ORACLE} ws=${WSPORT}) ══" - : > "${WORK}/status_${ID}.txt" # multicast route probe if [ "${NET}" = "multicast" ]; then @@ -187,6 +202,95 @@ while IFS='|' read -r ID WSPORT UDPPORT NET ORACLE TRIG CHECKS; do echo "FAIL" > "${WORK}/status_${ID}.txt" fi ${PY} "${SCRIPT_DIR}/plots.py" --scenario "${ID}" --dir "${WORK}" >/dev/null 2>&1 || true + ;; + + direct) + CFG="${F2}" + echo "" + echo "══ scenario ${ID} (kind=direct cfg=${CFG}) ══" + + DS_DIR="${SCRIPT_DIR}/../datasources" + INPUT_BIN="/tmp/udpstreamer_test_input.bin" + # The FileWriterDS output path is baked into the cfg (not per-scenario); + # the last `Filename = "..."` in the file is always the writer (the + # FileReaderDS input path is always listed first in these cfgs). + OUTPUT_BIN="$(grep -oP 'Filename\s*=\s*"\K[^"]+' "${REPO_ROOT}/${CFG}" | tail -1)" + rm -f "${INPUT_BIN}" "${OUTPUT_BIN}" + (cd "${DS_DIR}" && ${PY} -c "import gen_test_data; gen_test_data.generate()") > "${OUT_DIR}/gen_${ID}.log" 2>&1 + + APP_LOG="${OUT_DIR}/marte_${ID}.log" + timeout 8 "${MARTE_APP}" -l RealTimeLoader -f "${REPO_ROOT}/${CFG}" -s Running > "${APP_LOG}" 2>&1 & + APP_PID=$! + sleep 5 + cleanup + + MSG="$(${PY} -c " +import sys +sys.path.insert(0, '${DS_DIR}') +import validate_binary as V +ok, msg, details = V.validate('${INPUT_BIN}', '${OUTPUT_BIN}', label='${ID}') +print(msg) +sys.exit(0 if ok else 1) +" 2>&1)" && STATUS=PASS || STATUS=FAIL + echo " ${MSG}" + echo "${STATUS}" > "${WORK}/status_${ID}.txt" + echo "${ID}: ${STATUS}" + ;; + + recorder) + RCFG_MARTE="${F2}"; RCFG_HUB="${F3}" + echo "" + echo "══ scenario ${ID} (kind=recorder marte_cfg=${RCFG_MARTE} hub_cfg=${RCFG_HUB}) ══" + + DS_DIR="${SCRIPT_DIR}/../datasources" + INPUT_BIN="/tmp/udpstreamer_test_input.bin" + REC_DIR="/tmp/streamhub_rec_e2e" + rm -rf "${REC_DIR}"; mkdir -p "${REC_DIR}" + (cd "${DS_DIR}" && ${PY} -c "import gen_test_data; gen_test_data.generate()") > "${OUT_DIR}/gen_${ID}.log" 2>&1 + + HUB_LOG="${OUT_DIR}/hub_${ID}.log" + APP_LOG="${OUT_DIR}/marte_${ID}.log" + + # Start StreamHub first so it is ready to receive the CONFIG packet. + "${STREAMHUB_EX}" -cfg "${REPO_ROOT}/${RCFG_HUB}" > "${HUB_LOG}" 2>&1 & + HUB_PID=$! + sleep 1 + timeout 8 "${MARTE_APP}" -l RealTimeLoader -f "${REPO_ROOT}/${RCFG_MARTE}" -s Running > "${APP_LOG}" 2>&1 & + APP_PID=$! + + # Let data flow, then stop the streamer and give the push/flush thread + # time to write the recorder file before killing the hub. + sleep 7 + kill "${APP_PID}" 2>/dev/null; wait "${APP_PID}" 2>/dev/null; APP_PID="" + sleep 2 + cleanup + + # The recorder names files __.bin; pick the newest. + REC_FILE="$(ls -t "${REC_DIR}"/*.bin 2>/dev/null | head -1)" + if [ -z "${REC_FILE}" ]; then + echo " FAIL: no recorder output file found in ${REC_DIR}" + echo "FAIL" > "${WORK}/status_${ID}.txt" + echo "${ID}: FAIL" + else + MSG="$(${PY} -c " +import sys +sys.path.insert(0, '${DS_DIR}') +import validate_binary as V +ok, msg, details = V.validate('${INPUT_BIN}', '${REC_FILE}', label='${ID}') +print(msg) +sys.exit(0 if ok else 1) +" 2>&1)" && STATUS=PASS || STATUS=FAIL + echo " ${MSG}" + echo "${STATUS}" > "${WORK}/status_${ID}.txt" + echo "${ID}: ${STATUS}" + fi + ;; + + *) + echo " SKIP: unknown scenario kind '${KIND}' for ${ID}" + echo "SKIP" > "${WORK}/status_${ID}.txt" + ;; + esac done <<< "${LIST}" trap - EXIT @@ -207,11 +311,13 @@ sys.path.insert(0, os.environ["SCRIPT_DIR"]) try: from scenarios import SCENARIOS known = {s["id"]: s.get("known_issue") for s in SCENARIOS} + kind_map = {s["id"]: s["kind"] for s in SCENARIOS} except Exception: known = {} + kind_map = {} results = [] for sid in ids: - rec = {"id": sid} + rec = {"id": sid, "kind": kind_map.get(sid, "chain")} st = os.path.join(work, f"status_{sid}.txt") raw = open(st).read().strip() if os.path.exists(st) else "UNKNOWN" ki = known.get(sid)