@ 261101.7

261030.4 · 6182a9aa802e833d238ab04b403b428f42fe37f9

A black-box suite runs the built binary, starting with the 404 coin

make blackbox runs tests/ with pytest and httpx against a server in a throwaway home, on --public and on the form wwwvm's unit uses. Cases are TOML files named after the decision they check.

The coin shows on both forms for a path a served repo lacks. The cache message appears only without --public, for a path that names no repo.

reading declarations

Changes to tests/harness.py. All files

tests/harness.py

new file mode 100644@@ -0,0 +1,154 @@+"""Run the binary named by TEST_BIN in a throwaway home and ask it for pages."""++import os+import signal+import socket+import subprocess+import time+from pathlib import Path++import httpx++GIT_DATE = "2026-01-02T03:04:05Z"++# Every repo the seeded cache holds. The host never resolves, so no case+# reaches a network.+SEEDED = {"forge.invalid/acct/r": {"README.md": "# r\n"}}+++def binary():+    path = os.environ.get("TEST_BIN")+    if not path:+        raise RuntimeError("TEST_BIN must name the binary; make blackbox sets it")+    return Path(path).resolve()+++def environment(home):+    """The whole environment of a run: nothing leaks in from the caller's."""+    return {+        "PATH": os.environ["PATH"],+        "HOME": str(home),+        "XDG_CACHE_HOME": str(home / "cache"),+        "XDG_CONFIG_HOME": str(home / "config"),+        "XDG_STATE_HOME": str(home / "state"),+        "GIT_CONFIG_GLOBAL": os.devnull,+        "GIT_CONFIG_NOSYSTEM": "1",+        "TZ": "UTC",+    }+++def git(cwd, *args):+    env = {+        "PATH": os.environ["PATH"],+        "GIT_CONFIG_GLOBAL": os.devnull,+        "GIT_CONFIG_NOSYSTEM": "1",+        "GIT_AUTHOR_NAME": "a",+        "GIT_AUTHOR_EMAIL": "a@forge.invalid",+        "GIT_AUTHOR_DATE": GIT_DATE,+        "GIT_COMMITTER_NAME": "a",+        "GIT_COMMITTER_EMAIL": "a@forge.invalid",+        "GIT_COMMITTER_DATE": GIT_DATE,+    }+    done = subprocess.run(["git", *args], cwd=cwd, env=env, check=True, capture_output=True, text=True)+    return done.stdout.strip()+++def seed(cache, key, files):+    """Lay out one repo as the binary leaves it after a clone.++    A public server fetches only over https from the open internet, so a+    case with no network cannot have it clone. This is the one place the+    suite knows the cache's layout.+    """+    source = cache.parent / "sources" / key+    source.mkdir(parents=True)+    git(source, "init", "-q", "-b", "main")+    for name, text in files.items():+        (source / name).parent.mkdir(parents=True, exist_ok=True)+        (source / name).write_text(text)+    git(source, "add", ".")+    git(source, "commit", "-q", "-m", "first")+    head = git(source, "rev-parse", "HEAD")+    repo = cache / key+    repo.mkdir(parents=True)+    git(repo, "clone", "-q", "--bare", str(source), ".git")+    name = ".wt-" + head[:12]+    git(repo / ".git", "worktree", "add", "-q", "--detach", "--", str(repo / name), head)+    (repo / ".tree").symlink_to(name)+    (repo / ".fetched").touch()+    (repo / ".used").touch()+++def free_port():+    with socket.socket() as s:+        s.bind(("127.0.0.1", 0))+        return s.getsockname()[1]+++class Server:+    """One running binary. origin is where its repos answer."""++    def __init__(self, home, argv, origin, listens):+        self.origin = origin+        self.log = home / "stderr"+        self.proc = subprocess.Popen(+            [str(binary()), *argv],+            cwd=home,+            env=environment(home),+            stdin=subprocess.DEVNULL,+            stdout=subprocess.DEVNULL,+            stderr=self.log.open("w"),+        )+        self._wait(listens)++    def _wait(self, addr):+        end = time.monotonic() + 10+        while time.monotonic() < end:+            if self.proc.poll() is not None:+                raise RuntimeError(f"exit {self.proc.returncode}: {self.log.read_text()}")+            try:+                socket.create_connection(addr, timeout=0.2).close()+                return+            except OSError:+                time.sleep(0.02)+        raise TimeoutError(f"nothing listens on {addr}: {self.log.read_text()}")++    def get(self, path):+        """The page at path once any read it set going has ended."""+        end = time.monotonic() + 30+        while True:+            page = httpx.get(self.origin + path, trust_env=False, timeout=30)+            if page.status_code != 202 or time.monotonic() > end:+                return page+            time.sleep(0.1)++    def stop(self):+        self.proc.send_signal(signal.SIGTERM)+        try:+            self.proc.wait(10)+        except subprocess.TimeoutExpired:+            self.proc.kill()+            self.proc.wait()+++# public: the flag the README names for the open web.+# default: a reader's server, as wwwvm's unit runs it, whose online repos+# answer on the .2 beside the user's default address.+def start(form, home):+    """Start the binary in one of the forms a server runs in."""+    cache = home / "cache" / binary().name+    for key, files in SEEDED.items():+        seed(cache, key, files)+    port = free_port()+    if form == "public":+        return Server(home, ["--public", "--disk=1G", "-a", f"127.0.0.1:{port}"],+                      f"http://127.0.0.1:{port}", ("127.0.0.1", port))+    if form == "default":+        n = os.getuid() - 1000+        if not 0 <= n <= 255:+            raise OSError(f"uid {os.getuid()} has no default address")+        tree = home / "tree"+        tree.mkdir()+        return Server(home, ["--foreground", "--no-open", "--no-bug", "--disk=1G", "-a", f"127.31.{n}.1:{port}", str(tree)],+                      f"http://127.31.{n}.2:{port}", (f"127.31.{n}.2", port))+    raise ValueError(form)