personal memory agent
0

Configure Feed

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

solstone / tests / test_root_launchers.py
8.6 kB 294 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4from __future__ import annotations 5 6import os 7import shutil 8import stat 9import subprocess 10from pathlib import Path 11 12import pytest 13 14pytestmark = pytest.mark.skipif(os.name != "posix", reason="POSIX launcher tests") 15 16REPO_ROOT = Path(__file__).resolve().parents[1] 17LAUNCHER_DIR = REPO_ROOT / "scripts" / "root-launchers" 18REINSTALL = "Reinstall solstone and solstone-core." 19 20 21def _make_executable(path: Path) -> None: 22 path.chmod(path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) 23 24 25def _copy_launcher(bin_dir: Path, name: str = "sol") -> Path: 26 bin_dir.mkdir(parents=True, exist_ok=True) 27 launcher = bin_dir / name 28 shutil.copy2(LAUNCHER_DIR / name, launcher) 29 _make_executable(launcher) 30 return launcher 31 32 33def _write_core(bin_dir: Path, *, executable: bool = True) -> Path: 34 core = bin_dir / "solstone-core" 35 core.write_text( 36 "#!/bin/sh\n" 37 "printf 'cwd=%s\\n' \"$(pwd)\"\n" 38 "printf 'argv='\n" 39 'for arg in "$@"; do printf \'<%s>\' "$arg"; done\n' 40 "printf '\\nstdin='\n" 41 "cat\n" 42 "printf 'stub stderr\\n' >&2\n" 43 'exit "${STUB_EXIT:-23}"\n', 44 encoding="utf-8", 45 ) 46 if executable: 47 _make_executable(core) 48 else: 49 core.chmod(0o644) 50 return core 51 52 53def _run( 54 program: Path, 55 args: list[str | bytes] | None = None, 56 *, 57 cwd: Path | None = None, 58 stdin: bytes = b"payload", 59 env: dict[str, str] | None = None, 60) -> subprocess.CompletedProcess[bytes]: 61 return subprocess.run( 62 [program, *(args or [])], 63 input=stdin, 64 stdout=subprocess.PIPE, 65 stderr=subprocess.PIPE, 66 cwd=cwd, 67 env=env, 68 check=False, 69 ) 70 71 72def _run_with_argv0( 73 launcher_source: Path, 74 argv0: Path, 75 *, 76 cwd: Path | None = None, 77) -> subprocess.CompletedProcess[bytes]: 78 return subprocess.run( 79 ["/bin/sh", "-c", '. "$1"', argv0, launcher_source], 80 stdout=subprocess.PIPE, 81 stderr=subprocess.PIPE, 82 cwd=cwd, 83 check=False, 84 ) 85 86 87def _arg_bytes(arg: str | bytes) -> bytes: 88 if isinstance(arg, bytes): 89 return arg 90 return os.fsencode(arg) 91 92 93def _stdout(cwd: Path, args: list[str | bytes], stdin: bytes = b"payload") -> bytes: 94 argv = b"".join(b"<" + _arg_bytes(arg) + b">" for arg in args) 95 return f"cwd={cwd}\n".encode() + b"argv=" + argv + b"\nstdin=" + stdin 96 97 98def _error(prefix: str, detail: str) -> bytes: 99 return f"{prefix}: native launcher {detail}. {REINSTALL}\n".encode() 100 101 102def _missing_core_error(prefix: str, core: Path) -> bytes: 103 return ( 104 f"{prefix}: native solstone-core sibling is missing: {core}. {REINSTALL}\n" 105 ).encode() 106 107 108def _non_executable_core_error(prefix: str, core: Path) -> bytes: 109 return ( 110 f"{prefix}: native solstone-core sibling is not executable: {core}. " 111 f"{REINSTALL}\n" 112 ).encode() 113 114 115def test_launcher_files_differ_only_by_identity_line() -> None: 116 sol = (LAUNCHER_DIR / "sol").read_bytes().splitlines() 117 solstone = (LAUNCHER_DIR / "solstone").read_bytes().splitlines() 118 119 diffs = [ 120 (index, left, right) 121 for index, (left, right) in enumerate(zip(sol, solstone), start=1) 122 if left != right 123 ] 124 125 assert len(sol) == len(solstone) 126 assert diffs == [(4, b"id=sol", b"id=solstone")] 127 128 129def test_direct_invocation_preserves_process_contract(tmp_path: Path) -> None: 130 bin_dir = tmp_path / "bin" 131 launcher = _copy_launcher(bin_dir) 132 _write_core(bin_dir) 133 cwd = tmp_path / "cwd" 134 cwd.mkdir() 135 args: list[str | bytes] = ["one", "two words", b"raw-\xff"] 136 137 result = _run(launcher, args, cwd=cwd) 138 139 assert result.returncode == 23 140 assert result.stdout == _stdout( 141 cwd, 142 ["__solstone_identity=sol", *args], 143 ) 144 assert result.stderr == b"stub stderr\n" 145 146 147def test_solstone_launcher_uses_solstone_identity(tmp_path: Path) -> None: 148 bin_dir = tmp_path / "bin" 149 launcher = _copy_launcher(bin_dir, "solstone") 150 _write_core(bin_dir) 151 152 result = _run(launcher, ["status"], cwd=tmp_path) 153 154 assert result.returncode == 23 155 assert result.stdout == _stdout( 156 tmp_path, 157 ["__solstone_identity=solstone", "status"], 158 ) 159 assert result.stderr == b"stub stderr\n" 160 161 162def test_exit_status_is_propagated(tmp_path: Path) -> None: 163 bin_dir = tmp_path / "bin" 164 launcher = _copy_launcher(bin_dir) 165 _write_core(bin_dir) 166 env = dict(os.environ) 167 env["STUB_EXIT"] = "42" 168 169 result = _run(launcher, cwd=tmp_path, env=env) 170 171 assert result.returncode == 42 172 assert result.stdout == _stdout(tmp_path, ["__solstone_identity=sol"]) 173 assert result.stderr == b"stub stderr\n" 174 175 176def test_absolute_multi_hop_symlink_resolves_to_launcher_dir(tmp_path: Path) -> None: 177 bin_dir = tmp_path / "bin" 178 launcher = _copy_launcher(bin_dir) 179 _write_core(bin_dir) 180 links = tmp_path / "links" 181 links.mkdir() 182 second = links / "second" 183 first = links / "first" 184 second.symlink_to(launcher) 185 first.symlink_to(second) 186 187 result = _run(first, ["ok"], cwd=tmp_path) 188 189 assert result.returncode == 23 190 assert result.stdout == _stdout(tmp_path, ["__solstone_identity=sol", "ok"]) 191 assert result.stderr == b"stub stderr\n" 192 193 194def test_relative_multi_hop_symlink_resolves_to_launcher_dir(tmp_path: Path) -> None: 195 bin_dir = tmp_path / "bin" 196 launcher = _copy_launcher(bin_dir) 197 _write_core(bin_dir) 198 rel1 = tmp_path / "rel1" 199 rel2 = tmp_path / "rel2" 200 rel1.mkdir() 201 rel2.mkdir() 202 second = rel2 / "second" 203 first = rel1 / "first" 204 second.symlink_to(Path("..") / "bin" / launcher.name) 205 first.symlink_to(Path("..") / "rel2" / second.name) 206 207 result = _run(first, ["ok"], cwd=tmp_path) 208 209 assert result.returncode == 23 210 assert result.stdout == _stdout(tmp_path, ["__solstone_identity=sol", "ok"]) 211 assert result.stderr == b"stub stderr\n" 212 213 214def test_path_containing_space_resolves(tmp_path: Path) -> None: 215 bin_dir = tmp_path / "path with space" / "bin" 216 launcher = _copy_launcher(bin_dir) 217 _write_core(bin_dir) 218 219 result = _run(launcher, ["ok"], cwd=tmp_path) 220 221 assert result.returncode == 23 222 assert result.stdout == _stdout(tmp_path, ["__solstone_identity=sol", "ok"]) 223 assert result.stderr == b"stub stderr\n" 224 225 226def test_leading_dash_path_component_resolves(tmp_path: Path) -> None: 227 bin_dir = tmp_path / "-leading-dash" / "bin" 228 launcher = _copy_launcher(bin_dir) 229 _write_core(bin_dir) 230 231 result = _run(launcher, ["ok"], cwd=tmp_path) 232 233 assert result.returncode == 23 234 assert result.stdout == _stdout(tmp_path, ["__solstone_identity=sol", "ok"]) 235 assert result.stderr == b"stub stderr\n" 236 237 238def test_dangling_link_exits_78_without_stdout(tmp_path: Path) -> None: 239 launcher_source = _copy_launcher(tmp_path / "source-bin") 240 bin_dir = tmp_path / "bin" 241 missing_dir = bin_dir / "missing" 242 missing_dir.mkdir(parents=True) 243 launcher = bin_dir / "sol" 244 launcher.symlink_to(Path("missing") / "sol") 245 dangling_target = bin_dir / "missing" / "sol" 246 247 result = _run_with_argv0(launcher_source, launcher, cwd=tmp_path) 248 249 assert result.returncode == 78 250 assert result.stdout == b"" 251 assert result.stderr == _error( 252 "sol", 253 f"symlink is dangling: {dangling_target}", 254 ) 255 256 257def test_link_cycle_exits_78_without_stdout(tmp_path: Path) -> None: 258 launcher_source = _copy_launcher(tmp_path / "source-bin") 259 first = tmp_path / "first" 260 second = tmp_path / "second" 261 first.symlink_to(second.name) 262 second.symlink_to(first.name) 263 264 result = _run_with_argv0(launcher_source, first, cwd=tmp_path) 265 266 assert result.returncode == 78 267 assert result.stdout == b"" 268 assert result.stderr == _error("sol", "symlink cycle detected") 269 270 271def test_missing_sibling_core_exits_78_without_stdout_and_brands_command( 272 tmp_path: Path, 273) -> None: 274 bin_dir = tmp_path / "bin" 275 launcher = _copy_launcher(bin_dir, "solstone") 276 core = bin_dir / "solstone-core" 277 278 result = _run(launcher, cwd=tmp_path) 279 280 assert result.returncode == 78 281 assert result.stdout == b"" 282 assert result.stderr == _missing_core_error("solstone", core) 283 284 285def test_non_executable_sibling_core_exits_78_without_stdout(tmp_path: Path) -> None: 286 bin_dir = tmp_path / "bin" 287 launcher = _copy_launcher(bin_dir) 288 core = _write_core(bin_dir, executable=False) 289 290 result = _run(launcher, cwd=tmp_path) 291 292 assert result.returncode == 78 293 assert result.stdout == b"" 294 assert result.stderr == _non_executable_core_error("sol", core)