Env report Part 2: warn on misnamed env vars (unknown + compile-time defines)
The boot report printed the raw env and the resolved values, but it never
told the user when a name was WRONG - which is the failure mode that cost
real time: `TMH_NSTATES` was exported as an env var although it is a
compile-time `{.intdefine.}` (`guns/tm_horizon.nim:102`), so the export was
a silent no-op. Add the two warning paths, both boot-only and stdout:
* unknown TR_*/GUN_* names are named explicitly. The known set is built
from the modules' exported env-name constants; the remaining inline
reads are listed once, and common_libs/tests/test_env_report.nim scans
the tree and fails if a name read anywhere is missing.
* any `{.intdefine.}`/`{.strdefine.}`/`{.booldefine.}` symbol present as
an env var is flagged, with the real runtime equivalent when one exists
(`TMH_NSTATES` -> `TR_TMHORIZON_NSTATES`) and an explicit "does not
exist - this knob is compile-time only" when it does not. The map is
derived by grepping the tree; the same test re-greps and fails on drift.
Warnings are emitted only when the env is dirty, so a correct run keeps the
documented A/B/build shape. TR_ENV_REPORT=0 still suppresses everything.
test_env_report.nim: 25 checks (known set, pure helpers, tree literal scan,
tree define scan). All 15 existing guard tests keep their exact counts.
This commit is contained in:
@@ -311,6 +311,166 @@ proc printBuildIdentity() =
|
||||
discard
|
||||
echo "[env] build binary=", bin, " size=", size, " mtime=", mtime
|
||||
|
||||
# ── Part 2: warnings about env mistakes ──────────────────────────────────────
|
||||
#
|
||||
# This is the point of the report. A typo must be LOUD, not a silent no-op.
|
||||
# Two mistakes are caught:
|
||||
#
|
||||
# 1. an unknown TR_*/GUN_* name (e.g. TR_TMHORIZON_NSTATE, missing S);
|
||||
# 2. a compile-time `{.intdefine.}`/`{.strdefine.}`/`{.booldefine.}` symbol
|
||||
# exported as an env var (e.g. `TMH_NSTATES=2`), which is NEVER read at
|
||||
# runtime and therefore does nothing.
|
||||
#
|
||||
# The known-name set is assembled from the modules' exported env-name
|
||||
# constants; the remaining inline reads are listed once here. The guard test
|
||||
# `common_libs/tests/test_env_report.nim` scans the tree for env reads and for
|
||||
# intdefine/strdefine/booldefine symbols and fails loudly if a name read
|
||||
# anywhere (or a new define) is missing from these tables, so neither can
|
||||
# silently drift.
|
||||
|
||||
proc knownEnvNames*(): seq[string] =
|
||||
## Every TR_*/GUN_* name this project reads at runtime. Order is irrelevant;
|
||||
## callers put it in a set. Keep in sync with the tree scan in the guard test.
|
||||
result = @[
|
||||
# exported constants — the single source of truth (do not re-spell these)
|
||||
MetricEnvVar, SelectorModeEnvVar, TieBreakEnvVar,
|
||||
PowerPolicyEnvVar, PowerFarDistEnvVar, PowerFarCapEnvVar, PowerMidCapEnvVar,
|
||||
PowerRefEnvVar, PowerEnergyHiEnvVar, PowerEnergyLoEnvVar,
|
||||
PowerEnergyMinEnvVar, PowerEnergyMaxEnvVar, PowerFinishKillEnvVar,
|
||||
PatternRadOffsetEnvVar, PatternRadScaleEnvVar,
|
||||
TMH_SHIFT_ENV, TMH_BIG_MULT_ENV, TMH_LOG_ENV, TMH_RESET_ON_TARGET_ENV,
|
||||
TMH_WINDOW_ENV, TMH_RESET_DROP_ENV, TMH_NSTATES_ENV, TMH_ACCURVE_ENV,
|
||||
TMH_RETRAIN_EVERY_ENV, TMH_EPOCHS_ENV,
|
||||
]
|
||||
# rack names are constructed from the prefix + gun table, not spelled out
|
||||
for g in RackGunNames:
|
||||
result.add RackEnvPrefix & g
|
||||
# inline reads with no exported constant (the guard test scans for these)
|
||||
for n in [
|
||||
"GUN_SELECTOR_WINDOW", "GUN_SELECTOR_MINOBS", "GUN_SELECTOR_TIE",
|
||||
"GUN_SELECTOR_FLOOR", "GUN_SELECTOR_POOL", "GUN_SELECTOR_RANK",
|
||||
"GUN_SELECTOR_SHRINK", "GUN_SELECTOR_DWELL", "GUN_SELECTOR_MARGIN",
|
||||
"GUN_SELECTOR_POINT_TIE", "GUN_SELECTOR_SEED",
|
||||
"GUN_RACK_DISABLE", "GUN_STATS_PATH", "GUN_SHOTLOG_PATH",
|
||||
"TR_MOVEMENT", "TR_MOVEMENT_LOG", "TR_RECORD_WORLDSTATE",
|
||||
"TR_RADAR_FORCE_SPIN", "TR_RADAR_SCANLOG", "TR_RADAR_SCAN_LOG_PATH",
|
||||
"TR_TRACKER_PROBE", "TR_TRACKER_PROBE_PATH", "TR_VBULLET_ADMIT_ONLY",
|
||||
"TR_POWER_LOG",
|
||||
"TR_RAM_OPPORTUNITY", "TR_RAM_OPP_DIST", "TR_RAM_OPP_MARGIN",
|
||||
"TR_RAM_ABORT_DMG", "TR_RAM_PLAN", "TR_RAM_PLAN_DIST",
|
||||
"TR_RAM_PLAN_MARGIN", "TR_RAM_PLAN_HITRATE", "TR_RAM_LOG",
|
||||
"TR_TFIL_RANGE_LO", "TR_TFIL_RANGE_HI", "TR_TFIL_RANGE_TEMP",
|
||||
"TR_TFIL_RANGE_K", "TR_TFIL_CORRIDOR_HEAT", "TR_TFIL_WALL_HOTNESS",
|
||||
# harness vars (read by the test framework, inherited by the bot, so they
|
||||
# must NOT be reported as typos)
|
||||
"TR_SERVER_JAR", "TR_BATTLE_RUNNER", "TR_BATTLE_RUNNER_DIR",
|
||||
# the report's own switch
|
||||
EnvReportEnableEnvVar,
|
||||
]:
|
||||
result.add n
|
||||
|
||||
proc unknownEnvNames*(pairs: openArray[(string, string)]): seq[string] =
|
||||
## Pure: every TR_*/GUN_* name in `pairs` the bot does not read, sorted.
|
||||
## Anything else (e.g. PATH, JAVA_HOME) is deliberately ignored.
|
||||
let known = knownEnvNames().toHashSet()
|
||||
for (key, _) in pairs:
|
||||
if (key.startsWith("TR_") or key.startsWith("GUN_")) and key notin known:
|
||||
result.add key
|
||||
result.sort()
|
||||
|
||||
type
|
||||
DefineMapping* = object
|
||||
## A `{.intdefine.}`/`{.strdefine.}`/`{.booldefine.}` symbol and the runtime
|
||||
## env var (if any) that actually controls the same knob. `suggestedEnv` is
|
||||
## the env name a user would naturally guess, used only for the
|
||||
## "does not exist" text when `runtimeEnv` is empty.
|
||||
defineName*: string
|
||||
runtimeEnv*: string
|
||||
suggestedEnv*: string
|
||||
|
||||
## Derived by grepping the tree for intdefine/strdefine/booldefine (see the
|
||||
## guard test, which re-greps and fails if this table drifts):
|
||||
## tm_horizon.nim:101,102,103,106,108,139 — TMH_* ; only NSTATES has an env
|
||||
## tm_pattern.nim:50,53,55,57,59,62,65,68,69,85,87,92,94,96 — TM_*
|
||||
## tsetlin.nim:29,116,118,119,120 — TM_*
|
||||
## docs/env_reference.md documents the same table.
|
||||
const CompileTimeDefineMap*: seq[DefineMapping] = @[
|
||||
# tm_horizon.nim — the ONLY define with a runtime twin (`TR_TMHORIZON_NSTATES`)
|
||||
DefineMapping(defineName: "TMH_NSTATES", runtimeEnv: "TR_TMHORIZON_NSTATES"),
|
||||
DefineMapping(defineName: "TMH_NCLAUSES", suggestedEnv: "TR_TMHORIZON_NCLAUSES"),
|
||||
DefineMapping(defineName: "TMH_S_DEF", suggestedEnv: "TR_TMHORIZON_S_DEF"),
|
||||
DefineMapping(defineName: "TMH_MIN_OBS", suggestedEnv: "TR_TMHORIZON_MIN_OBS"),
|
||||
DefineMapping(defineName: "TMH_STALE_MAX", suggestedEnv: "TR_TMHORIZON_STALE_MAX"),
|
||||
# tm_pattern.nim — no env form at all; do not invent a plausible one
|
||||
DefineMapping(defineName: "TM_NCLAUSES"),
|
||||
DefineMapping(defineName: "TM_NSTATES"),
|
||||
DefineMapping(defineName: "TM_MIN_OBS"),
|
||||
DefineMapping(defineName: "TM_CLASSES"),
|
||||
DefineMapping(defineName: "TM_CONF_MARGIN_DEF"),
|
||||
DefineMapping(defineName: "TM_SHRINK_DEF"),
|
||||
DefineMapping(defineName: "TM_GF_MODE"),
|
||||
DefineMapping(defineName: "TM_SOFT_BETA_DEF"),
|
||||
DefineMapping(defineName: "TM_RADIAL_RANGE_DEF"),
|
||||
DefineMapping(defineName: "TM_RAD_MARGIN_DEF"),
|
||||
DefineMapping(defineName: "TM_REV_TURN_DEG_DEF"),
|
||||
DefineMapping(defineName: "TM_REV_MARGIN_DEF"),
|
||||
DefineMapping(defineName: "TM_REV_GAIN_DEF"),
|
||||
# tsetlin.nim — no env form at all
|
||||
DefineMapping(defineName: "TM_N_CLAUSES"),
|
||||
DefineMapping(defineName: "TM_N_STATES"),
|
||||
DefineMapping(defineName: "TM_T_DEF"),
|
||||
DefineMapping(defineName: "TM_WINDOW_SIZE"),
|
||||
# TM_S_DEF is shared by tm_pattern.nim AND tsetlin.nim (one -d sets both)
|
||||
DefineMapping(defineName: "TM_S_DEF"),
|
||||
]
|
||||
|
||||
proc isCompileTimeDefine*(name: string): bool =
|
||||
## True when `name` is one of our -d: symbols (never a runtime env var).
|
||||
for m in CompileTimeDefineMap:
|
||||
if m.defineName == name: return true
|
||||
|
||||
proc setCompileTimeDefines*(pairs: openArray[(string, string)]): seq[string] =
|
||||
## Pure: the -d: symbols present as env vars, sorted.
|
||||
for (key, _) in pairs:
|
||||
if isCompileTimeDefine(key): result.add key
|
||||
result.sort()
|
||||
|
||||
proc defineWarningMessage*(defineName, value: string): string =
|
||||
## The exact warning text for one -d: symbol set as an env var. "" when the
|
||||
## name is not a known define.
|
||||
for m in CompileTimeDefineMap:
|
||||
if m.defineName != defineName: continue
|
||||
let head = "[env] WARNING: " & defineName & "=" & value &
|
||||
" is a COMPILE-TIME define (-d:" & defineName & "=" & value &
|
||||
") and is NOT read at runtime; "
|
||||
if m.runtimeEnv.len > 0:
|
||||
return head & "the runtime equivalent is " & m.runtimeEnv & "=" & value & "."
|
||||
if m.suggestedEnv.len > 0:
|
||||
return head & m.suggestedEnv &
|
||||
" does not exist - this knob is compile-time only."
|
||||
return head & "no runtime env equivalent exists - this knob is " &
|
||||
"compile-time only."
|
||||
return ""
|
||||
|
||||
proc printMistakeWarnings*() =
|
||||
## The two warning paths, boot-only. Emits nothing when the env is clean, so
|
||||
## a correct run keeps the documented A/B/build shape.
|
||||
var pairs: seq[(string, string)]
|
||||
for key, val in envPairs():
|
||||
pairs.add (key, val)
|
||||
|
||||
let unknown = unknownEnvNames(pairs)
|
||||
let defines = setCompileTimeDefines(pairs)
|
||||
if unknown.len == 0 and defines.len == 0: return
|
||||
|
||||
echo "[env] --- warnings: misnamed variables ---"
|
||||
for key in unknown:
|
||||
echo "[env] WARNING: ", key,
|
||||
" is not a recognized TR_*/GUN_* variable; it is IGNORED. " &
|
||||
"Check the spelling (see docs/env_reference.md)."
|
||||
for key in defines:
|
||||
echo defineWarningMessage(key, getEnv(key, ""))
|
||||
|
||||
# ── the entry point ──────────────────────────────────────────────────────────
|
||||
|
||||
var envReportDone = false
|
||||
@@ -329,6 +489,7 @@ proc printEnvReport*(ctx: EnvReportContext) =
|
||||
|
||||
echo "[env] === ENVIRONMENT (boot report) ==="
|
||||
printRawEnvironment()
|
||||
printMistakeWarnings()
|
||||
printEffectiveValues(ctx)
|
||||
printBuildIdentity()
|
||||
echo "[env] === END ENVIRONMENT ==="
|
||||
|
||||
Reference in New Issue
Block a user