Ctrl K
ComunidadGuías…

scripts/test-bundle-compatibility.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Real pre-extension CLI compatibility; artifacts stay outside the checkout.

python3 scripts/test-bundle-compatibility.py --artifacts /tmp/croc-bundle-compat
The old executable is built from a Git archive of ebcdc31b, never a feature-
disabled new executable. Logs and SHA-256 manifests are retained for review.
"""

from bundle_bench_common import manifest, transfer_secret, stop_process
import argparse
import json
import os
from pathlib import Path
import socket
import subprocess
import tarfile
import time

BASE = "ebcdc31b413991e4892260b9f43697eb1797f7ca"
REPO = Path(__file__).resolve().parents[1]


def run(command, **kwargs):
    subprocess.run([str(part) for part in command], check=True, **kwargs)


def freeze(artifacts):
    source = artifacts / "frozen-ebcdc31b"
    if not source.exists():
        source.mkdir()
        archive = artifacts / "frozen-source.tar"
        run(["git", "archive", BASE, "--output", archive], cwd=REPO)
        with tarfile.open(archive) as stream:
            stream.extractall(source, filter="data")
        archive.unlink()
    (artifacts / "base-revision.txt").write_text(BASE + "\n")
    return source


def ports(count):
    reserved = []
    try:
        for _ in range(count):
            connection = socket.socket()
            connection.bind(("127.0.0.1", 0))
            reserved.append(connection)
        return [connection.getsockname()[1] for connection in reserved]
    finally:
        for connection in reserved:
            connection.close()


def transfer(sender, receiver, relay, source, output, extra_send=(), extra_receive=()):
    output.mkdir()
    destination = output / "received"
    destination.mkdir()
    secret = transfer_secret()
    common = [
        "--debug",
        "--relay",
        relay,
        "--relay6",
        "",
        "--yes",
        "--ignore-stdin",
        "--disable-clipboard",
    ]
    send_args = [
        sender,
        *common,
        "send",
        "--no-local",
        "--transport",
        "relay",
        *extra_send,
        source,
    ]
    receive_args = [receiver, *common, "--out", destination, *extra_receive]
    started = time.monotonic()
    processes = []
    try:
        with (output / "sender.log").open("w") as send_log, (
            output / "receiver.log"
        ).open("w") as receive_log:
            for command, log, name in [
                (send_args, send_log, "sender"),
                (receive_args, receive_log, "receiver"),
            ]:
                env = dict(
                    os.environ,
                    CROC_SECRET=secret,
                    CROC_CONFIG_DIR=str(output / f"config-{name}"),
                )
                process = subprocess.Popen(
                    [str(part) for part in command],
                    env=env,
                    stdin=subprocess.DEVNULL,
                    stdout=log,
                    stderr=subprocess.STDOUT,
                )
                processes.append(process)
                if name == "sender":
                    time.sleep(0.15)
            for process in reversed(processes):
                if process.wait(timeout=120) != 0:
                    raise RuntimeError(f"Transfer failed; see {output}")
        elapsed = time.monotonic() - started
        expected = manifest(source)
        actual = manifest(destination / source.name)
        if expected != actual:
            raise RuntimeError(f"Path, size, or SHA-256 mismatch; see {output}")
        sender_log = (output / "sender.log").read_text(
            encoding="utf-8", errors="replace"
        )
        receiver_log = (output / "receiver.log").read_text(
            encoding="utf-8", errors="replace"
        )
        result = {
            "seconds": elapsed,
            "files": len(actual),
            "manifest": actual,
            "bundle_requests": sender_log.count("read bundle-request message"),
            "bundle_messages": "bundle-request" in sender_log
            or "bundle-request" in receiver_log,
            "sender": str(sender),
            "receiver": str(receiver),
        }
        (output / "result.json").write_text(json.dumps(result, indent=2) + "\n")
        return result
    finally:
        for process in processes:
            stop_process(process)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--artifacts", type=Path, required=True)
    parser.add_argument("--old", type=Path)
    parser.add_argument("--new", type=Path)
    parser.add_argument("--freeze-only", action="store_true")
    args = parser.parse_args()
    artifacts = args.artifacts.resolve()
    artifacts.mkdir(parents=True, exist_ok=True)
    frozen = freeze(artifacts)
    if args.freeze_only:
        print(frozen)
        return
    suffix = ".exe" if os.name == "nt" else ""
    binaries = artifacts / "bin"
    binaries.mkdir(exist_ok=True)
    old, new = (
        args.old or binaries / f"old{suffix}",
        args.new or binaries / f"new{suffix}",
    )
    for binary, directory, supplied in [(old, frozen, args.old), (new, REPO, args.new)]:
        if not supplied:
            run(["go", "build", "-buildvcs=false", "-o", binary, "."], cwd=directory)
    relay_ports = ports(5)
    relay_address = f"127.0.0.1:{relay_ports[0]}"
    with (artifacts / "relay.log").open("w") as relay_log:
        relay = subprocess.Popen(
            [
                str(new),
                "relay",
                "--host",
                "127.0.0.1",
                "--ports",
                ",".join(map(str, relay_ports)),
            ],
            stdout=relay_log,
            stderr=subprocess.STDOUT,
        )
        try:
            deadline = time.monotonic() + 10
            while True:
                try:
                    with socket.create_connection(
                        ("127.0.0.1", relay_ports[0]), timeout=0.2
                    ):
                        break
                except OSError:
                    if time.monotonic() >= deadline:
                        raise RuntimeError("Relay did not start")
                    time.sleep(0.05)
            results = []
            for count in [3, 4, 256]:
                source = artifacts / f"fixture-{count}"
                source.mkdir(exist_ok=True)
                for index in range(count):
                    (source / f"{index:04}.bin").write_bytes(
                        bytes((i * 31 + index) % 251 for i in range(32768 + index))
                    )
                (artifacts / f"fixture-{count}-sha256.json").write_text(
                    json.dumps(manifest(source), indent=2)
                )
                for label, sender, receiver in [
                    ("new-old", new, old),
                    ("old-new", old, new),
                    ("new-new", new, new),
                ]:
                    result = transfer(
                        sender,
                        receiver,
                        relay_address,
                        source,
                        artifacts / f"{label}-{count}",
                    )
                    expected_bundles = (
                        (count + 127) // 128 if label == "new-new" and count >= 4 else 0
                    )
                    if result["bundle_requests"] != expected_bundles:
                        raise RuntimeError(
                            f"{label}/{count}: unexpected bundle count {result['bundle_requests']}"
                        )
                    if label != "new-new" and result["bundle_messages"]:
                        raise RuntimeError(
                            f"{label}/{count}: bundle messages reached an old peer"
                        )
                    results.append({"case": f"{label}/{count}", **result})
                    print(
                        f"{label}/{count}: {result['seconds']:.3f}s, {result['bundle_requests']} bundles, SHA-256 verified",
                        flush=True,
                    )
            (artifacts / "compatibility.json").write_text(
                json.dumps(results, indent=2) + "\n"
            )
        finally:
            relay.terminate()
            relay.wait(timeout=10)


if __name__ == "__main__":
    main()