Files
hermes-agent/tests/pm/test_project_relock.py
ethernet 99768fd127 feat(pm): hermes pm lock relocks uv.lock after a pyproject edit
Contributors had to run `python -m pm.build_env --source . --lock-only`, then
re-source activate, then call sync_venv for opt-in extras, while `hermes pm
lock` only pinned tool artifacts in pm/lock.json. Now `hermes pm lock` with no
arguments is the one step: it runs the same PM check_project_lock /
lock_project operations as build_env's --check-lock / --lock-only (same
exclude-newer quarantine), writes nothing when the lock is current, changes
no environment, and prints the activate command to run next. Activation
syncs [all] plus recorded extras only, and --test-extras replaces the
default, so a newly added extra outside [all] gets
`source ./activate --test-extras all,NAME` (`-TestExtras` on PowerShell).

`hermes pm lock --bump NAME VERSION` keeps writing pm/lock.json; NAME and
VERSION are now only accepted together. build_env --lock-only is unchanged
for scripts. Contributor docs, AGENTS.md, CONTRIBUTING.es.md, the pyproject
comment, and the uv.lock CI remediation text point at `hermes pm lock`.
2026-09-24 15:55:08 -04:00

63 lines
1.9 KiB
Python

"""`hermes pm lock` with no arguments relocks uv.lock through PM and nothing else."""
from __future__ import annotations
from argparse import Namespace
import pm
from pm import cli
from pm.package import InstallError
PYPROJECT = """\
[project]
name = "demo"
version = "0"
[project.optional-dependencies]
alpha = ["six"]
beta = ["idna"]
all = ["demo[alpha]"]
"""
LOCK = """\
version = 1
[[package]]
name = "demo"
version = "0"
source = { editable = "." }
[package.metadata]
provides-extras = ["alpha", "all"]
"""
def test_relock_writes_nothing_when_current_and_names_new_opt_in_extras(tmp_path, monkeypatch, capsys):
(tmp_path / "pyproject.toml").write_text(PYPROJECT, encoding="utf-8")
(tmp_path / "uv.lock").write_text(LOCK, encoding="utf-8")
monkeypatch.setattr(cli, "repo_root", lambda: tmp_path)
stale = []
relocks = []
def check(source, **kw):
assert (source, kw.get("explicit")) == (tmp_path, True)
if stale:
raise InstallError("venv", "The lockfile at `uv.lock` needs to be updated")
monkeypatch.setattr(pm, "check_project_lock", check)
monkeypatch.setattr(pm, "lock_project", lambda source, **kw: relocks.append((source, kw)))
monkeypatch.setattr(pm, "sync_venv", lambda *a, **kw: relocks.append("sync"))
before = (tmp_path / "uv.lock").read_bytes()
assert cli.cmd_lock(Namespace(name=None, version=None)) == 0
assert relocks == []
assert (tmp_path / "uv.lock").read_bytes() == before
assert "already current" in capsys.readouterr().out
stale.append(True)
assert cli.cmd_lock(Namespace(name=None, version=None)) == 0
# Relocking never syncs an environment; activation owns that.
assert relocks == [(tmp_path, {"explicit": True})]
out = capsys.readouterr().out
assert "activate" in out
# beta is new and outside [all]; alpha was already locked and is in [all].
assert "all,beta" in out and "alpha" not in out