8a5a66d0d8
Rebased onto main (~9700 commits): the branch predates the god-file decomposition and the tests/state -> tests/hermes_state move. Fixes for seams that moved: - cron memory contract patches cron.scheduler_delivery._resolve_origin and hermes_state_registry.acquire (where run_job now reads them). - state.db conformance imports _live_writer_holds_db from hermes_state_repair and drives the public create_quick_snapshot. - update receipt: serve runtimes are reconciled in their own unit vocabulary since #100479, so the "full accounting" row names the serve unit instead of borrowing a gateway relaunch. Deleted (change-detectors / source-greps / duplicates / dead code): - source-text scan of hermes_state*.py for "maintenance-shaped" defs and the symbol REGISTRY it fed (renames are not regressions). - per-op exact-outcome table under a live writer -> one invariant: refuse with a lock error or report zero work, DB stays intact. - copy_db_and_verify pins: the symbol is a revert-scheduled plugin-compat pointer, not production code (check_compat_pointers.py). - cron ON-direction tests already pinned by tests/cron/test_scheduler.py, plus a tautology that never called production code. - exact warning-wording asserts in the env deprecation truth table. - same-file duplicates: fixed-point (implied by idempotence), tripwire round-trips, absent-registry, sequential "race" re-enactment.
159 lines
5.7 KiB
Python
159 lines
5.7 KiB
Python
"""Truth-table regression test for the 'Deprecated .env settings' warning.
|
|
|
|
Behavior contract (pins the fix for the TERMINAL_CWD false-positive class:
|
|
#88829, #89016, #89389, #90299 — four reports in two weeks of the startup
|
|
warning firing when TERMINAL_CWD was merely in the inherited process
|
|
environment, e.g. bridged from ``terminal.cwd`` in config.yaml or set by
|
|
session restoration / cron / the gateway):
|
|
|
|
warn_deprecated_cwd_env_vars() emits the warning IFF the deprecated key
|
|
(TERMINAL_CWD or MESSAGING_CWD) has a genuinely non-empty value inside
|
|
the user's ~/.hermes/.env FILE.
|
|
|
|
* Process environment is IRRELEVANT — runtime bridges legitimately set
|
|
TERMINAL_CWD in os.environ (fix commit 31561e37e).
|
|
* config.yaml ``terminal.cwd`` is IRRELEVANT — it neither triggers nor
|
|
suppresses the warning (fix commit a93f1b2be removed suppression).
|
|
* Commented-out or empty-valued .env entries do NOT warn.
|
|
* There is NO quiet/non-interactive gating in the scanner itself; the
|
|
gateway calls it before setting HERMES_QUIET (gateway/run.py), so the
|
|
warning fires regardless of HERMES_QUIET.
|
|
|
|
Exercises the real scanner against a real temp HERMES_HOME .env file.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
REAL_PATH = "/home/user/projects/demo"
|
|
|
|
# Truth table rows:
|
|
# env_file: None = no .env file at all; str = literal .env file bytes
|
|
# proc_env: dict of process-environment vars to set (inherited, NOT .env)
|
|
# config_yaml: None = no config.yaml; str = literal config.yaml contents
|
|
# warn_keys: key names that MUST appear in the warning ((), i.e. empty,
|
|
# means the scanner must stay completely silent)
|
|
TRUTH_TABLE = [
|
|
pytest.param(None, {}, None, (), id="all-absent-silent"),
|
|
pytest.param(
|
|
None, {"TERMINAL_CWD": REAL_PATH}, None, (),
|
|
id="no-envfile-procenv-only-silent",
|
|
),
|
|
pytest.param(
|
|
"API_KEY=x\n", {"TERMINAL_CWD": "."}, None, (),
|
|
id="procenv-dot-only-silent-88829",
|
|
),
|
|
pytest.param(
|
|
"API_KEY=x\n", {"TERMINAL_CWD": REAL_PATH}, None, (),
|
|
id="procenv-realpath-only-silent-89016",
|
|
),
|
|
pytest.param(
|
|
"API_KEY=x\n",
|
|
{"TERMINAL_CWD": REAL_PATH},
|
|
f"terminal:\n cwd: {REAL_PATH}\n",
|
|
(),
|
|
id="config-bridged-procenv-silent-89389",
|
|
),
|
|
pytest.param(
|
|
"API_KEY=x\n", {}, f"terminal:\n cwd: {REAL_PATH}\n", (),
|
|
id="config-only-silent-90299",
|
|
),
|
|
pytest.param(
|
|
"TERMINAL_CWD=.\n", {}, None, ("TERMINAL_CWD",),
|
|
id="dotenv-dot-warns",
|
|
),
|
|
pytest.param(
|
|
f"TERMINAL_CWD={REAL_PATH}\n", {}, None, ("TERMINAL_CWD",),
|
|
id="dotenv-realpath-warns",
|
|
),
|
|
pytest.param(
|
|
"TERMINAL_CWD=.\n", {}, "terminal:\n cwd: /other/path\n",
|
|
("TERMINAL_CWD",),
|
|
id="dotenv-dot-warns-despite-config",
|
|
),
|
|
pytest.param(
|
|
f"TERMINAL_CWD={REAL_PATH}\n",
|
|
{"TERMINAL_CWD": REAL_PATH},
|
|
f"terminal:\n cwd: {REAL_PATH}\n",
|
|
("TERMINAL_CWD",),
|
|
id="dotenv-warns-despite-procenv-and-config",
|
|
),
|
|
pytest.param(
|
|
"TERMINAL_CWD=\n", {"TERMINAL_CWD": REAL_PATH}, None, (),
|
|
id="dotenv-empty-value-silent",
|
|
),
|
|
pytest.param(
|
|
"# TERMINAL_CWD=.\n", {"TERMINAL_CWD": "."}, None, (),
|
|
id="dotenv-commented-silent",
|
|
),
|
|
pytest.param(
|
|
f"export TERMINAL_CWD={REAL_PATH}\n", {}, None, ("TERMINAL_CWD",),
|
|
id="dotenv-export-prefix-warns",
|
|
),
|
|
pytest.param(
|
|
"TERMINAL_CWD=.\n", {"HERMES_QUIET": "1"}, None, ("TERMINAL_CWD",),
|
|
id="quiet-mode-no-gating-dotenv-warns",
|
|
),
|
|
pytest.param(
|
|
"API_KEY=x\n",
|
|
{"HERMES_QUIET": "1", "TERMINAL_CWD": "."},
|
|
None,
|
|
(),
|
|
id="quiet-mode-procenv-only-silent",
|
|
),
|
|
pytest.param(
|
|
"MESSAGING_CWD=/msg/path\n", {}, None, ("MESSAGING_CWD",),
|
|
id="dotenv-messaging-cwd-warns",
|
|
),
|
|
pytest.param(
|
|
"API_KEY=x\n", {"MESSAGING_CWD": "/msg/path"}, None, (),
|
|
id="procenv-messaging-cwd-silent",
|
|
),
|
|
pytest.param(
|
|
f"MESSAGING_CWD=/msg/path\nTERMINAL_CWD={REAL_PATH}\n",
|
|
{},
|
|
None,
|
|
("MESSAGING_CWD", "TERMINAL_CWD"),
|
|
id="dotenv-both-keys-warn",
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("env_file, proc_env, config_yaml, warn_keys", TRUTH_TABLE)
|
|
def test_deprecated_env_warning_truth_table(
|
|
env_file, proc_env, config_yaml, warn_keys, monkeypatch, tmp_path, capsys
|
|
):
|
|
"""Warning fires only for genuine .env-file entries — never for
|
|
inherited process environment or config.yaml (#88829 #89016 #89389 #90299).
|
|
"""
|
|
hermes_home = tmp_path / ".hermes"
|
|
hermes_home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
|
|
|
if env_file is not None:
|
|
(hermes_home / ".env").write_bytes(env_file.encode("utf-8"))
|
|
if config_yaml is not None:
|
|
(hermes_home / "config.yaml").write_text(config_yaml, encoding="utf-8")
|
|
|
|
# Baseline: deprecated keys absent from process env unless the row says so.
|
|
for key in ("TERMINAL_CWD", "MESSAGING_CWD", "HERMES_QUIET"):
|
|
monkeypatch.delenv(key, raising=False)
|
|
for key, value in proc_env.items():
|
|
monkeypatch.setenv(key, value)
|
|
|
|
import hermes_cli.config as config_module
|
|
|
|
# load_env() memoises on (path, mtime, size); invalidate so this row's
|
|
# freshly written file is what the scanner reads.
|
|
config_module.invalidate_env_cache()
|
|
|
|
config_module.warn_deprecated_cwd_env_vars()
|
|
|
|
err = capsys.readouterr().err
|
|
if warn_keys:
|
|
assert err.strip(), "expected a deprecation warning on stderr"
|
|
for key in warn_keys:
|
|
assert key in err, f"expected {key} in warning:\n{err}"
|
|
else:
|
|
assert err == "", f"expected silence, got:\n{err}"
|