Skip to content

Run the Avalonia tests headless so the suite completes - #1

Merged
jopdorp merged 1 commit into
jopdorp:linux-gui-portfrom
ledogar:headless-test-isolation
Sep 14, 2026
Merged

jopdorp merged 1 commit into
jopdorp:linux-gui-portfrom
ledogar:headless-test-isolation

Conversation

@ledogar

@ledogar ledogar commented Aug 8, 2026

Copy link
Copy Markdown

First of the PRs offered in #272 — starting here because it's self-contained, and because it makes the other PRs verifiable: right now dotnet test never returns, so there's no way to check a change by running the suite.

Not a macOS change. I found it while evaluating your branch as the base for a macOS port, but it's platform-independent — the same hang happens on Linux.

The problem

Two symptoms, one cause: the suite hangs when run as a whole, and three tests fail even when each class runs on its own.

Each test class guards AppBuilder.Setup behind its own class-local flag, but Setup is once-per-process — so the second class to run throws "Setup was already called". And desktop SetupWithoutStarting never starts a dispatcher loop, so a Dispatcher.UIThread.Invoke from a test waits on a pump that will never run. That's the hang.

The fix

Avalonia.Headless.XUnit exists for exactly this. [AvaloniaTestApplication] sets up one headless application per process, and [AvaloniaFact] runs a test on the headless dispatcher thread so UI work has somewhere to execute. Tests that touch controls or windows are marked [AvaloniaFact]; the rest stay plain [Fact]. The per-class EnsureAvalonia helpers are no longer needed.

It also removes the display requirement, so the suite can run in CI.

Measured on this branch

Before (4af7ef2) hangs past 120s, 3 failures logged on the way
After 39/39, about a second

Unrelated, but worth saying

Your reflective fallback in AvaloniaAudioDrivers.Available() — dropping through to Audio.Driver.Drivers instead of hardcoding the driver list — is what let a macOS Core Audio backend slot in with one ProjectReference and no changes to your code at all. That design decision is why the port took days rather than weeks.

Two more PRs to follow if you want them: the component library in the palette (closes the Phase 3 item at GUI_PORT_PLAN.md:85 — the ~87 parts in Circuit/Components/*.xml are currently unreachable from the editor), and the live-path fixes from the remaining findings.

🤖 Generated with Claude Code

The test suite hangs when run as a whole, and three tests fail even when
each class is run on its own.

Both come from how Avalonia is started for the tests. Each class guards
AppBuilder.Setup behind its own class-local flag, but Setup is
once-per-process, so the second class to run throws "Setup was already
called". And the desktop SetupWithoutStarting never starts a dispatcher
loop, so a Dispatcher.UIThread.Invoke from a test can wait for a pump that
will never run - which is the hang.

Avalonia.Headless.XUnit exists for exactly this: [AvaloniaTestApplication]
sets up one headless application per process, and [AvaloniaFact] runs a
test on the headless dispatcher thread, so UI work has somewhere to run.
Tests that touch controls or windows are marked [AvaloniaFact]; the rest
stay plain [Fact]. The per-class EnsureAvalonia helpers are no longer
needed.

Also means the suite needs no display, so it can run in CI.

Before: hangs past 120s, 3 failures logged on the way.
After:  39/39 in about a second.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants