feat: initialize XAS test console with native execution
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"""Read-only output discovery, history preservation and small runtime snapshots."""
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
from .artifacts import files_under
|
||||
|
||||
|
||||
def outputs(patterns):
|
||||
paths = []
|
||||
for pattern in patterns:
|
||||
expanded = glob.iglob(pattern) if glob.has_magic(pattern) else [pattern]
|
||||
for name in expanded:
|
||||
path = Path(name)
|
||||
if path.is_symlink():
|
||||
continue
|
||||
if path.is_file():paths.append(path)
|
||||
elif path.is_dir():paths.extend(files_under([path], limit=4097))
|
||||
if len(paths) > 4096:
|
||||
raise ValueError("原输出文件超过 4096 项,请收窄登记输出范围")
|
||||
return sorted(set(paths))
|
||||
|
||||
|
||||
def preserve(paths, directory, maximum=64 * 1024**3):
|
||||
sizes = [path.stat().st_size for path in paths]
|
||||
total = sum(sizes)
|
||||
if total > maximum or shutil.disk_usage(directory).free < total + 10 * 1024**3:
|
||||
raise ValueError("保护原有同名结果所需空间不足或超过 64 GiB,本次未启动")
|
||||
destination = directory / "previous-outputs"
|
||||
destination.mkdir(mode=0o700)
|
||||
manifest = []
|
||||
for path in paths:
|
||||
identity = hashlib.sha256(str(path).encode()).hexdigest()
|
||||
target = destination / identity
|
||||
before = path.stat()
|
||||
with path.open("rb") as source, target.open("xb") as output:
|
||||
shutil.copyfileobj(source, output, length=1024 * 1024)
|
||||
after = path.stat()
|
||||
if (before.st_size, before.st_mtime_ns) != (after.st_size, after.st_mtime_ns):
|
||||
raise ValueError("原输出仍被其他任务写入,请等待后再试")
|
||||
manifest.append({"path": str(path), "backup": identity, "size": before.st_size})
|
||||
(destination / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return len(manifest)
|
||||
|
||||
|
||||
def runtime_snapshot():
|
||||
result = {"containers": [], "tmux_sessions": [], "errors": []}
|
||||
try:
|
||||
raw = subprocess.run(["/usr/bin/docker", "ps", "-a", "--format", "{{json .}}"], capture_output=True, timeout=10, text=True)
|
||||
if raw.returncode == 0:
|
||||
for line in raw.stdout.splitlines():
|
||||
value = json.loads(line)
|
||||
result["containers"].append({key: value.get(key) for key in ("ID", "Names", "Image", "State", "Status")})
|
||||
else:result["errors"].append("容器状态读取失败")
|
||||
except (OSError, ValueError, subprocess.TimeoutExpired):result["errors"].append("容器状态不可用")
|
||||
try:
|
||||
raw = subprocess.run(["/usr/bin/tmux", "list-sessions", "-F", "#{session_id}\t#{session_name}"], capture_output=True, timeout=5, text=True)
|
||||
if raw.returncode == 0:
|
||||
result["tmux_sessions"] = [dict(zip(("id", "name"), line.split("\t", 1))) for line in raw.stdout.splitlines() if "\t" in line]
|
||||
except (OSError, subprocess.TimeoutExpired):result["errors"].append("tmux 状态不可用")
|
||||
return result
|
||||
|
||||
|
||||
def differences(before, after):
|
||||
old = {v["ID"]: v for v in before["containers"]}
|
||||
new = {v["ID"]: v for v in after["containers"]}
|
||||
sessions = {v["id"] for v in before["tmux_sessions"]}
|
||||
return {"created_containers": [v for k, v in new.items() if k not in old],
|
||||
"removed_containers": [v for k, v in old.items() if k not in new],
|
||||
"changed_containers": [v for k, v in new.items() if k in old and v["State"] != old[k]["State"]],
|
||||
"new_tmux_sessions": [v for v in after["tmux_sessions"] if v["id"] not in sessions],
|
||||
"note": "共享宿主前后快照差异不等于资源归属证明;不会自动删除这些容器或会话。"}
|
||||
Reference in New Issue
Block a user