95 lines
5.3 KiB
Python
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()
|