Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5cff3e9
nixos/test-driver: share graphical helpers
booxter Jul 17, 2026
24a3d82
nixos/tests: use dummy X driver in nspawn
booxter Jul 17, 2026
9c3ae4b
nixos/tests: let LightDM use nspawn seats
booxter Jul 17, 2026
ec40986
nixos/test-driver: export nspawn shell environment
booxter Sep 14, 2026
c8ca2f0
nixos/test-driver: set DISPLAY in nspawn
booxter Sep 14, 2026
e07596c
nixos/test-driver: wait for X in nspawn
booxter Jul 17, 2026
16c90a1
nixos/test-driver: capture nspawn screenshots
booxter Jul 17, 2026
55872d4
nixos/test-driver: implement nspawn keyboard input
booxter Sep 14, 2026
b13c508
nixos/tests/firefox: use machine keyboard interface
booxter Sep 14, 2026
b77f4ba
nixos/tests/firefox: record audio with PulseAudio
booxter Jul 17, 2026
2c50570
nixos/tests/firefox: run with nspawn
booxter Jul 17, 2026
246bfb4
nixos/tests/firefoxpwa: run with nspawn
booxter Jul 17, 2026
1420582
nixos/tests/firefox_decrypt: run with nspawn
booxter Jul 18, 2026
2141140
nixos/test-driver: share graphical display detection
booxter Sep 6, 2026
a867d54
nixos/test-driver: specialize machine configuration
booxter Sep 6, 2026
8de4347
nixos/testing: describe machine display targets
booxter Sep 6, 2026
74f2798
nixos/test-driver: model display exporters and viewers
booxter Sep 6, 2026
d335abc
nixos/testing: configure interactive X11 export through VNC
booxter Sep 6, 2026
474b148
nixos/test-driver: define VNC display viewer
booxter Sep 6, 2026
e029cc2
nixos/test-driver: implement exporting nspawn X11 session over VNC
booxter Sep 6, 2026
64f11cc
nixos/test-driver: document interactive displays
booxter Sep 7, 2026
2a94326
doc/rl-2611: mention nspawn X11 tests
booxter Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions nixos/doc/manual/development/developing-the-test-driver.chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,51 @@ Beyond the test driver itself, its integration into NixOS and Nixpkgs is importa
Finally, we have legacy entrypoints that users should move away from, but are cared for on a best effort basis.
These include `pkgs.nixosTest`, `testing-python.nix` and `make-test-python.nix`.

## Interactive display architecture {#sec-test-driver-display-architecture}

QEMU owns its emulated display hardware and opens its display window directly.
`systemd-nspawn` containers have no corresponding window, so the test driver
uses a pipeline to present their displays during interactive test runs:

```text
machine declaration host presentation

┌────────────────┐ ┌────────────── DisplaySession ──────────────┐
│ display target │ │ │
│ backend: X11 │────▶│ exporter ───▶ endpoint ───▶ viewer │
│ display: :0 │ │ X11 → VNC vnc://… VNC │
└────────────────┘ └────────────────────────────────────────────┘
backend selects exporter protocol selects viewer
```

A **display target** describes a display provided by a machine. Its backend
identifies the display technology, while backend-specific fields identify the
display and any authentication data. Tests normally declare targets through a
shared graphical-test module rather than selecting a presentation mechanism.

A **display exporter** understands one target backend and makes that display
available over a transport protocol. It returns a **display endpoint**, which
contains only the protocol and the URI needed to connect. A **display viewer**
is selected by that protocol and opens the endpoint on the host. A display
session connects these components and owns their asynchronous startup and
shutdown.

Keeping these roles separate means that tests do not choose a transport or a
host application. Exporters and viewers are supplied by the interactive driver
configuration, so regular test runs neither start them nor depend on their
packages. Multiple exporters can also reuse a viewer when they produce the
same protocol.

The current nspawn exporter starts an X11 VNC server inside the container and
relays a host-loopback endpoint into the container's network namespace. The
viewer remains in the host network namespace so it retains access to the
host's graphical session. These details are private to the exporter.

New display backends should fit the same boundary. For example, if an isolated
virtual DRM/KMS device can eventually be passed to the container, a
[ReFrame](https://github.com/AlynxZhou/reframe)-based exporter could expose its
framebuffer over VNC without changing the VNC viewer or test configuration.

## Testing changes to the test framework {#sec-test-the-test-framework}

We currently have limited unit tests for the framework itself. You may run these with `nix-build -A nixosTests.nixos-test-driver`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ back into the test driver command line upon its completion. This allows
you to inspect the state of the VMs after the test (e.g. to debug the
test script).

## Graphical tests {#sec-nixos-test-interactive-graphical}

When a graphical host display is available, QEMU machines open their regular
display window. For `systemd-nspawn` containers, `.driverInteractive` opens a
VNC viewer for each X11 display declared by the test. The viewer shows the same
display used by the test and accepts keyboard and mouse input.

Because the nspawn driver must run as root, preserve the host graphical-session
variables when starting it. For X11, use:

```ShellSession
$ sudo --preserve-env=DISPLAY,XAUTHORITY \
./result/bin/nixos-test-driver
```

For Wayland, use:

```ShellSession
$ sudo --preserve-env=WAYLAND_DISPLAY,XDG_RUNTIME_DIR \
./result/bin/nixos-test-driver
```

If neither `DISPLAY` nor `WAYLAND_DISPLAY` is set, the driver does not open
graphical viewers.

See the [interactive display architecture](#sec-test-driver-display-architecture)
for how display targets, exporters, and viewers are connected.

## Shell access to VMs in interactive mode {#sec-nixos-test-shell-access}

::: {.warning}
Expand Down
6 changes: 5 additions & 1 deletion nixos/doc/manual/development/writing-nixos-tests.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,16 @@ Some advantages of virtual machines over containers are:

- Virtual machines run a separate kernel, which allows testing kernel features
(kernel modules, etc.).
- Virtual machines support testing graphical applications on X11.
- Virtual machines allow testing NixOS modules that use systemd's namespacing options (such as `ProtectSystem=` or `MountAPIVFS=`).
- Virtual machines allow testing [`specialisation`](options.html#opt-specialisation).
(Switching to a specialisation requires the creation of SUID/SGID wrappers, which is disallowed in `systemd-nspawn` within the Nix sandbox.)
- Virtual machines allow the execution of `setuid` binaries.

Both backends support testing graphical applications on X11. Virtual machines
provide emulated display hardware, while containers use a headless X server.
See [running tests interactively](#sec-running-nixos-tests-interactively) for how
their displays are presented during debugging.

Refer to the sections on [QEMU virtual machines](#ssec-nixos-test-qemu-vms)
and [systemd-nspawn containers](#ssec-nixos-test-nspawn-containers) below
for more details on configuring each type of machine.
Expand Down
6 changes: 6 additions & 0 deletions nixos/doc/manual/redirects.json
Original file line number Diff line number Diff line change
Expand Up @@ -2420,6 +2420,9 @@
"sec-running-nixos-tests-interactively": [
"index.html#sec-running-nixos-tests-interactively"
],
"sec-nixos-test-interactive-graphical": [
"index.html#sec-nixos-test-interactive-graphical"
],
"sec-nixos-test-shell-access": [
"index.html#sec-nixos-test-shell-access"
],
Expand Down Expand Up @@ -2447,6 +2450,9 @@
"chap-developing-the-test-driver": [
"index.html#chap-developing-the-test-driver"
],
"sec-test-driver-display-architecture": [
"index.html#sec-test-driver-display-architecture"
],
"sec-test-the-test-framework": [
"index.html#sec-test-the-test-framework"
],
Expand Down
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2611.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@

- The `shell_interact()` function on interactive runs of NixOS VM tests has been deprecated. Use the SSH backdoor instead.

- NixOS tests using `systemd-nspawn` containers now support graphical X11 applications, including window queries, screenshots, and OCR. The [interactive test driver](#sec-running-nixos-tests-interactively) opens each container display in a VNC viewer for observation and input.

- NixOS VM tests now prefer to express durations and timeouts as `datetime.timedelta` values instead of bare numbers. Methods such as `machine.wait_until_succeeds`, `machine.sleep`, `retry`, and `polling_condition` now accept a `timedelta` (e.g., `machine.wait_for_unit("sshd.service", timeout=datetime.timedelta(minutes=1))`). Passing an `int`/`float` as seconds still works but now emits a deprecation warning. Argument names that explicitly defined units were preserved but have had `timedelta` equivalents introduced (`timeout_seconds` → `timeout`, `secs` → `duration`, `seconds_interval` → `interval`).

- `darwin.linux-builder-vz` has been added: a variant of `darwin.linux-builder` that runs the builder guest on Apple's Virtualization.framework via the new `vzvm` package, translating `x86_64-linux` builds with Rosetta instead of emulating them. Apple silicon hosts only. As part of this, the `nixos/modules/profiles/nix-builder-vm.nix` profile has been split into the backend-neutral `nixos/modules/profiles/nix-builder.nix` and a QEMU-specific part. Existing imports of `nix-builder-vm.nix` keep working unchanged.
Expand Down
4 changes: 3 additions & 1 deletion nixos/lib/test-driver/src/test_driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import ptpython.repl
from colorama import Fore, Style

from test_driver.config import load_driver_configuration
from test_driver.debug import Debug, DebugAbstract, DebugNop
from test_driver.driver import Driver, load_driver_configuration
from test_driver.driver import Driver
from test_driver.logger import (
CompositeLogger,
JunitXMLLogger,
Expand Down Expand Up @@ -178,6 +179,7 @@ def main() -> None:
logger=logger,
keep_machine_state=args.keep_machine_state,
debug=debugger,
interactive=bool(args.interactive),
) as driver:
if driver.config.enable_ssh_backdoor:
driver.dump_machine_ssh()
Expand Down
69 changes: 69 additions & 0 deletions nixos/lib/test-driver/src/test_driver/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import datetime as dt
import json
from pathlib import Path
from typing import Literal

from pydantic import BaseModel, Field

DisplayBackend = Literal["x11"]
DisplayProtocol = Literal["vnc"]


class X11DisplayTargetConfiguration(BaseModel):
backend: DisplayBackend
display: str = ":0"
xauthority: Path = Path("/root/.Xauthority")
Comment on lines +11 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm not sure the default values here are a good idea. I checked, and the surrounding nix code that generates this config should always embed these values. Including defaults here just feels like a recipe for getting bit by a typo or something that causes the surround nix code to stop providing them. Perhaps comments with example value(s) would be better?



DisplayTargetConfiguration = X11DisplayTargetConfiguration


class VncDisplayViewerConfiguration(BaseModel):
kind: Literal["vnc"]
executable: Path


DisplayViewerConfiguration = VncDisplayViewerConfiguration


class NspawnX11VncExporterConfiguration(BaseModel):
kind: Literal["x11-vnc"]
server: Path
relay: Path
Comment on lines +31 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what server and relay are. This might make sense when I read more of this PR, but the commit that introduced it ("nixos/test-driver: model display exporters and viewers") doesn't make sense by itself.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't made it far enough in this PR yet to read the code that actually sets/reads this relay value, but I've seen mentions of nsenter and socat in code I have not read yet.

Is it possible to avoid the tunneling and instead do something with a socket that we share through a bind mount with the container?



NspawnDisplayExporterConfiguration = NspawnX11VncExporterConfiguration


class MachineConfiguration(BaseModel):
name: str
start_script: Path


class QemuMachineConfiguration(MachineConfiguration):
pass


class NspawnMachineConfiguration(MachineConfiguration):
display_targets: list[DisplayTargetConfiguration] = Field(default_factory=list)
display_exporters: dict[DisplayBackend, NspawnDisplayExporterConfiguration] = Field(
default_factory=dict
)


class DriverConfiguration(BaseModel):
vms: dict[str, QemuMachineConfiguration]
containers: dict[str, NspawnMachineConfiguration]
display_viewers: dict[DisplayProtocol, DisplayViewerConfiguration] = Field(
default_factory=dict
)
vlans: list[int]
global_timeout: dt.timedelta
enable_ssh_backdoor: bool
test_script: Path


def load_driver_configuration(file_path: str) -> DriverConfiguration:
with open(file_path) as file:
data = json.load(file)
return DriverConfiguration.model_validate(data)
Loading
Loading