Files
hermes-agent/tests/pm/test_environment_script.py
ethernet b4d5092d3f refactor(activate): split interactive activation from running a command in the env
`activate` doubled as the thing scripts sourced non-interactively, so it
carried `--` argument guards and every caller had to tolerate its prompt and
`hermes` function. Composition (setup sync, bootstrap Python, pm env) now
lives in scripts/_activation.sh, and there are two consumers:

- `activate`: interactive, reversible (`deactivate`).
- new `scripts/run-in-hermes-env CMD...`: applies the env to one process and
  execs into it. Replaces `scripts/_hermes-python`; every Python script's
  shebang and `scripts/run_tests.sh` (which re-executes itself under it,
  however it was launched) use it.

`pm.environments --format sh` emits the env as shell exports, so the inline
JSON-to-export Python is gone from `activate`. An install that did not build
the test environment now reads as stale for every consumer.

Tests are split along the same seams: activation support fixtures, bash/ps1
activation, env-script quoting, and the runner's staleness.
2026-10-02 18:15:08 -04:00

43 lines
1.4 KiB
Python

"""``pm.environments.shell_exports``: the composed environment as a script a shell can evaluate.
Both ``activate`` and ``scripts/run-in-hermes-env`` evaluate this output, so the
contract is what a real shell reads back: any value survives the quoting, and
names the shell cannot assign are left out instead of failing the whole script.
"""
from __future__ import annotations
import subprocess
import pytest
from pm.environments import shell_exports
from tests.pm.activation_support import bash
NASTY = {
"PLAIN": "value",
"SPACES": "a b c",
"QUOTES": "it's a \"test\"",
"SHELL_SYNTAX": "$HOME `id` $(id) ; & | > < * ? [x] ~ #",
"BACKSLASHES": "C:\\path\\to\\ \\n \\'",
"EMPTY": "",
"MULTILINE": "first\nsecond",
}
# Prints each value NUL-terminated, in the order of NASTY.
PROBE = "printf '%s\\0' " + " ".join(f'"${name}"' for name in NASTY)
@pytest.mark.platforms("posix")
def test_a_real_shell_reads_every_value_back_unchanged():
script = shell_exports(NASTY) + "\n" + PROBE
run = subprocess.run([bash(), "-c", script], capture_output=True, text=True, timeout=30)
assert run.returncode == 0, run.stderr
assert run.stdout.split("\0")[:-1] == list(NASTY.values())
def test_names_no_shell_can_assign_are_left_out():
script = shell_exports({"ProgramFiles(ARM)": "x", "1BAD": "x", "GOOD": "y"})
assert "GOOD" in script
assert "ProgramFiles" not in script and "1BAD" not in script