test(harness): reorganize regression suites and preserve contract coverage
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from harness.tests._support.paths import ROOT
|
||||
SCRIPT = ROOT / 'harness/agents/review/skills/gitea-code-review/scripts/native_review.py'
|
||||
|
||||
|
||||
def load(path, name):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
from harness.tests._support.review import (
|
||||
FakeAPI,
|
||||
FakeLease,
|
||||
plan_fixture,
|
||||
publication_fixture,
|
||||
support,
|
||||
)
|
||||
class TestNativeLease(unittest.TestCase):
|
||||
def module(self):
|
||||
self.assertTrue(SCRIPT.is_file(), 'shared native review adapter is missing')
|
||||
m = load(SCRIPT, 'native_review_test_' + str(id(self)))
|
||||
# Every test gets a private registry root; production has no test-only
|
||||
# root override, so isolate through the journal module seam.
|
||||
private_root = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(private_root.cleanup)
|
||||
root = Path(private_root.name) / 'native-review-journals'
|
||||
m._journal.root_for = lambda anchor, root=root: root
|
||||
return m
|
||||
@staticmethod
|
||||
def canonical(m, plan):
|
||||
return m.canonical_journal_path(plan)
|
||||
def test_held_lease_checks_real_registered_worktree_and_expiry(self):
|
||||
import fcntl
|
||||
m = self.module()
|
||||
self.assertTrue(callable(getattr(m, 'HeldIssueLease', None)), 'real Issue lease guard is missing')
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td); policy = root / 'harness/operations/repository-cache.md'
|
||||
policy.parent.mkdir(parents=True); policy.write_text('canonical cache fixture')
|
||||
rs = root / 'repositories'; owner = rs / 'acme'; cache = owner / 'repo'; locks = cache / 'locks'
|
||||
for directory in [rs, owner, cache, locks]: directory.mkdir(mode=0o700)
|
||||
bare = cache / 'repository.git'; seed = root / 'seed'
|
||||
def git(*args):
|
||||
out = subprocess.run(['git', '-c', 'core.hooksPath=/dev/null', *map(str, args)], capture_output=True, text=True,
|
||||
env={'PATH': os.environ['PATH'], 'GIT_CONFIG_GLOBAL': '/dev/null', 'GIT_CONFIG_NOSYSTEM': '1'})
|
||||
self.assertEqual(out.returncode, 0, out.stderr)
|
||||
return out.stdout.strip()
|
||||
git('init', '--bare', bare); git('init', seed)
|
||||
git('-C', seed, 'config', 'user.name', 'Synthetic'); git('-C', seed, 'config', 'user.email', 'fixture@example.test')
|
||||
(seed / 'a.txt').write_text('synthetic')
|
||||
git('-C', seed, 'add', 'a.txt'); git('-C', seed, 'commit', '-m', 'fixture')
|
||||
git('--git-dir=' + str(bare), 'fetch', seed, 'HEAD:refs/heads/main')
|
||||
wt = cache / 'worktrees/issue-3/checkout'; wt.parent.mkdir(parents=True, mode=0o700)
|
||||
git('--git-dir=' + str(bare), 'worktree', 'add', '--detach', '--lock', wt, 'main')
|
||||
head = git('-C', wt, 'rev-parse', 'HEAD')
|
||||
p = plan_fixture(); p['target']['head'] = head; p['body'] = p['body'].replace('a' * 40, head)
|
||||
state = wt.parent / 'state.json'
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'acme', 'repository': 'repo',
|
||||
'issue': 3, 'created_by_role': 'gitea-impl-agent', 'mode': 'detached'}))
|
||||
state.chmod(0o600)
|
||||
path = locks / 'issue-3.lock'; fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o600)
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
lease = m.HeldIssueLease(fd, policy, time.monotonic() + 30)
|
||||
canonical_bytes = state.read_bytes(); canonical_stat = state.stat()
|
||||
lease.check(p)
|
||||
self.assertEqual(state.read_bytes(), canonical_bytes)
|
||||
self.assertEqual((state.stat().st_mode, state.stat().st_mtime_ns, state.stat().st_ino),
|
||||
(canonical_stat.st_mode, canonical_stat.st_mtime_ns, canonical_stat.st_ino))
|
||||
# The bounded compatibility admission is read-only: mutate only
|
||||
# this scratch fixture from canonical producer state to legacy impl.
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'acme', 'repository': 'repo',
|
||||
'issue': 3, 'created_by_role': 'impl', 'mode': 'detached'}))
|
||||
state.chmod(0o600)
|
||||
legacy_bytes = state.read_bytes(); legacy_stat = state.stat()
|
||||
try:
|
||||
lease.check(p)
|
||||
except m.ValidationError as exc:
|
||||
self.fail(f'valid legacy impl registration must be admitted: {exc}')
|
||||
after_stat = state.stat()
|
||||
self.assertEqual(state.read_bytes(), legacy_bytes)
|
||||
self.assertEqual((after_stat.st_mode, after_stat.st_mtime_ns, after_stat.st_ino),
|
||||
(legacy_stat.st_mode, legacy_stat.st_mtime_ns, legacy_stat.st_ino))
|
||||
for bad_role in [None, True, False, 1, [], {}, '', ' impl', 'impl ', 'Impl', 'IMPL', 'gitea-impl-agent ', 'review', 'gitea-review-agent', 'gitea-spec-agent', 'gitea-release-agent', 'other']:
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'acme', 'repository': 'repo',
|
||||
'issue': 3, 'created_by_role': bad_role, 'mode': 'detached'}))
|
||||
state.chmod(0o600)
|
||||
with self.subTest(bad_role=repr(bad_role)), self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'):
|
||||
lease.check(p)
|
||||
missing_role = json.loads(legacy_bytes); missing_role.pop('created_by_role')
|
||||
state.write_text(json.dumps(missing_role)); state.chmod(0o600)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'): lease.check(p)
|
||||
bool_issue = json.loads(legacy_bytes); bool_issue['issue'] = True
|
||||
state.write_text(json.dumps(bool_issue)); state.chmod(0o600)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'): lease.check(p)
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'other', 'repository': 'repo',
|
||||
'issue': 3, 'created_by_role': 'impl', 'mode': 'detached'})); state.chmod(0o600)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'): lease.check(p)
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'acme', 'repository': 'other',
|
||||
'issue': 3, 'created_by_role': 'impl', 'mode': 'detached'})); state.chmod(0o600)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'): lease.check(p)
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'acme', 'repository': 'repo',
|
||||
'issue': 4, 'created_by_role': 'impl', 'mode': 'detached'})); state.chmod(0o600)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'): lease.check(p)
|
||||
state.write_text(json.dumps({'schema': 'wrong/v1', 'owner': 'acme', 'repository': 'repo',
|
||||
'issue': 3, 'created_by_role': 'impl', 'mode': 'detached'})); state.chmod(0o600)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'): lease.check(p)
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'acme', 'repository': 'repo',
|
||||
'issue': 3, 'created_by_role': 'impl', 'mode': 'attached'})); state.chmod(0o600)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree registration identity mismatch'): lease.check(p)
|
||||
state.write_text(json.dumps({'schema': 'gitea-agent.worktree/v1', 'owner': 'acme', 'repository': 'repo',
|
||||
'issue': 3, 'created_by_role': 'impl', 'mode': 'detached'})); state.chmod(0o600)
|
||||
wrong_head = copy.deepcopy(p); wrong_head['target']['head'] = 'b' * 40
|
||||
wrong_head['body'] = wrong_head['body'].replace(head, 'b' * 40)
|
||||
with self.assertRaisesRegex(m.ValidationError, 'worktree HEAD mismatch'): lease.check(wrong_head)
|
||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||
with self.assertRaises(m.ValidationError): lease.check(p)
|
||||
self.assertNotIn('lock:', Path(f'/proc/self/fdinfo/{fd}').read_text())
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
from unittest.mock import patch
|
||||
fakebin = root / 'fakebin'; fakebin.mkdir()
|
||||
marker = root / 'path-git-executed'
|
||||
fakegit = fakebin / 'git'
|
||||
fakegit.write_text(f'#!/bin/sh\n/usr/bin/touch "{marker}"\nexit 1\n'); fakegit.chmod(0o700)
|
||||
with patch.dict(os.environ, {'PATH': str(fakebin)}): lease.check(p)
|
||||
self.assertFalse(marker.exists())
|
||||
lease.deadline_monotonic = time.monotonic() - 1
|
||||
with self.assertRaises(m.ValidationError): lease.check(p)
|
||||
lease.deadline_monotonic = time.monotonic() + 30
|
||||
(wt / 'a.txt').write_text('dirty')
|
||||
with self.assertRaises(m.ValidationError): lease.check(p)
|
||||
# Every failed source validation must close all transient FDs.
|
||||
before_fds = len(list((Path('/proc/self/fd')).iterdir()))
|
||||
for _ in range(20):
|
||||
with self.assertRaises(m.ValidationError): lease.check(p)
|
||||
after_fds = len(list((Path('/proc/self/fd')).iterdir()))
|
||||
self.assertLessEqual(after_fds, before_fds + 1)
|
||||
(wt / 'a.txt').unlink()
|
||||
for directory in [wt.parent.parent, wt.parent, wt]:
|
||||
directory.chmod(0o770)
|
||||
with self.subTest(directory=directory), self.assertRaises(m.ValidationError):
|
||||
lease.check(p)
|
||||
directory.chmod(0o700)
|
||||
finally:
|
||||
os.close(fd)
|
||||
def test_false_lease_result_does_not_authorize_publication(self):
|
||||
m = self.module(); p, before, after, exact = publication_fixture(); api = FakeAPI(p, before, after, exact)
|
||||
lease = FakeLease(); lease.check = lambda plan: False
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
with self.assertRaises(m.ValidationError): m.publish(p, before, api, lease, self.canonical(m, p), declaration={'mode': 'none'})
|
||||
self.assertEqual(api.posts, 0)
|
||||
def test_expired_lease_does_not_create_new_journal_directory(self):
|
||||
m = self.module(); p, before, after, exact = publication_fixture()
|
||||
lease = FakeLease(); lease.valid = False
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
directory = Path(td) / 'not-created'
|
||||
with self.assertRaises(m.ValidationError): m.publish(p, before, FakeAPI(p, before, after, exact), lease, directory)
|
||||
self.assertFalse(directory.exists())
|
||||
def test_broker_issue_lease_accepts_authenticated_matching_status(self):
|
||||
m = self.module(); p, before, after, exact = publication_fixture()
|
||||
now = time.monotonic()
|
||||
lease = m.BrokerIssueLease('/trusted/handle.json', '/trusted/policy.md', now + 30)
|
||||
handle = {'owner': 'acme', 'repository': 'repo', 'issue': 3, 'role': 'gitea-review-agent',
|
||||
'pid': 1234, 'deadline': now + 20, 'token': 'not-returned', 'socket': '/trusted/broker.sock'}
|
||||
status = {'status': 'held', 'owner': 'acme', 'repository': 'repo', 'issue': 3,
|
||||
'role': 'gitea-review-agent', 'pid': 1234, 'deadline': handle['deadline']}
|
||||
from unittest.mock import patch
|
||||
with patch.object(m._broker, 'read_handle_metadata', return_value={k: v for k, v in handle.items() if k != 'token'}), \
|
||||
patch.object(m._broker, 'request_handle', return_value=status), \
|
||||
patch.object(m, '_validate_registered_source') as source:
|
||||
self.assertIsNone(lease.check(p))
|
||||
source.assert_called_once_with(p, '/trusted/policy.md', lease.deadline_monotonic)
|
||||
def test_broker_issue_lease_rejects_wrong_identity_status_or_deadline(self):
|
||||
m = self.module(); p, before, after, exact = publication_fixture(); now = time.monotonic()
|
||||
lease = m.BrokerIssueLease('/trusted/handle.json', '/trusted/policy.md', now + 30)
|
||||
handle = {'pid': 1234, 'deadline': now + 20, 'token': 'hidden', 'socket': '/trusted/broker.sock'}
|
||||
cases = [
|
||||
{'status': 'held', 'owner': 'acme', 'repository': 'repo', 'issue': 3, 'role': 'gitea-spec-agent', 'pid': 1234, 'deadline': handle['deadline']},
|
||||
{'status': 'held', 'owner': 'other', 'repository': 'repo', 'issue': 3, 'role': 'gitea-review-agent', 'pid': 1234, 'deadline': handle['deadline']},
|
||||
{'status': 'released', 'owner': 'acme', 'repository': 'repo', 'issue': 3, 'role': 'gitea-review-agent', 'pid': 1234, 'deadline': handle['deadline']},
|
||||
{'status': 'held', 'owner': 'acme', 'repository': 'repo', 'issue': 3, 'role': 'gitea-review-agent', 'pid': 9999, 'deadline': handle['deadline']},
|
||||
{'status': 'held', 'owner': 'acme', 'repository': 'repo', 'issue': 3, 'role': 'gitea-review-agent', 'pid': 1234, 'deadline': now + 40},
|
||||
]
|
||||
from unittest.mock import patch
|
||||
for status in cases:
|
||||
with self.subTest(status=status):
|
||||
with patch.object(m._broker, 'read_handle_metadata', return_value={k: v for k, v in handle.items() if k != 'token'}), \
|
||||
patch.object(m._broker, 'request_handle', return_value=status), \
|
||||
patch.object(m, '_validate_registered_source') as source:
|
||||
with self.assertRaises(m.ValidationError): lease.check(p)
|
||||
source.assert_not_called()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user