personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

solstone / tests / _baseline_harness.py
7.5 kB 209 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4"""Shared isolation harness for API baseline tests and baseline regeneration. 5 6Used by: 7- tests/test_api_baselines.py - module-scoped fixtures 8- tests/verify_api.py - verify/update CLI mode 9 10Keeps both paths on identical isolation so generated baselines match the test 11oracle. 12""" 13 14from __future__ import annotations 15 16import atexit 17import json 18import os 19import shutil 20import subprocess 21import tempfile 22import threading 23from contextlib import contextmanager 24from pathlib import Path 25from typing import Iterator 26from unittest.mock import patch 27 28FROZEN_DATE = "2026-04-15" 29FROZEN_TZ_OFFSET = -7 30 31# The fixture journal is built once per process from an immutable snapshot of 32# HEAD rather than copied file-by-file from the live working tree. make dev / 33# make sandbox write runtime artifacts into tests/fixtures/journal (AGENTS.md 34# §6), so a concurrent writer can delete or replace a tracked file between 35# `git ls-files` and the copy — raising FileNotFoundError at fixture *setup* 36# (a spurious pytest ERROR, not a real failure). `git archive HEAD` reads the 37# committed tree from the object store, never the working tree, so it is immune 38# to concurrent working-tree mutation. 39_REPO_ROOT = Path(__file__).resolve().parent.parent 40_FIXTURE_JOURNAL_REL = "tests/fixtures/journal" 41_LIVE_FIXTURE_JOURNAL = (_REPO_ROOT / _FIXTURE_JOURNAL_REL).resolve() 42 43_snapshot_lock = threading.Lock() 44_snapshot_root: Path | None = None 45 46 47def _fixture_journal_snapshot() -> Path: 48 """Return an immutable, process-scoped snapshot of the tracked fixture journal. 49 50 Built once per process via `git archive HEAD` → tar, which reads HEAD from 51 the git object store and is therefore unaffected by concurrent writes to the 52 live tests/fixtures/journal tree. The same set of git-tracked files that 53 `git ls-files` selects (working tree clean ⇒ HEAD ≡ index), with symlinks 54 preserved as symlinks. The temp dir is removed at interpreter exit. 55 """ 56 global _snapshot_root 57 with _snapshot_lock: 58 if _snapshot_root is not None and _snapshot_root.exists(): 59 return _snapshot_root 60 tmp = Path(tempfile.mkdtemp(prefix="solstone-journal-snapshot-")) 61 archive = subprocess.run( 62 ["git", "archive", "HEAD", "--", _FIXTURE_JOURNAL_REL], 63 cwd=str(_REPO_ROOT), 64 capture_output=True, 65 check=True, 66 ) 67 subprocess.run( 68 ["tar", "-x", "-C", str(tmp)], 69 input=archive.stdout, 70 check=True, 71 ) 72 # `git archive` preserves the tests/fixtures/journal/ prefix in the tar. 73 _snapshot_root = (tmp / _FIXTURE_JOURNAL_REL).resolve() 74 atexit.register(shutil.rmtree, tmp, ignore_errors=True) 75 return _snapshot_root 76 77 78def copytree_tracked(src: Path, dst: Path) -> None: 79 """Copy only git-tracked files from src to dst. 80 81 For paths within tests/fixtures/journal (the journal fixture and any subtree 82 of it), copy from an immutable, process-scoped snapshot of HEAD instead of 83 enumerating and copying from the live working tree — see 84 `_fixture_journal_snapshot` for why this defeats the concurrent-write race. 85 For any other src, fall back to enumerating tracked files from the live tree. 86 """ 87 src = Path(src).resolve() 88 dst = Path(dst) 89 try: 90 rel = src.relative_to(_LIVE_FIXTURE_JOURNAL) 91 except ValueError: 92 rel = None 93 if rel is not None: 94 snap_src = _fixture_journal_snapshot() / rel 95 shutil.copytree(snap_src, dst, symlinks=True, dirs_exist_ok=True) 96 return 97 result = subprocess.run( 98 ["git", "ls-files", "."], 99 cwd=str(src), 100 capture_output=True, 101 text=True, 102 check=True, 103 ) 104 for rel_path in result.stdout.splitlines(): 105 if not rel_path: 106 continue 107 src_file = src / rel_path 108 dst_file = dst / rel_path 109 dst_file.parent.mkdir(parents=True, exist_ok=True) 110 if src_file.is_symlink(): 111 os.symlink(os.readlink(src_file), dst_file) 112 else: 113 shutil.copy2(src_file, dst_file) 114 115 116def prepare_isolated_journal(dst: Path) -> Path: 117 """Copy the git-tracked fixture journal into dst and return the absolute path.""" 118 src = Path("tests/fixtures/journal").resolve() 119 dst = dst.resolve() 120 copytree_tracked(src, dst) 121 return dst 122 123 124@contextmanager 125def isolated_app_env(journal: Path) -> Iterator[Path]: 126 """Patch env so create_app(journal) is fully isolated.""" 127 128 journal = Path(journal).resolve() 129 overrides = { 130 "SOLSTONE_JOURNAL": str(journal), 131 # Match tests/conftest.py's deterministic unit-test runtime. Without 132 # these, baseline regeneration probes host GPU/supervisor state while 133 # test_api_baselines compares against the isolated test contract. 134 "SOL_SKIP_SUPERVISOR_CHECK": "1", 135 "SOLSTONE_DISABLE_CONVEY_SIDE_RUNTIMES": "1", 136 } 137 previous = {key: os.environ.get(key) for key in overrides} 138 os.environ.update(overrides) 139 try: 140 from solstone.think.providers import local_cuda, local_install, local_vulkan 141 142 deterministic_devices = [ 143 local_vulkan.VulkanDevice( 144 1, 145 "NVIDIA GeForce GTX 1660 Ti", 146 local_vulkan.VK_TYPE_DISCRETE, 147 6390, 148 ) 149 ] 150 with ( 151 patch.object( 152 local_cuda, 153 "probe_nvidia_gpu", 154 lambda: local_cuda.NvidiaProbe( 155 index=None, 156 compute_cap=None, 157 driver_cuda_version=None, 158 vram_mib=None, 159 tiering_memory_mib=None, 160 memory_source=local_cuda.MEMORY_SOURCE_UNAVAILABLE, 161 detected=False, 162 ), 163 ), 164 patch.object( 165 local_install, 166 "probe_cuda_runtime_artifact_trust", 167 lambda _pin, **_kwargs: local_cuda.ArtifactTrust.ABSENT, 168 ), 169 patch.object( 170 local_install, 171 "has_persisted_installed_cuda_target", 172 lambda **_kwargs: False, 173 ), 174 patch.object( 175 local_vulkan, 176 "detect_gpus", 177 lambda: deterministic_devices, 178 ), 179 ): 180 yield journal 181 finally: 182 for key, value in previous.items(): 183 if value is None: 184 os.environ.pop(key, None) 185 else: 186 os.environ[key] = value 187 188 189def make_test_client(journal: Path): 190 """Create a Flask test client for an isolated journal.""" 191 from solstone.convey import create_app 192 193 app = create_app(journal=str(Path(journal).resolve())) 194 app.config["TESTING"] = True 195 client = app.test_client() 196 return client 197 198 199def mark_setup_complete(journal: Path, completed_at: int = 1700000000000) -> None: 200 """Mark a minimal test journal as past first-run setup.""" 201 202 config_path = Path(journal) / "config" / "journal.json" 203 config_path.parent.mkdir(parents=True, exist_ok=True) 204 if config_path.exists(): 205 config = json.loads(config_path.read_text(encoding="utf-8")) 206 else: 207 config = {} 208 config["setup"] = {"completed_at": completed_at} 209 config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")