Implement fail-closed ignored workspace routing
This commit is contained in:
parent
ba6a075839
commit
95104fa4e7
5 changed files with 6024 additions and 6 deletions
943
tools/ignored_workspace_benchmark.py
Normal file
943
tools/ignored_workspace_benchmark.py
Normal file
|
|
@ -0,0 +1,943 @@
|
|||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import resource
|
||||
import stat
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import zipfile
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CONTRACT_PATH = ROOT / "tests/fixtures/ignored-workspace-v1/benchmark.json"
|
||||
FIXTURE_CONTEXT = b"lazaret-ignored-workspace-fixture-v1\0"
|
||||
RESULT_CONTEXT = b"lazaret-ignored-workspace-result-v1\0"
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise AssertionError(message)
|
||||
|
||||
|
||||
def load_contract(path):
|
||||
contract = json.loads(Path(path).read_text())
|
||||
require(contract["schema"] == "lazaret-ignored-workspace-benchmark-v1", "unsupported benchmark contract")
|
||||
return contract
|
||||
|
||||
|
||||
def workload(contract, identifier):
|
||||
matches = [candidate for candidate in contract["workloads"] if candidate["id"] == identifier]
|
||||
require(len(matches) == 1, f"unknown workload: {identifier}")
|
||||
return matches[0]
|
||||
|
||||
|
||||
def derived_bytes(seed, path, length=64):
|
||||
digest = hashlib.sha256(seed.encode() + b"/" + os.fsencode(path)).digest()
|
||||
return (digest * ((length + len(digest) - 1) // len(digest)))[:length]
|
||||
|
||||
|
||||
def write(path, data, mode=0o644):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(data)
|
||||
path.chmod(mode)
|
||||
|
||||
|
||||
def backing_paths(arguments):
|
||||
visible = Path(arguments.visible_root)
|
||||
if arguments.host_root is None:
|
||||
require(arguments.isolated_root is None, "isolated root requires a host root")
|
||||
visible.mkdir(parents=True, exist_ok=False)
|
||||
return visible, visible, visible
|
||||
host = Path(arguments.host_root)
|
||||
isolated = Path(arguments.isolated_root)
|
||||
require(host != isolated, "host and isolated roots must differ")
|
||||
host.mkdir(parents=True, exist_ok=False)
|
||||
isolated.mkdir(parents=True, exist_ok=False)
|
||||
return visible, host, isolated
|
||||
|
||||
|
||||
def initialize_routed_workspace(arguments, host):
|
||||
host.mkdir(parents=True, exist_ok=False)
|
||||
git(arguments, host, "init", "--quiet", "--initial-branch=benchmark")
|
||||
patterns = [f"/bench/metadata/d{index:03d}/" for index in range(1, 100, 2)]
|
||||
patterns.extend(["/bench/uv/.venv/", "/bench/cargo/target/"])
|
||||
write(host / ".gitignore", ("\n".join(patterns) + "\n").encode())
|
||||
|
||||
|
||||
def materialize_metadata(specification, host, isolated, direct):
|
||||
relative = Path("bench/metadata")
|
||||
seed = specification["fixture_seed"]
|
||||
for directory_index in range(100):
|
||||
directory = Path(f"d{directory_index:03d}")
|
||||
backing = host if direct or directory_index % 2 == 0 else isolated
|
||||
for file_index in range(100):
|
||||
file = directory / f"f{file_index:03d}"
|
||||
write(backing / relative / file, derived_bytes(seed, file))
|
||||
return relative
|
||||
|
||||
|
||||
def git(arguments, root, *values, check=True):
|
||||
environment = dict(os.environ)
|
||||
environment.update({
|
||||
"GIT_CONFIG_NOSYSTEM": "1",
|
||||
"GIT_AUTHOR_NAME": "Lazaret Benchmark",
|
||||
"GIT_AUTHOR_EMAIL": "[email protected]",
|
||||
"GIT_COMMITTER_NAME": "Lazaret Benchmark",
|
||||
"GIT_COMMITTER_EMAIL": "[email protected]",
|
||||
"GIT_AUTHOR_DATE": "1700000000 +0000",
|
||||
"GIT_COMMITTER_DATE": "1700000000 +0000",
|
||||
})
|
||||
return subprocess.run(
|
||||
[arguments.git, *values], cwd=root, env=environment, check=check,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
|
||||
def materialize_git(arguments, specification, host, isolated, direct):
|
||||
root = host / "bench/git"
|
||||
root.mkdir(parents=True)
|
||||
git(arguments, root, "init", "--quiet", "--initial-branch=benchmark")
|
||||
git(arguments, root, "config", "user.name", "Lazaret Benchmark")
|
||||
git(arguments, root, "config", "user.email", "[email protected]")
|
||||
write(root / ".gitignore", b"ignored/\n")
|
||||
seed = specification["fixture_seed"]
|
||||
for index in range(1000):
|
||||
path = Path("tracked") / f"t{index:04d}"
|
||||
write(root / path, derived_bytes(seed, path))
|
||||
git(arguments, root, "add", "--", ".gitignore", "tracked")
|
||||
git(arguments, root, "commit", "--quiet", "-m", "fixture")
|
||||
for index in range(500):
|
||||
ordinary = Path("ordinary") / f"u{index:04d}"
|
||||
ignored = Path("ignored") / f"i{index:04d}"
|
||||
write(root / ordinary, derived_bytes(seed, ordinary))
|
||||
if direct:
|
||||
write(root / ignored, b"isolated:" + derived_bytes(seed, ignored, 32))
|
||||
else:
|
||||
write(root / ignored, b"host-sentinel:" + derived_bytes(seed, ignored, 32))
|
||||
write(isolated / "bench/git" / ignored, b"isolated:" + derived_bytes(seed, ignored, 32))
|
||||
return Path("bench/git")
|
||||
|
||||
|
||||
def wheel_record_line(path, payload):
|
||||
digest = base64.urlsafe_b64encode(hashlib.sha256(payload).digest()).rstrip(b"=").decode()
|
||||
return f"{path},sha256={digest},{len(payload)}"
|
||||
|
||||
|
||||
def build_wheel(wheelhouse, package_index, seed):
|
||||
distribution = f"lazaret_bench_pkg_{package_index:02d}"
|
||||
module = distribution
|
||||
version = "1.0.0"
|
||||
dist_info = f"{distribution}-{version}.dist-info"
|
||||
module_path = f"{module}/__init__.py"
|
||||
value = hashlib.sha256(seed.encode() + distribution.replace("_", "-").encode()).hexdigest()
|
||||
module_bytes = f'VALUE = "{value}"\n'.encode()
|
||||
requirements = "" if package_index else "".join(
|
||||
f"Requires-Dist: lazaret-bench-pkg-{dependency:02d} == 1.0.0\n" for dependency in range(1, 32)
|
||||
)
|
||||
metadata = (
|
||||
"Metadata-Version: 2.1\n"
|
||||
f"Name: lazaret-bench-pkg-{package_index:02d}\n"
|
||||
f"Version: {version}\n"
|
||||
f"{requirements}\n"
|
||||
).encode()
|
||||
wheel = b"Wheel-Version: 1.0\nGenerator: lazaret\nRoot-Is-Purelib: true\nTag: py3-none-any\n"
|
||||
entries = {
|
||||
module_path: module_bytes,
|
||||
f"{dist_info}/METADATA": metadata,
|
||||
f"{dist_info}/WHEEL": wheel,
|
||||
}
|
||||
record_path = f"{dist_info}/RECORD"
|
||||
records = [wheel_record_line(path, payload) for path, payload in sorted(entries.items())]
|
||||
records.append(f"{record_path},,")
|
||||
entries[record_path] = ("\n".join(records) + "\n").encode()
|
||||
destination = wheelhouse / f"{distribution}-{version}-py3-none-any.whl"
|
||||
with zipfile.ZipFile(destination, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
|
||||
for path, payload in sorted(entries.items()):
|
||||
info = zipfile.ZipInfo(path, (2020, 1, 1, 0, 0, 0))
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
info.external_attr = 0o100644 << 16
|
||||
archive.writestr(info, payload)
|
||||
|
||||
|
||||
def materialize_uv(arguments, specification, host, isolated, direct):
|
||||
root = host / "bench/uv"
|
||||
wheelhouse = root / "wheelhouse"
|
||||
wheelhouse.mkdir(parents=True)
|
||||
for index in range(32):
|
||||
build_wheel(wheelhouse, index, specification["fixture_seed"])
|
||||
venv = root / ".venv" if direct else isolated / "bench/uv/.venv"
|
||||
venv.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(
|
||||
[arguments.uv, "venv", "--offline", "--no-project", "--python", sys.executable, venv],
|
||||
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
)
|
||||
return Path("bench/uv")
|
||||
|
||||
|
||||
def cargo_package_name(index):
|
||||
return f"lazaret-bench-crate-{index:02d}"
|
||||
|
||||
|
||||
def materialize_cargo(arguments, specification, host, isolated, direct):
|
||||
root = host / "bench/cargo"
|
||||
root.mkdir(parents=True)
|
||||
members = [cargo_package_name(index) for index in range(32)]
|
||||
write(root / "Cargo.toml", ("[workspace]\nresolver = \"2\"\nmembers = [\n" + "".join(f' "{member}",\n' for member in members) + "]\n").encode())
|
||||
seed = specification["fixture_seed"].encode()
|
||||
for index, name in enumerate(members):
|
||||
package = root / name
|
||||
dependencies = ""
|
||||
if index == 0:
|
||||
dependencies = "\n[dependencies]\n" + "".join(
|
||||
f'{cargo_package_name(value).replace("-", "_")} = {{ package = "{cargo_package_name(value)}", path = "../{cargo_package_name(value)}", version = "=1.0.0" }}\n'
|
||||
for value in range(1, 32)
|
||||
)
|
||||
write(package / "Cargo.toml", (f'[package]\nname = "{name}"\nversion = "1.0.0"\nedition = "2024"\n{dependencies}').encode())
|
||||
if index == 0:
|
||||
expression = " ^ ".join(f"{cargo_package_name(value).replace('-', '_')}::VALUE" for value in range(1, 32))
|
||||
write(package / "src/main.rs", f"fn main() {{ println!(\"{{}}\", {expression}); }}\n".encode())
|
||||
else:
|
||||
value = int.from_bytes(hashlib.sha256(seed + name.encode()).digest()[:8], "big")
|
||||
write(package / "src/lib.rs", f"pub const VALUE: u64 = {value};\n".encode())
|
||||
subprocess.run(
|
||||
[arguments.cargo, "generate-lockfile", "--offline"], cwd=root, check=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
)
|
||||
target = root / "target" if direct else isolated / "bench/cargo/target"
|
||||
target.mkdir(parents=True)
|
||||
return Path("bench/cargo")
|
||||
|
||||
|
||||
def materialize(arguments):
|
||||
contract = load_contract(arguments.contract)
|
||||
specification = workload(contract, arguments.workload)
|
||||
visible = Path(arguments.visible_root)
|
||||
if arguments.host_root is None:
|
||||
visible, host, isolated = backing_paths(arguments)
|
||||
else:
|
||||
require(arguments.isolated_root is not None, "routed materialization requires an isolated root")
|
||||
host = Path(arguments.host_root)
|
||||
isolated = Path(arguments.isolated_root)
|
||||
require(host != isolated, "host and isolated roots must differ")
|
||||
initialize_routed_workspace(arguments, host)
|
||||
isolated.mkdir(parents=True, exist_ok=False)
|
||||
direct = arguments.host_root is None
|
||||
if specification["category"] == "metadata":
|
||||
relative = materialize_metadata(specification, host, isolated, direct)
|
||||
elif specification["category"] == "git":
|
||||
relative = materialize_git(arguments, specification, host, isolated, direct)
|
||||
elif specification["category"] == "package-manager":
|
||||
relative = materialize_uv(arguments, specification, host, isolated, direct)
|
||||
elif specification["category"] == "build":
|
||||
relative = materialize_cargo(arguments, specification, host, isolated, direct)
|
||||
else:
|
||||
raise AssertionError("unsupported workload category")
|
||||
manifest = {
|
||||
"schema": "lazaret-ignored-workspace-materialization-v1",
|
||||
"workload": arguments.workload,
|
||||
"placement": "direct" if direct else "routed",
|
||||
"visible_workload_root": os.fspath(visible / relative),
|
||||
}
|
||||
Path(arguments.output).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(arguments.output).write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
def fixture_identity(root, excluded_roots=()):
|
||||
records = []
|
||||
excluded = {os.fsencode(value) for value in excluded_roots}
|
||||
for directory, directories, files in os.walk(root, topdown=True, followlinks=False):
|
||||
directories.sort(key=os.fsencode)
|
||||
files.sort(key=os.fsencode)
|
||||
relative_directory = Path(directory).relative_to(root)
|
||||
if relative_directory == Path("."):
|
||||
directories[:] = [name for name in directories if os.fsencode(name) not in excluded]
|
||||
files[:] = [name for name in files if os.fsencode(name) not in excluded]
|
||||
for name in [*directories, *files]:
|
||||
path = Path(directory) / name
|
||||
relative = relative_directory / name
|
||||
metadata = os.lstat(path)
|
||||
if stat.S_ISDIR(metadata.st_mode):
|
||||
kind, payload = 1, b""
|
||||
elif stat.S_ISREG(metadata.st_mode):
|
||||
kind, payload = 2, path.read_bytes()
|
||||
elif stat.S_ISLNK(metadata.st_mode):
|
||||
kind, payload = 3, os.fsencode(os.readlink(path))
|
||||
else:
|
||||
raise AssertionError(f"unsupported fixture entry: {relative}")
|
||||
records.append((os.fsencode(relative), kind, metadata.st_mode & 0o7777, payload))
|
||||
records.sort(key=lambda record: record[0])
|
||||
stream = bytearray(FIXTURE_CONTEXT)
|
||||
stream.extend(struct.pack(">I", len(records)))
|
||||
for path, kind, mode, payload in records:
|
||||
stream.extend(struct.pack(">I", len(path)))
|
||||
stream.extend(path)
|
||||
stream.extend(struct.pack(">BIQ", kind, mode, len(payload)))
|
||||
stream.extend(hashlib.sha256(payload).digest())
|
||||
return hashlib.sha256(stream).digest()
|
||||
|
||||
|
||||
def workload_commands(arguments, identifier, root):
|
||||
if identifier == "metadata-10000":
|
||||
return [[arguments.metadata_benchmark, "metadata", "--root", root, "--bytewise-order", "--statx-mask", "basic-stats"]]
|
||||
if identifier == "git-2000-status-add-reset":
|
||||
return [
|
||||
[arguments.git, "status", "--porcelain=v2", "--untracked-files=all"],
|
||||
[arguments.git, "add", "--", "ordinary"],
|
||||
[arguments.git, "reset", "--quiet", "--mixed"],
|
||||
[arguments.git, "status", "--porcelain=v2", "--untracked-files=all"],
|
||||
]
|
||||
if identifier == "uv-offline-32-wheels":
|
||||
return [[arguments.uv, "pip", "install", "--offline", "--no-index", "--find-links", root / "wheelhouse", "--python", root / ".venv/bin/python", "lazaret-bench-pkg-00==1.0.0"]]
|
||||
if identifier == "cargo-offline-32-crates":
|
||||
return [[arguments.cargo, "check", "--offline", "--locked", "--workspace", "--target-dir", root / "target"]]
|
||||
raise AssertionError(f"unsupported workload: {identifier}")
|
||||
|
||||
|
||||
def workload_state(arguments, specification, root):
|
||||
if specification["category"] == "git":
|
||||
index = root / ".git/index"
|
||||
state = {"index_sha256": hashlib.sha256(index.read_bytes()).hexdigest()}
|
||||
if arguments.host_root is not None:
|
||||
sentinels = Path(arguments.host_root) / "bench/git/ignored"
|
||||
state["ignored_host_sentinels_identity"] = fixture_identity(sentinels).hex()
|
||||
return state
|
||||
if specification["category"] == "build":
|
||||
return {
|
||||
"source_identity": fixture_identity(root, ("target",)).hex(),
|
||||
"lock_sha256": hashlib.sha256((root / "Cargo.lock").read_bytes()).hexdigest(),
|
||||
}
|
||||
if specification["category"] == "package-manager":
|
||||
candidates = list((root / ".venv").glob("lib/python*/site-packages"))
|
||||
if not candidates:
|
||||
return {"installed_distribution_count": 0, "installed_values_identity": None}
|
||||
site_packages = candidates[0]
|
||||
distributions = sorted(site_packages.glob("lazaret_bench_pkg_*-1.0.0.dist-info"))
|
||||
values = []
|
||||
for index in range(32):
|
||||
module = site_packages / f"lazaret_bench_pkg_{index:02d}/__init__.py"
|
||||
if module.exists():
|
||||
values.append(module.read_bytes())
|
||||
return {
|
||||
"installed_distribution_count": len(distributions),
|
||||
"installed_values_identity": hashlib.sha256(b"".join(values)).hexdigest() if len(values) == 32 else None,
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
def verify_uv_installation(root, seed):
|
||||
site_packages = next((root / ".venv").glob("lib/python*/site-packages"), None)
|
||||
require(site_packages is not None, "installed virtual environment has no site-packages")
|
||||
distributions = list(site_packages.glob("lazaret_bench_pkg_*-1.0.0.dist-info"))
|
||||
require(len(distributions) == 32, "offline installation did not install all 32 distributions")
|
||||
for index in range(32):
|
||||
normalized = f"lazaret-bench-pkg-{index:02d}"
|
||||
expected = hashlib.sha256(seed.encode() + normalized.encode()).hexdigest()
|
||||
module = site_packages / normalized.replace("-", "_") / "__init__.py"
|
||||
require(module.read_text() == f'VALUE = "{expected}"\n', f"installed module {normalized} differs")
|
||||
|
||||
|
||||
def invoke(arguments, specification, capture):
|
||||
root = Path(arguments.root)
|
||||
initial_identity = fixture_identity(root)
|
||||
initial_state = workload_state(arguments, specification, root)
|
||||
records = []
|
||||
started = time.clock_gettime_ns(time.CLOCK_MONOTONIC_RAW)
|
||||
timeout_seconds = 4 * specification["absolute_median_ms"] / 1000
|
||||
for argv in workload_commands(arguments, arguments.workload, root):
|
||||
elapsed_seconds = (time.clock_gettime_ns(time.CLOCK_MONOTONIC_RAW) - started) / 1_000_000_000
|
||||
remaining = timeout_seconds - elapsed_seconds
|
||||
require(remaining > 0, "workload exceeded its sample timeout")
|
||||
result = subprocess.run(
|
||||
[os.fspath(value) for value in argv], cwd=root, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, timeout=remaining,
|
||||
)
|
||||
require(result.returncode == 0, f"workload command failed: {result.stderr.decode(errors='replace')}")
|
||||
if capture:
|
||||
records.append({"status": result.returncode, "stdout": result.stdout, "stderr": result.stderr})
|
||||
elapsed = time.clock_gettime_ns(time.CLOCK_MONOTONIC_RAW) - started
|
||||
post_identity = fixture_identity(root)
|
||||
post_state = workload_state(arguments, specification, root)
|
||||
if specification["category"] == "package-manager":
|
||||
verify_uv_installation(root, specification["fixture_seed"])
|
||||
if not capture:
|
||||
return None
|
||||
stream = bytearray(RESULT_CONTEXT)
|
||||
workload_bytes = arguments.workload.encode()
|
||||
stream.extend(struct.pack(">I", len(workload_bytes)))
|
||||
stream.extend(workload_bytes)
|
||||
stream.extend(initial_identity)
|
||||
stream.extend(struct.pack(">I", len(records)))
|
||||
for record in records:
|
||||
stream.extend(struct.pack(">iQ", record["status"], len(record["stdout"])))
|
||||
stream.extend(record["stdout"])
|
||||
stream.extend(struct.pack(">Q", len(record["stderr"])))
|
||||
stream.extend(record["stderr"])
|
||||
stream.extend(post_identity)
|
||||
return {
|
||||
"elapsed_ns": elapsed,
|
||||
"initial_fixture_identity": initial_identity.hex(),
|
||||
"post_fixture_identity": post_identity.hex(),
|
||||
"initial_workload_state": initial_state,
|
||||
"post_workload_state": post_state,
|
||||
"result_identity": hashlib.sha256(stream).hexdigest(),
|
||||
"commands": [
|
||||
{
|
||||
"status": record["status"],
|
||||
"stdout_base64": base64.b64encode(record["stdout"]).decode(),
|
||||
"stderr_base64": base64.b64encode(record["stderr"]).decode(),
|
||||
}
|
||||
for record in records
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def cpu_snapshot():
|
||||
fields = Path("/proc/stat").read_text().splitlines()[0].split()[1:]
|
||||
usage = resource.getrusage(resource.RUSAGE_SELF)
|
||||
children = resource.getrusage(resource.RUSAGE_CHILDREN)
|
||||
return {
|
||||
"system_ticks": sum(int(value) for value in fields),
|
||||
"idle_ticks": int(fields[3]) + int(fields[4]),
|
||||
"owned_seconds": usage.ru_utime + usage.ru_stime + children.ru_utime + children.ru_stime,
|
||||
}
|
||||
|
||||
|
||||
def memory_pressure_snapshot():
|
||||
pressure = {}
|
||||
path = Path("/sys/fs/cgroup/memory.pressure")
|
||||
if path.exists():
|
||||
for line in path.read_text().splitlines():
|
||||
category, *fields = line.split()
|
||||
pressure[category] = {key: int(value) for key, value in (field.split("=") for field in fields) if key == "total"}["total"]
|
||||
return pressure
|
||||
|
||||
|
||||
def run_sample(arguments):
|
||||
contract = load_contract(arguments.contract)
|
||||
specification = workload(contract, arguments.workload)
|
||||
require(arguments.preparation_state in {"unprimed", "primed"}, "invalid preparation state")
|
||||
sequence_started_ns = time.clock_gettime_ns(time.CLOCK_MONOTONIC_RAW)
|
||||
if arguments.preparation_state == "primed":
|
||||
invoke(arguments, specification, False)
|
||||
cpu_before = cpu_snapshot()
|
||||
pressure_before = memory_pressure_snapshot()
|
||||
sample = invoke(arguments, specification, True)
|
||||
pressure_after = memory_pressure_snapshot()
|
||||
cpu_after = cpu_snapshot()
|
||||
elapsed_seconds = sample["elapsed_ns"] / 1_000_000_000
|
||||
ticks_per_second = os.sysconf("SC_CLK_TCK")
|
||||
total_busy_seconds = (
|
||||
(cpu_after["system_ticks"] - cpu_before["system_ticks"])
|
||||
- (cpu_after["idle_ticks"] - cpu_before["idle_ticks"])
|
||||
) / ticks_per_second
|
||||
owned_seconds = max(0.0, cpu_after["owned_seconds"] - cpu_before["owned_seconds"])
|
||||
external_seconds = max(0.0, total_busy_seconds - owned_seconds)
|
||||
pressure_delta = {key: pressure_after.get(key, 0) - pressure_before.get(key, 0) for key in pressure_after.keys() | pressure_before.keys()}
|
||||
sample.update({
|
||||
"schema": "lazaret-ignored-workspace-benchmark-sample-v1",
|
||||
"workload": arguments.workload,
|
||||
"placement": arguments.placement,
|
||||
"preparation_state": arguments.preparation_state,
|
||||
"independent_run": arguments.independent_run,
|
||||
"sample_kind": arguments.sample_kind,
|
||||
"sample_index": arguments.sample_index,
|
||||
"pair_index": arguments.pair_index,
|
||||
"fixture_id": arguments.fixture_id,
|
||||
"sequence_started_ns": sequence_started_ns,
|
||||
"background_load": {
|
||||
"external_cpu_seconds": external_seconds,
|
||||
"reference_elapsed_seconds": elapsed_seconds,
|
||||
"memory_pressure_total_delta": pressure_delta,
|
||||
},
|
||||
})
|
||||
Path(arguments.output).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(arguments.output).write_text(json.dumps(sample, indent=2, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
def plan(arguments):
|
||||
contract = load_contract(arguments.contract)
|
||||
sampling = contract["sampling"]
|
||||
entries = []
|
||||
for independent_run in range(sampling["independent_runs"]):
|
||||
for specification in contract["workloads"]:
|
||||
for preparation_state in specification["preparation_states"]:
|
||||
for sample_kind, count in (
|
||||
("warmup", sampling["warmup_samples_per_cell"]),
|
||||
("measured", sampling["measured_samples_per_cell"]),
|
||||
):
|
||||
for sample_index in range(count):
|
||||
pair_index = len(entries) // 2
|
||||
placements = ["direct", "routed"] if pair_index % 2 == 0 else ["routed", "direct"]
|
||||
for placement in placements:
|
||||
entries.append({
|
||||
"workload": specification["id"],
|
||||
"preparation_state": preparation_state,
|
||||
"independent_run": independent_run,
|
||||
"sample_kind": sample_kind,
|
||||
"sample_index": sample_index,
|
||||
"pair_index": pair_index,
|
||||
"placement": placement,
|
||||
"fixture_id": f"run-{independent_run}-{specification['id']}-{preparation_state}-{sample_kind}-{sample_index}-{placement}",
|
||||
})
|
||||
output = {"schema": "lazaret-ignored-workspace-benchmark-plan-v1", "samples": entries}
|
||||
Path(arguments.output).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(arguments.output).write_text(json.dumps(output, indent=2, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
def version_output(argv):
|
||||
result = subprocess.run(argv, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def cpu_information():
|
||||
values = {}
|
||||
processors = []
|
||||
current = {}
|
||||
for line in Path("/proc/cpuinfo").read_text().splitlines() + [""]:
|
||||
if not line:
|
||||
if current:
|
||||
processors.append(current)
|
||||
current = {}
|
||||
continue
|
||||
key, separator, value = line.partition(":")
|
||||
if separator:
|
||||
current[key.strip()] = value.strip()
|
||||
allowed = sorted(os.sched_getaffinity(0))
|
||||
topology = []
|
||||
for cpu in allowed:
|
||||
base = Path(f"/sys/devices/system/cpu/cpu{cpu}")
|
||||
topology.append({
|
||||
"logical_cpu": cpu,
|
||||
"package": int((base / "topology/physical_package_id").read_text()),
|
||||
"core": int((base / "topology/core_id").read_text()),
|
||||
"governor": (base / "cpufreq/scaling_governor").read_text().strip(),
|
||||
})
|
||||
first = processors[0]
|
||||
values["cpu_model"] = first.get("model name", "")
|
||||
values["cpu_microcode"] = first.get("microcode", "")
|
||||
values["allocated_cpu_topology"] = topology
|
||||
return values
|
||||
|
||||
|
||||
def decode_mount_path(value):
|
||||
return value.replace("\\040", " ").replace("\\011", "\t").replace("\\012", "\n").replace("\\134", "\\")
|
||||
|
||||
|
||||
def mount_information(root):
|
||||
root = Path(root).resolve()
|
||||
candidates = []
|
||||
for line in Path("/proc/self/mountinfo").read_text().splitlines():
|
||||
left, right = line.split(" - ", 1)
|
||||
fields = left.split()
|
||||
mountpoint = Path(decode_mount_path(fields[4]))
|
||||
try:
|
||||
root.relative_to(mountpoint)
|
||||
except ValueError:
|
||||
continue
|
||||
right_fields = right.split()
|
||||
candidates.append((len(mountpoint.parts), {
|
||||
"mountpoint": os.fspath(mountpoint),
|
||||
"source": decode_mount_path(right_fields[1]),
|
||||
"filesystem_type": right_fields[0],
|
||||
"filesystem_mount_options": sorted(set(fields[5].split(",")) | set(right_fields[2].split(","))),
|
||||
}))
|
||||
require(candidates, "benchmark root has no mountinfo entry")
|
||||
return max(candidates, key=lambda candidate: candidate[0])[1]
|
||||
|
||||
|
||||
def storage_information(source):
|
||||
require(source.startswith("/dev/"), "qualification storage is not a local block device")
|
||||
tree = json.loads(version_output(["lsblk", "--json", "--output", "PATH,TYPE,MODEL,TRAN"]))["blockdevices"]
|
||||
flattened = []
|
||||
|
||||
def visit(device, parents):
|
||||
flattened.append((device, parents))
|
||||
for child in device.get("children", []):
|
||||
visit(child, [*parents, device])
|
||||
|
||||
for device in tree:
|
||||
visit(device, [])
|
||||
source_real = os.path.realpath(source)
|
||||
matches = [(device, parents) for device, parents in flattened if os.path.realpath(device.get("path", "")) == source_real]
|
||||
require(matches, f"cannot identify storage device for {source}")
|
||||
device, parents = matches[0]
|
||||
stack = [*parents, device]
|
||||
models = [model.strip() for entry in stack if (model := entry.get("model")) and model.strip()]
|
||||
transports = [entry.get("tran", "") for entry in stack if entry.get("tran")]
|
||||
return {
|
||||
"storage_model": models[-1] if models else "unreported",
|
||||
"storage_transport": transports[-1] if transports else "unreported",
|
||||
"storage_stack": [entry.get("type", "unknown") for entry in stack],
|
||||
}
|
||||
|
||||
|
||||
def current_cgroup_swap_max(
|
||||
membership_path=Path("/proc/self/cgroup"),
|
||||
cgroup_root=Path("/sys/fs/cgroup"),
|
||||
):
|
||||
memberships = Path(membership_path).read_text().splitlines()
|
||||
require(len(memberships) == 1, "current cgroup data is not unified cgroup v2")
|
||||
fields = memberships[0].split(":", 2)
|
||||
require(
|
||||
len(fields) == 3 and fields[0] == "0" and fields[1] == "" and fields[2].startswith("/"),
|
||||
"current cgroup data is not unified cgroup v2",
|
||||
)
|
||||
membership = PurePosixPath(fields[2])
|
||||
require(
|
||||
all(component not in {"", ".", ".."} for component in membership.parts[1:]),
|
||||
"current cgroup path is malformed",
|
||||
)
|
||||
root = Path(cgroup_root).resolve(strict=True)
|
||||
directory = root.joinpath(*membership.parts[1:]).resolve(strict=True)
|
||||
try:
|
||||
directory.relative_to(root)
|
||||
except ValueError as error:
|
||||
raise AssertionError("current cgroup path escapes cgroup root") from error
|
||||
value = (directory / "memory.swap.max").read_text().strip()
|
||||
require(value == "max" or value.isascii() and value.isdigit(), "invalid memory.swap.max value")
|
||||
return value
|
||||
|
||||
|
||||
def qualify(arguments):
|
||||
mount = mount_information(arguments.root)
|
||||
filesystem = os.statvfs(arguments.root)
|
||||
cpu = cpu_information()
|
||||
memory_kib = int(next(line.split()[1] for line in Path("/proc/meminfo").read_text().splitlines() if line.startswith("MemTotal:")))
|
||||
memory_max_path = Path("/sys/fs/cgroup/memory.max")
|
||||
memory_max = memory_max_path.read_text().strip() if memory_max_path.exists() else "max"
|
||||
memory_bytes = memory_kib * 1024
|
||||
if memory_max != "max":
|
||||
memory_bytes = min(memory_bytes, int(memory_max))
|
||||
swap_max = current_cgroup_swap_max()
|
||||
xfs = version_output([arguments.xfs_info, arguments.root]) if mount["filesystem_type"].lower() == "xfs" else ""
|
||||
result = {
|
||||
"schema": "lazaret-ignored-workspace-qualification-v1",
|
||||
"architecture": platform.machine(),
|
||||
"kernel_release": platform.release(),
|
||||
**cpu,
|
||||
"memory_bytes": memory_bytes,
|
||||
"benchmark_cgroup_swap_max": swap_max,
|
||||
**storage_information(mount["source"]),
|
||||
"storage_kind": "local-block-device",
|
||||
"storage_network_backed": False,
|
||||
"storage_copy_on_write": "reflink=0" not in xfs,
|
||||
"filesystem_type": mount["filesystem_type"].upper(),
|
||||
"filesystem_block_bytes": filesystem.f_frsize,
|
||||
"filesystem_mount_options": mount["filesystem_mount_options"],
|
||||
"fuse_userspace_version": version_output([arguments.fusermount, "--version"]),
|
||||
"runsc_version": version_output([arguments.runsc, "--version"]),
|
||||
"git_version": version_output([arguments.git, "--version"]),
|
||||
"uv_version": version_output([arguments.uv, "--version"]),
|
||||
"rust_version": version_output([arguments.rustc, "--version"]),
|
||||
}
|
||||
Path(arguments.output).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(arguments.output).write_text(json.dumps(result, indent=2, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
def kernel_at_least(release, minimum):
|
||||
numbers = []
|
||||
for component in release.split("."):
|
||||
digits = "".join(character for character in component if character.isdigit())
|
||||
if not digits:
|
||||
break
|
||||
numbers.append(int(digits))
|
||||
if len(numbers) == 2:
|
||||
break
|
||||
return tuple(numbers) >= minimum
|
||||
|
||||
|
||||
def validate_qualification(contract, qualification, failures):
|
||||
expected_fields = contract["qualification_environment"]["qualification_result_fields"]
|
||||
missing = [field for field in expected_fields if field not in qualification]
|
||||
if missing:
|
||||
failures.append(f"qualification result omits fields: {missing}")
|
||||
return
|
||||
if qualification.get("architecture") != "x86_64":
|
||||
failures.append("qualification architecture is not x86_64")
|
||||
if not kernel_at_least(qualification["kernel_release"], (6, 9)):
|
||||
failures.append("qualification kernel is older than Linux 6.9")
|
||||
topology = qualification["allocated_cpu_topology"]
|
||||
physical = {(cpu["package"], cpu["core"]) for cpu in topology}
|
||||
if len(physical) < 8 or len(physical) != len(topology):
|
||||
failures.append("CPU allocation is not one hardware thread from at least eight physical cores")
|
||||
if any(cpu["governor"] != "performance" for cpu in topology):
|
||||
failures.append("an allocated CPU is not using the performance governor")
|
||||
if qualification["memory_bytes"] < 17_179_869_184:
|
||||
failures.append("qualification memory is below 16 GiB")
|
||||
if qualification.get("benchmark_cgroup_swap_max") != "0":
|
||||
failures.append("swap is not disabled for the benchmark cgroup")
|
||||
if qualification.get("storage_kind") != "local-block-device" or qualification.get("storage_network_backed") is not False:
|
||||
failures.append("qualification storage is not a local non-network block device")
|
||||
if qualification.get("storage_copy_on_write") is not False:
|
||||
failures.append("qualification storage has copy-on-write enabled")
|
||||
if qualification["filesystem_type"] != "XFS" or qualification["filesystem_block_bytes"] != 4096:
|
||||
failures.append("qualification filesystem is not 4096-byte XFS")
|
||||
mount_options = set(qualification["filesystem_mount_options"])
|
||||
if not {"relatime", "inode64"}.issubset(mount_options):
|
||||
failures.append("qualification filesystem omits relatime or inode64")
|
||||
if mount_options & {"quota", "usrquota", "grpquota", "prjquota", "uquota", "gquota", "pquota"}:
|
||||
failures.append("qualification filesystem enables quota")
|
||||
|
||||
|
||||
def lower_median(values):
|
||||
ordered = sorted(values)
|
||||
require(ordered, "cannot calculate an empty median")
|
||||
return ordered[len(ordered) // 2]
|
||||
|
||||
|
||||
def relative_mad(values):
|
||||
median = lower_median(values)
|
||||
require(median > 0, "non-positive median")
|
||||
return lower_median([abs(value - median) for value in values]) / median
|
||||
|
||||
|
||||
def decode_command_records(sample):
|
||||
return [
|
||||
{
|
||||
"status": record["status"],
|
||||
"stdout": base64.b64decode(record["stdout_base64"], validate=True),
|
||||
"stderr": base64.b64decode(record["stderr_base64"], validate=True),
|
||||
}
|
||||
for record in sample["commands"]
|
||||
]
|
||||
|
||||
|
||||
def validate_result_identity(sample, failures):
|
||||
try:
|
||||
records = decode_command_records(sample)
|
||||
stream = bytearray(RESULT_CONTEXT)
|
||||
identifier = sample["workload"].encode()
|
||||
stream.extend(struct.pack(">I", len(identifier)))
|
||||
stream.extend(identifier)
|
||||
stream.extend(bytes.fromhex(sample["initial_fixture_identity"]))
|
||||
stream.extend(struct.pack(">I", len(records)))
|
||||
for record in records:
|
||||
stream.extend(struct.pack(">iQ", record["status"], len(record["stdout"])))
|
||||
stream.extend(record["stdout"])
|
||||
stream.extend(struct.pack(">Q", len(record["stderr"])))
|
||||
stream.extend(record["stderr"])
|
||||
stream.extend(bytes.fromhex(sample["post_fixture_identity"]))
|
||||
require(hashlib.sha256(stream).hexdigest() == sample["result_identity"], "result identity differs")
|
||||
require(all(record["status"] == 0 for record in records), "a command status is nonzero")
|
||||
return records
|
||||
except (AssertionError, KeyError, ValueError, TypeError) as error:
|
||||
failures.append(f"invalid sample {sample.get('fixture_id', '<unknown>')}: {error}")
|
||||
return []
|
||||
|
||||
|
||||
def validate_workload_oracle(specification, sample, records, failures):
|
||||
prefix = sample.get("fixture_id", "sample")
|
||||
if not records:
|
||||
return
|
||||
if specification["category"] == "metadata":
|
||||
try:
|
||||
output = json.loads(records[0]["stdout"])
|
||||
require(output["regular_files"] == 10_000, "metadata file count differs")
|
||||
require(output["content_bytes"] == 640_000, "metadata byte count differs")
|
||||
require(output["fixture_identity"] == sample["initial_fixture_identity"], "metadata fixture identity differs")
|
||||
except (AssertionError, KeyError, ValueError, TypeError) as error:
|
||||
failures.append(f"{prefix}: metadata oracle failed: {error}")
|
||||
elif specification["category"] == "git":
|
||||
if len(records) != 4 or records[0]["stdout"] != records[3]["stdout"]:
|
||||
failures.append(f"{prefix}: Git status streams differ")
|
||||
initial = sample.get("initial_workload_state", {})
|
||||
post = sample.get("post_workload_state", {})
|
||||
if initial.get("index_sha256") != post.get("index_sha256"):
|
||||
failures.append(f"{prefix}: Git index did not return to its initial identity")
|
||||
if sample["placement"] == "routed" and initial.get("ignored_host_sentinels_identity") != post.get("ignored_host_sentinels_identity"):
|
||||
failures.append(f"{prefix}: ignored host sentinels changed")
|
||||
elif specification["category"] == "build":
|
||||
if sample.get("initial_workload_state") != sample.get("post_workload_state"):
|
||||
failures.append(f"{prefix}: Cargo changed the lockfile or source tree")
|
||||
elif specification["category"] == "package-manager":
|
||||
post = sample.get("post_workload_state", {})
|
||||
if post.get("installed_distribution_count") != 32 or post.get("installed_values_identity") is None:
|
||||
failures.append(f"{prefix}: offline installation oracle differs")
|
||||
|
||||
|
||||
def verify_results(arguments):
|
||||
contract = load_contract(arguments.contract)
|
||||
samples = [json.loads(path.read_text()) for path in sorted(Path(arguments.samples).glob("*.json"))]
|
||||
sampling = contract["sampling"]
|
||||
failures = []
|
||||
qualification = json.loads(Path(arguments.qualification).read_text())
|
||||
validate_qualification(contract, qualification, failures)
|
||||
seen = set()
|
||||
fixture_ids = set()
|
||||
for sample in samples:
|
||||
if sample.get("schema") != "lazaret-ignored-workspace-benchmark-sample-v1":
|
||||
failures.append(f"unsupported sample schema: {sample.get('schema')}")
|
||||
key = tuple(sample.get(field) for field in (
|
||||
"workload", "preparation_state", "independent_run", "sample_kind",
|
||||
"sample_index", "pair_index", "placement",
|
||||
))
|
||||
if key in seen:
|
||||
failures.append(f"duplicate sample coordinate: {key}")
|
||||
seen.add(key)
|
||||
fixture_id = sample.get("fixture_id")
|
||||
if fixture_id in fixture_ids:
|
||||
failures.append(f"reused fixture identity: {fixture_id}")
|
||||
fixture_ids.add(fixture_id)
|
||||
records = validate_result_identity(sample, failures)
|
||||
try:
|
||||
validate_workload_oracle(workload(contract, sample["workload"]), sample, records, failures)
|
||||
except (AssertionError, KeyError) as error:
|
||||
failures.append(f"invalid sample workload: {error}")
|
||||
cells = []
|
||||
for specification in contract["workloads"]:
|
||||
for state in specification["preparation_states"]:
|
||||
run_medians = {"direct": [], "routed": [], "ratio": []}
|
||||
for run in range(sampling["independent_runs"]):
|
||||
matching = [sample for sample in samples if sample.get("workload") == specification["id"] and sample.get("preparation_state") == state and sample.get("independent_run") == run]
|
||||
for kind, expected_count in (("warmup", sampling["warmup_samples_per_cell"]), ("measured", sampling["measured_samples_per_cell"])):
|
||||
for placement in ("direct", "routed"):
|
||||
observed_count = sum(sample.get("sample_kind") == kind and sample.get("placement") == placement for sample in matching)
|
||||
if observed_count != expected_count:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: expected {expected_count} {kind} {placement} samples")
|
||||
for pair_index in {sample.get("pair_index") for sample in matching}:
|
||||
pair = [sample for sample in matching if sample.get("pair_index") == pair_index]
|
||||
if len(pair) == 2 and {sample.get("placement") for sample in pair} == {"direct", "routed"}:
|
||||
ordered = sorted(pair, key=lambda sample: sample.get("sequence_started_ns", 0))
|
||||
expected_first = "direct" if pair_index % 2 == 0 else "routed"
|
||||
if ordered[0].get("placement") != expected_first:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}/pair-{pair_index}: placement order differs")
|
||||
if pair[0].get("initial_fixture_identity") != pair[1].get("initial_fixture_identity"):
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}/pair-{pair_index}: initial fixtures differ")
|
||||
direct_sample = next(sample for sample in pair if sample["placement"] == "direct")
|
||||
routed_sample = next(sample for sample in pair if sample["placement"] == "routed")
|
||||
if specification["category"] in {"metadata", "git"}:
|
||||
direct_output = [record["stdout"] for record in decode_command_records(direct_sample)]
|
||||
routed_output = [record["stdout"] for record in decode_command_records(routed_sample)]
|
||||
if direct_output != routed_output:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}/pair-{pair_index}: routed output differs from direct oracle")
|
||||
for sample in matching:
|
||||
if sample.get("elapsed_ns", 0) > 4 * specification["absolute_median_ms"] * 1_000_000:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: sample exceeded timeout")
|
||||
background = sample.get("background_load", {})
|
||||
if background.get("external_cpu_seconds", float("inf")) > 0.05 * background.get("reference_elapsed_seconds", 0):
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: background CPU threshold exceeded")
|
||||
if any(background.get("memory_pressure_total_delta", {"missing": 1}).values()):
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: memory pressure caused reclaim")
|
||||
measured = [sample for sample in matching if sample.get("sample_kind") == "measured"]
|
||||
direct = sorted((sample for sample in measured if sample["placement"] == "direct"), key=lambda sample: sample["pair_index"])
|
||||
routed = sorted((sample for sample in measured if sample["placement"] == "routed"), key=lambda sample: sample["pair_index"])
|
||||
expected = sampling["measured_samples_per_cell"]
|
||||
if len(direct) != expected or len(routed) != expected:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: expected {expected} measured samples per placement")
|
||||
continue
|
||||
if [sample["pair_index"] for sample in direct] != [sample["pair_index"] for sample in routed]:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: paired sample identities differ")
|
||||
continue
|
||||
direct_times = [sample["elapsed_ns"] / 1_000_000 for sample in direct]
|
||||
routed_times = [sample["elapsed_ns"] / 1_000_000 for sample in routed]
|
||||
ratios = [right / left for left, right in zip(direct_times, routed_times, strict=True)]
|
||||
medians = {"direct": lower_median(direct_times), "routed": lower_median(routed_times), "ratio": lower_median(ratios)}
|
||||
for key in run_medians:
|
||||
run_medians[key].append(medians[key])
|
||||
calibration = specification["absolute_median_ms"] / specification["relative_median_ratio"]
|
||||
if medians["direct"] > calibration:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: direct calibration {medians['direct']:.3f} ms > {calibration:.3f} ms")
|
||||
if medians["routed"] > specification["absolute_median_ms"]:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: routed median exceeds absolute budget")
|
||||
if medians["ratio"] > specification["relative_median_ratio"]:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: median ratio exceeds relative budget")
|
||||
for label, values in (("direct", direct_times), ("routed", routed_times), ("ratio", ratios)):
|
||||
if relative_mad(values) > sampling["within_run_relative_mad_limit"]:
|
||||
failures.append(f"{specification['id']}/{state}/run-{run}: {label} relative MAD exceeds limit")
|
||||
cells.append({"workload": specification["id"], "preparation_state": state, "independent_run": run, "medians": medians})
|
||||
if all(len(values) == sampling["independent_runs"] for values in run_medians.values()):
|
||||
for label, values in run_medians.items():
|
||||
spread = (max(values) - min(values)) / lower_median(values)
|
||||
if spread > sampling["between_run_median_spread_limit"]:
|
||||
failures.append(f"{specification['id']}/{state}: {label} between-run spread exceeds limit")
|
||||
expected_samples = sum(
|
||||
len(specification["preparation_states"])
|
||||
* sampling["independent_runs"]
|
||||
* (sampling["warmup_samples_per_cell"] + sampling["measured_samples_per_cell"])
|
||||
* 2
|
||||
for specification in contract["workloads"]
|
||||
)
|
||||
if len(samples) != expected_samples:
|
||||
failures.append(f"expected {expected_samples} retained samples, observed {len(samples)}")
|
||||
result = {
|
||||
"schema": "lazaret-ignored-workspace-benchmark-result-v1",
|
||||
"disposition": "PASS" if not failures else "FAIL",
|
||||
"qualification": qualification,
|
||||
"cells": cells,
|
||||
"failures": failures,
|
||||
}
|
||||
Path(arguments.output).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(arguments.output).write_text(json.dumps(result, indent=2, sort_keys=True) + "\n")
|
||||
require(not failures, "benchmark qualification failed")
|
||||
|
||||
|
||||
def add_tool_arguments(parser):
|
||||
parser.add_argument("--git", default="git")
|
||||
parser.add_argument("--uv", default="uv")
|
||||
parser.add_argument("--cargo", default="cargo")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
prepare = subparsers.add_parser("prepare")
|
||||
prepare.add_argument("--contract", type=Path, default=CONTRACT_PATH)
|
||||
prepare.add_argument("--workload", required=True)
|
||||
prepare.add_argument("--visible-root", type=Path, required=True)
|
||||
prepare.add_argument("--host-root", type=Path)
|
||||
prepare.add_argument("--isolated-root", type=Path)
|
||||
prepare.add_argument("--output", type=Path, required=True)
|
||||
add_tool_arguments(prepare)
|
||||
schedule = subparsers.add_parser("plan")
|
||||
schedule.add_argument("--contract", type=Path, default=CONTRACT_PATH)
|
||||
schedule.add_argument("--output", type=Path, required=True)
|
||||
sample = subparsers.add_parser("sample")
|
||||
sample.add_argument("--contract", type=Path, default=CONTRACT_PATH)
|
||||
sample.add_argument("--workload", required=True)
|
||||
sample.add_argument("--root", type=Path, required=True)
|
||||
sample.add_argument("--placement", choices=("direct", "routed"), required=True)
|
||||
sample.add_argument("--preparation-state", choices=("unprimed", "primed"), required=True)
|
||||
sample.add_argument("--independent-run", type=int, required=True)
|
||||
sample.add_argument("--sample-kind", choices=("warmup", "measured"), required=True)
|
||||
sample.add_argument("--sample-index", type=int, required=True)
|
||||
sample.add_argument("--pair-index", type=int, required=True)
|
||||
sample.add_argument("--fixture-id", required=True)
|
||||
sample.add_argument("--host-root", type=Path)
|
||||
sample.add_argument("--metadata-benchmark", default="lazaret-workspace-benchmark")
|
||||
sample.add_argument("--output", type=Path, required=True)
|
||||
add_tool_arguments(sample)
|
||||
verify = subparsers.add_parser("verify")
|
||||
verify.add_argument("--contract", type=Path, default=CONTRACT_PATH)
|
||||
verify.add_argument("--samples", type=Path, required=True)
|
||||
verify.add_argument("--qualification", type=Path, required=True)
|
||||
verify.add_argument("--output", type=Path, required=True)
|
||||
qualification = subparsers.add_parser("qualify")
|
||||
qualification.add_argument("--root", type=Path, required=True)
|
||||
qualification.add_argument("--output", type=Path, required=True)
|
||||
qualification.add_argument("--fusermount", default="fusermount3")
|
||||
qualification.add_argument("--runsc", default="runsc")
|
||||
qualification.add_argument("--git", default="git")
|
||||
qualification.add_argument("--uv", default="uv")
|
||||
qualification.add_argument("--rustc", default="rustc")
|
||||
qualification.add_argument("--xfs-info", default="xfs_info")
|
||||
arguments = parser.parse_args()
|
||||
if arguments.command == "prepare":
|
||||
materialize(arguments)
|
||||
elif arguments.command == "plan":
|
||||
plan(arguments)
|
||||
elif arguments.command == "sample":
|
||||
run_sample(arguments)
|
||||
elif arguments.command == "qualify":
|
||||
qualify(arguments)
|
||||
else:
|
||||
verify_results(arguments)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
743
tools/ignored_workspace_verifier.py
Normal file
743
tools/ignored_workspace_verifier.py
Normal file
|
|
@ -0,0 +1,743 @@
|
|||
import argparse
|
||||
import contextlib
|
||||
import ctypes
|
||||
import errno
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import mmap
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FIXTURES = ROOT / "tests/fixtures/ignored-workspace-v1"
|
||||
NAMESPACE_MARKER = "LAZARET_IGNORED_WORKSPACE_PRIVATE_NAMESPACE"
|
||||
|
||||
|
||||
def command(argv, cwd=None, env=None, check=True):
|
||||
return subprocess.run(
|
||||
[os.fspath(value) for value in argv],
|
||||
cwd=cwd,
|
||||
env=env,
|
||||
check=check,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise AssertionError(message)
|
||||
|
||||
|
||||
def expect_errno(expected, action):
|
||||
try:
|
||||
action()
|
||||
except OSError as error:
|
||||
require(error.errno == expected, f"expected errno {expected}, observed {error.errno}")
|
||||
else:
|
||||
raise AssertionError(f"expected errno {expected}, operation succeeded")
|
||||
|
||||
|
||||
class MountedFixture:
|
||||
def __init__(self, work_root, router, global_excludes=None):
|
||||
self.root = Path(tempfile.mkdtemp(prefix="ignored-workspace-", dir=work_root))
|
||||
self.host = self.root / "host"
|
||||
self.isolated = self.root / "isolated"
|
||||
self.mount = self.root / "mount"
|
||||
for path in (self.host, self.isolated, self.mount):
|
||||
path.mkdir()
|
||||
command(["git", "init", "--quiet"], self.host)
|
||||
command(["git", "config", "user.email", "[email protected]"], self.host)
|
||||
command(["git", "config", "user.name", "Fixture"], self.host)
|
||||
self.router = Path(router)
|
||||
self.global_excludes = global_excludes
|
||||
self.process = None
|
||||
self.closed = False
|
||||
|
||||
def start(self):
|
||||
argv = [self.router, self.host, self.isolated, self.mount]
|
||||
if self.global_excludes is not None:
|
||||
argv.append(self.global_excludes)
|
||||
self.process = subprocess.Popen(argv, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
deadline = time.monotonic() + 10
|
||||
while time.monotonic() < deadline:
|
||||
if self.is_mounted():
|
||||
return self
|
||||
if self.process.poll() is not None:
|
||||
stderr = self.process.stderr.read().decode(errors="replace")
|
||||
raise RuntimeError(f"router exited before mount: {stderr}")
|
||||
time.sleep(0.01)
|
||||
raise TimeoutError("router did not mount within ten seconds")
|
||||
|
||||
def stop_router(self):
|
||||
if self.process is not None and self.process.poll() is None:
|
||||
try:
|
||||
self.process.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.process.terminate()
|
||||
self.process.wait(timeout=10)
|
||||
|
||||
def is_mounted(self):
|
||||
target = os.fsencode(self.mount)
|
||||
for line in Path("/proc/self/mountinfo").read_bytes().splitlines():
|
||||
fields = line.split(b" - ", 1)[0].split()
|
||||
if len(fields) >= 5 and mountinfo_path(fields[4]) == target:
|
||||
return True
|
||||
return False
|
||||
|
||||
def force_unmount(self):
|
||||
if not self.is_mounted():
|
||||
return
|
||||
result = command(["fusermount3", "-u", "-z", self.mount], check=False)
|
||||
require(result.returncode == 0 or not self.is_mounted(), result.stderr.decode(errors="replace"))
|
||||
|
||||
def shutdown(self):
|
||||
self.force_unmount()
|
||||
self.stop_router()
|
||||
require(not self.is_mounted(), "owned FUSE mount remains")
|
||||
|
||||
def close(self):
|
||||
if self.closed:
|
||||
return
|
||||
try:
|
||||
self.shutdown()
|
||||
shutil.rmtree(self.root)
|
||||
finally:
|
||||
self.closed = not self.root.exists() and not self.is_mounted()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, traceback):
|
||||
self.shutdown()
|
||||
|
||||
|
||||
def mountinfo_path(value):
|
||||
return value.replace(b"\\040", b" ").replace(b"\\011", b"\t").replace(b"\\012", b"\n").replace(b"\\134", b"\\")
|
||||
|
||||
|
||||
def write(path, data):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(data)
|
||||
|
||||
|
||||
def commit(fixture, relative="tracked", data=b"tracked"):
|
||||
write(fixture.host / relative, data)
|
||||
command(["git", "add", "--", relative], fixture.host)
|
||||
command(["git", "commit", "--quiet", "-m", relative], fixture.host)
|
||||
|
||||
|
||||
def ignored_pair(fixture, relative="cache/item", host=b"host", isolated=b"isolated"):
|
||||
write(fixture.host / ".gitignore", (relative.split("/", 1)[0] + "/\n").encode())
|
||||
write(fixture.host / relative, host)
|
||||
write(fixture.isolated / relative, isolated)
|
||||
|
||||
|
||||
def assert_pair_unchanged(fixture, relative, host, isolated):
|
||||
require((fixture.host / relative).read_bytes() == host, "host backing changed")
|
||||
require((fixture.isolated / relative).read_bytes() == isolated, "isolated backing changed")
|
||||
|
||||
|
||||
def classification_case(case_id, fixture):
|
||||
if case_id == "non-utf8-ignore-match":
|
||||
write(fixture.host / ".gitignore", b"*.obj\n")
|
||||
host_path = os.fsencode(fixture.host) + b"/\xff\xfe.obj"
|
||||
with open(host_path, "wb") as output:
|
||||
output.write(b"host-sentinel")
|
||||
path = os.fsencode(fixture.mount) + b"/\xff\xfe.obj"
|
||||
with fixture.start():
|
||||
require(not os.path.exists(path), "non-UTF8 host sentinel was exposed")
|
||||
descriptor = os.open(path, os.O_CREAT | os.O_WRONLY, 0o600)
|
||||
os.write(descriptor, b"isolated")
|
||||
os.close(descriptor)
|
||||
require(os.path.exists(os.fsencode(fixture.isolated) + b"/\xff\xfe.obj"), "non-UTF8 path was not isolated")
|
||||
require(Path(os.fsdecode(host_path)).read_bytes() == b"host-sentinel", "non-UTF8 host sentinel changed")
|
||||
return
|
||||
if case_id == "root-negation-precedence":
|
||||
write(fixture.host / ".gitignore", b"*.log\n!important.log\n")
|
||||
with fixture.start():
|
||||
write(fixture.mount / "ordinary.log", b"isolated")
|
||||
write(fixture.mount / "important.log", b"host")
|
||||
require((fixture.isolated / "ordinary.log").read_bytes() == b"isolated", "root ignore did not route")
|
||||
require((fixture.host / "important.log").read_bytes() == b"host", "negation did not restore host route")
|
||||
return
|
||||
if case_id == "nested-ignore-precedence":
|
||||
write(fixture.host / ".gitignore", b"*.tmp\n!nested/drop.tmp\n")
|
||||
write(fixture.host / "nested/.gitignore", b"drop.tmp\n!keep.tmp\n")
|
||||
with fixture.start():
|
||||
write(fixture.mount / "nested/drop.tmp", b"isolated")
|
||||
write(fixture.mount / "nested/keep.tmp", b"host")
|
||||
require((fixture.isolated / "nested/drop.tmp").is_file(), "nested ignored path not isolated")
|
||||
require((fixture.host / "nested/keep.tmp").is_file(), "nested negation not host-backed")
|
||||
return
|
||||
if case_id == "info-exclude":
|
||||
write(fixture.host / ".git/info/exclude", b"local.secret\n")
|
||||
write(fixture.host / "local.secret", b"host-sentinel")
|
||||
with fixture.start():
|
||||
require(not (fixture.mount / "local.secret").exists(), "info/exclude host sentinel was exposed")
|
||||
return
|
||||
if case_id == "global-excludes":
|
||||
global_file = fixture.root / "global-ignore"
|
||||
write(global_file, b"*.swap\n")
|
||||
fixture.global_excludes = global_file
|
||||
write(fixture.host / "editor.swap", b"host-sentinel")
|
||||
with fixture.start():
|
||||
require(not (fixture.mount / "editor.swap").exists(), "global exclude host sentinel was exposed")
|
||||
return
|
||||
if case_id == "ignore-case-folding":
|
||||
command(["git", "config", "core.ignoreCase", "true"], fixture.host)
|
||||
write(fixture.host / ".gitignore", b"build.log\n")
|
||||
write(fixture.host / "BUILD.LOG", b"host-sentinel")
|
||||
with fixture.start():
|
||||
require(not (fixture.mount / "BUILD.LOG").exists(), "case-folded host sentinel was exposed")
|
||||
return
|
||||
if case_id == "nested-repository-boundary":
|
||||
write(fixture.host / ".gitignore", b"*.cache\n")
|
||||
nested = fixture.host / "vendor/nested"
|
||||
nested.mkdir(parents=True)
|
||||
command(["git", "init", "--quiet"], nested)
|
||||
write(nested / ".gitignore", b"target/\n")
|
||||
write(nested / "target/sentinel", b"host-sentinel")
|
||||
with fixture.start():
|
||||
require(not (fixture.mount / "vendor/nested/target/sentinel").exists(), "nested host sentinel was exposed")
|
||||
write(fixture.mount / "vendor/nested/target/item", b"isolated")
|
||||
write(fixture.mount / "vendor/nested/ordinary.cache", b"host")
|
||||
require((fixture.isolated / "vendor/nested/target/item").is_file(), "nested policy not selected")
|
||||
require((fixture.host / "vendor/nested/ordinary.cache").is_file(), "parent policy crossed repository boundary")
|
||||
return
|
||||
if case_id == "live-ignore-publication":
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
write(fixture.host / "cache/item", b"host")
|
||||
with fixture.start():
|
||||
require((fixture.mount / "cache/item").read_bytes() == b"host", "initial host route missing")
|
||||
write(fixture.host / ".gitignore", b"cache/\n")
|
||||
require(not (fixture.mount / "cache/item").exists(), "published ignore did not hide host object")
|
||||
require((fixture.host / "cache/item").read_bytes() == b"host", "publication copied or removed host object")
|
||||
return
|
||||
raise AssertionError(f"classification case is not implemented: {case_id}")
|
||||
|
||||
|
||||
def atomic_case(case_id, fixture):
|
||||
if case_id == "cross-route-directory-exchange":
|
||||
write(fixture.host / ".gitignore", b"ignored/\n")
|
||||
(fixture.host / "source").mkdir()
|
||||
write(fixture.host / "source/marker", b"host")
|
||||
(fixture.isolated / "ignored/destination").mkdir(parents=True)
|
||||
write(fixture.isolated / "ignored/destination/marker", b"isolated")
|
||||
else:
|
||||
write(fixture.host / ".gitignore", b"ignored/\n")
|
||||
write(fixture.host / "source", b"source")
|
||||
if case_id in {"cross-route-file-rename", "cross-route-hard-link"}:
|
||||
(fixture.isolated / "ignored").mkdir()
|
||||
with fixture.start():
|
||||
if case_id == "cross-route-hard-link":
|
||||
expect_errno(errno.EXDEV, lambda: os.link(fixture.mount / "source", fixture.mount / "ignored/destination"))
|
||||
elif case_id == "cross-route-directory-exchange":
|
||||
rename_exchange(fixture.mount / "source", fixture.mount / "ignored/destination")
|
||||
else:
|
||||
expect_errno(errno.EXDEV, lambda: os.rename(fixture.mount / "source", fixture.mount / "ignored/destination"))
|
||||
if case_id == "cross-route-directory-exchange":
|
||||
require((fixture.host / "source/marker").read_bytes() == b"host", "source directory mutated after EXDEV")
|
||||
require((fixture.isolated / "ignored/destination/marker").read_bytes() == b"isolated", "destination directory mutated after EXDEV")
|
||||
else:
|
||||
require((fixture.host / "source").read_bytes() == b"source", "source mutated after EXDEV")
|
||||
require(not (fixture.host / "ignored/destination").exists() and not (fixture.isolated / "ignored/destination").exists(), "destination appeared after EXDEV")
|
||||
|
||||
|
||||
def rename_exchange(source, destination):
|
||||
observed = exchange_paths(source, destination)
|
||||
if observed == 0:
|
||||
raise AssertionError("cross-route RENAME_EXCHANGE succeeded")
|
||||
require(observed == errno.EXDEV, f"expected EXDEV from RENAME_EXCHANGE, observed {observed}")
|
||||
|
||||
|
||||
def exchange_paths(source, destination):
|
||||
libc = ctypes.CDLL(None, use_errno=True)
|
||||
result = libc.renameat2(-100, os.fsencode(source), -100, os.fsencode(destination), 2)
|
||||
if result == 0:
|
||||
return 0
|
||||
return ctypes.get_errno()
|
||||
|
||||
|
||||
def copy_file_range(source, destination, length):
|
||||
libc = ctypes.CDLL(None, use_errno=True)
|
||||
syscall = libc.copy_file_range
|
||||
syscall.argtypes = [
|
||||
ctypes.c_int,
|
||||
ctypes.POINTER(ctypes.c_longlong),
|
||||
ctypes.c_int,
|
||||
ctypes.POINTER(ctypes.c_longlong),
|
||||
ctypes.c_size_t,
|
||||
ctypes.c_uint,
|
||||
]
|
||||
syscall.restype = ctypes.c_ssize_t
|
||||
copied = 0
|
||||
while copied < length:
|
||||
result = syscall(source, None, destination, None, length - copied, 0)
|
||||
if result < 0:
|
||||
observed = ctypes.get_errno()
|
||||
raise OSError(observed, os.strerror(observed))
|
||||
if result == 0:
|
||||
break
|
||||
copied += result
|
||||
return copied
|
||||
|
||||
|
||||
def policy_transition_case(case_id, fixture):
|
||||
ignored_pair(fixture)
|
||||
if case_id == "non-ignored-becomes-ignored":
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
with fixture.start():
|
||||
if case_id == "ignored-becomes-non-ignored":
|
||||
require((fixture.mount / "cache/item").read_bytes() == b"isolated", "initial isolated route missing")
|
||||
old_descriptor = os.open(fixture.mount / "cache/item", os.O_RDWR)
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
require((fixture.mount / "cache/item").read_bytes() == b"host", "host route not selected")
|
||||
os.pwrite(old_descriptor, b"old-isolated", 0)
|
||||
os.close(old_descriptor)
|
||||
require((fixture.isolated / "cache/item").read_bytes().startswith(b"old-isolated"), "old handle rebound from isolated backing")
|
||||
else:
|
||||
require((fixture.mount / "cache/item").read_bytes() == b"host", "initial host route missing")
|
||||
old_descriptor = os.open(fixture.mount / "cache/item", os.O_RDWR)
|
||||
write(fixture.host / ".gitignore", b"cache/\n")
|
||||
require((fixture.mount / "cache/item").read_bytes() == b"isolated", "isolated route not selected")
|
||||
os.pwrite(old_descriptor, b"old-host", 0)
|
||||
os.close(old_descriptor)
|
||||
require((fixture.host / "cache/item").read_bytes().startswith(b"old-host"), "old handle rebound from host backing")
|
||||
if case_id == "ignored-becomes-non-ignored":
|
||||
require((fixture.host / "cache/item").read_bytes() == b"host", "transition copied to host")
|
||||
else:
|
||||
require((fixture.isolated / "cache/item").read_bytes() == b"isolated", "transition copied to isolated backing")
|
||||
|
||||
|
||||
def index_case(case_id, fixture):
|
||||
ignored_pair(fixture, "deps/lib")
|
||||
with fixture.start():
|
||||
if case_id == "guest-force-add-isolated-only":
|
||||
(fixture.host / "deps/lib").unlink()
|
||||
initial_index = (fixture.host / ".git/index").read_bytes() if (fixture.host / ".git/index").exists() else None
|
||||
result = command(["git", "add", "-f", "--", "deps/lib"], fixture.mount, check=False)
|
||||
require(result.returncode != 0, "isolated-only force-add succeeded")
|
||||
final_index = (fixture.host / ".git/index").read_bytes() if (fixture.host / ".git/index").exists() else None
|
||||
require(final_index == initial_index, "refused force-add published an index")
|
||||
require((fixture.isolated / "deps/lib").read_bytes() == b"isolated", "isolated object was lost")
|
||||
require(not (fixture.host / "deps/lib").exists(), "refused force-add created a host object")
|
||||
elif case_id == "host-established-tracked-cutover":
|
||||
(fixture.host / "deps/lib").unlink()
|
||||
old_descriptor = os.open(fixture.mount / "deps/lib", os.O_RDWR)
|
||||
write(fixture.host / "deps/lib", b"independent-host")
|
||||
command(["git", "add", "-f", "--", "deps/lib"], fixture.host)
|
||||
new_descriptor = os.open(fixture.mount / "deps/lib", os.O_RDONLY)
|
||||
require(os.read(new_descriptor, 64) == b"independent-host", "new handle did not select host object")
|
||||
os.pwrite(old_descriptor, b"old-handle", 0)
|
||||
os.close(old_descriptor)
|
||||
os.close(new_descriptor)
|
||||
require((fixture.host / "deps/lib").read_bytes() == b"independent-host", "old handle rebound to host")
|
||||
require((fixture.isolated / "deps/lib").read_bytes().startswith(b"old-handle"), "old handle did not remain isolated")
|
||||
else:
|
||||
(fixture.host / "deps/lib").unlink()
|
||||
command(["git", "update-index", "--add", "--cacheinfo", "100644", "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", "deps/lib"], fixture.host)
|
||||
expect_errno(errno.EIO, lambda: (fixture.mount / "deps/lib").read_bytes())
|
||||
require((fixture.isolated / "deps/lib").read_bytes() == b"isolated", "inconsistent isolated object was lost")
|
||||
|
||||
|
||||
def open_handle_case(case_id, fixture):
|
||||
ignored_pair(fixture, "cache/pinned", host=b"host-data", isolated=b"isolated-data")
|
||||
with fixture.start():
|
||||
if case_id == "mmap-policy-transition":
|
||||
descriptor = os.open(fixture.mount / "cache/pinned", os.O_RDWR)
|
||||
mapping = mmap.mmap(descriptor, 0, access=mmap.ACCESS_WRITE)
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
mapping[0:8] = b"mapped!!"
|
||||
mapping.flush()
|
||||
mapping.close()
|
||||
os.close(descriptor)
|
||||
require((fixture.isolated / "cache/pinned").read_bytes().startswith(b"mapped!!"), "mapping rebound")
|
||||
require((fixture.host / "cache/pinned").read_bytes() == b"host-data", "mapping copied back")
|
||||
elif case_id == "directory-stream-policy-snapshot":
|
||||
write(fixture.isolated / "cache/isolated-child", b"isolated")
|
||||
write(fixture.host / "cache/host-child", b"host")
|
||||
iterator = os.scandir(fixture.mount / "cache")
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
names = {entry.name for entry in iterator}
|
||||
iterator.close()
|
||||
require("isolated-child" in names and "host-child" not in names, "old stream mixed snapshots")
|
||||
new_names = {entry.name for entry in os.scandir(fixture.mount / "cache")}
|
||||
require("host-child" in new_names and "isolated-child" not in new_names, "new stream did not use new snapshot")
|
||||
else:
|
||||
descriptor = os.open(fixture.mount / "cache/pinned", os.O_RDWR)
|
||||
fcntl.lockf(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB, 1)
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
new_descriptor = os.open(fixture.mount / "cache/pinned", os.O_RDWR)
|
||||
fcntl.lockf(new_descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB, 1)
|
||||
require(lock_attempt(fixture.isolated / "cache/pinned") == errno.EAGAIN, "old isolated lock was transferred or lost")
|
||||
require(lock_attempt(fixture.host / "cache/pinned") == errno.EAGAIN, "new host lock was not established")
|
||||
os.close(descriptor)
|
||||
require(lock_attempt(fixture.isolated / "cache/pinned") == 0, "isolated lock remained after old handle closed")
|
||||
require(lock_attempt(fixture.host / "cache/pinned") == errno.EAGAIN, "isolated unlock affected host lock")
|
||||
os.close(new_descriptor)
|
||||
require(lock_attempt(fixture.host / "cache/pinned") == 0, "host lock remained after new handle closed")
|
||||
|
||||
|
||||
def lock_attempt(path):
|
||||
source = """
|
||||
import fcntl, os, sys
|
||||
descriptor = os.open(sys.argv[1], os.O_RDWR)
|
||||
try:
|
||||
fcntl.lockf(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB, 1)
|
||||
except OSError as error:
|
||||
sys.exit(error.errno)
|
||||
fcntl.lockf(descriptor, fcntl.LOCK_UN, 1)
|
||||
"""
|
||||
return subprocess.run([sys.executable, "-c", source, path], check=False).returncode
|
||||
|
||||
|
||||
def operation_case(case_id, fixture):
|
||||
ignored_pair(fixture, "cache/base")
|
||||
if case_id == "timestamps":
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
with fixture.start():
|
||||
if case_id == "extended-attributes-and-acls":
|
||||
path = fixture.mount / "cache/base"
|
||||
os.setxattr(path, b"user.lazaret", b"value")
|
||||
require(os.getxattr(path, b"user.lazaret") == b"value", "xattr round trip failed")
|
||||
require(os.getxattr(fixture.isolated / "cache/base", b"user.lazaret") == b"value", "xattr reached wrong backing")
|
||||
os.removexattr(path, b"user.lazaret")
|
||||
expect_errno(errno.ENODATA, lambda: os.getxattr(fixture.isolated / "cache/base", b"user.lazaret"))
|
||||
if shutil.which("setfacl"):
|
||||
command(["setfacl", "-m", f"u:{os.getuid()}:rw", path])
|
||||
mounted_acl = command(["getfacl", "--absolute-names", path]).stdout
|
||||
backing_acl = command(["getfacl", "--absolute-names", fixture.isolated / "cache/base"]).stdout
|
||||
require(b"user:" in mounted_acl and b"user:" in backing_acl, "POSIX ACL was not retained on selected backing")
|
||||
elif case_id == "sparse-copy-range":
|
||||
source = fixture.mount / "cache/source"
|
||||
destination = fixture.mount / "cache/destination"
|
||||
with source.open("wb") as output:
|
||||
output.write(b"x" * 8192)
|
||||
output.seek(1024 * 1024)
|
||||
output.write(b"tail")
|
||||
descriptor = os.open(source, os.O_RDONLY)
|
||||
require(os.lseek(descriptor, 0, os.SEEK_DATA) == 0, "SEEK_DATA did not find first extent")
|
||||
require(os.lseek(descriptor, 0, os.SEEK_HOLE) >= 8192, "SEEK_HOLE did not find sparse gap")
|
||||
os.close(descriptor)
|
||||
command(["fallocate", "--punch-hole", "--keep-size", "--offset", "0", "--length", "4096", source])
|
||||
require(source.read_bytes()[:4096] == bytes(4096), "hole punch retained data")
|
||||
with source.open("rb") as input_file, destination.open("wb") as output:
|
||||
require(
|
||||
copy_file_range(input_file.fileno(), output.fileno(), 1024 * 1024 + 4)
|
||||
== 1024 * 1024 + 4,
|
||||
"same-route copy range ended early",
|
||||
)
|
||||
require(input_file.tell() == 1024 * 1024 + 4, "same-route source offset differs")
|
||||
require(output.tell() == 1024 * 1024 + 4, "same-route destination offset differs")
|
||||
require(destination.read_bytes() == source.read_bytes(), "same-route copy differs")
|
||||
require(destination.stat().st_size == source.stat().st_size, "same-route copy size differs")
|
||||
write(fixture.host / "host-copy", b"unchanged")
|
||||
write(fixture.host / "unrelated-host", b"host-sentinel")
|
||||
write(fixture.isolated / "cache/unrelated-isolated", b"isolated-sentinel")
|
||||
source_before = (fixture.isolated / "cache/source").read_bytes()
|
||||
same_route_destination_before = (fixture.isolated / "cache/destination").read_bytes()
|
||||
with (fixture.mount / "host-copy").open("r+b") as output, source.open("rb") as input_file:
|
||||
write(fixture.host / ".gitignore", b"different/\n")
|
||||
os.lseek(input_file.fileno(), 4096, os.SEEK_SET)
|
||||
require(copy_file_range(input_file.fileno(), output.fileno(), 1) == 1, "cross-route copy count differs")
|
||||
require((fixture.host / "host-copy").read_bytes() == b"xnchanged", "cross-route destination range differs")
|
||||
require((fixture.isolated / "cache/source").read_bytes() == source_before, "cross-route copy mutated source")
|
||||
require(
|
||||
(fixture.isolated / "cache/destination").read_bytes() == same_route_destination_before,
|
||||
"cross-route copy mutated same-route destination",
|
||||
)
|
||||
require((fixture.host / "cache/source").exists() is False, "cross-route source handle was reclassified")
|
||||
require((fixture.host / "unrelated-host").read_bytes() == b"host-sentinel", "cross-route copy mutated unrelated host object")
|
||||
require(
|
||||
(fixture.isolated / "cache/unrelated-isolated").read_bytes() == b"isolated-sentinel",
|
||||
"cross-route copy mutated unrelated isolated object",
|
||||
)
|
||||
elif case_id == "symlink-confinement":
|
||||
os.symlink("../../outside", fixture.mount / "cache/link")
|
||||
require(os.readlink(fixture.mount / "cache/link") == "../../outside", "symlink target changed")
|
||||
write(fixture.host / "virtual-target", b"virtual-resolution")
|
||||
os.symlink("../virtual-target", fixture.mount / "cache/virtual-link")
|
||||
require(
|
||||
(fixture.mount / "cache/virtual-link").read_bytes() == b"virtual-resolution",
|
||||
"virtual symlink resolution changed",
|
||||
)
|
||||
command(
|
||||
[
|
||||
"cargo",
|
||||
"nextest",
|
||||
"run",
|
||||
"--offline",
|
||||
"--locked",
|
||||
"-p",
|
||||
"lazaret-ignored-workspace",
|
||||
"--test-threads",
|
||||
"2",
|
||||
"-E",
|
||||
"test(descriptor_traversal_race_never_follows_backing_symlinks)",
|
||||
],
|
||||
ROOT,
|
||||
)
|
||||
elif case_id == "timestamps":
|
||||
path = fixture.mount / "cache/base"
|
||||
stamp = 1_700_000_000_123_456_789
|
||||
os.utime(path, ns=(stamp, stamp), follow_symlinks=False)
|
||||
metadata = os.stat(path, follow_symlinks=False)
|
||||
require(metadata.st_mtime_ns == stamp, "nanosecond timestamp changed")
|
||||
require(os.stat(fixture.host / "cache/base", follow_symlinks=False).st_mtime_ns == stamp, "timestamp was not bidirectional")
|
||||
else:
|
||||
fifo = fixture.mount / "cache/fifo"
|
||||
os.mkfifo(fifo)
|
||||
with socket.socket(socket.AF_UNIX) as endpoint:
|
||||
original_directory = os.open(".", os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
os.chdir(fixture.mount)
|
||||
endpoint.bind("cache/socket")
|
||||
finally:
|
||||
os.fchdir(original_directory)
|
||||
os.close(original_directory)
|
||||
require(stat.S_ISFIFO(os.lstat(fixture.isolated / "cache/fifo").st_mode), "FIFO did not reach isolated backing")
|
||||
require(stat.S_ISSOCK(os.lstat(fixture.isolated / "cache/socket").st_mode), "socket did not reach isolated backing")
|
||||
expect_errno(errno.EPERM, lambda: os.mknod(fixture.mount / "cache/device", stat.S_IFCHR | 0o600, os.makedev(1, 3)))
|
||||
|
||||
|
||||
def git_case(case_id, fixture):
|
||||
commit(fixture)
|
||||
initial_branch = command(["git", "symbolic-ref", "--short", "HEAD"], fixture.host).stdout.strip()
|
||||
ignored_pair(fixture, "cache/sentinel")
|
||||
with fixture.start():
|
||||
if case_id == "git-ordinary-workflow":
|
||||
write(fixture.mount / "ordinary", b"ordinary")
|
||||
command(["git", "status", "--porcelain=v2"], fixture.mount)
|
||||
command(["git", "add", "ordinary"], fixture.mount)
|
||||
command(["git", "commit", "--quiet", "-m", "ordinary"], fixture.mount)
|
||||
write(fixture.mount / "tracked", b"changed")
|
||||
command(["git", "restore", "tracked"], fixture.mount)
|
||||
require((fixture.host / "ordinary").read_bytes() == b"ordinary", "ordinary commit was not bidirectional")
|
||||
require((fixture.host / "tracked").read_bytes() == b"tracked", "restore did not reach host worktree")
|
||||
elif case_id == "git-history-workflow":
|
||||
command(["git", "checkout", "-b", "topic"], fixture.mount)
|
||||
write(fixture.mount / "topic-file", b"topic")
|
||||
command(["git", "add", "topic-file"], fixture.mount)
|
||||
command(["git", "commit", "--quiet", "-m", "topic"], fixture.mount)
|
||||
command(["git", "checkout", initial_branch], fixture.mount)
|
||||
write(fixture.mount / "base-file", b"base")
|
||||
command(["git", "add", "base-file"], fixture.mount)
|
||||
command(["git", "commit", "--quiet", "-m", "base"], fixture.mount)
|
||||
command(["git", "checkout", "topic"], fixture.mount)
|
||||
command(["git", "rebase", initial_branch], fixture.mount)
|
||||
command(["git", "checkout", initial_branch], fixture.mount)
|
||||
command(["git", "merge", "topic"], fixture.mount)
|
||||
write(fixture.mount / "tracked", b"stash")
|
||||
command(["git", "stash"], fixture.mount)
|
||||
command(["git", "stash", "pop"], fixture.mount)
|
||||
command(["git", "restore", "tracked"], fixture.mount)
|
||||
require(command(["git", "rev-parse", "topic"], fixture.mount).stdout == command(["git", "rev-parse", "topic"], fixture.host).stdout, "reference update was not bidirectional")
|
||||
else:
|
||||
raise AssertionError("linked-worktree and Sentry cases use dedicated harnesses")
|
||||
assert_pair_unchanged(fixture, "cache/sentinel", b"host", b"isolated")
|
||||
|
||||
|
||||
def linked_worktree_case(case_id, fixture):
|
||||
del case_id
|
||||
shutil.rmtree(fixture.host)
|
||||
base = fixture.root / "base"
|
||||
base.mkdir()
|
||||
command(["git", "init", "--quiet"], base)
|
||||
command(["git", "config", "user.email", "[email protected]"], base)
|
||||
command(["git", "config", "user.name", "Fixture"], base)
|
||||
write(base / "tracked", b"tracked")
|
||||
command(["git", "add", "tracked"], base)
|
||||
command(["git", "commit", "--quiet", "-m", "base"], base)
|
||||
command(["git", "worktree", "add", "--quiet", fixture.host], base)
|
||||
write(fixture.host / ".gitignore", b"target/\n")
|
||||
write(fixture.host / "target/item", b"host-sentinel")
|
||||
with fixture.start():
|
||||
require(not (fixture.mount / "target/item").exists(), "linked-worktree host sentinel was exposed")
|
||||
write(fixture.mount / "target/item", b"isolated")
|
||||
result = command(["git", "status", "--porcelain=v2", "--untracked-files=all"], fixture.mount)
|
||||
require(b"target/item" not in result.stdout, "linked-worktree ignore policy was not applied")
|
||||
require((fixture.isolated / "target/item").read_bytes() == b"isolated", "linked-worktree isolated state missing")
|
||||
require((fixture.host / "target/item").read_bytes() == b"host-sentinel", "linked-worktree host sentinel changed")
|
||||
|
||||
|
||||
def sentry_case(case_id, fixture, sentry_binary):
|
||||
del case_id
|
||||
commit(fixture)
|
||||
transient = fixture.root / "transient-hooks"
|
||||
transient.mkdir()
|
||||
write(fixture.host / ".git/hooks/host-sentinel", b"host")
|
||||
host_config = (fixture.host / ".git/config").read_bytes()
|
||||
with fixture.start():
|
||||
process = subprocess.Popen(
|
||||
[sentry_binary, fixture.mount, fixture.host / ".git", transient],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
try:
|
||||
require(process.stdout.readline() == b"ready\n", "Git Sentry did not become ready")
|
||||
expect_errno(errno.EROFS, lambda: write(fixture.mount / ".git/config", b"changed"))
|
||||
require(not (fixture.mount / ".git/hooks/host-sentinel").exists(), "host hook remained visible")
|
||||
write(fixture.mount / ".git/hooks/sandbox-hook", b"transient")
|
||||
command(["git", "status", "--porcelain=v2"], fixture.mount)
|
||||
finally:
|
||||
process.stdin.close()
|
||||
return_code = process.wait(timeout=10)
|
||||
require(return_code == 0, process.stderr.read().decode(errors="replace"))
|
||||
require((fixture.mount / ".git/config").read_bytes() == host_config, "host config was not restored")
|
||||
require((fixture.mount / ".git/hooks/host-sentinel").read_bytes() == b"host", "host hooks were not restored")
|
||||
require((fixture.host / ".git/hooks/host-sentinel").read_bytes() == b"host", "host hook changed")
|
||||
require((transient / "sandbox-hook").read_bytes() == b"transient", "transient hook missing")
|
||||
|
||||
|
||||
def invalidation_or_race_case(case_id, fixture):
|
||||
ignored_pair(fixture)
|
||||
with fixture.start():
|
||||
if case_id == "concurrent-host-sandbox-mutation":
|
||||
barrier = threading.Barrier(2)
|
||||
errors = []
|
||||
def host_replace():
|
||||
try:
|
||||
barrier.wait()
|
||||
write(fixture.host / "ordinary.tmp", b"replacement")
|
||||
os.replace(fixture.host / "ordinary.tmp", fixture.host / "ordinary")
|
||||
except BaseException as error:
|
||||
errors.append(error)
|
||||
write(fixture.host / "ordinary", b"initial")
|
||||
require((fixture.mount / "ordinary").read_bytes() == b"initial", "initial host bytes differ")
|
||||
write(fixture.host / "ordinary", b"changed")
|
||||
require((fixture.mount / "ordinary").read_bytes() == b"changed", "same-size host overwrite was stale")
|
||||
thread = threading.Thread(target=host_replace)
|
||||
thread.start()
|
||||
barrier.wait()
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.replace(fixture.mount / "ordinary", fixture.mount / "ordinary-renamed")
|
||||
thread.join()
|
||||
require(not errors, f"host racer failed: {errors}")
|
||||
names = [entry.name for entry in os.scandir(fixture.mount)]
|
||||
require(len(names) == len(set(names)), "concurrent enumeration duplicated a name")
|
||||
candidates = [name for name in ("ordinary", "ordinary-renamed") if (fixture.mount / name).exists()]
|
||||
require(candidates, "native rename race lost every host-backed outcome")
|
||||
write(fixture.mount / candidates[0], b"sandbox-write")
|
||||
(fixture.mount / candidates[0]).unlink()
|
||||
require((fixture.isolated / "cache/item").read_bytes() == b"isolated", "race crossed backing")
|
||||
else:
|
||||
fixture.process.kill()
|
||||
fixture.process.wait(timeout=10)
|
||||
try:
|
||||
(fixture.mount / "cache/item").read_bytes()
|
||||
except OSError as error:
|
||||
require(error.errno in {errno.ENOTCONN, errno.EIO}, f"unexpected dead-router errno: {error.errno}")
|
||||
else:
|
||||
raise AssertionError("dead router fell through to host backing")
|
||||
fixture.force_unmount()
|
||||
assert_pair_unchanged(fixture, "cache/item", b"host", b"isolated")
|
||||
fixture.start()
|
||||
require((fixture.mount / "cache/item").read_bytes() == b"isolated", "recovered router selected wrong backing")
|
||||
|
||||
|
||||
CASE_GROUPS = {
|
||||
**{name: classification_case for name in [
|
||||
"non-utf8-ignore-match", "root-negation-precedence", "nested-ignore-precedence",
|
||||
"info-exclude", "global-excludes", "ignore-case-folding", "nested-repository-boundary",
|
||||
"live-ignore-publication",
|
||||
]},
|
||||
**{name: atomic_case for name in ["cross-route-file-rename", "cross-route-directory-exchange", "cross-route-hard-link"]},
|
||||
**{name: policy_transition_case for name in ["ignored-becomes-non-ignored", "non-ignored-becomes-ignored"]},
|
||||
**{name: index_case for name in ["guest-force-add-isolated-only", "host-established-tracked-cutover", "unmediated-inconsistent-index"]},
|
||||
**{name: open_handle_case for name in ["mmap-policy-transition", "locks-policy-transition", "directory-stream-policy-snapshot"]},
|
||||
**{name: operation_case for name in ["extended-attributes-and-acls", "sparse-copy-range", "symlink-confinement", "timestamps", "special-files"]},
|
||||
**{name: git_case for name in ["git-ordinary-workflow", "git-history-workflow"]},
|
||||
**{name: invalidation_or_race_case for name in ["concurrent-host-sandbox-mutation", "router-loss-fails-closed"]},
|
||||
}
|
||||
|
||||
|
||||
def run_corpus(arguments):
|
||||
inventory = json.loads((FIXTURES / "corpus.json").read_text())["cases"]
|
||||
selected = [case for case in inventory if arguments.case in (None, case["id"])]
|
||||
require(selected, "requested corpus case does not exist")
|
||||
missing = [case["id"] for case in inventory if case["id"] not in CASE_GROUPS and case["id"] not in {"linked-worktree-index", "git-sentry"}]
|
||||
require(not missing, f"corpus adapter is incomplete: {missing}")
|
||||
results = []
|
||||
Path(arguments.work_root).mkdir(parents=True, exist_ok=True)
|
||||
for case in selected:
|
||||
identifier = case["id"]
|
||||
started = time.monotonic_ns()
|
||||
fixture = MountedFixture(arguments.work_root, arguments.router)
|
||||
try:
|
||||
if identifier == "linked-worktree-index":
|
||||
linked_worktree_case(identifier, fixture)
|
||||
elif identifier == "git-sentry":
|
||||
sentry_case(identifier, fixture, arguments.git_sentry)
|
||||
else:
|
||||
CASE_GROUPS[identifier](identifier, fixture)
|
||||
disposition = "PASS"
|
||||
detail = None
|
||||
except BaseException as error:
|
||||
disposition = "FAIL"
|
||||
detail = repr(error)
|
||||
finally:
|
||||
try:
|
||||
fixture.close()
|
||||
except BaseException as cleanup_error:
|
||||
if disposition == "PASS":
|
||||
disposition = "FAIL"
|
||||
detail = f"cleanup failed: {cleanup_error!r}"
|
||||
else:
|
||||
detail = f"{detail}; cleanup failed: {cleanup_error!r}"
|
||||
results.append({"id": identifier, "disposition": disposition, "elapsed_ns": time.monotonic_ns() - started, "detail": detail})
|
||||
if disposition != "PASS" and arguments.fail_fast:
|
||||
break
|
||||
output = {"schema": "lazaret-ignored-workspace-corpus-result-v1", "results": results}
|
||||
Path(arguments.output).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(arguments.output).write_text(json.dumps(output, indent=2, sort_keys=True) + "\n")
|
||||
require(all(result["disposition"] == "PASS" for result in results), "one or more corpus cases failed")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
corpus = subparsers.add_parser("corpus")
|
||||
corpus.add_argument("--router", type=Path, required=True)
|
||||
corpus.add_argument("--git-sentry", type=Path, required=True)
|
||||
corpus.add_argument("--work-root", type=Path, required=True)
|
||||
corpus.add_argument("--output", type=Path, required=True)
|
||||
corpus.add_argument("--case")
|
||||
corpus.add_argument("--fail-fast", action="store_true")
|
||||
arguments = parser.parse_args()
|
||||
if arguments.command == "corpus":
|
||||
if arguments.case in (None, "git-sentry") and os.environ.get(NAMESPACE_MARKER) != "1":
|
||||
environment = os.environ.copy()
|
||||
environment[NAMESPACE_MARKER] = "1"
|
||||
os.execvpe(
|
||||
"unshare",
|
||||
[
|
||||
"unshare",
|
||||
"--user",
|
||||
"--map-root-user",
|
||||
"--mount",
|
||||
"--propagation",
|
||||
"private",
|
||||
"--",
|
||||
sys.executable,
|
||||
os.fspath(Path(__file__).resolve()),
|
||||
*sys.argv[1:],
|
||||
],
|
||||
environment,
|
||||
)
|
||||
run_corpus(arguments)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
81
tools/materialize-fuser
Executable file
81
tools/materialize-fuser
Executable file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
version=0.17.0
|
||||
crate_checksum=80a5eca878900c2e39e9e52fd797954b7fc39eeefc8558257114bfea6a698fcf
|
||||
patch_file="$root/patches/fuser-$version-lock-flags.patch"
|
||||
destination="$root/.lazaret/vendor/fuser-$version"
|
||||
cargo_home="${CARGO_HOME:-$HOME/.cargo}"
|
||||
fetch_tests=false
|
||||
|
||||
if [ "${1:-}" = "--test-dependencies" ] && [ "$#" -eq 1 ]; then
|
||||
fetch_tests=true
|
||||
elif [ "$#" -ne 0 ]; then
|
||||
printf '%s\n' "usage: tools/materialize-fuser [--test-dependencies]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
fail() {
|
||||
printf '%s\n' "materialize-fuser: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for command in cargo patch sha256sum tar; do
|
||||
command -v "$command" >/dev/null 2>&1 || fail "$command is required"
|
||||
done
|
||||
|
||||
[ -f "$patch_file" ] || fail "missing patch: $patch_file"
|
||||
patch_checksum="$(sha256sum "$patch_file" | cut -d ' ' -f 1)"
|
||||
stamp="$crate_checksum $patch_checksum"
|
||||
|
||||
fetch_test_dependencies() {
|
||||
local manifest="$destination/Cargo.toml"
|
||||
if ! cargo fetch --manifest-path "$manifest" --locked --offline >/dev/null 2>&1; then
|
||||
cargo fetch --manifest-path "$manifest" --locked
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -f "$destination/.lazaret-materialization" ] &&
|
||||
[ "$(tr -d '\n' < "$destination/.lazaret-materialization")" = "$stamp" ]; then
|
||||
if $fetch_tests; then
|
||||
fetch_test_dependencies
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
find_archive() {
|
||||
local candidate actual
|
||||
for candidate in "$cargo_home"/registry/cache/*/"fuser-$version.crate"; do
|
||||
[ -f "$candidate" ] || continue
|
||||
actual="$(sha256sum "$candidate" | cut -d ' ' -f 1)"
|
||||
if [ "$actual" = "$crate_checksum" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
archive="$(find_archive || true)"
|
||||
if [ -z "$archive" ]; then
|
||||
cargo --manifest-path "$root/Cargo.toml" --config 'paths=[]' fetch --locked
|
||||
archive="$(find_archive || true)"
|
||||
fi
|
||||
[ -n "$archive" ] || fail "Cargo did not fetch the checksum-matching fuser archive"
|
||||
|
||||
mkdir -p "$root/.lazaret/vendor"
|
||||
temporary="$(mktemp -d "$root/.lazaret/vendor/.fuser-$version.XXXXXX")"
|
||||
trap 'rm -rf "$temporary"' EXIT
|
||||
|
||||
tar -xzf "$archive" -C "$temporary"
|
||||
source="$temporary/fuser-$version"
|
||||
[ -d "$source" ] || fail "archive did not contain fuser-$version"
|
||||
patch --directory="$source" --strip=1 --batch --forward < "$patch_file"
|
||||
printf '%s\n' "$stamp" > "$source/.lazaret-materialization"
|
||||
|
||||
rm -rf "$destination"
|
||||
mv "$source" "$destination"
|
||||
if $fetch_tests; then
|
||||
fetch_test_dependencies
|
||||
fi
|
||||
4
tools/verify-ignored-workspace
Executable file
4
tools/verify-ignored-workspace
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
exec uv run --python '>=3.11' --no-project --offline --no-python-downloads --no-cache \
|
||||
python3 "$(dirname "$0")/ignored_workspace_verifier.py" "$@"
|
||||
4
tools/verify-ignored-workspace-benchmark
Executable file
4
tools/verify-ignored-workspace-benchmark
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
exec uv run --python '>=3.11' --no-project --offline --no-python-downloads --no-cache \
|
||||
python3 "$(dirname "$0")/ignored_workspace_benchmark.py" "$@"
|
||||
Loading…
Reference in a new issue