diff --git a/e2e/lib/komorebi_vm/core/scenarios/lifecycle.py b/e2e/lib/komorebi_vm/core/scenarios/lifecycle.py new file mode 100644 index 00000000..24ac6fa0 --- /dev/null +++ b/e2e/lib/komorebi_vm/core/scenarios/lifecycle.py @@ -0,0 +1,42 @@ +"""The daemon stops, its windows stay, and a daemon that starts again takes them up.""" + +from komorebi_vm.core.checks import Checks +from komorebi_vm.core.platform import Client, Platform + + +def run(platform: Platform, checks: Checks, clients: list[Client]) -> None: + """Stops the daemon and starts it again, with `clients` open, tiled windows of the first + workspace. + + What the daemon knew about workspaces is not asked for after the start, only that every window + that was in view is managed and tiled again. + """ + seen = [c for c in clients if not platform.hidden(c)] + platform.komorebic("stop") + checks.check( + "the daemon stops answering", + platform.wait_for(lambda: platform.komorebic_status("state")[0] != 0, timeout=20), + ) + checks.check( + "every window is still there", + all(platform.os_window(c) is not None for c in clients), + ) + + platform.start_komorebi() + checks.check( + "the daemon manages every window that was in view", + platform.wait_for(lambda: platform.state().managed() >= len(seen), timeout=30), + ) + area = platform.work_area() + frames = [w.frame for c in seen if (w := platform.os_window(c)) is not None] + checks.check("... they all have a frame", len(frames) == len(seen)) + checks.check("... inside the work area", all(f.inside(area) for f in frames)) + checks.check( + "... and not on each other", + not any(a.overlaps(b) for i, a in enumerate(frames) for b in frames[i + 1 :]), + ) + fresh = platform.spawn("after-the-restart") + checks.check( + "a window opened now is managed too", + platform.wait_for(lambda: platform.state().workspace_of(fresh.title) is not None), + ) diff --git a/e2e/lib/tests/fake_platform.py b/e2e/lib/tests/fake_platform.py index 4068e225..2c728707 100644 --- a/e2e/lib/tests/fake_platform.py +++ b/e2e/lib/tests/fake_platform.py @@ -66,6 +66,7 @@ class FakePlatform: self.display_count = 2 self.display_of: dict[str, int] = {} self.forgets_displays = False + self.running = True self.moves = moves self.focus_follows_move = focus_follows_move @@ -92,7 +93,14 @@ class FakePlatform: return bool(predicate()) def start_komorebi(self) -> None: - pass + """A new daemon knows nothing: every window is tiled on the first workspace.""" + everyone = [t for containers in self.spaces for stack in containers for t in stack] + self.spaces = [[[t] for t in everyone], [], []] + self.focuses = [0, 0, 0] + self.current = 0 + self.monocle = None + self.running = True + self._relayout() @property def containers(self) -> list[list[str]]: @@ -120,6 +128,8 @@ class FakePlatform: self.spaces[self.rules[title]].append([title]) else: self.containers.append([title]) + if len(self.containers) > len(self.tiles): + self._relayout() return Client(title) def _find(self, title: str) -> tuple[int, int]: @@ -202,6 +212,8 @@ class FakePlatform: def komorebic_status(self, *args: str) -> tuple[int, str]: """The fake's answer as a real `komorebic` gives it: exit 0, or 2 for what it lacks.""" + if not self.running: + return 1, "the daemon is not running" if args[:1] == ("state",) or " ".join(args) in PRINTED: return 0, "{}" try: @@ -212,6 +224,8 @@ class FakePlatform: def komorebic(self, *args: str) -> str: n = len(self.containers) match args: + case ("stop",): + self.running = False case ("focus", "right"): self.focus = min(self.focus + 1, n - 1) case ("focus", "left"): diff --git a/e2e/lib/tests/test_focus_moving.py b/e2e/lib/tests/test_focus_moving.py index 0d2d008f..86d09479 100644 --- a/e2e/lib/tests/test_focus_moving.py +++ b/e2e/lib/tests/test_focus_moving.py @@ -6,6 +6,7 @@ from komorebi_vm.core.scenarios import ( commands, focus, hotplug, + lifecycle, monocle, moving, resize, @@ -109,6 +110,28 @@ def test_a_daemon_that_is_right_passes_closing() -> None: assert checks.passed > 4 +def test_a_daemon_that_is_right_passes_lifecycle() -> None: + platform = FakePlatform() + checks = Checks() + clients = opened(platform, checks) + lifecycle.run(platform, checks, clients) + assert checks.failed == 0, checks.failures + assert checks.passed > 5 + + +def test_a_daemon_that_does_not_take_its_windows_up_again_fails_lifecycle() -> None: + class Forgetful(FakePlatform): + def start_komorebi(self) -> None: + self.running = True + self.spaces = [[], [], []] + + platform = Forgetful() + checks = Checks() + clients = opened(platform, checks) + lifecycle.run(platform, checks, clients) + assert checks.failed > 0 + + def test_a_command_the_port_lacks_is_skipped_by_name() -> None: platform = FakePlatform() checks = Checks()