- hl7-diff-set: compare a whole set of Windows-vs-Linux regression outputs in one command — takes two prefixes (+ optional --dir), auto-discovers routes by glob, runs hl7-diff per pair (--framing auto --ignore MSH.7, overridable), reports per-route PASS/DIFF/MISSING/EXTRA + summary, exit 0 iff all clean (MISSING and EXTRA both count as regressions). No more hand-written for-loop with hardcoded names. - F-8: hl7-diff --format text now shows SEG.FIELD left='...' right='...' with visible (empty)/<absent> tokens so a dropped component (e.g. Linux MSH.9.3 absent vs Windows ADT_A02) is obvious without opening the message files; --------- message N header now uses the real message index (was a stale counter). tsv/count formats unchanged. Consolidated by Clover; Vera CODE-PASS (Deliverables/2026-07-23-cloverleaf-larry-v0910-hl7-diff-set.md) — own-fixture verification of all route classifications + the absent-token render + F-7 no-hang. Live-acceptance = Bryan's regression run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
207 lines
7.9 KiB
Bash
Executable File
207 lines
7.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# hl7-diff-set.sh — run hl7-diff over a WHOLE SET of paired Windows-vs-Linux
|
|
# (or any baseline-vs-candidate) regression output files in one command,
|
|
# instead of hand-writing a for-loop with hardcoded route names.
|
|
#
|
|
# Built for hciroutetest's real on-disk layout (Bryan-direct, 2026-07-23):
|
|
# one directory holding paired files named <prefix>.<route>, where TWO
|
|
# prefixes (a baseline/"windows" set and a candidate/"linux" set) share a set
|
|
# of route suffixes after the last dot, e.g.:
|
|
# adt_in_regression_out_windows_single.adt_process_out
|
|
# adt_in_regression_out_linux_single.adt_process_out
|
|
# adt_in_regression_out_windows_single.lab_adt_out
|
|
# adt_in_regression_out_linux_single.lab_adt_out
|
|
# ...
|
|
#
|
|
# Usage:
|
|
# hl7-diff-set.sh <baseline-prefix> <candidate-prefix>
|
|
# [--dir PATH] # directory holding the paired files (default: .)
|
|
# [--ignore "FIELD,..."] # forwarded to hl7-diff. Default: MSH.7
|
|
# [--include-fields "F,.."] # forwarded to hl7-diff (overrides --ignore for that set)
|
|
# [--framing auto|nl|0x1c|len10] # forwarded to hl7-diff. Default: auto
|
|
# [--format text|tsv] # how THIS command reports. Default: text
|
|
#
|
|
# hl7-diff-set.sh adt_in_regression_out_windows_single adt_in_regression_out_linux_single \
|
|
# --dir /opt/cloverleaf/cis2025.01/integrator/adt/data/regression_testing/single
|
|
#
|
|
# What it does:
|
|
# 1. Globs "<dir>/<baseline-prefix>.*", derives each route suffix (the part
|
|
# after "<baseline-prefix>."), and pairs it with "<dir>/<candidate-prefix>.<suffix>".
|
|
# Also globs "<dir>/<candidate-prefix>.*" so a candidate-only route (no
|
|
# baseline file) is found too — nothing is silently skipped.
|
|
# 2. Runs the existing lib/hl7-diff.sh on every matched pair (this does NOT
|
|
# reimplement diffing — same engine, same --ignore/--framing semantics).
|
|
# 3. Reports PASS/DIFF/MISSING/EXTRA per route, plus a one-line summary.
|
|
#
|
|
# Per-route outcomes:
|
|
# PASS — both files present, 0 field differences (post-ignore)
|
|
# DIFF — both files present, N>0 field differences (--format text also
|
|
# shows the full hl7-diff --format text detail, indented)
|
|
# MISSING — baseline route has no matching candidate file (a dropped output
|
|
# IS a regression — this counts as a failure)
|
|
# EXTRA — candidate has a route the baseline does not (reported, also
|
|
# counts as non-clean — never silently skipped)
|
|
#
|
|
# --format text (default) prints a human report to stdout: per-route lines,
|
|
# DIFF detail indented beneath, then the summary line.
|
|
# --format tsv prints ONE machine row per route to stdout:
|
|
# route<TAB>status<TAB>diff_count
|
|
# (diff_count is "-" for MISSING/EXTRA, since no diff was actually run) —
|
|
# the summary line still prints, to STDERR, so stdout stays clean tsv.
|
|
# Recommended for scripting: hl7-diff.sh's OWN v0.9.9 text-mode message-
|
|
# header mislabel is fixed as of this release (F-8), but tsv remains the
|
|
# safer machine-parseable choice regardless.
|
|
#
|
|
# Exit codes: 0 = every route PASS (all clean); 1 = at least one route is
|
|
# DIFF/MISSING/EXTRA; 2 = usage/input error (bad flag, --dir missing, or
|
|
# neither prefix matched any file at all).
|
|
set -o pipefail
|
|
|
|
NC_SELF="$0"
|
|
LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)"
|
|
HL7DIFF="$LIB_DIR/hl7-diff.sh"
|
|
|
|
if [ -r "$LIB_DIR/cygwin-safe.sh" ]; then
|
|
# shellcheck disable=SC1090,SC1091
|
|
. "$LIB_DIR/cygwin-safe.sh"
|
|
else
|
|
coerce_int() { local r="${1:-}" d="${2:-0}" c; c=$(printf '%s' "$r" | tr -cd '0-9'); printf '%s' "${c:-$d}"; }
|
|
fi
|
|
|
|
die() { printf 'hl7-diff-set: %s\n' "$*" >&2; exit 2; }
|
|
|
|
DIR="."
|
|
IGNORE="MSH.7"
|
|
INCLUDE=""
|
|
FRAMING="auto"
|
|
FORMAT="text"
|
|
BASE_PREFIX=""
|
|
CAND_PREFIX=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--dir) shift; DIR="$1" ;;
|
|
--ignore) shift; IGNORE="$1" ;;
|
|
--include-fields) shift; INCLUDE="$1" ;;
|
|
--framing) shift; FRAMING="$1" ;;
|
|
--format) shift; FORMAT="$1" ;;
|
|
-h|--help) sed -n '2,52p' "$NC_SELF"; exit 0 ;;
|
|
-*) die "unknown flag: $1" ;;
|
|
*)
|
|
if [ -z "$BASE_PREFIX" ]; then BASE_PREFIX="$1"
|
|
elif [ -z "$CAND_PREFIX" ]; then CAND_PREFIX="$1"
|
|
else die "extra arg: $1"
|
|
fi
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[ -x "$HL7DIFF" ] || [ -f "$HL7DIFF" ] || die "backing tool not found: $HL7DIFF"
|
|
[ -n "$BASE_PREFIX" ] && [ -n "$CAND_PREFIX" ] || die "usage: hl7-diff-set.sh <baseline-prefix> <candidate-prefix> [--dir PATH] [--ignore FIELDS] [--framing auto|nl|0x1c|len10] [--format text|tsv] (run --help for full usage)"
|
|
[ -d "$DIR" ] || die "no such --dir: $DIR"
|
|
case "$FORMAT" in text|tsv) ;; *) die "bad --format (want text|tsv)" ;; esac
|
|
case "$FRAMING" in auto|nl|0x1c|len10) ;; *) die "bad --framing (want auto|nl|0x1c|len10)" ;; esac
|
|
|
|
# --- discovery: glob both prefixes, derive route suffixes -------------------
|
|
# Portable (bash 3.2 / MobaXterm-safe) glob: a literal no-match pattern stays
|
|
# un-expanded, so every candidate is existence-checked with `[ -e ]` before
|
|
# use — no reliance on nullglob (bash-4-leaning / shopt-dependent).
|
|
_base_suffixes=()
|
|
for _f in "$DIR/$BASE_PREFIX".*; do
|
|
[ -e "$_f" ] || continue
|
|
_bn="$(basename "$_f")"
|
|
_base_suffixes+=("${_bn#"$BASE_PREFIX".}")
|
|
done
|
|
|
|
_cand_suffixes=()
|
|
for _f in "$DIR/$CAND_PREFIX".*; do
|
|
[ -e "$_f" ] || continue
|
|
_bn="$(basename "$_f")"
|
|
_cand_suffixes+=("${_bn#"$CAND_PREFIX".}")
|
|
done
|
|
|
|
if [ "${#_base_suffixes[@]}" -eq 0 ] && [ "${#_cand_suffixes[@]}" -eq 0 ]; then
|
|
die "no files matched '$BASE_PREFIX.*' or '$CAND_PREFIX.*' in $DIR"
|
|
fi
|
|
|
|
# Union of both suffix lists, sorted + de-duped, for a stable/deterministic
|
|
# route order regardless of filesystem glob order.
|
|
ROUTES="$(printf '%s\n' "${_base_suffixes[@]}" "${_cand_suffixes[@]}" | grep -v '^$' | sort -u)"
|
|
|
|
# --- run each pair ------------------------------------------------------------
|
|
diff_args=(--ignore "$IGNORE" --framing "$FRAMING")
|
|
[ -n "$INCLUDE" ] && diff_args+=(--include-fields "$INCLUDE")
|
|
|
|
n_routes=0 n_pass=0 n_diff=0 n_missing=0 n_extra=0
|
|
|
|
if [ "$FORMAT" = "text" ]; then
|
|
printf 'hl7-diff-set: baseline=%s candidate=%s dir=%s\n' "$BASE_PREFIX" "$CAND_PREFIX" "$DIR"
|
|
printf ' ignore: %s framing: %s\n\n' "$IGNORE" "$FRAMING"
|
|
fi
|
|
|
|
while IFS= read -r route; do
|
|
[ -z "$route" ] && continue
|
|
n_routes=$((n_routes + 1))
|
|
bfile="$DIR/$BASE_PREFIX.$route"
|
|
cfile="$DIR/$CAND_PREFIX.$route"
|
|
|
|
if [ ! -f "$bfile" ]; then
|
|
n_extra=$((n_extra + 1))
|
|
if [ "$FORMAT" = "tsv" ]; then
|
|
printf '%s\tEXTRA\t-\n' "$route"
|
|
else
|
|
printf ' %-24s EXTRA (candidate-only route — no baseline file: %s)\n' "$route" "$bfile"
|
|
fi
|
|
continue
|
|
fi
|
|
if [ ! -f "$cfile" ]; then
|
|
n_missing=$((n_missing + 1))
|
|
if [ "$FORMAT" = "tsv" ]; then
|
|
printf '%s\tMISSING\t-\n' "$route"
|
|
else
|
|
printf ' %-24s MISSING (no candidate output — dropped route: %s)\n' "$route" "$cfile"
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
count="$("$HL7DIFF" "${diff_args[@]}" --format count "$bfile" "$cfile" 2>&1)"
|
|
count="$(coerce_int "$count" 0)"
|
|
|
|
if [ "$count" -eq 0 ]; then
|
|
n_pass=$((n_pass + 1))
|
|
if [ "$FORMAT" = "tsv" ]; then
|
|
printf '%s\tPASS\t0\n' "$route"
|
|
else
|
|
printf ' %-24s PASS\n' "$route"
|
|
fi
|
|
else
|
|
n_diff=$((n_diff + 1))
|
|
if [ "$FORMAT" = "tsv" ]; then
|
|
printf '%s\tDIFF\t%s\n' "$route" "$count"
|
|
else
|
|
printf ' %-24s DIFF (%s diff(s))\n' "$route" "$count"
|
|
"$HL7DIFF" "${diff_args[@]}" --format text "$bfile" "$cfile" 2>&1 | sed 's/^/ /'
|
|
printf '\n'
|
|
fi
|
|
fi
|
|
done <<EOF_ROUTES
|
|
$ROUTES
|
|
EOF_ROUTES
|
|
|
|
# --- summary ------------------------------------------------------------------
|
|
_summary="$n_routes routes: $n_pass clean, $n_diff differs"
|
|
[ "$n_missing" -gt 0 ] && _summary="$_summary, $n_missing missing"
|
|
[ "$n_extra" -gt 0 ] && _summary="$_summary, $n_extra extra"
|
|
|
|
if [ "$FORMAT" = "tsv" ]; then
|
|
printf '%s\n' "$_summary" >&2
|
|
else
|
|
printf '%s\n' "$_summary"
|
|
fi
|
|
|
|
if [ "$n_diff" -eq 0 ] && [ "$n_missing" -eq 0 ] && [ "$n_extra" -eq 0 ] && [ "$n_routes" -gt 0 ]; then
|
|
exit 0
|
|
fi
|
|
exit 1
|