Commit Graph
59 Commits
Author SHA1 Message Date
LGUG2Z c9ca340d4e feat(e2e): mark the initial workspace rule of the macOS daemon, and mark per scenario
An initial workspace rule applied to a window leaves it on the first workspace as well as putting it
on the one the rule names: after it, the state has the window in the containers of both, and it is
in view at its tile (the daemon's log says it hid and restored it). It is the same on every run, in
the scenario and on its own, so the two checks about it are marked as a known failure of the macOS
daemon.

The marks are of one scenario each now: the check that says a window is put away while another workspace
is in view is a check that passes in rules_exe, and a mark on its words would have said so.
2026-11-06 14:40:50 -08:00
LGUG2Z 8dc6390eb1 fix(e2e): wait for the macOS daemon to take up a command, and for its file watcher
komorebic is done when a command is in the daemon's queue, not when it has run, and a scenario opens a
window right after giving a rule or a name: both are events of the same queue, and the window could be
first. The platform asks for `state` after every command that changes something, which the daemon
answers in the order it got things (the rules scenario failed a different check on each run before,
and its named-workspace checks pass three runs in three now).

The daemon watches its configuration file with FSEvents, and a file written in the first half second
after it answered is never noticed (0 s: not reloaded in 12 s; 0.5 s: reloaded after 2.6 s; later
ones after two or three seconds). A start waits a second for it, as nobody edits a file that fast.
config_reload goes from 6 of 8 checks to 8 of 8.
2026-11-06 14:40:50 -08:00
LGUG2Z 5257e11b8d feat(e2e): what the macOS daemon does differently, marked, and what it does not have, skipped
The first run of the scenarios against a real guest ended with 32 failing checks of 586. A window the
daemon puts away is parked with a sliver showing (fixed in the platform), and the rest are differences of
the macOS daemon, marked on the checks that are about them, with the reason:

- stacks: it stacks by raising the focused window over the others, and never puts the others away
- fullscreen: it has no fullscreen detection, and tiles a window over the whole display
- layout_flip: it tiles 10 px below the top of the work area and 12 px above its bottom
- class: a window has none, the daemon matches the role, which every test window shares

placement is skipped as a whole: the macOS daemon has no initial_window_placement_rules.
2026-11-06 14:40:50 -08:00
LGUG2Z 098b7d011e fix(e2e): a window the daemon parked with a sliver showing is put away
macOS does not let a window go entirely off the screen, so the daemon hides one at the corner of
its display with one pixel of it showing (five for a window that scrolled away). The port
called a window hidden only when no pixel of it was on a display, so it said every window the
daemon had put away was in view. A window is put away now when no more than a sliver of it shows,
in either direction.

Against a real guest this takes workspaces, workspace_moves, monocle, lifecycle_hidden, rules_exe
and startup_rules from failing to passing (18, 18, 22, 23, 5 and 5 checks).
2026-11-06 14:40:50 -08:00
LGUG2Z 18d7f9a506 docs(e2e): how the macOS guest works and how to run it
A README for e2e/platforms/tart_macos: what a run does, why the grants live in the golden image and
are inherited through Terminal, why the agent is a Rust program (the stock image has no developer
tools), and the limits (one display, no input, two guests per host, Apple hardware only, so not in
CI). docs/dev/ci.md and e2e-to-unit.md said macOS had no integration coverage; they now say where it
runs and what it cannot reach.
2026-11-06 14:40:50 -08:00
LGUG2Z fa2742a75d feat(e2e): scripts that build the golden image of the macOS guest and its programs
build-golden.sh makes the golden Tart image from a vanilla one of Cirrus Labs: it provisions the
guest over SSH (provision.sh: the agent starts at login from a Terminal window, no state of earlier
runs is left, Terminal does not reopen old windows), stops for the one step that cannot be
scripted (a person allows Screen Recording and Accessibility for Terminal, once, and the grants live
in every clone), checks that the agent answers, and shuts the guest down from inside, so that
the last writes are on the disk the clones copy. build-bins.sh builds the programs under test for
the host and points the libiconv of the nix store at the system's, as the nix package does.
2026-11-06 14:40:50 -08:00
LGUG2Z 6823d13ddd feat(e2e): a runner for the macOS guest
python -m platforms.tart_macos.run GOLDEN BIN_DIR OUT SCENARIO... gives each scenario a fresh
clone of the golden image, uploads the programs under test, starts the daemon and runs the shared
scenario, on the checks, step loop and result file the Windows runner uses (komorebi_vm.runner).
The scenarios a guest cannot run (a second display, a pointer, a lock screen, a window class) are
listed with the reason and skipped, not failed.

The unit tests of the port need no guest: the ignore rules, which windows count as hidden, the
paths a scenario may ask for, and that every scenario the runner names is a module of the core.

tiling (10 checks) and focus (17) pass against a real guest.
2026-11-06 14:40:50 -08:00
LGUG2Z 9b236b14be feat(e2e): a macOS guest and platform for the shared scenarios
A clone of a golden Tart image is booted without a window and its agent (komorebi-e2e-agent, started
at login from the Terminal that holds the Accessibility and Screen Recording grants) is the only
thing the host talks to: no SSH, no password. MacosPlatform implements the Platform protocol
through it: the windows and displays are what the accessibility API says, the daemon is a program
the agent starts, a window the daemon put away is one off every display, and a screenshot is
screencapture.

A guest has one display and nothing here injects input, so has() says no to a pointer and to the
lock screen, and a second display is refused. The windows of the guest's own programs (the
Terminal, the desktop widgets of the image) are left to themselves by an ignore rule the platform
adds to every configuration.
2026-11-06 14:40:50 -08:00
LGUG2Z 80f221b8e7 refactor(e2e): one host client for the agent of every platform
The host side of the agent spoke a protocol that is the same for every platform, and lived in the
Windows port. Move it to komorebi_vm/agent.py, which the Windows module re-exports so that its
imports stay as they are, and give it the calls the protocol gained: spawn with an environment and
an output file, `status` of a spawned program and `send` to its standard input.
2026-11-06 14:40:49 -08:00
LGUG2Z ceaea8ca1b refactor(e2e): lift what a runner needs of a port out of the Windows runner
RecordedChecks (the checks a runner keeps for its result file), the step loop with its timings
and known failures, the commit under test and the step keyword helper have nothing to do with
Windows. Move them to komorebi_vm/runner.py, with the known-failure tables passed in, so that a
runner of another port uses them instead of importing a sibling port or copying them.

The Windows runner keeps its tables and the names it and its tests use. The unit tests of the core
and of the Windows runner pass unchanged (163), and ruff reports what it did before.
2026-11-06 14:40:49 -08:00
LGUG2Z c234b1d884 build(tools): route the justfile and tools to crates/ 2026-11-05 15:03:27 -08:00
LGUG2Z 3e21ebd3a4 feat(e2e): windows-nextest runs the Windows unit tests in the guest
The Windows crates' unit tests could only run in the (future) hosted Windows
CI. They run in the existing Windows e2e guest: nix run .#windows-nextest
boots the golden image like a scenario (restoring a saved state when
KOMOREBI_E2E_STATE is set), uploads the test executables that the check
windows-nextest-build cross-compiled, runs every test in a process of its own
(nextest.exe is not needed: komorebi's tests share global state, so one
process per test is what nextest does too) and prints a line per executable
and the failures, with OUT/cargo-tests.json for every result.

The driver e2e/platforms/qemu_windows/cargo_tests.py already existed and was
wired to nothing; it now takes the check output (tests.tsv) as well as cargo
messages, creates the saved state itself, prints the panic message of a
failure, and uploads the data files the tests read. Those are found by the
CARGO_MANIFEST_DIR baked into the executables, a path of the Nix build, so
the check also keeps schemas/ and the tests' fixtures and the driver puts them
at that path under C:. Without them 20 tests failed for the missing files.

docs/dev/build-and-test.md and docs/dev/flake-checks.md describe the check,
the app and the neutral crates in linux-nextest.

Verified: nix run .#windows-nextest on the golden image g8 (scale 1.5, state
~/e2e-w0/state-nextest): 964 tests in 37 executables, 964 passed, 0 failed,
202 s with the state restored (first run 163 s more to save the state).
One run only; not soaked. No e2e scenario, the golden image or the existing
e2e-windows behaviour changed.
2026-11-05 15:03:27 -08:00
LGUG2Z 22d2e1ae78 docs: fix the nix attribute names and paths the one flake renamed
Only the commands and paths that named an attribute or file of the old flakes: the kde source
filter list (nix/linux/root-crates.json), the Windows drift check (windows-default), the KWin
effect install checks (linux-*-install), the module evaluation check on a Mac and the VM suites
(legacyPackages.<system>.vm.NAME, which are also the heavyChecks for now), and the Windows e2e
agent package (windows-komorebi-e2e-agent). docs/dev/flake.md has the full table. The history
in docs/dev/unified-binaries.md keeps the names of the time it describes. On the rebase onto
wip-cutover-all the coordinator edits of ARCHITECTURE-UNIFIED.md were kept and these two lines
applied on top.

Verified: not run (documentation).
2026-11-05 15:03:27 -08:00
LGUG2Z f7f444efb6 build(tools): tools, justfile and scripts name the outputs of the one flake
fast-check classifies the paths of the final directories (komorebi-linux, komorebi-macos,
komorebi-hotkeys, nix/{windows,linux,macos}, docs and schemas per platform), runs the Linux
check from the repository root and evaluates one check and one package per platform in the
root flake; kde-closure-check reads nix/linux/root-crates.json and compares it with the cargo
tree of the Linux packages (the listed crates besides komorebi-linux, komorebi-hotkeys and
komorebi-daemon); mac-check.sh drops the nested-workspace branch; e2e-needed treats nix/windows,
nix/workspace-src.nix, nix/lib.nix and nix/common.nix as fixed inputs of the Windows binaries
and the Linux and macOS trees as outside them; build-ref.sh builds `windows-NAME` for a REF with
the one-flake layout and keeps the old names for an older REF with its own flake. The justfile
names linux-nix-options-validation and runs a VM suite as legacyPackages.<system>.vm.NAME
(the vm-* checks are heavyChecks for now). scripts/linux/{run-kde,vm-mac,sync-e2e-core,
scenario-status} find the repository root and the VM library from scripts/linux/ and nix/linux/
(on the rebase onto wip-cutover-all the coordinator versions of run-kde, vm-mac and
scenario-status were kept: same change).

Not touched: scripts/linux/compare-gen.py, a migration comparison between the three old trees
that still looks for komorebi-for-mac/ (obsolete; open).

Verified: bash -n on the changed scripts; tools/kde-closure-check passes (14 root crates);
tools/fast-check --list classifies this revision as nix-only. fast-check commands for the
platforms were not run.
2026-11-05 15:03:27 -08:00
LGUG2Z e1fc1499d4 test(e2e): the bar scenario lists processes with plain tasklist
The bar scenario counted komorebi-bar.exe processes through 'tasklist /fi "imagename eq komorebi-bar.exe" /fo csv /nh' run via the guest agent, which exits with status 1 there (quoting of the filter), so the process checks failed even though the bar was running and drawing (wgpu). 'tasklist /fo csv /nh' lists the process and the existing filter on lines starting with komorebi-bar.exe keeps the result the same. Found by the candidate 8 Windows gate (gate-cand8.md, bar-fix8.diff: with it the scenario ran 23/0). The file is also reflowed by ruff format (the CONFIG dict), which is formatting only. Verified: ruff format and ruff check on the file; the e2e run in the gate with the one-line change.
2026-11-05 15:03:27 -08:00
LGUG2Z 0a315a61bc test(e2e): the bar scenario's window checks are real again when wgpu draws the bar
The bar now falls back to wgpu (Direct3D 12, WARP) when glow has no OpenGL
context, so in the OpenGL-less golden guest the bar can open a window and the
window, stays-alive, daemon-restart and notify checks run for real. They are
skipped only when BOTH renderers fail, and the skip names both errors
(`skip ...: guest cannot draw: glow: ...; wgpu: ...`, taken from the bar's
"Error: glow: ...; wgpu: ..." line); any other early exit still fails. The
scenario starts the bar with RUST_LOG=info,wgpu_hal=debug,wgpu_core=warn so
bar.out keeps what wgpu says about its adapters on the first run, and prints
which renderer drew the window ("the bar draws with wgpu").

Unit tests: both renderers failing (classify + the error line), a bar that fell
back and runs (the glow warning is not an "Error:"), plus the earlier ones.

Verified: runner and library tests 163 passed; ruff clean on the scenario and
its test. NOT run: needs the win-e2e `bar` gate.
2026-11-05 15:03:26 -08:00
LGUG2Z fa2d630701 test(e2e): the bar scenario says when the guest cannot draw, and the bar config is tested
The Windows gate showed that the bar cannot open a window in the golden
guest (no OpenGL 2.0: "egui_glow requires opengl 2.0+"). The scenario now
reads the bar's own output (`classify`): that exact failure turns the
window, stays-alive and daemon-restart checks into visible
`skipped: guest has no OpenGL 2.0` lines (counted in `result.json` and
printed as `skipped: N` by the runner); the schema equality, "the bar reads
its configuration" (real: the configuration is parsed before the window),
`komorebic state` and `stop --bar` stay checks; any other early exit of
the bar (a configuration error, a panic) is a failure with the bar's
output in the message, and a bar that opens its window gets the full walk as
before. Unit tests for `classify` run with the runner tests.

komorebi-bar-core gets tests/config.rs: windows-only widgets and the `mouse`
block are accepted on every platform, `supported()/unsupported()/enabled()`
name and skip what the platform cannot run (D-coord-1), a Command mouse
message is a plain string and the tagged form is rejected (the e2e finding),
the network widget accepts `default_interface_options` and the old key
names, layouts read from their names, `KomobarConfig::read` fills the frame
and refuses a non-json file. The warning text itself is not asserted (no
tracing subscriber in the core).

docs/dev/bar-e2e.md records the limitation and an analysis of getting
rendering into the guest (wgpu with the WARP adapter recommended; a mingw
Mesa is not offered by nixpkgs); nothing was added.

Verified: runner and library tests (161 passed), ruff clean on the scenario
(three older findings in other files are not mine), bar-core native tests 3
unit + 7 oracle + 6 config. Windows clippy and the macOS check: see the
report. The scenario itself was not run.
2026-11-05 15:03:26 -08:00
LGUG2Z fdd9f0891d test(e2e): the bar scenario's mouse command is a plain string
`MouseMessage::Command(String)` is an untagged variant, so
`"on_middle_click": {"Command": ...}` in the scenario's bar configuration
did not deserialize and the bar exited at start ("data did not match any
variant of untagged enum MouseMessage"). The value is the string itself.

Found by the Windows gate on candidate 5 (win-e2e, S/e2e-win/gate-cand5.md,
bar section; the fix is its bar-fix.diff). Verified: ruff clean; not run.
2026-11-05 15:03:26 -08:00
LGUG2Z f0a0d71b83 test(e2e): a Windows scenario bar for komorebi-bar
New extras scenario e2e/platforms/qemu_windows/extras/bar.py, registered in
run.py as `bar` (runs after the tiling windows, like the other Windows
extras). It starts komorebi-bar in the guest with a configuration that has
storage, time, applications, komorebi workspaces and layout widgets, the
Windows-only Keyboard and Systray widgets and a `mouse` block (a message and
a command), and checks: `komorebi-bar --schema` equals the checked-in
schema.bar.json byte for byte; the process starts and its window titled
komorebi-bar appears, visible and sized; it runs 15 s (scaled); `komorebic
state` answers and komorebi does not tile the bar; commands that notify the
bar work; the bar survives `komorebic stop` and the daemon starting again as
the same process (it reconnects: it does NOT exit when the daemon stops, so
the task text "exits cleanly when komorebi stops" is checked as
`komorebic stop --bar` ending daemon and bar); screenshots are kept as
artefacts, no pixel assertions. No known failures.

Plumbing: run.py uploads komorebi-bar.exe when the binaries directory has it
(other scenarios do not need it, so older build-ref directories still work),
the nix app copies it and sets KOMOREBI_E2E_BAR_SCHEMA to the checked-in
schema (the e2e directory alone does not contain it), build-ref.sh builds it.
README and docs/dev/bar-e2e.md document it.

Verified: py_compile and ruff clean; the runner and library unit tests: 157
passed; flake.nix parses. NOT run: the scenario itself (the Windows guest is
reserved for gates), so every check is untested against a real bar and may
need adjusting (window title, timings, the Systray widget inside the VM).
Needs gate: win-e2e `bar`.
2026-11-05 10:30:05 -08:00
LGUG2Z ff3711dcb5 docs(e2e): document the Windows guest and what it found
README.md: building the image, running scenarios, the variables, the known
failures. W1.md: the measurements and findings behind them.
2026-10-30 18:59:32 -07:00
LGUG2Z 0aec364345 feat(e2e): no known failure of maximizing.user once kfw has the fix 2026-10-30 18:59:32 -07:00
LGUG2Z 503ccbc9c6 feat(e2e): add a probe of toggle-monocle under a user's maximize 2026-10-30 18:49:48 -07:00
LGUG2Z 8cf77519a2 fix(e2e): name toggle-monocle as what maximizing.user still fails 2026-10-30 18:45:15 -07:00
LGUG2Z e5c1e2fa75 feat(e2e): run the cargo tests of the workspace in the Windows guest
The tests are cross-compiled for Windows, so they run there: cargo_tests.py
uploads each test executable, lists its tests and runs every one in a
process of its own, as nextest does. The nix check never ran them
(doCheck is false).
2026-10-30 18:22:45 -07:00
LGUG2Z 345ddf30ea feat(e2e): the known difference of maximizing.user after kfw tracks a user's maximize
On a komorebi with 3fa3d429 the step passes its tracking checks; what is
left is that toggle-float and stack left/right are not ignored while a
window is maximized. The marker says so for such a build.
2026-10-30 18:22:45 -07:00
LGUG2Z b1d053b358 perf(nix): build the test window with the constant stamp too
komorebi-testwin-e2e is the e2e build's test window, so a change to it
rebuilds the crate and not its dependencies (86 s instead of minutes);
build-ref.sh uses it.
2026-10-30 18:22:45 -07:00
LGUG2Z fe95747267 fix(e2e): a temp path may have folders in it and a space in a name
temp_path takes wallpapers/my pictures/cool.png, as the wallpaper scenario
asks for; a name that leaves the folder is still refused.
2026-10-30 18:22:45 -07:00
LGUG2Z a007996f6a feat(e2e): no known-failure marker for a build that tracks a user's maximize
maximizing.user is a known difference only on a komorebi without 3fa3d429
(track a window the user maximizes or restores); on a build that has it
the step runs as any other.
2026-10-30 18:22:45 -07:00
LGUG2Z 9818a1b885 feat(e2e): add a probe of the wallpaper of the Windows guest 2026-10-30 18:22:45 -07:00
LGUG2Z 937430b70e feat(e2e): the wallpaper members and put_file on the Windows guest
put_file writes bytes to a path in the guest (a folder that has a space in
its name is made), wallpaper and set_wallpaper read and set the wallpaper
of a display through IDesktopWallpaper, with the COM calls in C# compiled by
Add-Type (wallpaper.ps1).
2026-10-30 18:22:45 -07:00
LGUG2Z 720306deca feat(e2e): run the columns and two_monitors steps of scrolling 2026-10-30 18:22:45 -07:00
LGUG2Z c51f79acb5 feat(e2e): add a probe of a resize drag from the top row of a frame 2026-10-30 18:22:45 -07:00
LGUG2Z fb549f5685 feat(e2e): say where a resize border is pressed on the Windows guest
The left, right and bottom borders of a window reach outside its visible
frame (the invisible DWM border), so the press is 4 px outside; the top has
none and its border is inside the window, so the press is in its first
rows. Outside the top the press hit the desktop and no drag started.
2026-10-30 18:22:45 -07:00
LGUG2Z 271abc3e9c feat(e2e): record two known differences of kfw as known failures
maximizing.user (a user's maximize is not the maximized window) and
hotplug.user_move (only a drag relocates a window to another monitor).
Their failing checks are known failures with the reason; a step that has
no failing check says the marker can go.
2026-10-30 18:22:45 -07:00
LGUG2Z 6c615cd4a8 fix(e2e): wait for the exit line of a killed daemon before starting another
crash() killed the daemon but not the command that launched it, which wrote
its exit code into the output file after the next launch had started, so
that launch looked as if it had ended and was started again.
2026-10-30 18:22:44 -07:00
LGUG2Z d4d293fbd8 fix(e2e): run a scenario again when QEMU aborted under it
QEMU has died with SIGABRT while a second display was plugged in (a
hotplug repeat and monitors.at_cursor), which is not a result about kfw.
Its stderr is kept, a dead guest is named as such, and the scenario is
run once more on a fresh guest.
2026-10-30 18:22:44 -07:00
LGUG2Z e22d82853c feat(e2e): run kfde's new scenarios on the Windows guest
monitors, layout_flip, title_rule, handover, queries, display_change and
fullscreen, with the extra functions their files have after run
(hotplug.user_move, resize.edge_cases, maximizing.user,
lifecycle_hidden.crash, monocle.focus_behaviour, monitors.at_cursor).
2026-10-30 18:22:44 -07:00
LGUG2Z 320b12b34c feat(e2e): the new Platform members on the Windows guest
spawn takes rename_to and after (the test window renames itself),
temp_path, crash (kills the daemon), fullscreen and user_maximize (the
user's own, by messages the window understands), with tests: a window
that is not there raises, a program name Windows rejects is rejected.
2026-10-30 18:22:44 -07:00
LGUG2Z 09ce64e3e4 test(e2e): test the runner of the Windows guest without a guest
The fix-commit check of the known-failure markers, the scope in the result
file, the step wrapper, the state key and the state that is not JSON.
2026-10-30 18:22:44 -07:00
LGUG2Z 5ef1666053 perf(e2e): key the saved state on the QEMU command line, not guest.py
Any edit to guest.py made a new state (76 to 145 s). The state now follows
the command line of the guest that is saved, with the paths as
placeholders, so an edit to the rest of guest.py does not.
2026-10-30 18:22:44 -07:00
LGUG2Z e440a73666 fix(e2e): a state that is not JSON is an error a wait goes past
komorebic state can print something else while the daemon is busy or in
the middle of a monitor change. The runner crashed on it (a hotplug run
did); now it is a RuntimeError with what was printed, which waits retry.
2026-10-30 18:22:44 -07:00
LGUG2Z da966bfc78 feat(e2e): drop a known-failure marker once the commit under test has the fix
The runner reads the commit from the binaries' commit file and asks git
whether it contains the fix of the bug a marker is for, so the same runner
tests a branch with the bug and one without it.
2026-10-30 18:22:44 -07:00
LGUG2Z a3468e870c feat(e2e): say which display of the Windows guest can change mode 2026-10-30 18:22:44 -07:00
LGUG2Z f0f369f606 feat(e2e): add probes of display modes and of changing one 2026-10-30 18:22:44 -07:00
LGUG2Z 9c5b71a8c3 feat(e2e): add the display_change extra for the Windows guest
With two windows on the second display, its mode is changed to two others
and back: the daemon's monitor follows, the windows stay inside the work
area and apart, and return to where they were. A draft for a shared
scenario.
2026-10-30 18:22:44 -07:00
LGUG2Z be9e6eb36f feat(e2e): change the mode of a display on the Windows guest
display.ps1 lists the display devices and changes the mode of one (the
logic is in C# because PowerShell cannot pass the structs by reference),
and WindowsPlatform.resize_display uses it. Only the QXL display lists
modes; the first one is the basic adapter, which has one.
2026-10-30 18:22:44 -07:00
LGUG2Z 116fadb823 feat(e2e): time the phases of a run
The result file and the log say how long the guest, the upload, the daemon
and the scenario took.
2026-10-30 18:22:44 -07:00
LGUG2Z 994d6565a6 refactor(e2e): run the scenarios of the Windows guest as suite steps
Every scenario goes through run_suite, so its checks are labelled
[shared] and the log says which scenario a check came from. Keyword
arguments go through a small wrapper because a step takes positional
ones.
2026-10-30 18:22:44 -07:00
LGUG2Z 7054d02110 feat(e2e): add what only Windows checks as extras
padding (the border's 7 px), cloaking (and what stop does to it), the
config file watch, the system's window tracking behind focus follows
mouse, and a second display with a window sent to it. Each is run after
the tiling windows as windows_<name>, its checks labelled [windows].
2026-10-30 18:22:44 -07:00
LGUG2Z 2f1df7decd fix(e2e): rewrite_config only writes the file
The daemon watches the configuration file it was started with, so the
extra replace-configuration after each write hid the reload that the
scenario is meant to check.
2026-10-30 18:22:44 -07:00