Dotfiles_ArchAP/tests/session_v2/crashes.py

95 lines
5.3 KiB
Python

"""SIGKILL réels de l'executor production, jamais du guard ni d'un utilisateur."""
import json
import os
import signal
import time
from common import *
from lab import Lab, Surfaces, build_helper, until
from session_v2.storage import atomic, private_dir, read
from session_v2.executor import Executor
from session_v2.ipc import Sway
def worker(path):
config = read(path)
def progress(phase, count):
if [phase, count] == config["point"]:
atomic(Path(config["runtime"]) / "kill-ready.json", {"phase": phase, "count": count})
while True: time.sleep(1)
class CommandCrashSway(Sway):
def command(self, text):
result = super().command(text)
if config["point"] == ["mid-command", 1]:
# L'IPC a réellement muté Sway, mais Recovery n'a encore reçu
# ni réponse ni postcondition : l'intention doit rester ambiguë.
atomic(Path(config["runtime"]) / "kill-ready.json", {"command": text})
while True: time.sleep(1)
return result
Executor(CommandCrashSway(config["socket"]), catalog(config["data"]), None, config["runtime"], config["binary"],
os.environ.copy(), progress).apply(config["data"], True)
def main():
cases = []
with Lab() as lab:
binary, helper_binary = build(lab.directory), build_helper(lab.directory)
for i, point in enumerate((("mid-command", 1), ("anchor", 1), ("all-anchors", 4), ("swap", 1), ("swap", 3),
("before-cleanup", 0), ("cleanup", 1), ("after-resume", 4), ("normal", 0), ("initial-stopped", 0))):
runtime = private_dir(lab.directory / f"crash-{i}")
data = fixture({"splitv": [{"splith": ["A", "B"]}, {"splith": ["C", "D"]}]})
apps = Surfaces(lab, helper_binary)
lab.command('workspace TEST_ONLY_SOURCE')
ids = [apps.create("TEST_ONLY_" + a["application_id"])["id"] for a in data["applications"]]
foreign = apps.create("labfy-v2-anchor-" + "f" * 32 + "-foreign")["id"]
helper = lab.spawn(["autotiling"])
time.sleep(.15)
helper_fd = os.pidfd_open(helper.pid)
if point[0] == "initial-stopped": signal.pidfd_send_signal(helper_fd, signal.SIGSTOP)
config = {"socket": lab.socket, "runtime": str(runtime), "binary": str(binary), "data": data, "point": list(point)}
atomic(runtime / "config.json", config)
process = lab.spawn([sys.executable, "-B", str(Path(__file__).resolve()), "--worker", str(runtime / "config.json")])
killed = None
try:
if point[0] not in ("normal", "initial-stopped"):
until(lambda: (runtime / "kill-ready.json").exists() or process.poll() is not None, 15)
assert process.poll() is None
killed = time.monotonic()
process.kill()
process.wait(timeout=25)
report = runtime / "session-v2-restore-attempt.json"
until(lambda: report.exists() and read(report)["status"] != "running", 10)
if killed: until(lambda: read(report)["phase"] == "recovered", 10)
result = read(report)
state = Path(f"/proc/{helper.pid}/status").read_text().split("State:\t", 1)[1][0]
windows = lab.windows()
row = {"point": list(point), "returncode": process.returncode, "report": result,
"kill_to_recovery_ms": (time.monotonic() - killed) * 1000 if killed else None,
"apps_preserved": set(ids).issubset(n["id"] for n in windows),
"foreign_preserved": any(n["id"] == foreign for n in windows),
"state_preserved": state == "T" if point[0] == "initial-stopped" else state not in ("T", "t"),
"anchors_remaining": sum(n.get("app_id", "").startswith("labfy-v2-anchor-") and n["id"] != foreign for n in windows)}
assert row["apps_preserved"] and row["foreign_preserved"] and row["state_preserved"] and row["anchors_remaining"] == 0, row
assert result["status"] == ("interrupted" if killed else "success"), row
if killed: assert row["kill_to_recovery_ms"] < 3000
if point[0] == "mid-command":
from session_v2.recovery import JOURNAL, recovery_status
journal = read(runtime / JOURNAL)
assert journal["operations"] == 1 and journal["windows"][0]["pending"], journal
assert journal["intention"]["command"] == read(runtime / "kill-ready.json")["command"]
assert recovery_status(runtime)["status"] == "RECOVERY_REQUIRED"
row["ambiguous_intention_preserved"] = True
cases.append(row)
print(point, "PASS", flush=True)
finally:
if process.poll() is None: process.kill(); process.wait()
signal.pidfd_send_signal(helper_fd, signal.SIGCONT)
helper.terminate(); helper.wait(timeout=3)
os.close(helper_fd)
apps.close()
out = private_dir(EVIDENCE)
atomic(out / "crash.json", {"cases": cases})
if __name__ == "__main__":
if "--worker" in sys.argv: worker(sys.argv[2])
else: main()