mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
`hermes dump` and the startup banner both call `git rev-parse HEAD` to
report the running commit, but `.dockerignore` line 2 excludes `.git` —
so inside the published image `hermes dump` shows
`version: ... [(unknown)]` and the banner drops its `· upstream <sha>`
suffix entirely. That makes support triage from container bug reports
impossible: we can't tell which commit the user is actually running.
Fix: thread the build-time SHA through as a Docker build-arg, write it
to `/opt/hermes/.hermes_build_sha` in the image, and have a new
`hermes_cli/build_info.get_build_sha()` read it as a fallback after the
existing live-git lookup fails. Output format is unchanged in both
callsites — same 8-char short SHA whether resolved live or baked.
Wiring:
- Dockerfile: `ARG HERMES_GIT_SHA=` + write-file step after the source
copy. Empty/missing arg → no file written → callers fall through to
live git (so local `docker build` without --build-arg is unchanged).
- docker-publish.yml: passes `HERMES_GIT_SHA=${{ github.sha }}` on all
four build-push-action steps (amd64/arm64, smoke-test + final push).
- dump.py:_get_git_commit() / banner.py:get_git_banner_state(): try
live git first, fall back to baked SHA, then to legacy `(unknown)`
/ None. Banner returns `upstream == local, ahead=0` because a built
image is by definition pinned to one commit.
Coverage:
- Unit tests cover build_info (file present/absent/empty/error,
truncation, whitespace), dump (live-git wins, both fallbacks,
identical output-format regression guard), and banner (no-repo +
baked, no-repo + no-sha, shallow-clone fallback).
- tests/docker/test_dump_build_sha.py is an integration regression
guard that runs against the real image, reads
`/opt/hermes/.hermes_build_sha`, and asserts `hermes dump` surfaces
its content (or stays at `(unknown)` if no file).
- Verified end-to-end: `docker build --build-arg HERMES_GIT_SHA=abc...`
→ `docker run ... dump` reports `[abc12345]`; without the build-arg
it reports `[(unknown)]` as before.
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
"""Tests for hermes_cli.build_info — baked-in build SHA resolution.
|
|
|
|
The build SHA is written by the Dockerfile's ``HERMES_GIT_SHA`` build-arg
|
|
into ``<project_root>/.hermes_build_sha``. These tests cover the read-side
|
|
helper: missing file, malformed file, truncation, and error tolerance.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
|
|
def test_get_build_sha_returns_none_when_file_absent(tmp_path):
|
|
"""Source installs: no file present → None, callers fall back to git."""
|
|
from hermes_cli import build_info
|
|
|
|
missing = tmp_path / ".hermes_build_sha" # never created
|
|
|
|
with patch.object(build_info, "_BUILD_SHA_FILE", missing):
|
|
assert build_info.get_build_sha() is None
|
|
|
|
|
|
def test_get_build_sha_reads_baked_file(tmp_path):
|
|
"""Docker image case: file exists with full 40-char SHA → truncated to 8."""
|
|
from hermes_cli import build_info
|
|
|
|
sha_file = tmp_path / ".hermes_build_sha"
|
|
sha_file.write_text("abcdef1234567890abcdef1234567890abcdef12\n")
|
|
|
|
with patch.object(build_info, "_BUILD_SHA_FILE", sha_file):
|
|
assert build_info.get_build_sha() == "abcdef12"
|
|
|
|
|
|
def test_get_build_sha_respects_short_argument(tmp_path):
|
|
"""``short=N`` truncates to N chars; ``short<=0`` returns full SHA."""
|
|
from hermes_cli import build_info
|
|
|
|
sha_file = tmp_path / ".hermes_build_sha"
|
|
full_sha = "abcdef1234567890abcdef1234567890abcdef12"
|
|
sha_file.write_text(full_sha + "\n")
|
|
|
|
with patch.object(build_info, "_BUILD_SHA_FILE", sha_file):
|
|
assert build_info.get_build_sha(short=12) == "abcdef123456"
|
|
assert build_info.get_build_sha(short=0) == full_sha
|
|
assert build_info.get_build_sha(short=-1) == full_sha
|
|
|
|
|
|
def test_get_build_sha_strips_whitespace(tmp_path):
|
|
"""The Dockerfile uses ``printf '%s\\n'`` — strip the trailing newline."""
|
|
from hermes_cli import build_info
|
|
|
|
sha_file = tmp_path / ".hermes_build_sha"
|
|
sha_file.write_text(" abcdef1234567890\n\n")
|
|
|
|
with patch.object(build_info, "_BUILD_SHA_FILE", sha_file):
|
|
assert build_info.get_build_sha() == "abcdef12"
|
|
|
|
|
|
def test_get_build_sha_returns_none_for_empty_file(tmp_path):
|
|
"""A whitespace-only file is treated as absent."""
|
|
from hermes_cli import build_info
|
|
|
|
sha_file = tmp_path / ".hermes_build_sha"
|
|
sha_file.write_text(" \n\n")
|
|
|
|
with patch.object(build_info, "_BUILD_SHA_FILE", sha_file):
|
|
assert build_info.get_build_sha() is None
|
|
|
|
|
|
def test_get_build_sha_swallows_read_errors(tmp_path):
|
|
"""Any IO exception from the read returns None — never raises."""
|
|
from hermes_cli import build_info
|
|
|
|
sha_file = tmp_path / ".hermes_build_sha"
|
|
sha_file.write_text("abcdef1234567890\n")
|
|
|
|
with patch.object(build_info, "_BUILD_SHA_FILE", sha_file), \
|
|
patch.object(Path, "read_text", side_effect=OSError("boom")):
|
|
assert build_info.get_build_sha() is None
|