diff --git a/docs/dev/ci.md b/docs/dev/ci.md index e08bccba..14f7990c 100644 --- a/docs/dev/ci.md +++ b/docs/dev/ci.md @@ -70,15 +70,17 @@ installs nextest on the Windows runners, which have no nix, and `windows.yaml` ( | format | `fmt`, `checkSets.fmt` | the same | the same | | clippy | `lint` (cross build) | `lint` | `macos-hosted` only (`macos-lint`); `mac-typecheck` type-checks on Linux | | unit tests | `windows-native-test` on Windows, and `windows.yaml` on every event | `test` (`linux-test`) | `macos-hosted` only | -| integration | none (the guest is experimental: `windows-guest.yaml`) | `vm-fast`, `vm-slow` | none | +| integration | none (the guest is experimental: `windows-guest.yaml`) | `vm-fast`, `vm-slow` | none in CI; by hand on a Mac: `nix run .#e2e-macos` (a Tart guest, `e2e/platforms/tart_macos`) | | options and schemas | `linux-options`, `macos-options` on Linux; the Windows schemas are checked by the Windows e2e Python | `linux-options` | `macos-options` on Linux; the schemas by `macos-hosted` | The gaps: - No Windows e2e: the guest needs the evaluation ISO and about 20 minutes of Windows Setup, and nothing says a hosted runner can do it. - macOS runs on every change to its paths, on `macos-latest` (Apple silicon, the latest macOS, the only one supported): standard hosted runners are free and - unlimited on a public repository. Nothing starts the daemon there, so tiling on macOS is not exercised; whether a hosted runner has a GUI session with the - Accessibility and Screen Recording permissions is not known. + unlimited on a public repository. Nothing starts the daemon there, so tiling on macOS is not exercised in CI; whether a hosted runner has a GUI session with the + Accessibility and Screen Recording permissions is not known. The shared e2e scenarios do run on macOS, by hand, on a Mac (`nix run .#e2e-macos`, + `e2e/platforms/tart_macos/README.md`): a hosted runner is itself a virtual machine and cannot start a macOS guest, and a guest runs on Apple + hardware only, two at a time, so CI would need a Mac of its own. - The nightly rustfmt of `fmt` is the nightly of the day; `checkSets.fmt` in stage 1 uses the one the flake pins. When only `fmt` fails, the nightly moved. - `mac-typecheck` takes the std of `aarch64-apple-darwin` from the toolchain of the runner (`rustup target add`), because the dev shell of the flake only has the Windows target. clippy could run the same way (`cargo clippy --target aarch64-apple-darwin`); `tools/mac-check.sh` does not. diff --git a/docs/dev/e2e-to-unit.md b/docs/dev/e2e-to-unit.md index b9e1de7c..07d1238b 100644 --- a/docs/dev/e2e-to-unit.md +++ b/docs/dev/e2e-to-unit.md @@ -59,9 +59,11 @@ times (Windows `komorebi/src/window.rs:1178`, KDE `window.rs:661`, macOS `window | lifecycle (7), lifecycle_hidden (13), locking (7), handover (6), fullscreen (6), display_change (4), focus_follows_mouse (7) | stop/start restores windows, screen lock, minimize/unmanage/manage, fullscreen, mode change, pointer focus | A for hide/restore: `container_behaviour.rs` `restoring_*`, `workspace_layers.rs` `hiding_a_workspace_*`; the process, session, OS events and pointer are C | - | - | - | | Windows extras: bar (21), windows_padding, cloaking, config_file, tracking, second_display, display_change | bar process and window, 7 px border, cloaking, file watch, system focus setting, QXL display | bar schema: A (`crates/komorebi-bar-core/tests/schema_oracle.rs`); rest C | - | - | - | -## macOS-origin behaviour (never e2e-tested): what the shared tests now give +## macOS-origin behaviour: what the shared tests give besides the e2e -macOS has no e2e. The shared-crate tests that exist protect its code path as far as the hooks do: +The shared e2e scenarios run on macOS in a Tart guest, by hand on a Mac (`nix run .#e2e-macos`, +`e2e/platforms/tart_macos/README.md`; not in CI). What a macOS guest cannot do (a second display, a +pointer, a lock screen) is still never e2e-tested. The shared-crate tests that exist protect its code path as far as the hooks do: `state_roundtrip.rs` `a_macos_shaped_state_is_written_as_it_was_read_with_the_corner_last`, `a_macos_state_cannot_be_read_without_its_corner`, the `StubWindow` that is `Clone` but not `Copy`, and now `e2e_layouts.rs` (the tiles every macOS tiling goes through). Proposed B tests, platform diff --git a/e2e/platforms/tart_macos/README.md b/e2e/platforms/tart_macos/README.md new file mode 100644 index 00000000..0b5751c7 --- /dev/null +++ b/e2e/platforms/tart_macos/README.md @@ -0,0 +1,74 @@ +# The macOS guest + +The scenarios of the shared core (`e2e/lib/komorebi_vm/core/scenarios`) run against the real +`komorebi` daemon of macOS in a virtual machine, the way they run on KDE (a NixOS test) and on Windows +(`../qemu_windows`). A scenario sees a [`Platform`](../../lib/komorebi_vm/core/platform.py); this +directory is the one for macOS. + +```text +host (Apple silicon) guest (a clone of the golden image) + python -m platforms.tart_macos.run macOS, logged in, one 1280x800 display + Guest ── tart clone / run --no-graphics ──► Terminal (holds the grants) + MacosPlatform ── agent protocol, TCP 7878 ──► komorebi-e2e-agent answers the host + komorebi the daemon under test + komorebi-testwin the windows to tile +``` + +## What a run does + +For each scenario, `Guest` clones the golden image (copy on write, so it costs nothing), boots it +without a window, and waits for the agent, which answers about 20 s after the clone starts. The programs +under test are uploaded through the agent, the daemon is started by it, the shared scenario runs, and +the clone is deleted. `OUT//result.json` names the scenario, the commit and every check, as +the Windows runner's does; a failed run also keeps the daemon's output, the processes and a screenshot. + +## Why it is built this way + +- **Permissions are the hard part.** The daemon needs Screen Recording and Accessibility, which macOS + only grants to a person's click. They are granted once to `Terminal` in the golden image and are + inherited by every clone. The agent is started at login by a launch agent that opens Terminal on it + (`provision.sh`), so the agent, the daemon it spawns and the test windows are all attributed to + Terminal, and no clone asks anything. A signed binary of our own would need a grant per build. +- **The agent is a Rust program** (`crates/komorebi-e2e-agent`, the same crate and protocol as the + Windows agent, with `macos.rs` for the verbs) because the stock image has no developer tools: `swift`, + `clang` and `python3` there are stubs. It reads windows and displays through the accessibility API, as + the daemon does, so a window's frame is what the application says. +- **No SSH at run time.** The vanilla image has `admin`/`admin` and SSH on, which only + `build-golden.sh` uses. +- **The windows that are not the test's are left alone** by an ignore rule the platform adds to every + configuration: the Terminal, and the desktop widgets of the image (Notification Center draws them as + windows, and they are tiled otherwise). + +## Limits + +- **One display.** A macOS guest has one virtual display, so what needs a second one (`hotplug`, + `monitors`, `display_change`, `unknown_monitor`) is skipped with the reason. +- **No input is injected**, so there is no pointer (`focus_follows_mouse`, drags) and no lock screen. +- **At most two guests at once on a host.** Apple's licence and Virtualization.framework both stop a + third, so a host runs two scenarios in parallel at the most. +- **Apple hardware only.** A hosted GitHub runner is itself a virtual machine and cannot start one, so + this does not run in CI: it runs on a Mac you have (see `docs/dev/ci.md`). +- **The wallpaper and the zoom button** are not driven yet. + +## Running it + +```sh +# once: the golden image (30 GB of disk; one manual step, the grants, which the script stops for) +e2e/platforms/tart_macos/build-bins.sh target/e2e-macos +e2e/platforms/tart_macos/build-golden.sh target/e2e-macos + +# every time: this checkout's daemon against the image +nix run .#e2e-macos -- target/e2e-out tiling focus +``` + +`nix run .#e2e-macos` builds the programs under test and brings `tart` (nixpkgs has it as an unfree +package, so it is allowed for that one only). `KOMOREBI_E2E_BINS=dir` runs the programs of `dir` +(`build-bins.sh` makes one), `KOMOREBI_E2E_GOLDEN=name` another image, `KOMOREBI_E2E_SCALE=1.5` makes +every wait longer for a busy host. The image has to be built again when the agent changes, and for a +new macOS. + +The unit tests of the port need no guest and run with those of the Windows one +(`python -m pytest lib/tests platforms/qemu_windows/tests platforms/tart_macos/tests`, in `e2e`, with +`PYTHONPATH=lib:.`; the `windows-e2e-tests` check does it). + +Do not run it alongside a heavy build, which makes the daemon slow to answer and the waits fail.