swayfx-mosaic/tooling/probes/step20c-egl-fx/test_probe.py

225 lines
10 KiB
Python

"""Tests offline uniquement ; aucun bwrap, aucun helper EGL/FX exécuté."""
import contextlib
import io
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
import run
class ProtocolTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.lab = Path(self.temporary.name)
self.lab.chmod(0o700)
(self.lab / "bin").mkdir()
(self.lab / "fixture").mkdir()
(self.lab / "bin/probe").write_bytes(b"INERT-NOT-ELF")
(self.lab / "fixture/run.py").write_text("# inert\n")
self.files = [self.lab / "bin/probe", self.lab / "fixture/run.py",
Path("/usr/bin/bwrap"), Path("/usr/bin/prlimit")]
self.manifest = {
"schema": 1, "lab": str(self.lab), "argv": run.command(self.lab),
"environment": run.ENV, "supervisor_environment": run.HOST_ENV,
"files": {str(path): {"sha256": run.digest(path), "resolved": str(path.resolve())}
for path in self.files},
}
def seal(self):
(self.lab / "manifest.json").write_text(json.dumps(self.manifest))
return run.digest(self.lab / "manifest.json")
def verify(self, seal=None):
if seal is None:
seal = self.seal()
with patch.object(run, "__file__", str(self.lab / "fixture/run.py")), \
patch.dict(os.environ, run.HOST_ENV, clear=True):
return run.verify(self.lab, seal)
def test_valid_identity(self):
self.assertEqual(self.verify()["schema"], 1)
def test_manifest_drift(self):
seal = self.seal()
(self.lab / "manifest.json").write_text("{}")
with self.assertRaises(run.Refusal):
self.verify(seal)
def test_binary_drift(self):
seal = self.seal()
(self.lab / "bin/probe").write_bytes(b"CHANGED")
with self.assertRaises(run.Refusal):
self.verify(seal)
def test_script_identity_drift(self):
seal = self.seal()
(self.lab / "fixture/run.py").write_text("# changed\n")
with self.assertRaises(run.Refusal):
self.verify(seal)
def test_forbidden_mount_and_devices(self):
for device in ("/dev/dri", "/dev/input", "/dev/udmabuf", "/etc", "/sys", "/run/user"):
with self.subTest(device=device):
self.manifest["argv"] = run.command(self.lab) + ["--ro-bind", device, device]
with self.assertRaises(run.Refusal):
self.verify()
def test_environment_pwd_and_extra(self):
for altered in ({**run.ENV, "PWD": "/"}, {**run.ENV, "HOME": "/home/host"}):
self.manifest["environment"] = altered
with self.assertRaises(run.Refusal):
self.verify()
def test_supervisor_inherited_environment(self):
seal = self.seal()
with patch.dict(os.environ, {**run.HOST_ENV, "LD_PRELOAD": "bad"}, clear=True):
with self.assertRaises(run.Refusal):
run.verify(self.lab, seal)
def test_wrong_launcher_identity(self):
seal = self.seal()
with patch.dict(os.environ, run.HOST_ENV, clear=True):
with self.assertRaises(run.Refusal):
run.verify(self.lab, seal)
def test_private_mode(self):
self.lab.chmod(0o755)
with self.assertRaises(run.Refusal):
self.verify()
def test_budget_exclusive_no_retry(self):
run.durable(self.lab / "ATTEMPT", b"one", exclusive=True)
with self.assertRaises(FileExistsError):
run.durable(self.lab / "ATTEMPT", b"two", exclusive=True)
self.assertEqual((self.lab / "ATTEMPT").read_bytes(), b"one")
def test_default_plan_has_no_write_or_spawn(self):
before = sorted(self.lab.rglob("*"))
with patch.object(sys, "argv", ["run.py", "--lab", str(self.lab), "--manifest-sha256", "x"]), \
patch.object(run, "verify", return_value=self.manifest), \
patch.object(run, "durable", side_effect=AssertionError("write")), \
patch.object(subprocess, "Popen", side_effect=AssertionError("native boundary")), \
contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(run.main(), 0)
self.assertEqual(sorted(self.lab.rglob("*")), before)
def test_invalid_identity_stops_before_native_boundary(self):
with patch.object(sys, "argv", ["run.py", "--lab", str(self.lab), "--manifest-sha256", "bad",
"--approve-native-probe"]), \
patch.object(run, "supervise", side_effect=AssertionError("native boundary")), \
contextlib.redirect_stderr(io.StringIO()):
self.assertEqual(run.main(), 20)
self.assertFalse((self.lab / "ATTEMPT").exists())
def test_consumed_budget_refuses_passive_plan(self):
run.durable(self.lab / "ATTEMPT", b"one", exclusive=True)
with patch.object(sys, "argv", ["run.py", "--lab", str(self.lab), "--manifest-sha256", "x"]), \
patch.object(run, "verify", return_value=self.manifest), \
contextlib.redirect_stderr(io.StringIO()):
self.assertEqual(run.main(), 20)
def test_spawn_failure_consumes_budget(self):
arguments = ["run.py", "--lab", str(self.lab), "--manifest-sha256", "test", "--approve-native-probe"]
with patch.object(sys, "argv", arguments), \
patch.object(run, "verify", return_value=self.manifest), \
patch.object(run, "supervise", side_effect=OSError("mock spawn failure")) as native, \
contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
self.assertEqual(run.main(), 20)
self.assertEqual(run.main(), 20)
self.assertEqual(native.call_count, 1)
self.assertEqual(json.loads((self.lab / "result.json").read_text())["cause"], "spawn_failure")
class DrainTests(unittest.TestCase):
def fake_python(self, script, **kwargs):
return run.supervise([sys.executable, "-I", "-c", script], **kwargs)
def test_exit_and_channels(self):
result, channels = self.fake_python("import os; os.write(1,b'out'); os.write(2,b'err')")
self.assertEqual(result["returncode"], 0)
self.assertTrue(result["collected"])
self.assertEqual(channels, [b"out", b"err"])
def test_nonzero_exit(self):
result, _ = self.fake_python("raise SystemExit(7)")
self.assertEqual(result["returncode"], 7)
self.assertEqual(result["cause"], "exit")
def test_exact_output_bound(self):
result, channels = self.fake_python(f"import os; os.write(1,b'x'*{run.LIMIT}); os.write(2,b'y'*{run.LIMIT})")
self.assertEqual(result["cause"], "exit")
self.assertEqual(list(map(len, channels)), [run.LIMIT, run.LIMIT])
def test_output_overflow(self):
result, channels = self.fake_python(f"import os; os.write(1,b'x'*{run.LIMIT + 1})")
self.assertEqual(result["cause"], "output_overflow")
self.assertEqual(len(channels[0]), run.LIMIT)
self.assertTrue(result["collected"])
def test_timeout_collection(self):
result, _ = self.fake_python("import time; time.sleep(30)", payload_seconds=0.1, total_seconds=0.8)
self.assertEqual(result["cause"], "timeout")
self.assertTrue(result["collected"])
self.assertLess(result["elapsed"], 1.0)
def test_closed_pipes_preserve_cleanup_reserve(self):
result, _ = self.fake_python("import os,time; os.close(1); os.close(2); time.sleep(30)",
payload_seconds=0.1, total_seconds=0.8)
self.assertEqual(result["cause"], "timeout")
self.assertTrue(result["collected"])
self.assertLess(result["elapsed"], 0.6)
def test_read_failure_collects_owned_child(self):
real_popen = subprocess.Popen
children = []
def remember(*args, **kwargs):
child = real_popen(*args, **kwargs)
children.append(child)
return child
real_read = os.read
def fail_drain(fd, size):
if size == 65536:
raise OSError("mock read failure")
return real_read(fd, size)
with patch.object(run.subprocess, "Popen", side_effect=remember), \
patch.object(run.os, "read", side_effect=fail_drain):
result, _ = self.fake_python("import os,time; os.write(1,b'x'); time.sleep(30)")
self.assertEqual(result["cause"], "supervisor_failure")
self.assertIsNotNone(children[0].returncode)
def test_collection_failure_explicit(self):
real_wait = subprocess.Popen.wait
def delayed_wait(child, *args, **kwargs):
real_wait(child, timeout=1)
raise subprocess.TimeoutExpired("mock collection", 0)
with patch.object(subprocess.Popen, "wait", delayed_wait):
result, _ = self.fake_python("pass", payload_seconds=0.05, total_seconds=0.2)
self.assertEqual(result["cause"], "cleanup_unproven")
self.assertFalse(result["collected"])
class SourceTests(unittest.TestCase):
def test_device_guard_precedes_fx_and_ownership(self):
source = Path(__file__).with_name("probe.c").read_text()
self.assertLess(source.index("query_display(display, EGL_DEVICE_EXT"),
source.index("renderer = fx_renderer_create_egl(egl)"))
self.assertLess(source.index("if (!software || drm)"), source.index("renderer = fx_renderer_create_egl(egl)"))
self.assertIn("} else if (egl != NULL)", source)
self.assertIn("egl = NULL;", source)
for forbidden in ("eglQueryDevicesEXT(", "wlr_fx_renderer_get_egl(", "wl_display_create(",
"wlr_backend_start(", "wlr_headless_add_output(", "wlr_allocator_autocreate(",
"wlr_buffer_create(", "glDraw", "->texture_caps", "->egl", "->display"):
self.assertNotIn(forbidden, source)
self.assertIn('"PWD=/tmp"', source)
self.assertIn("modifiers > 4096", source)
if __name__ == "__main__":
unittest.main()