[verified] Bind managed-lock test imports to HARNESS_ROOT
This commit is contained in:
@@ -1,5 +1,45 @@
|
|||||||
# Canonical harness tests
|
# Canonical harness tests
|
||||||
|
|
||||||
|
## Installation-root prerequisite
|
||||||
|
|
||||||
|
Before executing the suite, provide `HARNESS_ROOT` in the process environment.
|
||||||
|
It names the installation root containing `harness/` and `repositories/`, not
|
||||||
|
the `harness/` directory itself. For the current installation, configure the
|
||||||
|
current agent's `$HERMES_HOME/.env` with:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
HARNESS_ROOT=/opt/data/gitea-agent
|
||||||
|
```
|
||||||
|
|
||||||
|
The tests consume the environment supplied by Hermes; they do not open or
|
||||||
|
source `.env` files, search other profiles, or implement a second configuration
|
||||||
|
loader. When running outside Hermes, supply the variable explicitly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export HARNESS_ROOT=/opt/data/gitea-agent
|
||||||
|
python3 -B "$HARNESS_ROOT/harness/tests/run.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
On another system, replace the value with that installation's absolute root.
|
||||||
|
The managed-lock subprocess contention test requires a nonempty, existing
|
||||||
|
absolute directory and compares its resolved location with the installation
|
||||||
|
containing the test file. Missing, empty, relative, nonexistent, non-directory,
|
||||||
|
or different-installation values raise an explicit `HARNESS_ROOT` error before
|
||||||
|
creating a lock or starting the contention subprocess. There is no fallback to
|
||||||
|
`Path.cwd()` or to the test-file-derived root. Only the validated import root
|
||||||
|
is passed as a subprocess argument; no `.env` contents are read or serialized.
|
||||||
|
This test-only prerequisite does not change the production lock implementation
|
||||||
|
or the canonical runner's source-bound collection behavior; `--collect-only`
|
||||||
|
does not require the variable.
|
||||||
|
|
||||||
|
An external-cwd regression runs only the contention test from a temporary
|
||||||
|
working directory, with `PYTHONPATH` removed and an explicit fixture
|
||||||
|
`HARNESS_ROOT`, using isolated Python and bounded subprocess timeouts. Invalid
|
||||||
|
configuration coverage checks that neither lock acquisition nor subprocess
|
||||||
|
execution occurs. It does not recursively invoke the entire suite.
|
||||||
|
|
||||||
|
## Running the canonical suite
|
||||||
|
|
||||||
The canonical runner is anchored to its own source tree and works from any
|
The canonical runner is anchored to its own source tree and works from any
|
||||||
current directory. From the repository root:
|
current directory. From the repository root:
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,61 @@ class ManagedLockTests(unittest.TestCase):
|
|||||||
after = path.stat()
|
after = path.stat()
|
||||||
self.assertEqual((before.st_ino, before.st_size, before.st_mode), (after.st_ino, after.st_size, after.st_mode))
|
self.assertEqual((before.st_ino, before.st_size, before.st_mode), (after.st_ino, after.st_size, after.st_mode))
|
||||||
|
|
||||||
|
def test_lock_contention_from_external_cwd_without_pythonpath(self):
|
||||||
|
root = Path(__file__).resolve().parents[3]
|
||||||
|
script = """
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, sys.argv[1])
|
||||||
|
suite = unittest.defaultTestLoader.loadTestsFromName(
|
||||||
|
'harness.tests.common.test_managed_lock.ManagedLockTests.'
|
||||||
|
'test_other_process_cannot_acquire_while_context_alive')
|
||||||
|
result = unittest.TextTestRunner().run(suite)
|
||||||
|
raise SystemExit(not result.wasSuccessful())
|
||||||
|
"""
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.pop("PYTHONPATH", None)
|
||||||
|
env["HARNESS_ROOT"] = str(root)
|
||||||
|
with tempfile.TemporaryDirectory() as cwd:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-I", "-B", "-c", script, str(root)],
|
||||||
|
cwd=cwd, env=env, text=True, capture_output=True, timeout=30)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||||
|
|
||||||
|
def test_invalid_harness_root_stops_before_lock_and_subprocess(self):
|
||||||
|
with tempfile.TemporaryDirectory() as other:
|
||||||
|
file_path = Path(other) / "not-a-directory"
|
||||||
|
file_path.write_text("fixture")
|
||||||
|
for value in (None, "", "relative", other,
|
||||||
|
str(Path(other) / "missing"), str(file_path)):
|
||||||
|
with self.subTest(value=value), patch.dict(os.environ), \
|
||||||
|
patch(__name__ + ".ManagedLock") as lock, \
|
||||||
|
patch(__name__ + ".subprocess.run") as run:
|
||||||
|
if value is None:
|
||||||
|
os.environ.pop("HARNESS_ROOT", None)
|
||||||
|
else:
|
||||||
|
os.environ["HARNESS_ROOT"] = value
|
||||||
|
run.return_value.stdout = "blocked"
|
||||||
|
error = None
|
||||||
|
try:
|
||||||
|
self.test_other_process_cannot_acquire_while_context_alive()
|
||||||
|
except Exception as exc:
|
||||||
|
error = exc
|
||||||
|
self.assertIsInstance(error, RuntimeError)
|
||||||
|
self.assertIn("HARNESS_ROOT", str(error))
|
||||||
|
lock.assert_not_called()
|
||||||
|
run.assert_not_called()
|
||||||
|
|
||||||
def test_other_process_cannot_acquire_while_context_alive(self):
|
def test_other_process_cannot_acquire_while_context_alive(self):
|
||||||
|
value = os.environ.get("HARNESS_ROOT")
|
||||||
|
if not value:
|
||||||
|
raise RuntimeError("HARNESS_ROOT must be configured")
|
||||||
|
root = Path(value)
|
||||||
|
if not root.is_absolute() or not root.is_dir():
|
||||||
|
raise RuntimeError("HARNESS_ROOT must be an existing absolute directory")
|
||||||
|
root = root.resolve()
|
||||||
|
if root != Path(__file__).resolve().parents[3]:
|
||||||
|
raise RuntimeError("HARNESS_ROOT does not match this test installation")
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as d:
|
||||||
path = Path(d) / "process.lock"
|
path = Path(d) / "process.lock"
|
||||||
script = """
|
script = """
|
||||||
@@ -255,7 +309,7 @@ except LockTimeoutError:
|
|||||||
print('blocked')
|
print('blocked')
|
||||||
"""
|
"""
|
||||||
with ManagedLock(path):
|
with ManagedLock(path):
|
||||||
result = subprocess.run([sys.executable, "-c", script, str(path), str(Path.cwd())], text=True, capture_output=True, check=True)
|
result = subprocess.run([sys.executable, "-c", script, str(path), str(root)], text=True, capture_output=True, check=True, timeout=30)
|
||||||
self.assertEqual(result.stdout.strip(), "blocked")
|
self.assertEqual(result.stdout.strip(), "blocked")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user