#!/usr/bin/env python3 """ Unit tests for nl52.protocol — DOD/DRD parsing. No hardware required. Run: python3 test_nl52_protocol.py """ import sys from nl52.protocol import ( parse_dod, parse_drd, parse_level, is_result_code, ResultError, RESULT_CODES, ) PASS = 0 FAIL = 0 def check(name, cond, detail=""): global PASS, FAIL if cond: PASS += 1 print(f" ✓ {name}") else: FAIL += 1 print(f" ✗ {name} {('— ' + detail) if detail else ''}") def test_level_tokens(): print("\n[1] Level token parsing") check("plain value", parse_level("55.5") == 55.5) check("space-padded value", parse_level(" 60.1") == 60.1) check("disabled channel '--.-' -> None", parse_level(" --.-") is None) check("dashes-only -> None", parse_level("---.-") is None) check("empty -> None", parse_level(" ") is None) check("negative value", parse_level("-3.2") == -3.2) def test_result_codes(): print("\n[2] Result code recognition") check("R+0000 is a result code", is_result_code("R+0000")) check("R+0004 is a result code", is_result_code(" R+0004 ")) check("data line is not a result code", not is_result_code("55.5,54.2")) check("all 5 codes documented", set(RESULT_CODES) == { "R+0000", "R+0001", "R+0002", "R+0003", "R+0004"}) err = ResultError("R+0004") check("ResultError carries meaning", "Status error" in err.meaning, err.meaning) def test_dod_full(): print("\n[3] DOD? full 14-field parse") # d1..d14: Lp,Leq,LE,Lmax,Lmin,Ly,LN1..5,subLp,overload,underrange resp = "55.5,54.2,60.1,50.3,45.2,12.3,48.1,50.0,52.3,44.1,43.0,40.2,0,0" s = parse_dod(resp) check("lp", s.lp == 55.5, str(s.lp)) check("leq", s.leq == 54.2) check("le", s.le == 60.1) check("lmax", s.lmax == 50.3) check("lmin", s.lmin == 45.2) check("ly", s.ly == 12.3) check("ln1", s.ln1 == 48.1) check("ln5", s.ln5 == 43.0) check("sub_lp", s.sub_lp == 40.2) check("overload False", s.overload is False) check("underrange False", s.underrange is False) check("raw preserved", s.raw == resp) def test_dod_disabled_channels(): print("\n[4] DOD? with disabled display channels ('--.-')") # When display is OFF, d2..d12 come back as ' --.-' resp = "55.5, --.-, --.-, --.-, --.-, --.-, --.-, --.-, --.-, --.-, --.-, --.-,1,0" s = parse_dod(resp) check("lp still present", s.lp == 55.5) check("leq disabled -> None", s.leq is None) check("sub_lp disabled -> None", s.sub_lp is None) check("overload True", s.overload is True) check("underrange False", s.underrange is False) def test_dod_space_padded(): print("\n[5] DOD? space-padded fixed-width fields") resp = " 55.5, 54.2, 60.1, 50.3, 45.2, 12.3, 48.1, 50.0, 52.3, 44.1, 43.0, 40.2, 0, 1" s = parse_dod(resp) check("padded lp", s.lp == 55.5) check("padded sub_lp", s.sub_lp == 40.2) check("padded underrange True", s.underrange is True) def test_drd(): print("\n[6] DRD? sample parse (d0..d8)") # d0=counter,d1=Lp,d2=Leq,d3=Lmax,d4=Lmin,d5=Ly,d6=subLp,d7=ovl,d8=under resp = "12,55.5,54.2,60.1,50.3, --.-,40.2,0,1" s = parse_drd(resp) check("counter int", s.counter == 12, str(s.counter)) check("lp", s.lp == 55.5) check("leq", s.leq == 54.2) check("lmax", s.lmax == 60.1) check("lmin", s.lmin == 50.3) check("ly disabled -> None", s.ly is None) check("sub_lp", s.sub_lp == 40.2) check("overload False", s.overload is False) check("underrange True", s.underrange is True) def test_short_field_counts(): print("\n[7] Tolerance of short/odd field counts") s = parse_dod("55.5,54.2") # only first two present check("partial DOD: lp", s.lp == 55.5) check("partial DOD: leq", s.leq == 54.2) check("partial DOD: missing -> None", s.lmax is None) d = parse_drd("7") # only counter check("partial DRD: counter", d.counter == 7) check("partial DRD: missing lp -> None", d.lp is None) def main(): test_level_tokens() test_result_codes() test_dod_full() test_dod_disabled_channels() test_dod_space_padded() test_drd() test_short_field_counts() print(f"\n{'='*50}\nResults: {PASS} passed, {FAIL} failed") return FAIL == 0 if __name__ == "__main__": sys.exit(0 if main() else 1)