Skip to content

Bug: Remove SlowLayout test category — tests hang due to ForceRenderTimerTick deadlock and over-broad tagging, not inherent headless limitations #238

Description

@JoshuaRowePhantom

Summary

Tests tagged [Trait("Category", "SlowLayout")] are excluded from the fast test suite with the comment "can hang in headless environments." Investigation shows the hangs are caused by specific fixable bugs, not fundamental headless limitations. ~60–70% of SlowLayout tests don't touch the visual tree at all and were swept into the category by bulk class-level tagging. The category and its filter should be eliminated.

Root Cause 1 — ForceRenderTimerTick() Self-Deadlock

The primary hang is in MainWindowIntegrationTests.cs:

window.Show();
for (var i = 0; i < 10; i++)
{
    Dispatcher.UIThread.RunJobs();
    AvaloniaHeadlessPlatform.ForceRenderTimerTick(); // ← deadlock
}

[AvaloniaFact] tests run on the UIThread. ForceRenderTimerTick() posts a synthetic render tick and synchronously waits for the UIThread to process it — but the UIThread is already blocked inside that call. Classic re-entrant deadlock with no nested message pump to escape it.

Fix: Write a WaitForLayoutAsync(Window) helper (alongside the existing WaitForWorkspaceTabAsync / WaitForAgentReadyAsync helpers) and replace every ForceRenderTimerTick loop with a call to it:

internal static Task WaitForLayoutAsync(Window window, TimeSpan? timeout = null)
{
    var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
    EventHandler? handler = null;
    handler = (_, _) => { window.LayoutUpdated -= handler; tcs.TrySetResult(); };
    window.LayoutUpdated += handler;
    var wait = tcs.Task;
    if (timeout is { } t) wait = wait.WaitAsync(t);
    return wait;
}

Root Cause 2 — Over-Broad Class-Level Tagging

A single bulk commit tagged entire test classes with [Trait("Category", "SlowLayout")], sweeping in ViewModel-only tests that have no visual-tree dependency and run fine without Avalonia at all. Examples:

  • CloneEntityShortcutHandlerTests — no visual tree
  • WebViewModelTests — no visual tree
  • ReviewWorktreeShortcutHandlerTests — no visual tree
  • WorkspaceGuiContextProviderTests — no visual tree
  • AgentChatOutputControlTests — uses HeadlessControllableBrowser stub, no real window

These tests use [AvaloniaFact] only because the class was bulk-tagged, not because they need it. Downgrading them to [Fact] removes them from the PerAssembly Avalonia domain and cuts startup pressure.

Three Categories of Tests Currently in SlowLayout

Class Examples Genuinely needs visual tree?
Genuine layout tests MainWindowIntegrationTests, AgentGuiMainWindowIntegrationTests window.Show()
ViewModel-only (swept in by bulk tagging) CloneEntityShortcutHandlerTests, WebViewModelTests
Control tests with stub browser AgentChatOutputControlTests

Required Fixes

Fix A — Replace ForceRenderTimerTick() with WaitForLayoutAsync

Write the WaitForLayoutAsync(Window) helper and replace all ForceRenderTimerTick loop call sites with it.

Fix B — Downgrade ViewModel-only tests from [AvaloniaFact] to [Fact]

For any SlowLayout-tagged test that does not call window.Show() or manipulate the visual tree, remove [Trait("Category", "SlowLayout")] and change [AvaloniaFact][Fact] / [AvaloniaTheory][Theory].

End State

After fixes A and B, the genuine layout tests no longer hang and run correctly in the headless environment. The Category!=SlowLayout filter in run-tests.ps1 fast mode should be deleted — no separate mode is needed. The SlowLayout category is removed from all tests.

Affected Files

  • Phantom.Workspaces.Tests\MainWindowIntegrationTests.cs — add WaitForLayoutAsync helper, replace ForceRenderTimerTick loop
  • Phantom.Workspaces.Tests\AgentGuiMainWindowIntegrationTests.cs — review for same pattern
  • All test files with bulk class-level [Trait("Category", "SlowLayout")] on ViewModel-only tests — Fix B
  • scripts\run-tests.ps1 — delete Category!=SlowLayout filter from fast mode

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfailed-verificationBug failed automated verification

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions