#!/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 ., 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 # [--dir PATH] # directory holding the paired files. # # DEFAULTS TO THE CURRENT DIRECTORY (.) if # # omitted -- --dir is an OVERRIDE, not # # required. Run from inside the regression # # directory and just pass the two prefixes. # [--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 # # # from inside the regression directory -- no --dir needed: # cd /opt/cloverleaf/cis2025.01/integrator/adt/data/regression_testing/single # hl7-diff-set.sh adt_in_regression_out_windows_single adt_in_regression_out_linux_single # # # or from anywhere, with an explicit --dir override: # 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 # # The FIRST prefix argument is always LEFT/baseline; the SECOND is always # RIGHT/candidate -- consistently, everywhere (the per-run header states this # explicitly so you never have to remember or guess which is which; v0.9.11). # # What it does: # 1. Globs "/.*", derives each route suffix (the part # after "."), and pairs it with "/.". # Also globs "/.*" 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: # routestatusdiff_count # (diff_count is "-" for MISSING/EXTRA, since no diff was actually run) — # the left/right identification header and the summary line still print, # 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,70p' "$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 [--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 # v0.9.11 (Bryan UX ask): name which side is which, ONCE, up front — the # per-diff-line `left=... right=...` labels (F-8, v0.9.10) tell you a value # came from "left" or "right", but not WHICH FILE PREFIX left/right actually # is. First positional arg is always left/baseline, second is always # right/candidate -- consistently, everywhere -- so state it plainly here. # text mode: part of the normal stdout report. tsv mode: routed to STDERR # (same as the summary line) so stdout stays pure machine-parseable tsv rows. if [ "$FORMAT" = "text" ]; then printf 'hl7-diff-set: baseline=%s candidate=%s dir=%s\n' "$BASE_PREFIX" "$CAND_PREFIX" "$DIR" printf ' left (baseline) = %s.*\n' "$BASE_PREFIX" printf ' right (candidate) = %s.*\n' "$CAND_PREFIX" printf ' ignore: %s framing: %s\n\n' "$IGNORE" "$FRAMING" else { printf 'hl7-diff-set: baseline=%s candidate=%s dir=%s\n' "$BASE_PREFIX" "$CAND_PREFIX" "$DIR" printf ' left (baseline) = %s.*\n' "$BASE_PREFIX" printf ' right (candidate) = %s.*\n' "$CAND_PREFIX" printf ' ignore: %s framing: %s\n' "$IGNORE" "$FRAMING" } >&2 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 <&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