Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 696317@bnt-lap001 Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
92 lines
3 KiB
Python
92 lines
3 KiB
Python
"""Every adapter's source_digest must cover every local file it executes.
|
|
|
|
Each *-adapter.py is imported in a fresh interpreter with
|
|
importlib.util.spec_from_file_location instrumented; the local .py files
|
|
actually loaded (sys.modules __file__ under the repo plus every
|
|
spec_from_file_location target) must be a subset of its SOURCE_FILES.
|
|
"""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
REPO = ROOT.parents[2]
|
|
|
|
PROBE = r'''
|
|
import importlib.util, json, sys
|
|
from pathlib import Path
|
|
root, repo, adapter = Path(sys.argv[1]), Path(sys.argv[2]), sys.argv[3]
|
|
seen = set()
|
|
_orig = importlib.util.spec_from_file_location
|
|
def _spy(name, location=None, *a, **k):
|
|
if location is not None:
|
|
seen.add(str(Path(location).resolve()))
|
|
return _orig(name, location, *a, **k)
|
|
importlib.util.spec_from_file_location = _spy
|
|
spec = _orig('adapter_under_probe', root / adapter)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
for m in list(sys.modules.values()):
|
|
f = getattr(m, '__file__', None)
|
|
if f:
|
|
seen.add(str(Path(f).resolve()))
|
|
local = sorted(p for p in seen
|
|
if p.endswith('.py') and Path(p).is_relative_to(repo)
|
|
and '.venv' not in Path(p).parts and 'site-packages' not in Path(p).parts)
|
|
local.append(str((root / adapter).resolve()))
|
|
pinned = []
|
|
for entry in mod.SOURCE_FILES:
|
|
pinned.append(str((repo / entry[0] / entry[1]).resolve()) if isinstance(entry, tuple)
|
|
else str((root / entry).resolve()))
|
|
print(json.dumps({'loaded': sorted(set(local)), 'pinned': sorted(pinned)}))
|
|
'''
|
|
|
|
# Adapters outside this fix's scope that fail the check (reported, not fixed).
|
|
KNOWN_FAILING = set()
|
|
|
|
|
|
def _probe_env():
|
|
# The probe runs with cwd=ROOT, so a relative PYTHONPATH entry would stop
|
|
# pointing where the caller meant it to; resolve entries against our cwd.
|
|
env = dict(os.environ)
|
|
if env.get('PYTHONPATH'):
|
|
env['PYTHONPATH'] = os.pathsep.join(
|
|
str(Path(p).resolve()) for p in env['PYTHONPATH'].split(os.pathsep) if p)
|
|
return env
|
|
|
|
|
|
def probe(adapter):
|
|
out = subprocess.run([sys.executable, '-c', PROBE, str(ROOT), str(REPO), adapter],
|
|
capture_output=True, text=True, env=_probe_env(), cwd=ROOT)
|
|
if out.returncode:
|
|
raise AssertionError(f'{adapter} probe failed: {out.stderr[-2000:]}')
|
|
return json.loads(out.stdout.strip().splitlines()[-1])
|
|
|
|
|
|
def unpinned(adapter):
|
|
r = probe(adapter)
|
|
return sorted(set(r['loaded']) - set(r['pinned']))
|
|
|
|
|
|
class AdapterSourceClosure(unittest.TestCase):
|
|
pass
|
|
|
|
|
|
def _make(adapter):
|
|
def test(self):
|
|
missing = unpinned(adapter)
|
|
self.assertEqual(missing, [], f'{adapter} loads files its source_digest does not pin')
|
|
if adapter in KNOWN_FAILING:
|
|
test = unittest.expectedFailure(test)
|
|
return test
|
|
|
|
|
|
for _p in sorted(ROOT.glob('*-adapter.py')):
|
|
setattr(AdapterSourceClosure, 'test_' + _p.stem.replace('-', '_'), _make(_p.name))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|