Download scripts/run_tests.sh from SaylorTwift/hermes-agent: direct link, hf CLI and curl.
- Browser
- Download file 9.02 kB
-
https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/scripts/run_tests.sh
- Command line
-
hf download hf://SaylorTwift/hermes-agent/scripts/run_tests.sh
-
curl -L -o run_tests.sh https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/scripts/run_tests.sh
9.02 kB
| # Canonical test runner for hermes-agent. Run this instead of calling | |
| # `pytest` directly to guarantee your local run matches CI behavior. | |
| # | |
| # What this script enforces: | |
| # * Per-file isolation via scripts/run_tests_parallel.py β each test | |
| # file runs in its own freshly-spawned `python -m pytest <file>` | |
| # subprocess. No xdist, no shared workers, no module-level leakage | |
| # between files. | |
| # * TZ=UTC, LANG=C.UTF-8, PYTHONHASHSEED=0 (deterministic) | |
| # * Env vars blanked (conftest.py also does this, but this | |
| # is belt-and-suspenders for anyone running pytest outside our | |
| # conftest path β e.g. on a single file) | |
| # * Proper venv activation (probes .venv, venv, then ~/.hermes/...) | |
| # | |
| # Usage: | |
| # scripts/run_tests.sh # full suite | |
| # scripts/run_tests.sh -j 4 # cap parallelism | |
| # scripts/run_tests.sh tests/agent/ # discover only here | |
| # scripts/run_tests.sh tests/agent/ tests/acp_adapter/ # multiple roots | |
| # scripts/run_tests.sh tests/foo.py # single file | |
| # scripts/run_tests.sh tests/foo.py -q # path + bare pytest flag | |
| # scripts/run_tests.sh tests/foo.py -v --tb=long # bare flags "just work" | |
| # scripts/run_tests.sh -k 'pattern' # value flags pass through too | |
| # scripts/run_tests.sh tests/foo.py -- --tb=long # explicit '--' still works | |
| # | |
| # Bare pytest flags (anything starting with '-' that isn't one of this | |
| # runner's own options: -j/--jobs, --paths, --slice, --file-timeout, etc.) | |
| # are forwarded to each per-file pytest invocation automatically β no '--' | |
| # separator required. The explicit '--' form still works and stacks with | |
| # bare flags. Positional path arguments override the default discovery | |
| # root (tests/). | |
| set -euo pipefail | |
| # ββ Locate repo root ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" | |
| # ββ Locate python βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Probe local venvs first; fall back to the Nix devShell's editable venv | |
| # (HERMES_PYTHON is exported by the devShell hook and ships [dev] extras: | |
| # pytest, pytest-asyncio, pytest-timeout, ruff, ty). | |
| # | |
| # A candidate must have pytest INSTALLED, not merely exist. The release venv | |
| # at ~/.hermes/hermes-agent/venv has bin/activate but no pytest, so an | |
| # existence-only probe selected it in checkouts/worktrees without a local | |
| # .venv β every file then died with "No module named pytest" and the run | |
| # reported "0 tests passed" (which reads green at a glance even though the | |
| # exit code is 1). Skip such a venv and keep probing instead. | |
| VENV="" | |
| VENV_PYTHON="" | |
| SKIPPED_VENVS="" | |
| for candidate in "$REPO_ROOT/.venv" "$REPO_ROOT/venv" "$HOME/.hermes/hermes-agent/venv"; do | |
| if [ -f "$candidate/bin/activate" ]; then | |
| if "$candidate/bin/python" -c 'import pytest' 2>/dev/null; then | |
| VENV="$candidate" | |
| VENV_PYTHON="$candidate/bin/python" | |
| break | |
| fi | |
| SKIPPED_VENVS="$SKIPPED_VENVS $candidate" | |
| fi | |
| # Native Windows venv layout: python.exe and activate live under | |
| # Scripts/, and there is no bin/. Anyone running this script from | |
| # Git Bash / MSYS with a `python -m venv`- or uv-created venv hits | |
| # this branch β without it the canonical runner refuses to start. | |
| if [ -f "$candidate/Scripts/activate" ]; then | |
| if "$candidate/Scripts/python.exe" -c 'import pytest' 2>/dev/null; then | |
| VENV="$candidate" | |
| VENV_PYTHON="$candidate/Scripts/python.exe" | |
| break | |
| fi | |
| SKIPPED_VENVS="$SKIPPED_VENVS $candidate" | |
| fi | |
| done | |
| if [ -n "$SKIPPED_VENVS" ]; then | |
| for skipped in $SKIPPED_VENVS; do | |
| echo "βΆ skipping venv without pytest: $skipped" >&2 | |
| done | |
| fi | |
| if [ -n "$VENV" ]; then | |
| PYTHON="$VENV_PYTHON" | |
| elif [ -n "${HERMES_PYTHON:-}" ] && [ -x "$HERMES_PYTHON" ] \ | |
| && "$HERMES_PYTHON" -c 'import pytest' 2>/dev/null; then | |
| # Guard with an import check: HERMES_PYTHON may point at the RELEASE | |
| # venv (no pytest) when inherited from a wrapped `hermes` binary rather | |
| # than the devShell hook. | |
| PYTHON="$HERMES_PYTHON" | |
| echo "βΆ no local venv β using Nix dev venv via HERMES_PYTHON: $PYTHON" | |
| else | |
| echo "error: no virtualenv with pytest found in $REPO_ROOT/.venv or $REPO_ROOT/venv," >&2 | |
| echo " and HERMES_PYTHON is not a python with pytest (enter the Nix devShell or create a venv)" >&2 | |
| if [ -n "$SKIPPED_VENVS" ]; then | |
| echo " (skipped for missing pytest:$SKIPPED_VENVS β install dev extras there, or create $REPO_ROOT/.venv)" >&2 | |
| fi | |
| exit 1 | |
| fi | |
| # ββ Live-gateway plugin (computed before we drop env) βββββββββββββββββββββββ | |
| EXTRA_PYTHONPATH="" | |
| EXTRA_PYTEST_PLUGINS="" | |
| if [ -f "$HOME/.hermes/pytest_live_guard.py" ]; then | |
| EXTRA_PYTHONPATH="$HOME/.hermes" | |
| EXTRA_PYTEST_PLUGINS="pytest_live_guard" | |
| fi | |
| # ββ Windows location variables (computed before we drop env) βββββββββββββββ | |
| # `env -i` forwards HOME, which is enough on POSIX. Native Windows CPython | |
| # resolves Path.home() from USERPROFILE (or HOMEDRIVE+HOMEPATH), stdlib | |
| # platform paths come from LOCALAPPDATA/APPDATA, ssl/sockets need SYSTEMROOT, | |
| # and tempfile needs TEMP/TMP. Dropping them breaks collection on native | |
| # Windows (issues #67385, #70813). These are location variables, not | |
| # credentials, so forwarding them keeps the isolation intent intact. Each is | |
| # only forwarded when actually set, so POSIX runs are byte-for-byte unchanged. | |
| WIN_ENV=() | |
| for _win_var in USERPROFILE HOMEDRIVE HOMEPATH LOCALAPPDATA APPDATA SYSTEMROOT TEMP TMP; do | |
| if [ -n "${!_win_var:-}" ]; then | |
| WIN_ENV+=("$_win_var=${!_win_var}") | |
| fi | |
| done | |
| # ββ Test-runner knobs (computed before we drop env) ββββββββββββββββββββββββ | |
| # The runner's own documented environment knobs must survive the hermetic | |
| # `env -i` below, or they are silent no-ops for anyone invoking this script: | |
| # | |
| # * HERMES_TEST_WORKERS / PATHS / FILE_TIMEOUT / FILE_RETRIES / SLICE are | |
| # read by run_tests_parallel.py at argparse-default time β inside the | |
| # stripped environment. | |
| # * HERMES_TEST_IMAGE is read by tests/docker/conftest.py to skip its | |
| # session-scoped `docker build`. CI's docker.yml sets it to the image | |
| # the build step just loaded; stripping it made every per-file pytest | |
| # subprocess rebuild the 5GB image from a cold builder cache instead | |
| # (~4 min per worker per run, and the rebuilt image lacked the | |
| # HERMES_GIT_SHA build-arg the workflow bakes in). | |
| # | |
| # These are test-infrastructure knobs, not credentials β same class as the | |
| # HERMES_RUN_SLOW_PET_TESTS / HERMES_E2E_BROWSER opt-ins already forwarded. | |
| # Keep this an explicit allowlist (no HERMES_TEST_* glob) so the "no | |
| # credential can leak" property stays auditable at a glance. | |
| TEST_ENV=() | |
| for _test_var in HERMES_TEST_IMAGE HERMES_TEST_WORKERS HERMES_TEST_PATHS \ | |
| HERMES_TEST_FILE_TIMEOUT HERMES_TEST_FILE_RETRIES HERMES_TEST_SLICE; do | |
| if [ -n "${!_test_var:-}" ]; then | |
| TEST_ENV+=("$_test_var=${!_test_var}") | |
| fi | |
| done | |
| # ββ Run in hermetic env ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # env -i: start with empty environment, opt-in only what we need. | |
| # No credential var can leak β you'd have to explicitly add it here. | |
| echo "βΆ running per-file parallel test suite via run_tests_parallel.py" | |
| echo " (TZ=UTC LANG=C.UTF-8 PYTHONHASHSEED=0; clean env)" | |
| cd "$REPO_ROOT" | |
| # ββ Pre-compile .pyc bytecode cache βββββββββββββββββββββββββββββββββββββββββ | |
| # Each test file runs in its own subprocess via run_tests_parallel.py. | |
| # Pre-building the bytecode cache once here (instead of each subprocess | |
| # compiling on first import) avoids redundant work across ~2000 processes. | |
| # Uses git to list tracked .py files (skips venv, node_modules, etc). | |
| echo "βΆ pre-compiling bytecode cache" | |
| "$PYTHON" -m compileall -q -j 0 -- $(git ls-files '*.py') >/dev/null 2>&1 || true | |
| echo "βΆ launching test runner" | |
| exec env -i \ | |
| PATH="$PATH" \ | |
| HOME="$HOME" \ | |
| ${WIN_ENV[@]+"${WIN_ENV[@]}"} \ | |
| ${TEST_ENV[@]+"${TEST_ENV[@]}"} \ | |
| TZ=UTC \ | |
| LANG=C.UTF-8 \ | |
| LC_ALL=C.UTF-8 \ | |
| PYTHONHASHSEED=0 \ | |
| PYTHONUTF8=1 \ | |
| ${HERMES_RUN_SLOW_PET_TESTS:+HERMES_RUN_SLOW_PET_TESTS="$HERMES_RUN_SLOW_PET_TESTS"} \ | |
| ${HERMES_E2E_BROWSER:+HERMES_E2E_BROWSER="$HERMES_E2E_BROWSER"} \ | |
| ${EXTRA_PYTHONPATH:+PYTHONPATH="$EXTRA_PYTHONPATH"} \ | |
| ${EXTRA_PYTEST_PLUGINS:+PYTEST_PLUGINS="$EXTRA_PYTEST_PLUGINS"} \ | |
| "$PYTHON" "$SCRIPT_DIR/run_tests_parallel.py" "$@" | |