feat: publish Dever core with project scaffolding and versioned AI skills

This commit is contained in:
Antigravity committed 2026-10-04 13:37:30 +00:00
1 parent 70fa73cdc2
commit ac458ffa7f
960 files changed
+221319 -1163

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
# Linux 质量门
`run.py` 仅串行编排既有 Cargo、Python、SDK、PostgreSQL fixture 和 performance 运行器;没有下载工具、全局安装或环境变量产品配置。根 `config/setting.json` 保留原有字段,在既有 `performance` 对象内增加 `linux_ci`,与 peer 路径同级。顶层 `linux_ci` 不受支持,会被 runtime 严格配置解析拒绝:
```json
{
"performance": {
"network_peer": "/absolute/network_bench",
"live_peer": "/absolute/live_bench",
"linux_ci": {
"tools": {
"cargo": "/absolute/toolchain/bin/cargo",
"cargo_fmt": "/absolute/rustfmt/bin/cargo-fmt",
"cargo_clippy": "/absolute/clippy/bin/cargo-clippy",
"python": "/absolute/python3",
"node": "/absolute/node",
"go": "/absolute/go"
},
"cargo_config": [
"profile.dev.package.miniz_oxide.opt-level=3",
"profile.dev.package.flate2.opt-level=3",
"profile.dev.package.sha2.opt-level=3"
],
"output": "/absolute/repository/target/linux-ci",
"command_timeout_seconds": 14400,
"remove_test_executables": true,
"cgroup_parent": "/sys/fs/cgroup/system.slice/dever-linux-ci.service",
"inputs": [],
"required_inputs": {},
"author_outputs": [],
"author_network": false,
"soak": {
"artifacts": "/absolute/prepared-protocols",
"cms_manifest": "/absolute/prepared-cms/manifest.json",
"seconds": 1800,
"cms_concurrency": 16,
"http2_rate": 5000,
"rss_late_growth_mib": 8
}
}
}
}
```
替换路径,勿直接复制示例运行。下文 CI 字段均位于 `performance.linux_ci`。`inputs` 列出固定原始作者输入及准备好的资产文件/目录,逐文件计 SHA-256;工具、协议产物、CMS manifest/可执行文件/配置和 `performance.network_peer/live_peer` 也自动计摘要,`linux_ci` 对象不作为 peer 路径解析。输出目录不得包含在 inputs 中。报告只存摘要,不复制数据库密码。
`required_inputs` 为 `author`、`native`、`postgres`、`sandbox`、`bounded-perf`、`soak` 各提供非空绝对路径列表,必须落在 `inputs` 声明范围内。缺路径记录 blocked,不算 pass。作者 fixture 的具体固定路径/准备命令仍以 `sdk/native-release.md`、`test/ecosystem-release/prepare.py`、`test/ecosystem-release/build_inputs.py` 和对应 ignored 原因文本为准;gate 不把现存文件冒充由当前源码重建的 provenance。
显式 LLVM Worker 验收读取 `target/native-release-inputs/component-fixture`:先用当前源码构建 `dever-tests` 的 `component-fixture` bin,再将程序复制到该准备目录,记录源码/程序摘要,并把固定副本列入 `inputs` 和 `required_inputs.native`。不能将 `target/debug/component-fixture` 当作不可变输入:Cargo 在 default/all-feature 组合间会重新链接这个输出。普通 Worker 测试仍使用 Cargo 为各自组合生成的 `CARGO_BIN_EXE_component-fixture`,不改质量门的输入变更拒绝规则。
`author_outputs` 精确声明 author 会制作的 pack、下载结果和 receipt 路径。author 阶段只将其余原始输入当作不可变输入,另外记录完整输出摘要;后续验收按准备后完整输入摘要绑定。不要把整个作者输入根排除掉。显式 `author_network: true` 才能执行已分类的固定官方下载/源码构建。新 ignored 必须人工更新 `ignored.json`;inventory 核对源码声明及实际 all-feature libtest 名单,执行时一律使用完整 `--exact` 名称。内部 probe 记录 parent-only,只由所属父用例运行。
资源准备归外部托管者:自有空父启用 memory/cpu,限制总 CPU<=2、memory<=4GiB、swap=0;runner/Cargo/负载客户端放 `control` 子组,自有 PostgreSQL 可放兄弟子组,64/128MiB 服务子组由现有 performance owner 创建。gate 只读检查现有父限制与自身归属,不创建/修改主机策略。作者工具所需固定 PATH/动态库路径可由外部托管者显式准备,不写入产品配置。
```sh
python3 -B test/ci/run.py plan
python3 -B test/ci/run.py run --stage static
python3 -B test/ci/run.py run --stage default
python3 -B test/ci/run.py run --stage all-features
python3 -B test/ci/run.py run --stage features
python3 -B test/ci/run.py run --stage inventory
python3 -B test/ci/run.py run --stage author
python3 -B test/ci/run.py run --stage python
python3 -B test/ci/run.py run --stage sdk
python3 -B test/ci/run.py run --stage native
python3 -B test/ci/run.py run --stage postgres
python3 -B test/ci/run.py run --stage sandbox
python3 -B test/ci/run.py run --stage bounded-perf
```
以上命令由同一受限托管进程串行执行。若 author 改变输入,应在作者准备完成后再跑正式静态/default/all-feature门;较早输入版本的证据会显示过期。每阶段写入新的独占时间目录,开始即记录 running;缺输入记录 blocked,失败命令保留 exit/count/日志与原始异常。`summary` 与 inventory 消费者只接受最新一次尝试的当前源码/输入证据,不能越过较新的失败或中断回用旧 pass;所有必选阶段通过才返回0。源码摘要覆盖未提交文件,不只看 Git HEAD。
default/all-features 由实际 Cargo metadata 注册 target 和已解析 feature 形成串行矩阵,覆盖 lib/bin/integration/doctest;default 显式保留 workspace 解析的 package features,最小 feature 阶段另按单 package 验证。只有显式启用 `remove_test_executables` 时才删除 Cargo 本次返回、`profile.test=true` 且位于 target 下 `deps/` 的测试可执行文件,失败阶段同样清理;不删除作者工具、静态 init、依赖 archive 或整个 target。删除对象在日志 JSON 中可审计,重跑 Cargo 可恢复。inventory 按 package/target/name 三元组核对完整性,跨 target 同名用例不能代替缺失项。
Python 阶段包含 `test/ci`、`test/performance`、`test/ecosystem-release` 三个目录;后两者必须已配置 `performance.network_peer/live_peer`,真实自有 loopback检查由主代理执行。三 SDK 路径全部必需,三个明确成功标志全部出现才通过。
PostgreSQL 只接受显式 loopback端口和恰好一个 `{case}` 库名模板,拒绝 query 覆盖 host/dbname 等。增加 `performance.linux_ci.owned_postgres: {"data_directory":"/absolute/owned-cluster","port":51234}`,由托管者创建所需基库并在 finally 停止该自有实例;gate复用现有用例的 schema/tenant 清理,不替用户启动或连接既有业务数据库。
长测复用原 runner,逐阶段分别执行:
```sh
for stage in cms-64 cms-128 http2-64 http2-128 recovery-64 recovery-128 live-64 live-128; do
python3 -B test/ci/run.py run --stage "$stage" || exit "$?"
done
python3 -B test/ci/run.py summary
```
每个 CMS 源码/h2c/TLS/恢复协议/TCP/WS/SSE 子场景至少30分钟,服务在整个子场景保持同一进程。HTTP/2选代表 JSON 路径;其他body路径仍由短合同测试覆盖。live/recovery 使用60×30秒循环,受现有1小时生命周期限制;总默认矩阵约9小时加准备。HTTP/2要求零错误、成功>=99%计划请求;CMS逐回复验证实际发布列表。全部要求OOM=0、断连FD恢复。RSS记录原始采样和四段中位数,最后两段中位数增量默认不得超过8MiB;这是本轮可配置回归预算,不把 allocator保留直接等同泄漏。失效报告和失败负载保留,校准降速后须完整重跑。
CMS读取时长支持0.1..3600秒;长测客户端用固定2048桶统计延迟,不随请求数保存无限样本。分位数为对数桶上界,包含请求与结果核对时间。报告记录实际时长及统计方式,不与此前精确短测分位数直接混算。
CMS先关闭控制客户端,再记录FD基线;断连后按FD目标及重复数核对所有非数据库资源。负载前已确定的SQLite主文件/WAL句柄各不得超过配置的`max_connections`,同进程SHM句柄不得超过1。`descriptors_before`、`descriptors_after`和`pooled_descriptor_limits`保存实际依据;总`fd_delta_after_disconnect`保留为观测值,不把正常池扩容当成连接泄漏。每个源码目录的`report.json`在失败时也保存已完成的发布/读取数据、资源采样、预算及错误,总报告收集该失败case。HTTP/2/live无数据库池,继续使用原来的完整FD回落门。
+298
View File
@@ -0,0 +1,298 @@
"""显式 ignored 验收与现有长测运行器的 Linux gate 接线。"""
import json
from pathlib import Path
import re
from urllib.parse import parse_qsl, unquote, urlsplit
from linux_gate import (ROOT, MissingInput, absolute_file, run_command,
target_command, workspace_targets, remove_executables, input_fingerprint)
from settings import write_private_json
from process import resource_trend
from cms import validate_read_descriptor_recovery
def execute(command, config, directory, result, *, single_harness=False):
record, content = run_command(command, directory, len(result["commands"]),
config.get("command_timeout_seconds", 14400),
single_harness=single_harness)
result["commands"].append(record)
return record, content
def classify_cases(names, manifest, target):
by_name = {}
for row in manifest:
key = (row["package"], row["target"], row["name"])
if key in by_name:
raise RuntimeError(f"duplicate ignored classification: {key}")
by_name[key] = row
classified = []
for name in names:
leaf = name.rsplit("::", 1)[-1]
key = (target["package"], target["target"], leaf)
if key not in by_name:
raise RuntimeError(f"unclassified ignored test: {name}")
classified.append({**by_name[key], "exact": name})
if len({case["name"] for case in classified}) != len(classified):
raise RuntimeError("same ignored leaf appears in multiple modules; classify by a distinct source name")
return classified
def audit_classification():
manifest = json.loads((ROOT / "test/ci/ignored.json").read_text())
stages = {"probe", "author", "native", "postgres", "sandbox", "bounded-perf"}
if any(row["stage"] not in stages for row in manifest):
raise RuntimeError("ignored classification contains an unknown execution stage")
expected = {(row["source"], row["name"], row["reason"]) for row in manifest}
actual = set()
pattern = re.compile(r'#\[ignore(?:\s*=\s*"([^"]*)")?\]\s*(?:#\[[^\]]*\]\s*)*(?:pub\s+)?(?:async\s+)?fn\s+(\w+)')
for directory in ("test", "crates"):
for path in (ROOT / directory).rglob("*.rs"):
for match in pattern.finditer(path.read_text()):
actual.add((str(path.relative_to(ROOT)), match[2], match[1] or ""))
if actual != expected:
raise RuntimeError(f"ignored classification drift: new={sorted(actual - expected)}, removed={sorted(expected - actual)}")
return manifest
def test_executable(content, target):
artifacts = []
for line in content.splitlines():
if not line.startswith("{"):
continue
message = json.loads(line)
if (message.get("reason") == "compiler-artifact" and message.get("profile", {}).get("test")
and message.get("target", {}).get("name") == target["target"] and message.get("executable")):
artifacts.append(message["executable"])
if len(set(artifacts)) != 1:
raise RuntimeError(f"expected one libtest executable for {target}")
return artifacts[0]
def inventory(config, directory, result):
manifest = audit_classification()
targets, target_directory = workspace_targets(config, directory, result, True)
cases = []
for target in targets:
if target["selector"] == ["--doc"]:
continue
_, content = execute([*target_command(config, target), "--no-run", "--message-format=json"], config, directory, result)
executable = test_executable(content, target)
_, listing = execute([executable, "--ignored", "--list", "--format", "terse"], config, directory, result)
names = [line.removesuffix(": test") for line in listing.splitlines() if line.endswith(": test")]
cases.extend({**case, "target": target, "target_directory": str(target_directory)}
for case in classify_cases(names, manifest, target))
result.setdefault("removed_test_executables", []).extend(
remove_executables(content, target_directory, config.get("remove_test_executables", False)))
write_private_json(directory / "inventory.json", cases, replace=(directory / "inventory.json").exists())
missing = {(row["package"], row["target"], row["name"]) for row in manifest} - {
(case["target"]["package"], case["target"]["target"], case["name"]) for case in cases}
if missing:
raise RuntimeError(f"ignored source cases missing from Linux all-feature libtest inventory: {sorted(missing)}")
result["classified_cases"] = len(cases)
result["internal_probes"] = [{"target": case["target"], "exact": case["exact"],
"status": "parent-only", "reason": case["reason"]}
for case in cases if case["stage"] == "probe"]
def current_inventory(document, config, result):
paths = sorted((Path(config.get("output", ROOT / "target/linux-ci")) / "inventory").glob("*/result.json"))
if paths:
path = paths[-1]
record = json.loads(path.read_text())
if (record.get("source"), record.get("inputs"), record.get("status")) == (result["source"], input_fingerprint(document, config, "inventory"), "passed"):
return json.loads((path.parent / "inventory.json").read_text())
raise MissingInput("run inventory for the current source and input fingerprints first")
def validate_postgres(document):
setting = document.get("database", {}).get("postgres_test", {})
url = urlsplit(setting.get("url", ""))
database = unquote(url.path.removeprefix("/"))
if (setting.get("type") != "postgres" or url.scheme not in ("postgres", "postgresql")
or url.hostname not in ("127.0.0.1", "::1") or url.port is None
or database.count("{case}") != 1
or any(key.lower() in ("dbname", "host", "hostaddr", "port", "service") for key, _ in parse_qsl(url.query))):
raise MissingInput("database.postgres_test must select the explicitly owned loopback cluster and one {case} database template")
def require_inputs(stage, config):
required = config.get("required_inputs", {}).get(stage)
if not required:
raise MissingInput(f"performance.linux_ci.required_inputs.{stage} must declare the prepared author/fixture input paths")
declared = [Path(value).resolve() for value in config.get("inputs", [])]
for value in required:
path = Path(value)
if not path.is_absolute() or not path.exists():
raise MissingInput(f"missing {stage} input: {value}")
if not any(path.resolve() == root or path.resolve().is_relative_to(root) for root in declared):
raise MissingInput(f"{stage} input is not fingerprinted by performance.linux_ci.inputs: {value}")
def ignored_stage(stage, document, config, directory, result):
require_inputs(stage, config)
if stage == "postgres":
validate_postgres(document)
if not config.get("owned_postgres"):
raise MissingInput("performance.linux_ci.owned_postgres must record the owned cluster data_directory and port")
owned = config["owned_postgres"]
if urlsplit(document["database"]["postgres_test"]["url"]).port != owned.get("port"):
raise MissingInput("PostgreSQL URL does not match performance.linux_ci.owned_postgres.port")
if not Path(owned.get("data_directory", "")).is_absolute():
raise MissingInput("owned_postgres.data_directory must be absolute")
if stage == "author" and config.get("author_network") is not True:
raise MissingInput("author stage fetches fixed upstream inputs; explicitly set performance.linux_ci.author_network=true")
if stage == "author" and not config.get("author_outputs"):
raise MissingInput("declare performance.linux_ci.author_outputs separately from immutable raw author inputs")
cases = [case for case in current_inventory(document, config, result) if case["stage"] == stage]
if not cases:
raise RuntimeError(f"no classified cases for {stage}")
# 固定作者先准备工具,再执行真实源码包构建;其他用例按 target 复用链接产物。
cases.sort(key=lambda case: (0 if case["name"].startswith("prepare_") else 1,
case["target"]["package"], case["target"]["target"], case["exact"]))
target_key = None
build_content = None
target_directory = ROOT / "target"
for case in cases:
target = case["target"]
target_directory = Path(case["target_directory"])
result["target_directory"] = str(target_directory)
key = json.dumps(target, sort_keys=True)
if key != target_key:
if build_content is not None:
result.setdefault("removed_test_executables", []).extend(
remove_executables(build_content, target_directory, config.get("remove_test_executables", False)))
_, build_content = execute([*target_command(config, target), "--no-run", "--message-format=json"], config, directory, result)
executable = test_executable(build_content, target)
target_key = key
record, _ = execute([executable, "--ignored", "--exact", case["exact"], "--test-threads=1", "--nocapture"],
config, directory, result, single_harness=True)
if record.get("rust_tests") != {"passed": 1, "failed": 0, "ignored": 0}:
raise RuntimeError(f"exact ignored selection did not execute one case: {case['exact']}")
result.setdefault("cases", []).append({"exact": case["exact"], "target": target, "status": "passed"})
result.setdefault("removed_test_executables", []).extend(
remove_executables(build_content, target_directory, config.get("remove_test_executables", False)))
def validate_budget(config):
parent = Path(config.get("cgroup_parent", ""))
if not parent.is_absolute() or not (parent / "cgroup.controllers").is_file():
raise MissingInput("performance.linux_ci.cgroup_parent must select the owned empty cgroup v2 parent")
if (parent / "cgroup.procs").read_text().strip():
raise MissingInput("CI runner must be in a control child, not the cgroup parent")
if not {"cpu", "memory"} <= set((parent / "cgroup.subtree_control").read_text().split()):
raise MissingInput("owned parent requires enabled memory/cpu child controllers")
quota, period = (parent / "cpu.max").read_text().split()
memory = (parent / "memory.max").read_text().strip()
if quota == "max" or int(quota) / int(period) > 2 or memory == "max" or int(memory) > 4 * 1024 ** 3:
raise MissingInput("owned parent must bound total CPU <=2 and memory <=4GiB")
if (parent / "memory.swap.max").read_text().strip() != "0":
raise MissingInput("owned parent must disable swap")
membership = Path("/proc/self/cgroup").read_text().strip().split("::", 1)[1]
current = Path("/sys/fs/cgroup") / membership.lstrip("/")
if current == parent or not current.is_relative_to(parent):
raise MissingInput("CI runner is outside the configured owned parent control subgroup")
return parent
def validate_soak(report, suite, seconds, growth_limit, memory_mib):
if report.get("status") not in ("passed", "complete"):
raise RuntimeError("soak runner did not complete")
cases = list(report["cases"].values()) if suite == "cms" else report["cases"]
expected = 3 if suite == "live" else 2
if len(cases) != expected:
raise RuntimeError(f"expected {expected} {suite} cases, received {len(cases)}")
if suite == "cms":
coverage = set(report["cases"])
required = {"dever", "md"}
else:
coverage = {case["protocol" if suite == "live" else "transport"] for case in cases}
required = {"tcp", "ws", "sse"} if suite == "live" else {"http", "https"}
if any(case["suite"] != suite or (suite != "live" and case["implementation"] != "dever") for case in cases):
raise RuntimeError("soak report contains a different suite or implementation")
if coverage != required:
raise RuntimeError("soak report does not cover every required source or transport")
for case in cases:
budget = case.get("cgroup") if suite == "cms" else case.get("budget_after_stop")
if budget is None or any(budget["events"][key] for key in ("oom", "oom_kill")):
raise RuntimeError("missing memory-budget evidence or nonzero OOM")
if budget["max"] != str(memory_mib * 1024 ** 2):
raise RuntimeError("soak used a different memory limit from the selected stage")
if suite == "cms":
load = case["concurrent_reads"]
if load["elapsed_seconds"] < seconds or load["errors"] or load["succeeded"] == 0:
raise RuntimeError("CMS duration, business load or FD recovery failed")
validate_read_descriptor_recovery(load)
trend = load["trend"]
elif suite == "live":
if (case["result"]["elapsed_ms"] < seconds * 1000 or case["result"]["errors"]
or any(case["fd_delta_after_cycles"])):
raise RuntimeError("live duration, protocol or FD recovery failed")
samples = [phase["last"] for phase in case["phases"] if phase["phase"] == "disconnected"]
trend = resource_trend(samples)
else:
loads = [case["load"]] if suite == "http2" else [cycle["load"] for cycle in case["cycles"]]
disconnected = [case["disconnected"]] if suite == "http2" else [cycle["disconnected"] for cycle in case["cycles"]]
if any(sample["fd_delta"] != 0 for sample in disconnected):
raise RuntimeError("HTTP/2 did not restore file descriptors after disconnect")
if sum(load["elapsed_seconds"] for load in loads) < seconds:
raise RuntimeError("HTTP/2 measured load is shorter than the required duration")
for load in loads:
if load["errors"] or load["succeeded"] <= 0 or load["succeeded"] < .99 * load["scheduled"]:
raise RuntimeError("HTTP/2 did not achieve 99% scheduled successful replies with zero errors")
trend = resource_trend(case["raw"])
if trend is None or trend["rss_late_growth_bytes"] > growth_limit:
raise RuntimeError("late RSS growth exceeds the configured budget; inspect raw resource samples")
case["gate_resource_trend"] = trend
def soak_stage(stage, config, directory, result):
suite, memory = stage.rsplit("-", 1)
suite = {"recovery": "http2-recovery"}.get(suite, suite)
require_inputs("soak", config)
parent = validate_budget(config)
settings = config.get("soak", {})
seconds = settings.get("seconds", 1800)
if not 1800 <= seconds <= 2700:
raise ValueError("performance.linux_ci.soak.seconds must be 1800..2700")
python = str(absolute_file(config["tools"].get("python"), "performance.linux_ci.tools.python"))
output = directory / "measurement"
if suite == "cms":
manifest = str(absolute_file(settings.get("cms_manifest"), "performance.linux_ci.soak.cms_manifest"))
command = [python, "-B", "test/performance/cms.py", "run", "--manifest", manifest,
"--concurrency", str(settings.get("cms_concurrency", 16)), "--read-seconds", str(seconds)]
else:
artifacts = Path(settings.get("artifacts", ""))
if not artifacts.is_absolute() or not (artifacts / "manifest.json").is_file():
raise MissingInput("performance.linux_ci.soak.artifacts requires prepared protocol artifacts")
command = [python, "-B", "test/performance/run.py", "run", "--artifacts", str(artifacts),
"--suite", suite, "--implementations", "dever", "--repeats", "1", "--interval", "0.1"]
if suite == "http2":
command += ["--duration", str(seconds), "--warmup", "5", "--paths", "/json",
"--rates", str(settings.get("http2_rate", 5000)), "--http2-connections", "4"]
else:
if seconds % 30:
raise ValueError("cyclic soak seconds must be divisible by 30")
command += ["--duration", "30", "--cycles", str(seconds // 30)]
command += (["--connection-tiers", "32"] if suite == "live" else
["--recovery-rate", str(settings.get("http2_rate", 5000)),
"--recovery-path", "/json", "--recovery-connections", "4"])
if "server_cpu" in settings:
command += ["--server-cpu", str(settings["server_cpu"]), "--client-cpus", str(settings["client_cpu"])]
command += ["--output", str(output), "--memory-mib", memory, "--cgroup-parent", str(parent)]
execute(command, config, directory, result)
report = json.loads((output / "report.json").read_text())
validate_soak(report, suite, seconds, settings.get("rss_late_growth_mib", 8) * 1024 ** 2, int(memory))
write_private_json(directory / "validated-report.json", report)
result["measurement"] = str(output / "report.json")
result["coverage"] = "CMS dever/md; HTTP/2 h2c/TLS JSON; recovery h2c/TLS; live TCP/WS/SSE, each selected stage only"
def run_stage(stage, document, config, directory, result):
if stage == "inventory":
inventory(config, directory, result)
elif stage in ("author", "native", "postgres", "sandbox", "bounded-perf"):
ignored_stage(stage, document, config, directory, result)
else:
soak_stage(stage, config, directory, result)
+1738
View File
File diff suppressed because it is too large. Load diff
+372
View File
@@ -0,0 +1,372 @@
"""Linux 质量门:串行复用现有检查,按源码和输入摘要保存阶段证据。"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import signal
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[2]
sys.path.append(str(ROOT / "test/performance"))
sys.modules["linux_gate"] = sys.modules[__name__]
from settings import configured_peer, write_private_json
STAGES = ("static", "default", "all-features", "features", "python", "sdk",
"inventory", "author", "native", "postgres", "sandbox", "bounded-perf",
"cms-64", "cms-128", "http2-64", "http2-128", "recovery-64", "recovery-128",
"live-64", "live-128")
SOURCE_ROOTS = ("crates", "test", "sdk", "library", "examples", ".cargo")
class MissingInput(Exception):
pass
def digest_file(path):
with path.open("rb") as source:
return hashlib.file_digest(source, "sha256").hexdigest()
def source_fingerprint(root=ROOT):
paths = [root / name for name in ("Cargo.toml", "Cargo.lock")]
for name in SOURCE_ROOTS:
directory = root / name
if directory.exists():
paths.extend(path for path in directory.rglob("*") if path.is_file()
and not {"__pycache__", "target", ".git"}.intersection(path.parts))
digest = hashlib.sha256()
for path in sorted(paths):
digest.update(str(path.relative_to(root)).encode() + b"\0")
digest.update(digest_file(path).encode() + b"\0")
return digest.hexdigest()
def absolute_file(value, name):
path = Path(value or "")
if not path.is_absolute() or not path.is_file():
raise MissingInput(f"{name} requires an existing absolute file path")
return path
def load_config():
path = ROOT / "config/setting.json"
if not path.is_file():
raise MissingInput("create config/setting.json with performance.linux_ci (see test/ci/README.md)")
document = json.loads(path.read_text())
performance = document.get("performance") if isinstance(document, dict) else None
config = performance.get("linux_ci") if isinstance(performance, dict) else None
if not isinstance(config, dict):
raise MissingInput("config/setting.json must define performance.linux_ci")
return document, config
def input_fingerprint(document, config, stage=None):
inputs = {}
for name, value in config.get("tools", {}).items():
path = Path(value)
if not path.is_absolute():
raise ValueError("performance.linux_ci.tools must use absolute paths")
inputs[f"tool:{name}:{value}"] = digest_file(path) if path.is_file() else "missing"
paths = set(config.get("inputs", []))
paths.update(str(configured_peer(name, ROOT / "config/setting.json"))
for name in ("network_peer", "live_peer") if name in document.get("performance", {}))
soak = config.get("soak", {})
if soak.get("artifacts"):
paths.add(soak["artifacts"])
if soak.get("cms_manifest"):
manifest = Path(soak["cms_manifest"])
paths.add(str(manifest))
if manifest.is_file():
for case in json.loads(manifest.read_text()).get("cases", {}).values():
paths.update(case[key] for key in ("executable", "config"))
for value in sorted(paths):
path = Path(value)
if not path.is_absolute():
raise ValueError("performance.linux_ci.inputs must use absolute paths")
if stage == "inventory":
continue # 清单依赖当前源码和工具,不消费作者资产。
outputs = [Path(value) for value in config.get("author_outputs", [])] if stage == "author" else []
if any(path == output or path.is_relative_to(output) for output in outputs):
continue
if path.is_dir():
for child in sorted(path.rglob("*")):
if child.is_file() and not any(child == output or child.is_relative_to(output) for output in outputs):
inputs[str(child)] = digest_file(child)
else:
inputs[value] = digest_file(path) if path.is_file() else "missing"
# 保存摘要而非数据库密码/令牌;配置任何变化都使旧证据失效。
return hashlib.sha256(json.dumps([document, inputs], sort_keys=True).encode()).hexdigest()
def command_plan(stage, config):
tools = config.get("tools", {})
cargo = str(absolute_file(tools.get("cargo"), "performance.linux_ci.tools.cargo"))
common = ["--offline", "--locked", "-j1", *cargo_config(config)]
test = [cargo, "test", *common, "--workspace"]
if stage == "static":
fmt = str(absolute_file(tools.get("cargo_fmt"), "performance.linux_ci.tools.cargo_fmt"))
clippy = str(absolute_file(tools.get("cargo_clippy"), "performance.linux_ci.tools.cargo_clippy"))
return [[fmt, "fmt", "--all", "--check"],
*[[clippy, "clippy", *common, "--workspace", "--all-targets", *features,
"--", "-D", "warnings"] for features in ([], ["--all-features"])]]
if stage in ("default", "all-features"):
return [[*test, *(["--all-features"] if stage == "all-features" else []),
"--", "--test-threads=1"]]
if stage == "features":
profiles = {
"dever-runtime": ["", "auth-crypto", "crypto", "wire", "external", "api", "database", "sqlite", "postgres", "sqlite,postgres"],
"dever-backend-bridge": ["", "embedded", "runtime-abi", "runtime-database", "runtime-api", "runtime-external", "runtime-sqlite", "runtime-postgres", "runtime-sqlite,runtime-postgres"],
}
return [[cargo, "check", *common, "-p", package, "--lib", "--no-default-features",
*(["--features", features] if features else [])]
for package, combinations in profiles.items() for features in combinations] + [
[cargo, "tree", "--offline", "--locked", "-p", "dever-cli", "--edges", "features"]]
python = str(absolute_file(tools.get("python"), "performance.linux_ci.tools.python"))
if stage == "python":
return [[python, "-B", "-m", "unittest", "discover", "-s", directory, "-p", "test_*.py", "-v"]
for directory in ("test/ci", "test/performance", "test/ecosystem-release")]
if stage == "sdk":
return [[python, "-B", "test/external-sdk/check.py", "--python", python,
"--node", str(absolute_file(tools.get("node"), "performance.linux_ci.tools.node")),
"--go", str(absolute_file(tools.get("go"), "performance.linux_ci.tools.go"))]]
raise ValueError(f"stage has a dedicated runner: {stage}")
def cargo_config(config):
return [value for override in config.get("cargo_config", []) for value in ("--config", override)]
def workspace_targets(config, directory, result, all_features):
cargo = str(absolute_file(config["tools"].get("cargo"), "performance.linux_ci.tools.cargo"))
command = [cargo, "metadata", "--offline", "--locked", "--format-version", "1", *cargo_config(config)]
if all_features:
command.append("--all-features")
record, content = run_command(command, directory, len(result["commands"]), 120)
result["commands"].append(record)
metadata = next(json.loads(line) for line in content.splitlines() if line.startswith('{"packages":'))
active = {node["id"]: set(node["features"]) for node in metadata["resolve"]["nodes"]}
targets = []
result["target_directory"] = metadata["target_directory"]
for package in metadata["packages"]:
if package["id"] not in metadata["workspace_members"]:
continue
for target in package["targets"]:
required = set(target.get("required-features", []))
if not required <= active[package["id"]]:
continue
kind = target["kind"][0]
if target["test"] and kind in ("lib", "rlib", "staticlib", "bin", "test"):
selector = ["--lib"] if kind in ("lib", "rlib", "staticlib") else ["--" + kind, target["name"]]
targets.append({"package": package["name"], "target": target["name"], "selector": selector,
"features": sorted(active[package["id"]])})
if target["doctest"] and kind in ("lib", "rlib", "staticlib"):
targets.append({"package": package["name"], "target": target["name"], "selector": ["--doc"],
"features": sorted(active[package["id"]])})
return targets, Path(metadata["target_directory"])
def target_command(config, target, all_features=True):
features = (["--all-features"] if all_features else
["--features", ",".join(target["features"])] if target.get("features") else [])
return [config["tools"]["cargo"], "test", "--offline", "--locked", "-j1", *cargo_config(config),
"-p", target["package"], *target["selector"], *features]
def remove_executables(content, target_directory, enabled):
if not enabled:
return []
removed = []
for line in content.splitlines():
if not line.startswith("{"):
continue
try:
message = json.loads(line)
except json.JSONDecodeError:
continue
if (message.get("reason") == "compiler-artifact" and message.get("profile", {}).get("test")
and message.get("executable")):
path = Path(message["executable"]).resolve()
if not path.is_relative_to(target_directory.resolve()) or path.parent.name != "deps":
raise RuntimeError(f"refusing to remove a non-test output: {path}")
if path.exists():
path.unlink()
removed.append(str(path))
return removed
def cleanup_test_executables(directory, config, result):
if not config.get("remove_test_executables", False) or "target_directory" not in result:
return
for command_file in sorted(directory.glob("*.command.json")):
command = json.loads(command_file.read_text())
arguments = command["command"]
if arguments[0] != config["tools"]["cargo"] or "--message-format=json" not in arguments:
continue
log = Path(command["log"])
result.setdefault("removed_test_executables", []).extend(
remove_executables(log.read_text(errors="replace"), Path(result["target_directory"]), True))
def run_workspace(stage, config, directory, result):
all_features = stage == "all-features"
targets, target_directory = workspace_targets(config, directory, result, all_features)
result["targets"] = targets
passed = 0
for target in targets:
command = [*target_command(config, target, all_features), "--message-format=json", "--", "--test-threads=1"]
print(f"{stage}: {target['package']} {' '.join(target['selector'])}", flush=True)
record, content = run_command(command, directory, len(result["commands"]), config.get("command_timeout_seconds", 14400))
result["commands"].append(record)
passed += record.get("rust_tests", {}).get("passed", 0)
result.setdefault("removed_test_executables", []).extend(
remove_executables(content, target_directory, config.get("remove_test_executables", False)))
if not targets or not passed:
raise RuntimeError("workspace target matrix executed no Rust tests")
result["passed_tests"] = passed
def run_command(command, directory, number, timeout, *, single_harness=False):
path = directory / f"{number:03d}.log"
started = time.time()
record = {"command": command, "log": str(path), "started": started}
write_private_json(directory / f"{number:03d}.command.json", record)
failure = None
child = None
try:
with path.open("x") as output:
child = subprocess.Popen(command, cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
start_new_session=True)
try:
child.wait(timeout=timeout)
except BaseException:
try:
# Python 测量器以 KeyboardInterrupt 执行其 finally,回收独立进程组和 cgroup。
os.killpg(child.pid, signal.SIGINT)
child.wait(timeout=10)
except subprocess.TimeoutExpired:
os.killpg(child.pid, signal.SIGKILL)
child.wait(timeout=5)
except ProcessLookupError:
child.wait()
raise
except BaseException as error:
failure = error
record["error"] = str(error) or type(error).__name__
record["exit_code"] = child.returncode if child is not None else None
record["elapsed_seconds"] = time.time() - started
content = path.read_text(errors="replace")
counts = re.findall(r"test result: (?:ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored", content)
if single_harness:
# 直接运行一个 libtest 时,最后一条是父 harness;前面的可能是其子进程探针。
counts = counts[-1:]
if counts:
record["rust_tests"] = {name: sum(int(row[index]) for row in counts)
for index, name in enumerate(("passed", "failed", "ignored"))}
record["python_tests"] = sum(map(int, re.findall(r"Ran (\d+) tests? in", content)))
write_private_json(directory / f"{number:03d}.command.json", record, replace=True)
if failure is not None:
raise failure
if record["exit_code"]:
raise RuntimeError(f"command exited {record['exit_code']}: {path}")
if re.search(r"OK \(skipped=|: unavailable", content):
raise MissingInput(f"command skipped required verification: {path}")
return record, content
def check_output(stage, record, content):
if stage in ("default", "all-features") and record.get("rust_tests", {}).get("passed", 0) == 0:
raise RuntimeError("workspace command did not execute any Rust tests")
if stage == "python" and record["python_tests"] == 0:
raise RuntimeError("Python discovery executed no tests")
if stage == "sdk" and any(f"{language}: handshake, health, result, business error, cancel, shutdown and rejection passed"
not in content for language in ("Python", "JavaScript", "Go")):
raise RuntimeError("all three SDK acceptance results are required")
if stage == "features" and "dever-core feature \"reference\"" in content:
raise RuntimeError("production CLI depends on the reference feature")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("action", choices=("plan", "run", "summary"))
parser.add_argument("--stage", choices=STAGES)
args = parser.parse_args()
document, config = load_config()
output = Path(config.get("output", ROOT / "target/linux-ci"))
if not output.is_absolute():
raise ValueError("performance.linux_ci.output must be absolute")
source = source_fingerprint()
if args.action == "summary":
results = {}
for stage in STAGES:
candidates = sorted((output / stage).glob("*/result.json"))
record = json.loads(candidates[-1].read_text()) if candidates else {}
stage_inputs = input_fingerprint(document, config, stage)
current = (record.get("source") == source and record.get("inputs") == stage_inputs
and (stage != "author" or record.get("outputs") == input_fingerprint(document, config)))
results[stage] = record["status"] if current else "missing-current-evidence"
print(json.dumps(results, indent=2))
return 0 if all(status == "passed" for status in results.values()) else 1
if args.action == "plan":
inputs = input_fingerprint(document, config, args.stage)
print(json.dumps({"stages": STAGES, "source": source, "inputs": inputs,
"commands": command_plan(args.stage, config) if args.stage in STAGES[:6] else None}, indent=2))
return 0
if args.stage is None:
parser.error("run requires --stage (all stages are serialized by the caller)")
directory = output / args.stage / f"{time.time_ns()}"
directory.mkdir(parents=True, exist_ok=False)
result = {"stage": args.stage, "source": source, "inputs": None, "status": "running", "commands": []}
write_private_json(directory / "result.json", result)
try:
try:
inputs = input_fingerprint(document, config, args.stage)
except (ValueError, OSError) as error:
raise MissingInput(str(error)) from error
result["inputs"] = inputs
write_private_json(directory / "result.json", result, replace=True)
from acceptance import validate_budget
validate_budget(config)
if args.stage in ("default", "all-features"):
run_workspace(args.stage, config, directory, result)
elif args.stage in STAGES[:6]:
for number, command in enumerate(command_plan(args.stage, config)):
print(f"{args.stage}: {' '.join(command)}", flush=True)
record, content = run_command(command, directory, number, config.get("command_timeout_seconds", 14400))
result["commands"].append(record)
check_output(args.stage, record, content)
else:
from acceptance import run_stage
run_stage(args.stage, document, config, directory, result)
current_document, current_config = load_config()
final_inputs = input_fingerprint(current_document, current_config, args.stage)
if args.stage == "author":
result["outputs"] = input_fingerprint(current_document, current_config)
if source_fingerprint() != source or final_inputs != inputs:
raise RuntimeError("source or declared inputs changed while the stage ran; evidence is stale")
result["status"] = "passed"
except MissingInput as error:
result.update(status="blocked", error=str(error))
except (Exception, KeyboardInterrupt) as error:
result.update(status="failed", error=str(error) or type(error).__name__)
finally:
try:
cleanup_test_executables(directory, config, result)
except Exception as error:
result.update(status="failed", cleanup_error=str(error))
result["commands"] = [json.loads(path.read_text()) for path in sorted(directory.glob("*.command.json"))]
write_private_json(directory / "result.json", result, replace=True)
print(json.dumps(result, indent=2))
return 0 if result["status"] == "passed" else 1
if __name__ == "__main__":
try:
sys.exit(main())
except (MissingInput, ValueError, OSError) as error:
print(f"blocked: {error}", file=sys.stderr)
sys.exit(2)
+41
View File
@@ -0,0 +1,41 @@
"""验证共享分配账本:线程缓存正常析构,真实泄漏和错误结果必须拒绝。"""
from pathlib import Path
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[2]
DRIVER = ROOT / "test/native-runtime-abi/managed-driver.c"
FIXTURE = ROOT / "test/native-runtime-abi/driver-fixture.c"
WRAPPED = ("malloc", "calloc", "realloc", "free", "posix_memalign", "realpath")
class DriverTests(unittest.TestCase):
def run_fixture(self, scenario):
with tempfile.TemporaryDirectory() as temporary:
executable = Path(temporary) / "ledger"
subprocess.run([
"/usr/bin/cc", "-std=c11", "-Wall", "-Wextra", "-Werror", "-O2",
f"-DDEVER_LEDGER_SCENARIO={scenario}", str(DRIVER), str(FIXTURE),
*[f"-Wl,--wrap={name}" for name in WRAPPED], "-pthread", "-B/usr/bin/",
"-o", str(executable),
], check=True, env={}, capture_output=True, text=True, timeout=20)
return subprocess.run([str(executable)], env={}, capture_output=True,
text=True, timeout=5)
def test_late_thread_cache_is_released_before_counting(self):
result = self.run_fixture(0)
self.assertEqual(result.returncode, 0, result.stderr)
def test_owned_leaks_and_changed_results_fail(self):
for scenario, message in ((1, "allocation imbalance: 1"),
(2, "changed its result on repeated execution: 7")):
with self.subTest(scenario=scenario):
result = self.run_fixture(scenario)
self.assertNotEqual(result.returncode, 0)
self.assertIn(message, result.stderr)
if __name__ == "__main__":
unittest.main()
+317
View File
@@ -0,0 +1,317 @@
"""质量门防假通过合同;不运行 Cargo、数据库或真实压力测试。"""
import importlib.util
import contextlib
import io
import json
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import patch
spec = importlib.util.spec_from_file_location("linux_gate", Path(__file__).with_name("run.py"))
gate = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = gate
spec.loader.exec_module(gate)
import acceptance
from acceptance import classify_cases, validate_postgres, validate_soak
class GateTests(unittest.TestCase):
def test_ci_config_uses_the_shared_performance_section(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "config").mkdir()
setting = root / "config/setting.json"
config = {"output": str(root / "results"), "inputs": []}
document = {"performance": {"linux_ci": config}}
with patch.object(gate, "ROOT", root):
with self.assertRaisesRegex(gate.MissingInput, r"performance\.linux_ci"):
gate.load_config()
setting.write_text(json.dumps(document))
self.assertEqual(gate.load_config(), (document, config))
before = gate.input_fingerprint(document, config)
config["command_timeout_seconds"] = 120
self.assertNotEqual(before, gate.input_fingerprint(document, config))
for invalid in ({"linux_ci": config}, {"performance": {}},
{"performance": []}, {"performance": {"linux_ci": []}}):
setting.write_text(json.dumps(invalid))
with self.subTest(document=invalid), self.assertRaisesRegex(
gate.MissingInput, r"performance\.linux_ci"):
gate.load_config()
def test_performance_peer_fingerprints_resolve_project_relative_paths(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "config").mkdir()
(root / "peers").mkdir()
peer = root / "peers/network"
peer.write_text("original peer")
live_peer = root / "peers/live"
live_peer.write_text("original live peer")
document = {"performance": {"network_peer": "peers/network", "live_peer": str(live_peer),
"linux_ci": {"inputs": []}}}
(root / "config/setting.json").write_text(json.dumps(document))
with patch.object(gate, "ROOT", root):
before = gate.input_fingerprint(document, {})
peer.write_text("changed peer")
self.assertNotEqual(before, gate.input_fingerprint(document, {}))
before = gate.input_fingerprint(document, {})
live_peer.write_text("changed live peer")
self.assertNotEqual(before, gate.input_fingerprint(document, {}))
for config in ({"inputs": ["peers/network"]}, {"tools": {"cargo": "peers/network"}}):
with self.subTest(config=config), self.assertRaisesRegex(ValueError, "absolute paths"):
gate.input_fingerprint(document, config)
def test_default_targets_preserve_workspace_resolved_features(self):
target = {"package": "dever-backend-bridge", "selector": ["--test", "backend_bridge"],
"features": ["embedded", "runtime-api"]}
command = gate.target_command({"tools": {"cargo": "/explicit/cargo"}}, target, False)
self.assertEqual(command[-2:], ["--features", "embedded,runtime-api"])
self.assertNotIn("--all-features", command)
def test_sdk_missing_and_partial_execution_fail(self):
config = {"tools": {"cargo": sys.executable, "python": sys.executable}}
with self.assertRaises(gate.MissingInput):
gate.command_plan("sdk", config)
with self.assertRaises(RuntimeError):
gate.check_output("sdk", {}, "Python: handshake passed")
with self.assertRaises(RuntimeError):
gate.check_output("python", {"python_tests": 0}, "OK")
def test_classification_rejects_unknown_duplicate_and_wrong_target(self):
row = {"package": "sandbox", "target": "isolation", "name": "restricted_probe", "stage": "probe"}
target = {"package": "sandbox", "target": "isolation"}
self.assertEqual(classify_cases(["restricted_probe"], [row], target)[0]["stage"], "probe")
for names, rows, owner in ((["unknown"], [row], target), (["restricted_probe"], [row, row], target),
(["restricted_probe"], [row], {**target, "target": "other"}),
(["a::restricted_probe", "b::restricted_probe"], [row], target)):
with self.assertRaises(RuntimeError):
classify_cases(names, rows, owner)
def test_postgres_requires_owned_loopback_case_template_without_override(self):
def document(url):
return {"database": {"postgres_test": {"type": "postgres", "url": url}}}
validate_postgres(document("postgresql://fixture@127.0.0.1:51234/dever_{case}"))
for url in ("postgresql://fixture@db.example:5432/dever_{case}",
"postgresql://fixture@127.0.0.1:5432/fixed", "postgresql://fixture@127.0.0.1/dever_{case}",
"postgresql://fixture@127.0.0.1:5432/dever_{case}?dbname=production"):
with self.assertRaises(gate.MissingInput):
validate_postgres(document(url))
def test_failed_command_keeps_exit_and_counts(self):
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
with self.assertRaises(RuntimeError):
gate.run_command([sys.executable, "-c", "print('test result: FAILED. 2 passed; 1 failed; 3 ignored'); raise SystemExit(7)"], directory, 0, 5)
record = json.loads((directory / "000.command.json").read_text())
self.assertEqual(record["exit_code"], 7)
self.assertEqual(record["rust_tests"], {"passed": 2, "failed": 1, "ignored": 3})
def test_single_harness_counts_parent_instead_of_nested_probe(self):
child = "test result: ok. 1 passed; 0 failed; 0 ignored"
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
for number, (single, parent_passed, parent_ignored, exit_code) in enumerate(
((False, 1, 0, 0), (True, 1, 0, 0), (True, 0, 0, 0),
(True, 0, 1, 0), (True, 0, 0, 7))):
parent_failed = int(exit_code != 0)
parent = (f"test result: {'FAILED' if parent_failed else 'ok'}. "
f"{parent_passed} passed; {parent_failed} failed; {parent_ignored} ignored")
command = [sys.executable, "-c",
f"print({child!r}); print({parent!r}); raise SystemExit({exit_code})"]
with self.subTest(single=single, parent=parent):
if exit_code:
with self.assertRaises(RuntimeError):
gate.run_command(command, directory, number, 5, single_harness=single)
else:
gate.run_command(command, directory, number, 5, single_harness=single)
record = json.loads((directory / f"{number:03d}.command.json").read_text())
self.assertEqual(record["exit_code"], exit_code)
self.assertEqual(record["rust_tests"], {
"passed": parent_passed + int(not single),
"failed": parent_failed, "ignored": parent_ignored})
def test_exact_ignored_acceptance_requires_one_successful_parent(self):
target = {"package": "sandbox", "target": "isolation"}
case = {"name": "parent", "exact": "parent", "stage": "sandbox",
"target": target, "target_directory": "/unused/target"}
for counts in ({"passed": 1, "failed": 0, "ignored": 0},
{"passed": 0, "failed": 0, "ignored": 0},
{"passed": 0, "failed": 0, "ignored": 1},
{"passed": 1, "failed": 1, "ignored": 0},
{"passed": 2, "failed": 0, "ignored": 0}, None):
with (self.subTest(counts=counts),
patch.object(acceptance, "require_inputs"),
patch.object(acceptance, "current_inventory", return_value=[case]),
patch.object(acceptance, "target_command", return_value=["cargo"]),
patch.object(acceptance, "test_executable", return_value="/unused/test"),
patch.object(acceptance, "remove_executables", return_value=[]),
patch.object(acceptance, "execute", side_effect=[({}, "build"),
({"rust_tests": counts}, "test")]) as execute):
result = {"commands": []}
expected = counts == {"passed": 1, "failed": 0, "ignored": 0}
with contextlib.nullcontext() if expected else self.assertRaisesRegex(RuntimeError, "one case"):
acceptance.ignored_stage("sandbox", {}, {}, Path("/unused"), result)
self.assertTrue(execute.call_args.kwargs["single_harness"])
self.assertEqual(len(result.get("cases", [])), int(expected))
def test_source_and_author_input_fingerprints_detect_real_changes(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
for name in ("Cargo.toml", "Cargo.lock"):
(root / name).write_text(name)
(root / "test").mkdir()
before = gate.source_fingerprint(root)
(root / "test/new.py").write_text("changed")
self.assertNotEqual(before, gate.source_fingerprint(root))
output = root / "generated.pack"
output.write_text("old")
config = {"inputs": [str(root)], "author_outputs": [str(output)]}
author_before = gate.input_fingerprint({}, config, "author")
native_before = gate.input_fingerprint({}, config, "native")
output.write_text("new")
self.assertEqual(author_before, gate.input_fingerprint({}, config, "author"))
self.assertNotEqual(native_before, gate.input_fingerprint({}, config, "native"))
(root / "Cargo.lock").write_text("changed immutable input")
self.assertNotEqual(author_before, gate.input_fingerprint({}, config, "author"))
def test_cleanup_cannot_delete_author_binary(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
executable = root / "native-acceptance-init"
executable.write_text("owned author fixture")
message = json.dumps({"reason": "compiler-artifact", "profile": {"test": True}, "executable": str(executable)})
with self.assertRaises(RuntimeError):
gate.remove_executables(message, root, True)
self.assertTrue(executable.exists())
def test_failed_workspace_command_still_cleans_its_test_executable(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
executable = root / "target/debug/deps/test-owned"
executable.parent.mkdir(parents=True)
executable.write_text("rebuildable executable")
config = {"output": str(root / "results"), "remove_test_executables": True,
"tools": {"cargo": sys.executable}}
def fail_workspace(stage, config, directory, result):
result["target_directory"] = str(root / "target")
message = json.dumps({"reason": "compiler-artifact", "profile": {"test": True},
"executable": str(executable)})
gate.run_command([sys.executable, "-c", f"print({message!r}); raise SystemExit(7)",
"--message-format=json"],
directory, 0, 5)
with (patch.object(gate, "load_config", return_value=({}, config)),
patch.object(gate, "source_fingerprint", return_value="source"),
patch.object(gate, "run_workspace", side_effect=fail_workspace),
patch.object(acceptance, "validate_budget"),
patch.object(sys, "argv", ["run.py", "run", "--stage", "default"]),
contextlib.redirect_stdout(io.StringIO())):
self.assertEqual(gate.main(), 1)
self.assertFalse(executable.exists())
record = json.loads(next((root / "results/default").glob("*/result.json")).read_text())
self.assertEqual(record["status"], "failed")
self.assertIn("command exited 7", record["error"])
self.assertEqual(record["removed_test_executables"], [str(executable)])
def test_missing_input_is_saved_as_blocked_stage_evidence(self):
with tempfile.TemporaryDirectory() as temporary:
config = {"output": temporary}
with (patch.object(gate, "load_config", return_value=({}, config)),
patch.object(gate, "source_fingerprint", return_value="source"),
patch.object(gate, "input_fingerprint", side_effect=FileNotFoundError("missing peer")),
patch.object(sys, "argv", ["run.py", "run", "--stage", "python"]),
contextlib.redirect_stdout(io.StringIO())):
self.assertEqual(gate.main(), 1)
record = json.loads(next((Path(temporary) / "python").glob("*/result.json")).read_text())
self.assertEqual(record["status"], "blocked")
self.assertIn("missing peer", record["error"])
def test_inventory_never_reuses_pass_after_a_newer_failure_or_interruption(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
config = {"output": temporary}
fingerprint = gate.input_fingerprint({}, config, "inventory")
for index, status in enumerate(("passed", "running")):
directory = root / "inventory" / str(index)
directory.mkdir(parents=True)
(directory / "result.json").write_text(json.dumps({"source": "source", "inputs": fingerprint,
"status": status}))
(directory / "inventory.json").write_text("[]")
with self.assertRaises(gate.MissingInput):
acceptance.current_inventory({}, config, {"source": "source"})
output = io.StringIO()
with (patch.object(gate, "load_config", return_value=({}, config)),
patch.object(gate, "source_fingerprint", return_value="source"),
patch.object(gate, "STAGES", ("inventory",)),
patch.object(sys, "argv", ["run.py", "summary"]),
contextlib.redirect_stdout(output)):
self.assertEqual(gate.main(), 1)
self.assertEqual(json.loads(output.getvalue()), {"inventory": "running"})
(root / "inventory/1/result.json").write_text(json.dumps({
"source": "source", "inputs": fingerprint, "status": "failed"}))
with self.assertRaises(gate.MissingInput):
acceptance.current_inventory({}, config, {"source": "source"})
def test_inventory_detects_a_missing_owner_even_when_leaf_names_match(self):
row = {"package": "package", "target": "first", "name": "same_name", "stage": "native"}
target = {"package": "package", "target": "first", "selector": ["--lib"]}
artifact = json.dumps({"reason": "compiler-artifact", "profile": {"test": True},
"target": {"name": "first"}, "executable": "/owned/test"})
with (tempfile.TemporaryDirectory() as temporary,
patch.object(acceptance, "audit_classification", return_value=[row, {**row, "target": "second"}]),
patch.object(acceptance, "workspace_targets", return_value=([target], Path(temporary))),
patch.object(acceptance, "execute", side_effect=[({}, artifact), ({}, "same_name: test\n")])):
with self.assertRaisesRegex(RuntimeError, "second"):
acceptance.inventory({"tools": {"cargo": "/cargo"}}, Path(temporary), {"commands": []})
def test_soak_rejects_short_load_errors_drops_missing_budget_and_fd_leak(self):
samples = [{"rss_bytes": 100, "fds": 3, "elapsed_seconds": index * 600} for index in range(4)]
case = {"suite": "http2", "implementation": "dever", "transport": "http",
"budget_after_stop": {"events": {"oom": 0, "oom_kill": 0}, "max": str(64 * 1024 ** 2)},
"load": {"elapsed_seconds": 1800, "scheduled": 1000, "succeeded": 1000, "errors": 0},
"disconnected": {"fd_delta": 0}, "raw": samples}
report = {"status": "complete", "cases": [case, {**case, "transport": "https"}]}
validate_soak(report, "http2", 1800, 1024, 64)
for field, value in (("elapsed_seconds", 1799), ("errors", 1), ("succeeded", 989)):
invalid = json.loads(json.dumps(report))
invalid["cases"][0]["load"][field] = value
with self.assertRaises(RuntimeError):
validate_soak(invalid, "http2", 1800, 1024, 64)
for field, value in (("transport", "https"), ("implementation", "hyper"),
("disconnected", {"fd_delta": 1}),
("budget_after_stop", {"events": {"oom": 0, "oom_kill": 0}, "max": "max"})):
invalid = json.loads(json.dumps(report))
invalid["cases"][0][field] = value
with self.assertRaises(RuntimeError):
validate_soak(invalid, "http2", 1800, 1024, 64)
case["budget_after_stop"] = None
with self.assertRaises(RuntimeError):
validate_soak(report, "http2", 1800, 1024, 64)
def test_cms_soak_checks_descriptor_identity_and_pool_bound_not_total_count(self):
load = {"elapsed_seconds": 1800, "succeeded": 1000, "errors": 0,
"fd_delta_after_disconnect": 1,
"descriptors_before": {"3": "socket:[listener]", "4": "/fixture/platform.db"},
"descriptors_after": {"3": "socket:[listener]", "4": "/fixture/platform.db",
"5": "/fixture/platform.db"},
"pooled_descriptor_limits": {"/fixture/platform.db": 4},
"trend": {"rss_late_growth_bytes": 0}}
case = {"cgroup": {"events": {"oom": 0, "oom_kill": 0}, "max": str(64 * 1024 ** 2)},
"concurrent_reads": load}
report = {"status": "passed", "cases": {"dever": case, "md": case}}
validate_soak(report, "cms", 1800, 1024, 64)
for descriptors in ({**load["descriptors_after"], "3": "socket:[replacement]"},
{**load["descriptors_after"], "9": "socket:[leaked]"},
{**load["descriptors_after"], **{str(n): "/fixture/platform.db" for n in range(10, 15)}}):
invalid = json.loads(json.dumps(report))
invalid["cases"]["md"]["concurrent_reads"]["descriptors_after"] = descriptors
with self.subTest(descriptors=descriptors), self.assertRaises(RuntimeError):
validate_soak(invalid, "cms", 1800, 1024, 64)
if __name__ == "__main__":
unittest.main()