Dotfiles_ArchAP/tests/test_session_tree_restore.py

252 lines
9.9 KiB
Python

"""STEP19C : compilation pure du sous-ensemble d'arbre Sway prouvé."""
from __future__ import annotations
import copy
import importlib.util
from pathlib import Path
from types import SimpleNamespace
import unittest
ROOT = Path(__file__).resolve().parents[1]
MODULE = ROOT / "quickshell/.config/quickshell/labfy-sway/session/session_tree_restore.py"
def load_module():
spec = importlib.util.spec_from_file_location("session_tree_restore_test", MODULE)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
tree = load_module()
def load_executor():
path = ROOT / "quickshell/.config/quickshell/labfy-sway/session/session_executor.py"
spec = importlib.util.spec_from_file_location("session_tree_executor_test", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
executor = load_executor()
def make_snapshot(
*,
live=False,
layout="splith",
order=(1, 2, 3),
nested=False,
workspace_layout="splith",
floating_four=False,
):
prefix = "l" if live else "w"
con_base = 100 if live else 900
windows = []
for number in range(1, 5 if floating_four else 4):
window_id = f"{prefix}{number}"
parent = f"{prefix}root"
branch = "tiling"
index = order.index(number) if number in order else 0
floating = False
if number == 4:
parent = None
branch = "floating"
index = 0
floating = True
elif nested and number in {2, 3}:
parent = f"{prefix}nested"
index = number - 2
windows.append({
"window_id": window_id,
"runtime": {"con_id": con_base + number, "pid": 200 + number},
"workspace": "8",
"floating": floating,
"percent": 0.5 if nested and number in {2, 3} else 1 / 3,
"tree_position": {"parent_container_id": parent, "branch": branch, "index": index},
})
children = []
for number in order:
if nested and number == 2:
children.append({"kind": "container", "id": f"{prefix}nested"})
continue
if nested and number == 3:
continue
children.append({"kind": "window", "id": f"{prefix}{number}"})
containers = [{
"container_id": f"{prefix}root",
"layout": layout,
"percent": 1.0,
"children": children,
"floating_children": [],
}]
if nested:
containers.append({
"container_id": f"{prefix}nested",
"layout": "splitv" if layout == "splith" else "splith",
"percent": 2 / 3,
"children": [
{"kind": "window", "id": f"{prefix}2"},
{"kind": "window", "id": f"{prefix}3"},
],
"floating_children": [],
})
roots = {
"tiling": [{"kind": "container", "id": f"{prefix}root"}],
"floating": ([{"kind": "window", "id": f"{prefix}4"}] if floating_four else []),
}
return {
"workspaces": [{"name": "8", "layout": workspace_layout, "roots": roots}],
"containers": containers,
"windows": windows,
}
def make_plan(source, live, *, omit=()):
actions = []
for number in range(1, len(source["windows"]) + 1):
if number in omit:
continue
live_window = live["windows"][number - 1]
actions.append({
"action": "reuse-window",
"source_window": f"w{number}",
"live_window": copy.deepcopy(live_window),
})
actions.append({"action": "restore-tree-position", "source_window": "w1"})
return {"actions": actions}
class TreeCompilerTest(unittest.TestCase):
def compile(self, source, live, **kwargs):
return tree.compile_tree_restore(source, live, make_plan(source, live, **kwargs))
def test_identical_tree_has_no_operations(self):
result = self.compile(make_snapshot(), make_snapshot(live=True))
self.assertEqual(result["operations"], [])
def test_root_splith(self):
result = self.compile(make_snapshot(layout="splith"), make_snapshot(live=True, layout="tabbed"))
self.assertEqual(result["operations"][-1]["layout"], "splith")
def test_root_splitv(self):
result = self.compile(make_snapshot(layout="splitv"), make_snapshot(live=True, layout="splith"))
self.assertEqual(result["operations"][-1]["layout"], "splitv")
def test_root_tabbed(self):
result = self.compile(make_snapshot(layout="tabbed"), make_snapshot(live=True, layout="splith"))
self.assertEqual(result["operations"][-1]["layout"], "tabbed")
def test_root_stacked(self):
result = self.compile(make_snapshot(layout="stacked"), make_snapshot(live=True, layout="splith"))
self.assertEqual(result["operations"][-1]["layout"], "stacked")
def test_child_order_diff_uses_typed_relative_moves(self):
result = self.compile(make_snapshot(order=(1, 2, 3)), make_snapshot(live=True, order=(3, 1, 2)))
self.assertEqual([item["operation"] for item in result["operations"]], ["move-relative"])
self.assertEqual(result["operations"][0]["relation"], "after")
def test_nested_supported_tree(self):
result = self.compile(make_snapshot(nested=True), make_snapshot(live=True))
self.assertEqual(
[item["operation"] for item in result["operations"]],
["set-split-orientation", "move-relative"],
)
def test_nested_unsupported_tree(self):
source = make_snapshot(nested=True)
nested = source["containers"][1]
nested["children"] = [{"kind": "container", "id": "wdeep"}]
source["containers"].append({
"container_id": "wdeep", "layout": "splith", "percent": 1.0,
"children": [{"kind": "window", "id": "w2"}, {"kind": "window", "id": "w3"}],
"floating_children": [],
})
with self.assertRaisesRegex(tree.TreeRestoreError, "unsupported-nested-tree"):
self.compile(source, make_snapshot(live=True))
def test_missing_target_window(self):
with self.assertRaisesRegex(tree.TreeRestoreError, "missing-target-window"):
self.compile(make_snapshot(), make_snapshot(live=True), omit=(3,))
def test_unexpected_live_window(self):
source = make_snapshot(floating_four=True)
live = make_snapshot(live=True, floating_four=True)
live["workspaces"][0]["roots"]["floating"] = []
live["workspaces"][0]["roots"]["tiling"].append({"kind": "window", "id": "l4"})
live["windows"][3]["floating"] = False
with self.assertRaisesRegex(tree.TreeRestoreError, "unexpected-live-window"):
self.compile(source, live)
def test_floating_branch_is_preserved_and_excluded(self):
result = self.compile(make_snapshot(floating_four=True), make_snapshot(live=True, floating_four=True))
self.assertEqual(result["operations"], [])
self.assertNotIn("w4", repr(result["workspaces"][0]["target_tree"]))
def test_percent_is_explicitly_unsupported(self):
source = make_snapshot()
live = make_snapshot(live=True)
live["windows"][0]["percent"] = 0.9
result = self.compile(source, live)
self.assertEqual(result["percent_capability"], "unsupported")
self.assertEqual(result["operations"], [])
def test_determinism(self):
source = make_snapshot(layout="stacked", order=(1, 2, 3))
live = make_snapshot(live=True, layout="splitv", order=(3, 2, 1))
self.assertEqual(self.compile(source, live), self.compile(copy.deepcopy(source), copy.deepcopy(live)))
def test_hostile_values_are_rejected(self):
with self.assertRaisesRegex(tree.TreeRestoreError, "invalid-operation-layout"):
tree.validate_operation({
"operation": "set-container-layout", "workspace": "8", "source_window": "w1",
"layout": "tabbed; exec evil",
})
with self.assertRaisesRegex(tree.TreeRestoreError, "invalid-operation-workspace"):
tree.validate_operation({
"operation": "move-relative", "workspace": "8; exec evil", "source_window": "w1",
"relation": "after", "reference_window": "w2",
})
def test_runtime_tree_drift_stops_before_second_command(self):
source = make_snapshot(layout="tabbed")
live_before = make_snapshot(live=True, layout="splith")
live_after_first = make_snapshot(live=True, layout="tabbed")
live_drifted = make_snapshot(live=True, layout="tabbed", order=(2, 1, 3))
states = iter((live_before, live_after_first, live_drifted))
commands = []
class Planner:
@staticmethod
def match_windows(saved, candidates):
return {str(saved[0]["window_id"]): {"confidence": "exact", "live_index": 0}}, []
compilation = {
"operations": [
{"operation": "set-container-layout", "workspace": "8", "source_window": "w1", "layout": "tabbed"},
{"operation": "set-container-layout", "workspace": "8", "source_window": "w1", "layout": "stacked"},
]
}
with self.assertRaisesRegex(executor.RestoreExecutionError, "RESTORE_TREE_DRIFTED"):
executor._execute_tree_compilation(
source,
make_plan(source, live_before),
compilation,
{"w1": 101, "w2": 102, "w3": 103},
collector=lambda: copy.deepcopy(next(states)),
planner_module=Planner,
tree_module=tree,
runner=lambda argv, _timeout: commands.append(list(argv)) or SimpleNamespace(
returncode=0, stdout='[{"success":true}]', stderr=""
),
monotonic=lambda: 0.0,
sleeper=lambda _delay: None,
)
self.assertEqual(len(commands), 1)
if __name__ == "__main__":
unittest.main()