## Guard test for the boot-time env report's Part 2 (the warnings). ## ## NO battle, NO Java, NO server. Run with: ## nim c -r common_libs/tests/test_env_report.nim ## ## It pins the two tables that must not drift: ## 1. the KNOWN TR_*/GUN_* name set covers every env read in the tree, so a ## new knob cannot silently start producing a false "unknown variable" ## warning (and, conversely, the set cannot rot); ## 2. the compile-time define map covers every `{.intdefine.}`/`{.strdefine.}` ## /`{.booldefine.}` symbol in the tree, and classifies the ONE real env ## twin (`TMH_NSTATES` -> `TR_TMHORIZON_NSTATES`) correctly. import std/[os, strutils, sets, sequtils] import gun_harness/selector import "../../ModularBot_garage/src/env_report" var failures = 0 proc check(name: string, ok: bool) = if ok: echo "PASS: ", name else: echo "FAIL: ", name; inc failures const repoRoot = currentSourcePath().parentDir.parentDir.parentDir # ── the source surface the scan walks (the bot's real modules) ─────────────── proc scannedFiles(): seq[string] = ## .nim files under common_libs (excluding common_libs/tests, which are ## probes/measurement scripts, not bot code) and ModularBot_garage/src. ## env_report.nim is excluded: it DEFINES the known set, so scanning it is ## circular (it also names "suggested but nonexistent" env vars on purpose). for dir in [repoRoot / "common_libs", repoRoot / "ModularBot_garage" / "src"]: for path in walkDirRec(dir): if not path.endsWith(".nim"): continue if "/tests/" in path: continue if path.endsWith("/env_report.nim"): continue result.add path proc literalsOn(line: string): seq[string] = ## Double-quoted string literals on one line (env names have no escapes). var i = 0 while i < line.len: if line[i] == '"': var j = i + 1 while j < line.len and line[j] != '"': inc j if j >= line.len: break result.add line[i + 1 ..< j] i = j + 1 else: inc i proc isKnownOrPrefix(name: string, known: HashSet[string]): bool = ## A literal counts as covered when it is a known name OR a prefix of one ## (`TR_RACK_` is the rack prefix; `TR_`/`GUN_` are the filter prefixes). if name in known: return true for k in known: if k.startsWith(name): return true proc defineSymbolsOn(line: string): seq[string] = ## ` TMH_NCLAUSES* {.intdefine.} = 40` -> "TMH_NCLAUSES" for pragma in ["{.intdefine.}", "{.strdefine.}", "{.booldefine.}"]: if pragma in line: var sym = line.split('{')[0].strip() if sym.endsWith("*"): sym.setLen(sym.len - 1) let parts = sym.splitWhitespace() if parts.len > 0: result.add parts[^1] # ── 1. the known-name set ──────────────────────────────────────────────────── proc testKnownNames() = let known = knownEnvNames() let s = known.toHashSet() check "known set has no duplicates", known.len == s.len check "known set only holds TR_*/GUN_* names", known.allIt(it.startsWith("TR_") or it.startsWith("GUN_")) check "known set covers all 17 rack names", RackGunNames.allIt((RackEnvPrefix & it) in s) for name in ["TR_MOVEMENT", "TR_POWER_ENERGY_MIN", "TR_TMHORIZON_NSTATES", "GUN_VBULLET_METRIC", "TR_ENV_REPORT", "GUN_SELECTOR_SEED", "TR_RACK_TMHORIZON", "TR_TMHORIZON_NSTATES"]: check "known set contains " & name, name in s # ── 2. the pure warning helpers ────────────────────────────────────────────── proc testUnknownEnvNames() = let unknown = unknownEnvNames(@[ ("TR_POWER_LOG", "1"), ("GUN_STATS_PATH", "/tmp/x"), ("TR_TMHORIZON_NSTATE", "5"), ("PATH", "/bin"), ("TMH_NSTATES", "2"), ("JAVA_HOME", "/jdk")]) check "unknownEnvNames finds the misspelled TR_* only", unknown == @["TR_TMHORIZON_NSTATE"] check "unknownEnvNames ignores non TR_*/GUN_* names", unknownEnvNames(@[("PATH", "/bin")]) == @[] proc testDefineMap() = check "isCompileTimeDefine(TMH_NSTATES)", isCompileTimeDefine("TMH_NSTATES") check "isCompileTimeDefine(TM_S_DEF)", isCompileTimeDefine("TM_S_DEF") check "isCompileTimeDefine(TR_TMHORIZON_NSTATES) is false", not isCompileTimeDefine("TR_TMHORIZON_NSTATES") check "defineWarningMessage is empty for a non-define", defineWarningMessage("TR_MOVEMENT", "x") == "" let m1 = defineWarningMessage("TMH_NSTATES", "2") check "TMH_NSTATES warns it is compile-time and names TR_TMHORIZON_NSTATES", "COMPILE-TIME define (-d:TMH_NSTATES=2)" in m1 and "TR_TMHORIZON_NSTATES=2" in m1 let m2 = defineWarningMessage("TMH_NCLAUSES", "40") check "TMH_NCLAUSES says TR_TMHORIZON_NCLAUSES does not exist", "TR_TMHORIZON_NCLAUSES does not exist" in m2 let m3 = defineWarningMessage("TM_S_DEF", "1.5") check "TM_S_DEF says this knob is compile-time only", "compile-time only" in m3 let defs = setCompileTimeDefines(@[ ("TMH_NSTATES", "2"), ("TR_MOVEMENT", "tfil"), ("TM_S_DEF", "1.5")]) check "setCompileTimeDefines finds both -d symbols", defs == @["TMH_NSTATES", "TM_S_DEF"] # ── 3. the tree scans (the anti-drift guard) ───────────────────────────────── proc testTreeScan() = let known = knownEnvNames().toHashSet() var literals = 0 var missing: seq[string] for path in scannedFiles(): for line in lines(path): for lit in literalsOn(line): if lit.startsWith("TR_") or lit.startsWith("GUN_"): inc literals if not isKnownOrPrefix(lit, known): missing.add path.extractFilename & ":" & lit check "tree scan is alive (>= 50 TR_*/GUN_* literals)", literals >= 50 check "every TR_*/GUN_* literal in the tree is in the known set: " & $missing, missing.len == 0 proc testDefineScan() = let mapped = CompileTimeDefineMap.mapIt(it.defineName).toHashSet() var found: seq[string] for path in scannedFiles(): for line in lines(path): for sym in defineSymbolsOn(line): found.add sym var missing: seq[string] for sym in found: if sym notin mapped: missing.add sym check "tree define scan is alive (>= 20 symbols)", found.len >= 20 check "every intdefine/strdefine/booldefine is in the map: " & $missing, missing.len == 0 # ── driver ─────────────────────────────────────────────────────────────────── testKnownNames() testUnknownEnvNames() testDefineMap() testTreeScan() testDefineScan() if failures > 0: echo "\n", failures, " check(s) FAILED" quit(1) echo "\nAll env-report checks passed."