# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Unit tests for the `vllm launch` CLI subcommand."""

import argparse
import hashlib
import json
import os
import signal
import subprocess
import sys
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any
from unittest.mock import create_autospec, patch

import pytest

import vllm.snapshot.runtime as snapshot_runtime
import vllm.snapshot.server as snapshot_server
from vllm.entrypoints.cli import snapshot as snapshot_cli
from vllm.entrypoints.cli.launch import (
    LaunchSubcommand,
    RenderSubcommand,
    cmd_init,
)
from vllm.snapshot.controller import (
    LocalSnapshotTools,
    SnapshotCreateError,
    SnapshotRestoreError,
    _child_engine_argv,
    create_snapshot,
    restore_snapshot,
)
from vllm.snapshot.manifest import (
    SnapshotCompatibilityError,
    SnapshotManifest,
    SnapshotRuntimeIdentity,
    SnapshotSecurityError,
    read_manifest,
    validate_artifact_root,
    validate_identity,
    write_manifest_atomic,
)
from vllm.snapshot.runtime import ProcessInventory, _decode_endpoint, _TcpSocketRecord
from vllm.snapshot.server import SnapshotCanaryError
from vllm.snapshot.types import Oracle, oracles_match
from vllm.utils.argparse_utils import FlexibleArgumentParser


@pytest.fixture
def launch_parser():
    parser = FlexibleArgumentParser(description="test")
    subparsers = parser.add_subparsers(required=False, dest="subparser")
    LaunchSubcommand().subparser_init(subparsers)
    return parser


def test_subcommand_name():
    assert LaunchSubcommand().name == "launch"


def test_cmd_init_returns_subcommand():
    result = cmd_init()
    assert len(result) == 1
    assert isinstance(result[0], LaunchSubcommand)


# -- Parsing: `vllm launch render` --


def test_parse_launch_render(launch_parser):
    args = launch_parser.parse_args(["launch", "render", "--model", "test-model"])
    assert args.launch_component == "render"


def test_parse_launch_requires_component(launch_parser):
    with pytest.raises(SystemExit):
        launch_parser.parse_args(["launch", "--model", "test-model"])


def test_parse_launch_invalid_component(launch_parser):
    with pytest.raises(SystemExit):
        launch_parser.parse_args(["launch", "unknown", "--model", "test-model"])


# -- Dispatch --


def test_cmd_launch_render_calls_run():
    args = argparse.Namespace(model_tag=None, model="test-model")
    with patch("vllm.entrypoints.cli.launch.uvloop.run") as mock_uvloop_run:
        RenderSubcommand.cmd(args)
        mock_uvloop_run.assert_called_once()


def test_cmd_launch_model_tag_overrides():
    args = argparse.Namespace(
        model_tag="tag-model",
        model="original-model",
        launch_command=lambda a: None,
    )
    LaunchSubcommand.cmd(args)
    assert args.model == "tag-model"


def test_cmd_launch_model_tag_none():
    args = argparse.Namespace(
        model_tag=None,
        model="original-model",
        launch_command=lambda a: None,
    )
    LaunchSubcommand.cmd(args)
    assert args.model == "original-model"


def test_cmd_dispatches():
    called = {}

    def fake_dispatch(args):
        called["args"] = args

    args = argparse.Namespace(launch_command=fake_dispatch)
    LaunchSubcommand.cmd(args)
    assert "args" in called


# -- Module registration --


def test_subparser_init_returns_parser():
    parser = FlexibleArgumentParser(description="test")
    subparsers = parser.add_subparsers(required=False, dest="subparser")
    result = LaunchSubcommand().subparser_init(subparsers)
    assert isinstance(result, FlexibleArgumentParser)


def test_launch_registered_in_main():
    """Verify that launch module is importable as a CLI module."""
    import vllm.entrypoints.cli.launch as launch_module

    assert hasattr(launch_module, "cmd_init")
    subcmds = launch_module.cmd_init()
    assert any(s.name == "launch" for s in subcmds)


_MODEL_REVISION = "c1899de289a04d12100db370d81485cdf75e47ca"


def _run_cli(argv: list[str]) -> None:
    from vllm.entrypoints.cli import main as cli_main

    with patch.object(sys, "argv", ["vllm", *argv]):
        cli_main.main()


def test_snapshot_environment_contract(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
):
    from vllm.entrypoints.serve.utils import api_utils

    monkeypatch.setattr(api_utils, "cli_env_setup", pytest.fail)
    secret = "snapshot-secret"
    monkeypatch.setenv("VLLM_API_KEY", secret)
    monkeypatch.setenv("VLLM_USER_SETTING", secret)
    monkeypatch.setenv("VLLM_WORKER_MULTIPROC_METHOD", secret)
    environment = dict(LocalSnapshotTools()._environment_identity())
    assert (
        environment["VLLM_USER_SETTING"]
        == hashlib.sha256(b"VLLM_USER_SETTING\0snapshot-secret").hexdigest()
    )
    assert (
        environment["VLLM_USER_SETTING"] != environment["VLLM_WORKER_MULTIPROC_METHOD"]
    )
    assert secret not in str(environment)
    assert "VLLM_API_KEY" not in environment

    monkeypatch.delenv("VLLM_WORKER_MULTIPROC_METHOD")
    implicit_worker_default = dict(LocalSnapshotTools()._environment_identity())
    monkeypatch.setenv("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
    assert dict(LocalSnapshotTools()._environment_identity()) == implicit_worker_default

    monkeypatch.setenv("VLLM_USER_SETTING", f"{secret}-changed")
    changed_environment = dict(LocalSnapshotTools()._environment_identity())
    assert changed_environment["VLLM_USER_SETTING"] != environment["VLLM_USER_SETTING"]

    artifact = tmp_path / "snapshot"
    artifact.mkdir(mode=0o700)
    write_manifest_atomic(artifact, _manifest(environment=tuple(environment.items())))
    before = os.environ.copy()

    _run_cli(["snapshot", "inspect", str(artifact)])

    assert os.environ == before
    output = capsys.readouterr().out
    assert secret not in output
    inspected = json.loads(output)
    inspected_environment = dict(inspected["environment"])
    assert inspected_environment == environment

    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "current_identity", lambda _uuid: _runtime_identity())
    manifest = tools.make_manifest(
        argparse.Namespace(
            hf_token=secret,
            model_tag="Qwen/Qwen3-0.6B",
            revision=_MODEL_REVISION,
            served_model_name=None,
            tokenizer_revision=None,
        ),
        (
            "Qwen/Qwen3-0.6B",
            "--api-key",
            secret,
            f"{secret}-alternate",
            f"--api-key={secret}",
            "--hf-token",
            secret,
            f"--hf-token={secret}",
        ),
        ProcessInventory(100, (100, 101), (101,), "GPU-abc"),
        _oracle(),
        tmp_path,
    )

    assert manifest.engine_argv == (
        "Qwen/Qwen3-0.6B",
        "--api-key",
        "***",
        "***",
        "--api-key=***",
        "--hf-token",
        "***",
        "--hf-token=***",
    )


@pytest.mark.parametrize(
    ("configured", "expected"),
    [(None, "1"), ("0", "0"), ("1", "1")],
)
def test_snapshot_child_defaults_nccl_ib_without_mutating_parent(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    configured: str | None,
    expected: str,
):
    if configured is None:
        monkeypatch.delenv("NCCL_IB_DISABLE", raising=False)
    else:
        monkeypatch.setenv("NCCL_IB_DISABLE", configured)
    parent_environment = os.environ.copy()
    process = create_autospec(subprocess.Popen, instance=True)
    process.pid = 100
    popen = create_autospec(subprocess.Popen, return_value=process)
    monkeypatch.setattr(snapshot_runtime.subprocess, "Popen", popen)

    LocalSnapshotTools().launch_child(tmp_path, ("model",))

    assert popen.call_args.kwargs["env"]["NCCL_IB_DISABLE"] == expected
    assert os.environ == parent_environment


def test_snapshot_environment_identity_applies_effective_nccl_ib_default(
    monkeypatch: pytest.MonkeyPatch,
):
    monkeypatch.delenv("NCCL_IB_DISABLE", raising=False)
    implicit = LocalSnapshotTools()._environment_identity()
    monkeypatch.setenv("NCCL_IB_DISABLE", "1")
    explicit_disabled = LocalSnapshotTools()._environment_identity()
    monkeypatch.setenv("NCCL_IB_DISABLE", "0")
    explicit_enabled = LocalSnapshotTools()._environment_identity()

    assert implicit == explicit_disabled
    assert explicit_enabled != implicit


@pytest.mark.parametrize(
    "case",
    [
        (("--hf_token", "SECRET"), ("--hf_token", "***")),
        (("--hf_token=SECRET",), ("--hf_token=***",)),
        (("--api_key", "SECRET"), ("--api_key", "***")),
        (("--api_key=SECRET",), ("--api_key=***",)),
        (("--hf-tok", "SECRET"), ("--hf-tok", "***")),
        (("--hf-tok=SECRET",), ("--hf-tok=***",)),
        (("--hf_tok", "SECRET"), ("--hf_tok", "***")),
        (("--api-k", "SECRET", "SECOND"), ("--api-k", "***", "***")),
        (("--api-k=SECRET",), ("--api-k=***",)),
        (("--hf-overrides", "{}"), ("--hf-overrides", "{}")),
    ],
)
def test_snapshot_manifest_redacts_accepted_secret_options(case):
    engine_argv, expected = case
    tools = create_autospec(LocalSnapshotTools, instance=True)
    tools.current_identity.return_value = _runtime_identity()
    tools._artifact_bytes.return_value = 0
    manifest = LocalSnapshotTools.make_manifest(
        tools,
        snapshot_server.parse_vllm_args(["model", *engine_argv]),
        engine_argv,
        ProcessInventory(100, (100, 101), (101,), "GPU-abc"),
        _oracle(),
        Path(),
    )

    assert manifest.engine_argv == expected
    if "SECRET" in " ".join(engine_argv):
        assert "***" in " ".join(manifest.engine_argv)
        assert "SECRET" not in " ".join(manifest.engine_argv)


def parse_snapshot(*argv: str):
    parser = FlexibleArgumentParser()
    subparsers = parser.add_subparsers(dest="subparser", required=True)
    snapshot_cli.SnapshotSubcommand(
        create_requested=argv[:1] == ("create",)
    ).subparser_init(subparsers)
    return parser.parse_args(["snapshot", *argv])


@pytest.mark.parametrize("option", ["--snapshot-dir", "--snapshot_dir", "--snapshot-d"])
@pytest.mark.parametrize("joined", [False, True])
def test_snapshot_create_forwards_only_engine_options(option, joined, monkeypatch):
    directory = [f"{option}=/tmp/snapshot"] if joined else [option, "/tmp/snapshot"]
    argv = ["create", "Qwen/Qwen3-0.6B", "--revision", _MODEL_REVISION, *directory]
    args = parse_snapshot(*argv)
    monkeypatch.setattr(sys, "argv", ["vllm", "snapshot", *argv])

    child = snapshot_server.parse_vllm_args(list(_child_engine_argv(args, None)))

    assert child.model == "Qwen/Qwen3-0.6B"
    assert child.revision == _MODEL_REVISION
    assert child.enable_sleep_mode


@pytest.mark.parametrize("model_in_cli", [False, True])
def test_snapshot_create_config_preserves_cli_precedence(
    tmp_path, monkeypatch, model_in_cli
):
    config = tmp_path / "engine.yaml"
    config.write_text("model: config-model\nmax-model-len: 512\ndtype: float16\n")
    model = ["Qwen/Qwen3-0.6B"] if model_in_cli else []
    argv = [
        "create",
        *model,
        "--snapshot-dir",
        str(tmp_path / "snapshot"),
        "--config",
        str(config),
        "--revision",
        _MODEL_REVISION,
        "--max-model-len",
        "1024",
    ]
    args = parse_snapshot(*argv)
    monkeypatch.setattr(sys, "argv", ["vllm", "snapshot", *argv])
    child = snapshot_server.parse_vllm_args(list(_child_engine_argv(args, None)))

    assert (
        (args.model_tag or args.model)
        == child.model
        == ("Qwen/Qwen3-0.6B" if model_in_cli else "config-model")
    )
    assert args.max_model_len == child.max_model_len == 1024
    assert args.dtype == child.dtype == "float16"


@pytest.mark.parametrize(
    ("probe", "status", "output"),
    [
        (None, 0, ""),
        ("nvidia-smi", 1, ""),
        ("find", 1, ""),
        ("ss", 1, ""),
        ("nvidia-smi", 0, "12345"),
        ("find", 0, "link_remap.fixture"),
        ("ss", 0, "LISTEN"),
    ],
)
def test_snapshot_e2e_cleanup_requires_successful_empty_probes(probe, status, output):
    script = (
        Path(__file__).resolve().parents[3]
        / ".buildkite/scripts/initialized-snapshot-e2e.sh"
    )
    if not script.exists():
        pytest.skip("snapshot E2E script is not packaged in this test image")
    source = script.read_text()
    # Exercise the real probes without entering Docker/CRIU setup.
    functions = source[source.index("link_remaps() {") : source.index("wait_clean() {")]
    shell = (
        """set -Eeuo pipefail
GPU_UUID=fixture PORT_ONE=18001 PORT_TWO=18002 LINK_REMAP_BASELINE=''
SNAPSHOT_PIDS=(999999999)
nvidia-smi() { :; }
find() { :; }
ss() { :; }
"""
        + functions
    )
    if probe:
        shell += f'\n{probe}() {{ printf %s "{output}"; return {status}; }}\n'
    shell += "\nif state_clean; then exit 0; else exit 1; fi\n"

    result = subprocess.run(["bash", "-c", shell], capture_output=True, timeout=10)

    assert result.returncode == (0 if probe is None else 1), result.stderr


@pytest.mark.parametrize("container_attempted", [False, True])
def test_snapshot_e2e_cleanup_checks_state_only_after_container_attempt(
    container_attempted: bool,
):
    script = (
        Path(__file__).resolve().parents[3]
        / ".buildkite/scripts/initialized-snapshot-e2e.sh"
    )
    if not script.exists():
        pytest.skip("snapshot E2E script is not packaged in this test image")
    source = script.read_text()
    cleanup = source[source.index("cleanup() {") : source.index("on_exit() {")]
    shell = (
        "set -Eeuo pipefail\n"
        "CONTAINER_NAME='' PORT_TWO=18002\n"
        f"CHECK_CLEANUP_STATE={int(container_attempted)}\n"
        "wait_clean() { echo checked-state; return 1; }\n"
        + cleanup
        + "\nif cleanup; then exit 0; else exit 1; fi\n"
    )

    result = subprocess.run(["bash", "-c", shell], capture_output=True, timeout=10)

    assert result.returncode == int(container_attempted), result.stderr
    assert (b"checked-state" in result.stdout) == container_attempted


def test_snapshot_create_cli_accepts_only_pinned_compact_mode(
    monkeypatch: pytest.MonkeyPatch,
):
    monkeypatch.setattr(snapshot_cli.platform, "system", lambda: "Linux")
    monkeypatch.setattr(snapshot_cli.platform, "machine", lambda: "x86_64")
    base = ("create", "Qwen/Qwen3-0.6B", "--snapshot-dir=/tmp/snapshot")
    compact = parse_snapshot(*base, "--revision", _MODEL_REVISION)

    snapshot_cli.validate_create_args(compact)
    assert compact.model_tag == "Qwen/Qwen3-0.6B"

    dispatched: list[argparse.Namespace] = []
    monkeypatch.setattr(snapshot_cli, "run_create", dispatched.append)
    _run_cli(["snapshot", *base, "--revision", _MODEL_REVISION])
    assert dispatched[0].revision == _MODEL_REVISION


@pytest.mark.parametrize(
    ("attribute", "value", "message"),
    [
        ("prefill_context_parallel_size", 2, "prefill context parallel size 1"),
        ("model_tag", None, "immutable remote model"),
        ("logprobs_mode", "raw_logits", "log-probability mode"),
        ("speculative_config", {}, "does not support speculative decoding"),
        ("spec_method", "ngram", "does not support speculative decoding"),
        ("spec_model", "draft-model", "does not support speculative decoding"),
        ("spec_tokens", 3, "does not support speculative decoding"),
    ],
)
def test_snapshot_create_rejects_invalid_config(
    attribute: str,
    value: object,
    message: str,
    tmp_path: Path,
):
    args = parse_snapshot(
        "create",
        "Qwen/Qwen3-0.6B",
        "--snapshot-dir=/tmp/snapshot",
        "--revision",
        _MODEL_REVISION,
    )
    if attribute == "model_tag":
        local_model = tmp_path / "model"
        local_model.mkdir()
        value = str(local_model)
    setattr(args, attribute, value)
    with pytest.raises(ValueError, match=message):
        snapshot_cli.validate_create_args(args)


def _oracle(
    token_ids: tuple[int, ...] = (12095,),
    text: str = " Paris",
    logprob: float = -0.125,
) -> Oracle:
    return Oracle(token_ids, text, logprob)


def _manifest(**changes: object) -> SnapshotManifest:
    manifest = SnapshotManifest(
        schema_version=1,
        boundary="post-engine-init-reloadable-state-released",
        created_at="2026-08-06T00:00:00Z",
        artifact_bytes=8,
        vllm_version="0.1.dev1+snapshot",
        python_version="3.12.3",
        torch_version="2.9.0",
        cuda_runtime="12.9",
        driver_version="575.57.08",
        criu_version="4.1",
        cuda_checkpoint_sha256="a" * 64,
        kernel_release="6.8.0",
        host_id="host-a",
        gpu_name="NVIDIA A10",
        gpu_uuid="GPU-abc",
        model="Qwen/Qwen3-0.6B",
        served_model_name="Qwen/Qwen3-0.6B",
        model_revision="model-sha",
        tokenizer_revision="tokenizer-sha",
        engine_argv=("Qwen/Qwen3-0.6B",),
        environment=(("VLLM_USE_V1", "1"),),
        process_tree=(100, 101),
        cuda_holders=(101,),
        oracle_token_ids=(12095,),
        oracle_text=" Paris",
        oracle_sampled_token_logprob=-0.125,
    )
    return SnapshotManifest.model_validate(
        {**manifest.model_dump(mode="python"), **changes}, strict=True
    )


def _runtime_identity(**changes: object) -> Any:
    fields = SnapshotRuntimeIdentity.model_fields.keys()
    payload = _manifest().model_dump(mode="python", include=fields)
    return SnapshotRuntimeIdentity.model_validate({**payload, **changes}, strict=True)


def _fake_snapshot_tools() -> Any:
    tools = create_autospec(LocalSnapshotTools, instance=True)
    tools.launch_child.return_value = 100
    tools.wait_ready.return_value = _oracle()
    tools.inventory.return_value = ProcessInventory(100, (100, 101), (101,), "GPU-abc")

    tools.current_identity.side_effect = lambda _gpu_uuid: _runtime_identity()
    tools.restore.return_value = 100
    tools.request_oracle.return_value = _oracle()
    return tools


@pytest.mark.parametrize("value", ["0", "-1", "nan", "inf"])
def test_snapshot_timeout_must_be_positive_and_finite(
    value: str, monkeypatch: pytest.MonkeyPatch
):
    monkeypatch.setenv("VLLM_SNAPSHOT_TIMEOUT_S", value)
    with pytest.raises(ValueError, match="positive finite number"):
        LocalSnapshotTools()


def test_snapshot_create_abort_reaps_after_process_group_cleanup(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    tools = LocalSnapshotTools()
    process = subprocess.Popen(
        [sys.executable, "-c", "raise SystemExit(1)"],
        stdin=subprocess.DEVNULL,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        start_new_session=True,
    )
    tools._children[process.pid] = process
    (tmp_path / "child.log").write_text("root failed")
    killed: list[tuple[int, signal.Signals]] = []
    monkeypatch.setattr(
        os,
        "killpg",
        lambda pid, sig: killed.append((pid, sig)),
    )
    monkeypatch.setattr(os, "waitid", lambda *_args: object(), raising=False)

    try:
        with pytest.raises(SnapshotCreateError, match="root failed"):
            tools.wait_ready(tmp_path, process.pid)
        assert process.returncode is None
        tools.abort_create(process.pid)
    finally:
        if process.pid in tools._children:
            tools.abort_create(process.pid)

    assert killed == [(process.pid, signal.SIGKILL)]
    assert process.pid not in tools._children


def _supply_waitid(monkeypatch: pytest.MonkeyPatch) -> None:
    """`wait_ready` binds Linux-only `os.waitid` before it reads the marker."""
    monkeypatch.setattr(os, "waitid", lambda *_args: None, raising=False)


def test_snapshot_wait_ready_reads_back_the_oracle_the_child_wrote(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    _supply_waitid(monkeypatch)
    snapshot_server.write_ready_atomic(tmp_path / "ready.json", _oracle())

    assert LocalSnapshotTools().wait_ready(tmp_path, os.getpid()) == _oracle()


@pytest.mark.parametrize(
    "payload",
    [
        {"token_ids": [12095], "sampled_token_logprob": -0.125},
        {"token_ids": ["12095"], "text": " Paris", "sampled_token_logprob": -0.125},
    ],
)
def test_snapshot_wait_ready_rejects_a_malformed_marker(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, payload: dict[str, object]
):
    _supply_waitid(monkeypatch)
    (tmp_path / "ready.json").write_text(json.dumps(payload))

    with pytest.raises(SnapshotCreateError, match="ready marker invalid"):
        LocalSnapshotTools().wait_ready(tmp_path, os.getpid())


def test_snapshot_create_abort_rejects_surviving_process_group(
    monkeypatch: pytest.MonkeyPatch,
):
    tools = LocalSnapshotTools()
    process = create_autospec(subprocess.Popen, instance=True)
    process.wait.side_effect = subprocess.TimeoutExpired("snapshot child", 10)
    tools._children[100] = process
    monkeypatch.setattr(os, "killpg", lambda _pid, _sig: None)

    with pytest.raises(SnapshotCreateError, match="survived SIGKILL"):
        tools.abort_create(100)


@pytest.mark.asyncio
async def test_snapshot_child_rejects_mismatch_before_publication(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    from vllm.entrypoints.launchers.api_server import entry as api_server

    canaries = iter((_oracle(), _oracle((42,), " Lyon")))

    @asynccontextmanager
    async def engine_context():
        yield object()

    async def next_canary(_engine: object) -> Oracle:
        return next(canaries)

    async def return_none(_engine: object) -> None:
        return None

    monkeypatch.setattr(
        api_server, "build_async_engine_client", lambda _args: engine_context()
    )
    monkeypatch.setattr(snapshot_server, "run_engine_canary", next_canary)
    monkeypatch.setattr(snapshot_server, "_release_reloadable_state", return_none)
    monkeypatch.setattr(snapshot_server, "_restore_reloadable_state", return_none)
    monkeypatch.setattr(snapshot_server, "detach_snapshot_streams", lambda: None)
    monkeypatch.setattr(
        snapshot_server,
        "write_ready_atomic",
        lambda *_args: pytest.fail("mismatched snapshot was published"),
    )
    control = snapshot_server.ControlArgs(
        ready_file=tmp_path / "ready.json",
        release_file=tmp_path / "release.json",
        release_timeout_s=1,
    )
    with pytest.raises(SnapshotCanaryError, match="rehearsal changed"):
        await snapshot_server.run_vllm_snapshot_child(control, argparse.Namespace())


def test_snapshot_release_marker_ignores_unknown_keys(tmp_path: Path):
    marker = tmp_path / "release.json"
    marker.write_text(json.dumps({"release": True, "port": 8000, "unknown": 1}))

    listener = snapshot_server.read_release_marker(marker)

    assert listener == snapshot_server.ListenerConfig(host=None, port=8000)


@pytest.mark.parametrize(
    "payload",
    [
        ["release"],
        {"port": 8000},
        {"release": "true", "port": 8000},
        {"release": 1, "port": 8000},
        {"release": True, "port": 0},
        {"release": True, "port": 70000},
        {"release": True, "port": True},
        {"release": True, "port": "8000"},
        {"release": True, "port": 8000, "host": 5},
    ],
)
def test_snapshot_release_marker_rejects_an_invalid_payload(
    tmp_path: Path, payload: object
):
    marker = tmp_path / "release.json"
    marker.write_text(json.dumps(payload))

    with pytest.raises(snapshot_server.SnapshotBarrierError, match="release marker"):
        snapshot_server.read_release_marker(marker)


def test_snapshot_create_rolls_back_failed_dump(tmp_path: Path):
    target = tmp_path / "snapshot"
    tools = _fake_snapshot_tools()
    tools.dump.side_effect = RuntimeError("dump failed")
    with pytest.raises(RuntimeError, match="dump failed"):
        create_snapshot(
            argparse.Namespace(snapshot_dir=str(target), model_tag="Qwen/Qwen3-0.6B"),
            tools=tools,
        )
    tools.abort_create.assert_called_once_with(100)
    assert not target.exists()


@pytest.mark.parametrize(
    "failure_kind", ["called-process", "timeout", "missing", "unreadable"]
)
def test_snapshot_create_preserves_bounded_criu_diagnostics_after_cleanup(
    tmp_path: Path, failure_kind: str, caplog: pytest.LogCaptureFixture
):
    target = tmp_path / "snapshot"
    tools = _fake_snapshot_tools()
    criu_tools = LocalSnapshotTools()
    criu_tools.plugin_dir = tmp_path
    original_run = criu_tools._run
    criu_tools._privileged = lambda: []  # type: ignore[method-assign]
    captured = "captured stderr " + "x" * 9000
    if failure_kind == "timeout":
        primary: subprocess.SubprocessError = subprocess.TimeoutExpired(
            ["criu", "dump"], 1, output=b"captured stdout\xff", stderr=captured.encode()
        )
    else:
        primary = subprocess.CalledProcessError(
            1, ["criu", "dump"], output="captured stdout", stderr=captured
        )

    def run(command: list[str], **_kwargs: object):
        if "tail" not in command:
            raise primary
        if failure_kind == "unreadable":
            raise PermissionError("log is root-owned")
        if failure_kind == "timeout":
            return subprocess.CompletedProcess(command, 0, "timeout log tail", "")
        return original_run(command, **_kwargs)

    criu_tools._run = run  # type: ignore[method-assign]

    def fail_dump(workdir: Path, _inventory: ProcessInventory) -> None:
        images = workdir / "images"
        images.mkdir()
        if failure_kind != "missing":
            (images / "dump.log").write_text(
                "omitted dump prefix" + "d" * 9000 + "distinctive dump diagnostic"
            )
        criu_tools._criu("dump", workdir, [])

    tools.dump.side_effect = fail_dump
    with pytest.raises(type(primary)) as excinfo:
        create_snapshot(
            argparse.Namespace(snapshot_dir=str(target), model_tag="Qwen/Qwen3-0.6B"),
            tools=tools,
        )

    assert excinfo.value is primary
    assert not target.exists()
    diagnostics = "\n".join(caplog.messages)
    assert "captured stdout" in diagnostics
    assert "captured stderr" not in diagnostics
    assert "x" * 100 in diagnostics
    assert len(diagnostics) < 17000
    if failure_kind in {"missing", "unreadable"}:
        assert "dump.log unavailable" in diagnostics
        expected_error = (
            "CalledProcessError" if failure_kind == "missing" else "PermissionError"
        )
        assert expected_error in diagnostics
    elif failure_kind == "called-process":
        assert "distinctive dump diagnostic" in diagnostics
        assert "omitted dump prefix" not in diagnostics
    else:
        assert "timeout log tail" in diagnostics
    if failure_kind == "timeout":
        assert "captured stdout�" in diagnostics


def test_snapshot_criu_failure_selects_restore_log(
    tmp_path: Path, caplog: pytest.LogCaptureFixture
):
    tools = LocalSnapshotTools()
    tools.plugin_dir = tmp_path
    tools._privileged = lambda: []  # type: ignore[method-assign]
    artifact = tmp_path / "snapshot"
    images = artifact / "images"
    images.mkdir(parents=True)
    (images / "restore.log").write_text(
        "omitted restore prefix" + "r" * 9000 + "distinctive restore diagnostic"
    )
    primary = subprocess.CalledProcessError(1, ["criu", "restore"])
    original_run = tools._run
    tailed: list[str] = []

    def run(command: list[str], **_kwargs: object):
        if "tail" not in command:
            raise primary
        tailed.append(command[-1])
        return original_run(command, **_kwargs)

    tools._run = run  # type: ignore[method-assign]

    with pytest.raises(subprocess.CalledProcessError) as excinfo:
        tools._criu("restore", artifact, [])

    assert excinfo.value is primary
    assert tailed == [str(images / "restore.log")]
    diagnostics = "\n".join(caplog.messages)
    assert "distinctive restore diagnostic" in diagnostics
    assert "omitted restore prefix" not in diagnostics


def _restore_fixture(tmp_path: Path) -> argparse.Namespace:
    artifact = tmp_path / "snapshot"
    artifact.mkdir(mode=0o700)
    write_manifest_atomic(artifact, _manifest())
    return argparse.Namespace(snapshot_dir=str(artifact), host="127.0.0.1", port=9000)


def test_snapshot_restore_rejects_identity_mismatch(tmp_path: Path):
    args = _restore_fixture(tmp_path)

    identity_mismatch = _fake_snapshot_tools()
    identity_mismatch.current_identity.side_effect = lambda _gpu_uuid: (
        _runtime_identity(vllm_version="different")
    )
    with pytest.raises(SnapshotCompatibilityError, match="vllm_version"):
        restore_snapshot(args, tools=identity_mismatch)
    identity_mismatch.restore.assert_not_called()


def test_snapshot_identity_mismatch_names_every_differing_field():
    with pytest.raises(SnapshotCompatibilityError) as mismatch:
        validate_identity(
            _manifest(),
            _runtime_identity(vllm_version="different", gpu_uuid="GPU-other"),
        )

    assert str(mismatch.value) == "snapshot mismatch: vllm_version, gpu_uuid"


def test_snapshot_restore_cleans_oracle_mismatch(tmp_path: Path):
    args = _restore_fixture(tmp_path)
    oracle_mismatch = _fake_snapshot_tools()
    oracle_mismatch.request_oracle.return_value = _oracle(logprob=-0.5)
    with pytest.raises(SnapshotRestoreError, match="oracle mismatch"):
        restore_snapshot(args, tools=oracle_mismatch)
    oracle_mismatch.cleanup.assert_called_once()
    oracle_mismatch.complete_restore.assert_not_called()
    assert oracles_match(_oracle(logprob=-100.0), _oracle(logprob=-100.0005))
    assert not oracles_match(_oracle(logprob=-100.0), _oracle(logprob=-100.002))


@pytest.mark.parametrize(
    ("invalid_update", "diagnostic"),
    [
        ({"unexpected": True}, "unexpected"),
        ({"schema_version": True}, "schema_version"),
        ({"boundary": "unknown"}, "boundary"),
        ({"process_tree": [100, 100]}, "root"),
        ({"cuda_holders": [999]}, "root"),
        ({"oracle_token_ids": [1, 2]}, "oracle_token_ids"),
        ({"oracle_sampled_token_logprob": 0}, "oracle_sampled_token_logprob"),
        (
            {"oracle_sampled_token_logprob": float("nan")},
            "oracle_sampled_token_logprob",
        ),
    ],
)
def test_snapshot_manifest_validation(
    tmp_path: Path,
    invalid_update: dict[str, object],
    diagnostic: str,
):
    artifact = tmp_path / "snapshot"
    artifact.mkdir(mode=0o700)
    write_manifest_atomic(artifact, _manifest())
    invalid = _manifest().model_dump(mode="json")
    invalid.update(invalid_update)
    (artifact / "manifest.json").write_text(json.dumps(invalid))
    with pytest.raises(
        SnapshotCompatibilityError,
        match=f"invalid snapshot manifest: {diagnostic}",
    ):
        read_manifest(artifact)


def _local_restore_artifact(tmp_path: Path, name: str = "snapshot") -> Path:
    artifact = tmp_path / name
    artifact.mkdir(mode=0o700)
    (artifact / "child.log").write_bytes(b"startup log")
    (artifact / "child.log.snapshot-size").write_text("11\n")
    return artifact


def _mark_snapshot_pids_free(monkeypatch: pytest.MonkeyPatch) -> list[int]:
    probed_pids: list[int] = []

    def missing(pid: int, _signal: int) -> None:
        probed_pids.append(pid)
        raise ProcessLookupError

    monkeypatch.setattr(snapshot_runtime.os, "kill", missing)
    return probed_pids


@pytest.mark.parametrize(
    ("probe_error", "diagnostic"),
    [
        (None, "already occupied: 100"),
        (PermissionError(), "already occupied: 100"),
        (OSError("probe failed"), "availability probe failed: 100"),
    ],
)
def test_snapshot_restore_rejects_unavailable_pid_before_mutation(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    probe_error: OSError | None,
    diagnostic: str,
):
    args = _restore_fixture(tmp_path)
    artifact = Path(args.snapshot_dir)
    (artifact / "child.log").write_bytes(b"startup log")
    (artifact / "release.json").write_bytes(b"release sentinel")
    before = {path.name: path.read_bytes() for path in artifact.iterdir()}
    tools = LocalSnapshotTools()

    def fail_probe(_pid: int, _signal: int) -> None:
        if probe_error is not None:
            raise probe_error

    monkeypatch.setattr(snapshot_runtime.os, "kill", fail_probe)
    monkeypatch.setattr(tools, "preflight", lambda *_args: None)
    monkeypatch.setattr(tools, "current_identity", lambda _uuid: _runtime_identity())
    monkeypatch.setattr(tools, "_criu", lambda *_args: pytest.fail("CRIU called"))

    with pytest.raises(SnapshotRestoreError, match=diagnostic):
        restore_snapshot(args, tools=tools)
    assert {path.name: path.read_bytes() for path in artifact.iterdir()} == before


@pytest.mark.parametrize("mode", ["pinned", "unpinned", "cleanup-fails"])
def test_snapshot_restore_terminates_only_a_pinned_tree(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    mode: str,
):
    _mark_snapshot_pids_free(monkeypatch)
    artifact = _local_restore_artifact(tmp_path)
    manifest = _manifest()
    root_pid = manifest.process_tree[0]
    tools = LocalSnapshotTools()
    terminated: list[int] = []

    def terminate_and_wait(pid: int) -> None:
        terminated.append(pid)
        if mode == "cleanup-fails":
            raise SnapshotRestoreError("restored process cleanup is incomplete")
        tools._restored_processes.pop(pid)

    def pin(
        _artifact: Path, process_tree: tuple[int, ...], _holders: tuple[int, ...]
    ) -> None:
        if mode == "unpinned":
            raise SnapshotRestoreError(
                "restored session does not match the captured process tree"
            )
        tools._restored_processes[process_tree[0]] = ()

    monkeypatch.setattr(tools, "cleanup", terminate_and_wait)
    monkeypatch.setattr(tools, "_pin_restored_tree", pin)
    monkeypatch.setattr(tools, "_criu", lambda *_args: None)
    monkeypatch.setattr(
        tools,
        "_run",
        lambda command, **_kwargs: subprocess.CompletedProcess(command, 0, "333\n", ""),
    )

    with pytest.raises(SnapshotRestoreError, match="does not match") as failure:
        tools.restore(artifact, manifest)

    # A pinned tree is ours: it must be terminated and waited for, not left to
    # honour the abort marker. An unpinned PID is never signalled.
    assert terminated == ([] if mode == "unpinned" else [root_pid])
    if mode == "cleanup-fails":
        # The failure to terminate is reported beside the primary error, and a
        # tree that may still be running stays pinned.
        assert "restore cleanup failed" in str(failure.value)
        assert tools._restored_processes == {root_pid: ()}
    else:
        assert tools._restored_processes == {}
    assert json.loads((artifact / "release.json").read_text())["release"] is False


def test_snapshot_private_path_and_link_remap_security(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    _mark_snapshot_pids_free(monkeypatch)
    real = tmp_path / "real"
    real.mkdir(mode=0o700)
    linked = tmp_path / "linked"
    linked.symlink_to(real, target_is_directory=True)
    with pytest.raises(SnapshotSecurityError, match="symlink"):
        validate_artifact_root(linked, creating=False)

    unsafe = tmp_path / "unsafe"
    unsafe.mkdir()
    unsafe.chmod(0o777)
    with pytest.raises(SnapshotSecurityError, match="world-writable"):
        validate_artifact_root(unsafe / "snapshot", creating=True)

    artifact = _local_restore_artifact(tmp_path)
    saved_remaps = artifact / "link-remaps"
    saved_remaps.mkdir()
    (saved_remaps / "link_remap.270").write_bytes(b"semaphore state")
    shm_dir = tmp_path / "shm"
    shm_dir.mkdir()
    target = shm_dir / "link_remap.270"
    replacement = tmp_path / "replacement"
    replacement.write_bytes(b"semaphore state")
    target.symlink_to(replacement)
    tools = LocalSnapshotTools()
    tools.shm_dir = shm_dir
    monkeypatch.setattr(tools, "_criu", lambda *_args: pytest.fail("CRIU called"))
    with pytest.raises(SnapshotRestoreError, match="conflicting CRIU link remap"):
        tools.restore(artifact, _manifest())


@pytest.mark.parametrize("inventory_error", [None, PermissionError("denied")])
def test_snapshot_dump_rejects_unavailable_link_remap_inventory_before_criu(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    inventory_error: PermissionError | None,
):
    tools = LocalSnapshotTools()
    workdir = tmp_path / "snapshot"
    workdir.mkdir()
    tools.shm_dir = tmp_path / "missing-shm"
    if inventory_error is not None:
        tools.shm_dir.mkdir()

        def fail_inventory(_path: Path):
            raise inventory_error

        monkeypatch.setattr(Path, "iterdir", fail_inventory)
    monkeypatch.setattr(tools, "_criu", lambda *_args: pytest.fail("CRIU called"))

    with pytest.raises(SnapshotCreateError, match="inventory CRIU link remaps"):
        tools.dump(workdir, ProcessInventory(100, (100,), (100,), "GPU"))


def test_snapshot_restore_rejects_saved_remap_without_prefix(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    _mark_snapshot_pids_free(monkeypatch)
    artifact = _local_restore_artifact(tmp_path)
    saved = artifact / "link-remaps"
    saved.mkdir()
    (saved / "270").write_bytes(b"invalid remap name")
    tools = LocalSnapshotTools()
    tools.shm_dir = tmp_path / "shm"
    tools.shm_dir.mkdir()
    monkeypatch.setattr(tools, "_criu", lambda *_args: pytest.fail("CRIU called"))

    with pytest.raises(SnapshotRestoreError, match="invalid saved link remap"):
        tools.restore(artifact, _manifest())

    assert not tuple(tools.shm_dir.iterdir())


def test_snapshot_link_remap_inventory_matches_only_numeric_suffixes(tmp_path: Path):
    for name in (
        "link_remap.0",
        "link_remap.270",
        "link_remap.",
        "link_remap.12a",
        "link-remap.12",
        "prefix.link_remap.12",
        "270",
    ):
        (tmp_path / name).touch()
    tools = LocalSnapshotTools()
    tools.shm_dir = tmp_path

    assert tools._link_remap_names() == {"link_remap.0", "link_remap.270"}


def test_snapshot_dump_preserves_primary_failure_when_cleanup_inventory_fails(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    tools = LocalSnapshotTools()
    workdir = tmp_path / "snapshot"
    workdir.mkdir()
    inventory_calls = 0

    def inventory_names() -> set[str]:
        nonlocal inventory_calls
        inventory_calls += 1
        if inventory_calls > 1:
            raise SnapshotCreateError(
                "could not inventory CRIU link remaps: PermissionError: denied"
            )
        return set()

    monkeypatch.setattr(tools, "_link_remap_names", inventory_names)

    def fail_dump(*_args):
        raise RuntimeError("dump failed")

    monkeypatch.setattr(tools, "_criu", fail_dump)

    with pytest.raises(SnapshotCreateError) as failure:
        tools.dump(workdir, ProcessInventory(100, (100,), (100,), "GPU"))

    assert "RuntimeError: dump failed" in str(failure.value)
    assert "cleanup inventory failed" in str(failure.value)


@pytest.mark.parametrize(
    ("blocked_state", "message"),
    [("tcp", "external established TCP"), ("io_uring", "kernel.io_uring_disabled=1")],
)
def test_snapshot_rejects_unsafe_process_state_before_criu(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, blocked_state: str, message: str
):
    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "_tree_pids", lambda _root_pid: (100, 101))
    monkeypatch.setattr(tools, "_cuda_process_rows", lambda: ("101, GPU-abc",))
    monkeypatch.setattr(
        tools,
        "_descriptor_inventory",
        lambda _tree: (
            (101,) if blocked_state == "io_uring" else (),
            {41: 101},
        ),
    )
    tcp_record = _TcpSocketRecord(
        family="AF_INET",
        local_raw="3C00000A:D6F6",
        remote_raw="01010101:01BB" if blocked_state == "tcp" else "00000000:0000",
        inode=41,
    )
    monkeypatch.setattr(tools, "_tcp_records", lambda: (tcp_record,))
    with pytest.raises(SnapshotCreateError, match=message) as excinfo:
        tools.inventory(100, tmp_path)
    if blocked_state == "tcp":
        assert "pid 101" in str(excinfo.value)
        assert "1.1.1.1:443" in str(excinfo.value)


def test_snapshot_rejects_open_infiniband_device_before_criu(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "_tree_pids", lambda _root_pid: (100, 101))
    monkeypatch.setattr(tools, "_cuda_process_rows", lambda: ("101, GPU-abc",))
    monkeypatch.setattr(
        tools,
        "_descriptor_targets",
        lambda pid: ("/dev/infiniband/uverbs7",) if pid == 101 else (),
    )
    monkeypatch.setattr(tools, "_criu", lambda *_args: pytest.fail("CRIU called"))

    with pytest.raises(SnapshotCreateError) as failure:
        tools.inventory(100, tmp_path)

    message = str(failure.value)
    assert "pid 101" in message
    assert "/dev/infiniband/uverbs7" in message
    assert "NCCL_IB_DISABLE=1" in message


def test_decode_endpoint_families():
    assert _decode_endpoint("AF_INET", "3C00000A:D6F6") == "10.0.0.60:55030"
    assert (
        _decode_endpoint("AF_INET6", "00000000000000000000000001000000:01BB")
        == "[::1]:443"
    )


def test_tcp_records_skips_missing_tables(tmp_path, monkeypatch):
    tcp, tcp6 = tmp_path / "tcp", tmp_path / "tcp6"
    contents = "header\n0: 0100007F:1F90 00000000:0000 01 0 0 0 0 0 41\n"
    tcp.write_text(contents)
    tables = (("AF_INET", tcp), ("AF_INET6", tcp6))
    monkeypatch.setattr(snapshot_runtime, "_TCP_TABLES", tables)

    tools = LocalSnapshotTools()
    assert tuple(record.family for record in tools._tcp_records()) == ("AF_INET",)

    tcp6.write_text(contents)
    families = tuple(record.family for record in tools._tcp_records())
    assert families == ("AF_INET", "AF_INET6")


def test_snapshot_manifest_records_external_cache_files(tmp_path: Path):
    artifact = tmp_path / "snapshot"
    artifact.mkdir(mode=0o700)
    recorded = (
        (str(tmp_path / "kernel.so"), "b" * 64),
        (str(tmp_path / "launcher.lock"), "locked"),
    )
    write_manifest_atomic(artifact, _manifest(external_cache_files=recorded))

    assert read_manifest(artifact).external_cache_files == recorded

    # A manifest written before this field existed still loads.
    legacy = _manifest().model_dump(mode="json")
    del legacy["external_cache_files"]
    (artifact / "manifest.json").write_text(json.dumps(legacy))

    assert read_manifest(artifact).external_cache_files == ()


def _pin_generated_cache_roots(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    """Keep the resolver off whatever cache roots the host running this has."""
    monkeypatch.setenv("VLLM_CACHE_ROOT", str(tmp_path / "vllm_cache"))
    monkeypatch.setenv("TRITON_CACHE_DIR", str(tmp_path / "triton_cache_default"))
    monkeypatch.setenv("TORCHINDUCTOR_CACHE_DIR", str(tmp_path / "inductor_cache"))


def _unreadable(_path: Path) -> str:
    raise OSError("cache file is unreadable")


def test_snapshot_inventory_records_open_generated_cache_files(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    cache_root = tmp_path / "triton_cache"
    cache_root.mkdir()
    kernel = cache_root / "kernel.so"
    kernel.write_bytes(b"compiled kernel")
    lock = cache_root / "launcher.lock"
    lock.write_bytes(b"held by pid 100")
    weights = tmp_path / "model.safetensors"
    weights.write_bytes(b"weights")
    artifact = tmp_path / "snapshot"
    artifact.mkdir(mode=0o700)
    (artifact / "child.log.lock").write_bytes(b"")
    _pin_generated_cache_roots(monkeypatch, tmp_path)
    monkeypatch.setenv("TRITON_CACHE_DIR", str(cache_root))

    targets = {
        100: (str(kernel), str(weights), "socket:[41]"),
        101: (str(lock), str(cache_root), str(artifact / "child.log.lock")),
        102: (f"{cache_root / 'evicted.so'} (deleted)",),
    }
    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "_tree_pids", lambda _root_pid: (100, 101, 102))
    monkeypatch.setattr(tools, "_descriptor_targets", lambda pid: targets[pid])
    monkeypatch.setattr(tools, "_cuda_process_rows", lambda: ("101, GPU-abc",))
    monkeypatch.setattr(tools, "_tcp_records", lambda: ())
    monkeypatch.setattr(tools, "current_identity", lambda _uuid: _runtime_identity())

    manifest = tools.make_manifest(
        argparse.Namespace(
            model_tag="Qwen/Qwen3-0.6B",
            revision=_MODEL_REVISION,
            served_model_name=None,
            tokenizer_revision=None,
        ),
        ("Qwen/Qwen3-0.6B",),
        tools.inventory(100, artifact),
        _oracle(),
        artifact,
    )

    # Weights, sockets, the cache directory itself, files inside the artifact,
    # and deleted targets that CRIU remaps are all left out.
    assert manifest.external_cache_files == (
        (str(kernel), hashlib.sha256(b"compiled kernel").hexdigest()),
        (str(lock), "locked"),
    )


def test_snapshot_inventory_marks_a_cache_file_it_cannot_read(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    cache_root = tmp_path / "triton_cache"
    cache_root.mkdir()
    kernel = cache_root / "kernel.so"
    kernel.write_bytes(b"compiled kernel")
    lock = cache_root / "launcher.lock"
    lock.write_bytes(b"held by pid 100")
    artifact = tmp_path / "snapshot"
    artifact.mkdir(mode=0o700)
    _pin_generated_cache_roots(monkeypatch, tmp_path)
    monkeypatch.setenv("TRITON_CACHE_DIR", str(cache_root))

    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "_tree_pids", lambda _root_pid: (100,))
    monkeypatch.setattr(
        tools, "_descriptor_targets", lambda _pid: (str(kernel), str(lock))
    )
    monkeypatch.setattr(tools, "_cuda_process_rows", lambda: ("100, GPU-abc",))
    monkeypatch.setattr(tools, "_tcp_records", lambda: ())
    monkeypatch.setattr(tools, "_sha256", _unreadable)

    # A file that cannot be digested keeps its own marker, so the manifest says
    # which entries restore can check for existence only.
    assert tools.inventory(100, artifact).external_cache_files == (
        (str(kernel), "unreadable"),
        (str(lock), "locked"),
    )


def test_snapshot_inventory_matches_resolved_cache_roots_not_siblings(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    # Descriptor targets come back kernel-canonical, so the fixture must be too.
    base = Path(os.path.realpath(tmp_path))
    real_cache = base / "mnt_cache"
    real_cache.mkdir()
    kernel = real_cache / "kernel.so"
    kernel.write_bytes(b"compiled kernel")
    linked_cache = base / "linked_cache"
    linked_cache.symlink_to(real_cache, target_is_directory=True)
    sibling = base / "vllm_cache-backup"
    sibling.mkdir()
    stale = sibling / "stale.so"
    stale.write_bytes(b"stale kernel")
    artifact = base / "snapshot"
    artifact.mkdir(mode=0o700)

    _pin_generated_cache_roots(monkeypatch, base)
    monkeypatch.setenv("TRITON_CACHE_DIR", str(linked_cache))

    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "_tree_pids", lambda _root_pid: (100,))
    monkeypatch.setattr(
        tools, "_descriptor_targets", lambda _pid: (str(kernel), str(stale))
    )
    monkeypatch.setattr(tools, "_cuda_process_rows", lambda: ("100, GPU-abc",))
    monkeypatch.setattr(tools, "_tcp_records", lambda: ())

    # A cache root reached through a symlink still covers the file the kernel
    # reports by its real path, and a directory that merely shares the root's
    # name prefix is not part of the cache at all.
    assert tools.inventory(100, artifact).external_cache_files == (
        (str(kernel), hashlib.sha256(b"compiled kernel").hexdigest()),
    )


def test_snapshot_restore_rejects_rotated_cache_file_before_mutation(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    _mark_snapshot_pids_free(monkeypatch)
    kernel = tmp_path / "kernel.so"
    kernel.write_bytes(b"compiled kernel")
    lock = tmp_path / "launcher.lock"
    lock.write_bytes(b"held by pid 100")
    manifest = _manifest(
        external_cache_files=(
            (str(kernel), hashlib.sha256(b"compiled kernel").hexdigest()),
            (str(lock), "locked"),
        )
    )
    artifact = _local_restore_artifact(tmp_path)
    before = {path.name: path.read_bytes() for path in artifact.iterdir()}
    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "_criu", lambda *_args: pytest.fail("CRIU called"))

    kernel.write_bytes(b"recompiled kernel")
    with pytest.raises(SnapshotRestoreError, match="changed since capture") as changed:
        tools.restore(artifact, manifest)
    assert str(kernel) in str(changed.value)

    # Restoring the digest gets past the first entry, so the lock file is only
    # checked for existence: the second failure proves the first one passed.
    kernel.write_bytes(b"compiled kernel")
    lock.unlink()
    with pytest.raises(SnapshotRestoreError, match="vanished since capture") as gone:
        tools.restore(artifact, manifest)
    assert str(lock) in str(gone.value)

    assert {path.name: path.read_bytes() for path in artifact.iterdir()} == before


def test_snapshot_restore_rejects_a_cache_file_it_cannot_read(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
    _mark_snapshot_pids_free(monkeypatch)
    kernel = tmp_path / "kernel.so"
    kernel.write_bytes(b"compiled kernel")
    manifest = _manifest(
        external_cache_files=(
            (str(kernel), hashlib.sha256(b"compiled kernel").hexdigest()),
        )
    )
    artifact = _local_restore_artifact(tmp_path)
    tools = LocalSnapshotTools()
    monkeypatch.setattr(tools, "_criu", lambda *_args: pytest.fail("CRIU called"))
    monkeypatch.setattr(tools, "_sha256", _unreadable)

    # A recorded digest that cannot be read again is a failed check, not a file
    # to wave through on existence.
    with pytest.raises(
        SnapshotRestoreError, match="unreadable since capture"
    ) as blocked:
        tools.restore(artifact, manifest)
    assert str(kernel) in str(blocked.value)


def test_snapshot_runtime_installer_logs_the_failing_exit_code():
    installer = Path(__file__).parents[3] / "tools" / "install_snapshot_runtime.sh"
    if not installer.is_file():
        pytest.skip("installer script is not shipped in this test image")
    timing = [
        line
        for line in installer.read_text().splitlines()
        if line.startswith(("_snapshot_install_started=", "trap "))
    ]
    assert len(timing) == 2

    result = subprocess.run(
        ["bash", "-c", "\n".join([*timing, "exit 7"])],
        capture_output=True,
        text=True,
    )

    assert result.returncode == 7
    assert "(exit 7)" in result.stdout
