Harden desktop recovery and pre-release stability checks

Bound IPC transport deadlines while preserving portal leases, supervise desktop helper and agent recovery, and reserve final floating window geometry during placement.

Stabilize CLI output and isolated tests, expand workspace CI, resolve formatting and lint failures, and bundle the MIT license with installer compatibility.
This commit is contained in:
Samuel Onoja committed 2026-10-07 09:08:23 +01:00
1 parent a00f1c6f3a
commit a6f24e8188
30 files changed
+810 -44

No files matched your search

@@ -69,13 +69,27 @@ class FloatingPlacementTest(unittest.TestCase):
return child
def command(*args):
return json.loads(subprocess.check_output([str(repo / "target/debug/feresectl"), "-j", *args], env=ipc_environment(env), timeout=5))
return json.loads(subprocess.check_output([str(repo / os.environ.get("FERESE_TEST_CTL", "target/debug/feresectl")), "-j", *args], env=ipc_environment(env), timeout=5))
def settled_geometry(query, fields):
deadline = time.monotonic() + 5
stable_since, previous = time.monotonic(), None
while True:
values = command(query)
geometry = [tuple(value[field] for field in fields) for value in values]
if geometry != previous:
stable_since, previous = time.monotonic(), geometry
if geometry and time.monotonic() - stable_since >= .5:
return values
if time.monotonic() > deadline:
self.fail(f"{query} geometry did not settle: {geometry}")
time.sleep(.025)
with (root / "compositor.log").open("w") as log:
try:
bus = launch(["dbus-daemon", "--session", "--nofork", "--print-address=1"], stdout=subprocess.PIPE, text=True)
env["DBUS_SESSION_BUS_ADDRESS"] = bus.stdout.readline().strip()
compositor = launch([str(repo / "target/debug/ferese"), "--backend", "nested"], stdout=log, stderr=log)
compositor = launch([str(repo / os.environ.get("FERESE_TEST_BINARY", "target/debug/ferese")), "--backend", "nested"], stdout=log, stderr=log)
deadline = time.monotonic() + 10
while not (ipc_socket(runtime)).is_socket():
if compositor.poll() is not None or time.monotonic() > deadline:
@@ -83,6 +97,9 @@ class FloatingPlacementTest(unittest.TestCase):
time.sleep(.025)
socket = next(p for p in runtime.glob("wayland-*") if not p.name.endswith(".lock"))
env["WAYLAND_DISPLAY"] = str(socket)
# The host configures the nested window after its initial
# output is advertised. Restore memory against that final size.
settled_geometry("outputs", ["name", "x", "y", "width", "height", "scale"])
for app in ["remembered", "second", "third"]:
launch([str(root / "client"), "ferese.test.placement." + app, "240", "160"], stdout=log, stderr=log)
deadline = time.monotonic() + 5
@@ -93,6 +110,10 @@ class FloatingPlacementTest(unittest.TestCase):
if time.monotonic() > deadline:
self.fail((root / "compositor.log").read_text() + repr(windows))
time.sleep(.025)
# IPC reports presented bounds. Let opening animations settle
# before comparing the remembered rectangle with a later restore.
# Mapping above remains rapid to exercise placement during opening.
windows = settled_geometry("windows", ["app_id", "x", "y", "width", "height"])
remembered = next(w for w in windows if w["app_id"].endswith("remembered"))
output = next(o for o in command("get-outputs") if o["enabled"])
self.assertAlmostEqual(remembered["x"], output["x"] + .1 * output["width"], delta=1)
+20 -1
View File
@@ -113,15 +113,34 @@ m.main()
self.assertEqual((self.bundles[0] / name).stat().st_mode & 0o777, 0o755)
def without_appearance_wallpapers(self, bundle):
self.without_files(bundle, {'wallpapers/ferese-wallpaper-dark.jpg', 'wallpapers/ferese-wallpaper-light.png'})
def without_files(self, bundle, removed):
manifest_path = bundle / 'manifest.json'
manifest = json.loads(manifest_path.read_text())
removed = {'wallpapers/ferese-wallpaper-dark.jpg', 'wallpapers/ferese-wallpaper-light.png'}
for target in removed:
(bundle / target).unlink()
manifest['files'] = [item for item in manifest['files'] if item['target'] not in removed]
manifest_path.write_text(json.dumps(manifest))
def test_license_is_bundled_and_required_for_new_releases(self):
self.assertEqual((self.bundles[0] / 'licenses/Ferese-LICENSE.txt').read_bytes(), (REPO / 'LICENSE').read_bytes())
incomplete = Path(self.temporary.name) / 'incomplete'
shutil.copytree(self.bundles[0], incomplete)
self.without_files(incomplete, {'licenses/Ferese-LICENSE.txt'})
result = self.run_command('install', incomplete, success=False)
self.assertIn('Bundle is incomplete', result.stderr)
self.assertFalse((self.base / 'current').exists())
def test_upgrade_and_rollback_accept_installed_release_before_license(self):
self.install()
self.without_files(self.base / 'releases/first', {'licenses/Ferese-LICENSE.txt'})
self.install(1)
self.run_command('rollback')
self.assertEqual(os.readlink(self.base / 'current'), 'releases/first')
self.assertEqual(installer.Installation(self.root).manifest('first')['release'], 'first')
def test_upgrade_and_rollback_accept_installed_release_before_appearance_wallpapers(self):
self.install()
legacy = self.base / 'releases/first'
+3 -1
View File
@@ -14,6 +14,7 @@ import subprocess
import tempfile
import time
import unittest
from session_socket import ipc_environment
@unittest.skipUnless(os.environ.get("FERESE_TEST_PORTAL") == "1", "nested portal test is opt-in")
@@ -45,7 +46,7 @@ class PortalTest(unittest.TestCase):
def launch(name, command, **kwargs):
log = (root / (name + ".log")).open("w")
logs.append(log)
child = subprocess.Popen(command, env=env, stderr=log,
child = subprocess.Popen(command, env=ipc_environment(env), stderr=log,
stdout=kwargs.pop("stdout", log), start_new_session=True, **kwargs)
processes.append(child)
return child
@@ -95,6 +96,7 @@ class PortalTest(unittest.TestCase):
sockets = lambda: [p for p in runtime.glob("wayland-*") if not p.name.endswith(".lock")]
until(sockets)
env["WAYLAND_DISPLAY"] = str(sockets()[0])
env = ipc_environment(env)
# Private bus, including the real portal frontend and permission store.
bus = launch("bus", ["dbus-daemon", "--session", "--nofork", "--print-address=1"], stdout=subprocess.PIPE)
env["DBUS_SESSION_BUS_ADDRESS"] = bus.stdout.readline().decode().strip()
+33 -1
View File
@@ -13,7 +13,7 @@ SCRIPT = Path(__file__).resolve().parents[2] / 'packaging/ferese-session-shell'
class SessionStartupTest(unittest.TestCase):
def run_session(self, direct=True, fail_target=False, shell_status=0, terminate=False,
display=None, xauthority=None, extra_env=None):
display=None, xauthority=None, extra_env=None, programs_override=None):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
helper = root / 'ferese-session-shell'
@@ -27,6 +27,7 @@ class SessionStartupTest(unittest.TestCase):
'ferese-polkit-agent': 'exec sleep 30\n',
'ferese-shell': 'printf "shell:%s\\n" "$WAYLAND_SOCKET" >> "$TEST_ROOT/events"\nif [[ $TEST_WAIT == 1 ]]; then exec sleep 30; fi\nexit "$TEST_STATUS"\n',
}
programs.update(programs_override or {})
for name, body in programs.items():
path = root / name
path.write_text('#!/bin/bash\n' + body)
@@ -128,6 +129,37 @@ class SessionStartupTest(unittest.TestCase):
self.assertEqual(status, 143)
self.assertEqual(events[-1], '--user stop ferese-session.target')
def test_agent_failure_recovers_without_restarting_shell(self):
status, events = self.run_session(programs_override={
'ferese-polkit-agent': (
'echo agent >> "$TEST_ROOT/events"\n'
'if [[ ! -f $TEST_ROOT/agent-failed ]]; then touch "$TEST_ROOT/agent-failed"; exit 7; fi\n'
'touch "$TEST_ROOT/agent-recovered"\nexec sleep 30\n'),
'ferese-shell': (
'echo shell >> "$TEST_ROOT/events"\n'
'for ((i=0; i<200; i++)); do\n'
' [[ -f $TEST_ROOT/agent-recovered ]] && exit 0\n'
' sleep .01\ndone\nexit 1\n'),
})
self.assertEqual(status, 0)
self.assertEqual(events.count('agent'), 2)
self.assertEqual(events.count('shell'), 1)
self.assertEqual(events[-1], '--user stop ferese-session.target')
def test_recovered_shell_does_not_repeat_autostart(self):
programs = {
'feresectl': 'touch "$TEST_ROOT/autostarted"\necho autostart >> "$TEST_ROOT/events"\n',
'ferese-shell': (
'if [[ ${FERESE_CLIENT_RESTART:-0} == 1 ]]; then sleep .1; exit 0; fi\n'
'for ((i=0; i<100; i++)); do\n'
' [[ -f $TEST_ROOT/autostarted ]] && exit 0\n'
' sleep .01\ndone\nexit 1\n'),
}
for restarted in ('0', '1'):
status, events = self.run_session(extra_env={'FERESE_CLIENT_RESTART': restarted}, programs_override=programs)
self.assertEqual(status, 0)
self.assertEqual(events.count('autostart'), int(restarted == '0'))
if __name__ == '__main__':
unittest.main()