@ 261101.7
current
"""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)