#!/usr/bin/env python3 """ validate_binary.py — Compare MARTe2 binary input and output files. Usage: python3 validate_binary.py [--label NAME] Reads MARTe2 binary format (header: 4B numSigs, per-sig: 2B type + 32B name + 4B elems), then compares signal count, element counts, and data rows between input and output. Output rows are matched against input rows via set membership to handle async FileWriter flush ordering. """ import struct, sys, os def read_binary(path): """Read MARTe2 binary file. Returns (signals, raw_data_bytes).""" if not os.path.exists(path): return None, None with open(path, 'rb') as f: data = f.read() if len(data) < 4: return None, None ns = struct.unpack_from(' len(data): break tc = struct.unpack_from(' 0 else 0 n_rows_out = len(out_raw) // out_row_bytes if out_row_bytes > 0 else 0 if n_rows_out == 0: return False, f"[{label}] Output file has no data rows", {} # Build input row set input_rows = set() for r in range(n_rows_in): input_rows.add(in_raw[r * in_row_bytes:(r + 1) * in_row_bytes]) # Classify each output row into exactly one of three buckets: # zero — all-zero row (benign startup transient before first DATA) # matching — non-zero row that equals some input row (correct transport) # mismatching — non-zero row that matches no input row (data corruption) zero_rows = 0 matching = 0 mismatching = 0 first_zero = -1 first_match = -1 first_mismatch = -1 for r in range(n_rows_out): row = out_raw[r * out_row_bytes:(r + 1) * out_row_bytes] if not any(row): zero_rows += 1 if first_zero < 0: first_zero = r elif row in input_rows: matching += 1 if first_match < 0: first_match = r else: mismatching += 1 if first_mismatch < 0: first_mismatch = r details = { "n_signals": len(in_sigs), "signal_names": [s[0] for s in in_sigs], "signal_sizes": [s[2] for s in in_sigs], "row_bytes": in_row_bytes, "n_rows_in": n_rows_in, "n_rows_out": n_rows_out, "zero_rows": zero_rows, "matching_rows": matching, "mismatching_rows": mismatching, "first_zero_row": first_zero, "first_matching_row": first_match, "first_mismatch_row": first_mismatch, "input_bytes": len(in_raw), "output_bytes": len(out_raw), } # Fail threshold: # - any non-zero output row that matches no input row → data corruption → FAIL # - no matching rows at all (incl. all-zero output) → FAIL if matching == 0: return False, (f"[{label}] FAIL: no output row matches any input row " f"({n_rows_out} rows: {zero_rows} zero, {mismatching} corrupted)"), details if mismatching > 0: return False, (f"[{label}] FAIL: {mismatching} non-zero output rows match no input row " f"(first at row {first_mismatch})"), details if zero_rows > 0: return True, (f"[{label}] WARN: {matching}/{n_rows_out} rows match " f"({zero_rows} startup zero rows; first match at row {first_match})"), details return True, f"[{label}] PASS: all {n_rows_out} rows match input", details def main(): import argparse p = argparse.ArgumentParser(description="Validate MARTe2 binary output against input") p.add_argument("input", help="Input binary file") p.add_argument("output", help="Output binary file") p.add_argument("--label", default="e2e", help="Test label for output messages") p.add_argument("--json", default=None, help="Write the metrics (incl. pass/fail) to this JSON file") args = p.parse_args() passed, msg, details = validate(args.input, args.output, args.label) if args.json is not None: import json record = dict(details) record["label"] = args.label record["passed"] = passed record["status"] = "PASS" if passed else "FAIL" record["message"] = msg.strip() with open(args.json, "w") as jf: json.dump(record, jf, indent=2) # Always print details if details: print(f" Signals: {details['n_signals']} ({', '.join(f'{n}({s})' for n,s in zip(details['signal_names'], details['signal_sizes']))})") print(f" Row size: {details['row_bytes']} B") print(f" Input: {details['input_bytes']} B ({details['n_rows_in']} rows)") print(f" Output: {details['output_bytes']} B ({details['n_rows_out']} rows)") print(f" Matching: {details['matching_rows']}/{details['n_rows_out']}") print(f" Zero rows: {details['zero_rows']}/{details['n_rows_out']}") print(f" Mismatching: {details['mismatching_rows']}/{details['n_rows_out']} (non-zero, unmatched)") if details['first_matching_row'] >= 0: print(f" First matching row: {details['first_matching_row']}") if details['first_zero_row'] >= 0: print(f" First zero row: {details['first_zero_row']}") if details['first_mismatch_row'] >= 0: print(f" First mismatching row: {details['first_mismatch_row']}") print(f" {'✓' if passed else '✗'} {msg}") sys.exit(0 if passed else 1) if __name__ == "__main__": main()