Browse Source

Stabilize the bookmark-highlight tests against dispatcher stalls

The bookmark navigation tests asserted the one-shot line highlight by
polling the text view's renderer collection, but the adorner
self-dismisses after an ~800 ms lifetime driven by a DispatcherTimer.
On a loaded CI runner (the desktop job runs the UI and decompiler test
suites concurrently) the dispatcher can stall long enough that the
adorner registers and is dismissed again before the test's next
predicate check, so the wait misses the entire play and burns its full
60 s timeout; raising the timeout cannot help with that. Record the
last played line on DecompilerTextView as persistent evidence of the
one-shot highlight and assert that instead - it also pins the highlight
to the expected line, which the presence check never did.

Assisted-by: Claude:claude-fable-5:Claude Code
pull/3964/head
Christoph Wille 1 month ago committed by Siegfried Pammer
parent
commit
0c287f5cde
  1. 32
      ILSpy.Tests/Bookmarks/BookmarkNavigationViewTests.cs
  2. 8
      ILSpy/TextView/DecompilerTextView.axaml.cs

32
ILSpy.Tests/Bookmarks/BookmarkNavigationViewTests.cs

@ -23,8 +23,6 @@ using Avalonia.Headless.NUnit; @@ -23,8 +23,6 @@ using Avalonia.Headless.NUnit;
using Avalonia.Threading;
using Avalonia.VisualTree;
using AvaloniaEdit.Rendering;
using AwesomeAssertions;
using ICSharpCode.ILSpy.AppEnv;
@ -97,11 +95,11 @@ public class BookmarkNavigationViewTests @@ -97,11 +95,11 @@ public class BookmarkNavigationViewTests
await vm.DockWorkspace.WaitForDecompiledTextAsync();
view = await window.WaitForComponent<DecompilerTextView>();
// Wait until the one-shot highlight has registered, then assert without any further delay: the
// adorner self-dismisses after an ~800 ms lifetime, so a fixed-length pump on a loaded CI runner
// can outlast it and observe an empty collection. The same deferred apply also lands the caret.
await Waiters.WaitForAsync(() => view.Editor.TextArea.TextView.BackgroundRenderers
.OfType<LineHighlightAdorner>().Any());
// Wait until the one-shot highlight has played. The adorner itself self-dismisses after an
// ~800 ms lifetime and a stalled CI dispatcher can register and dismiss it inside a single
// pump, so polling the renderer collection can miss the entire play; LastHighlightPlayedLine
// is the view's persistent record of it. The same deferred apply also lands the caret.
await Waiters.WaitForAsync(() => view.LastHighlightPlayedLine != null);
int targetLine = view.GetLineForBookmark(bookmark) ?? -1;
targetLine.Should().BeGreaterThan(1, "the bookmark resolves to a line below the top in the freshly shown document");
@ -110,9 +108,9 @@ public class BookmarkNavigationViewTests @@ -110,9 +108,9 @@ public class BookmarkNavigationViewTests
view.Editor.TextArea.Caret.Line.Should().Be(targetLine,
"bookmark navigation from non-decompiler content must scroll to the saved line");
// P2: the one-shot line highlight is playing on the freshly shown view.
view.Editor.TextArea.TextView.BackgroundRenderers.OfType<LineHighlightAdorner>()
.Should().ContainSingle("the destination line must be highlighted after the content switch");
// P2: the one-shot line highlight played on the freshly shown view, on the right line.
view.LastHighlightPlayedLine.Should().Be(targetLine,
"the destination line must be highlighted after the content switch");
}
// Regression: when the active tab is frozen, navigating to a bookmark in a different node must
@ -160,21 +158,21 @@ public class BookmarkNavigationViewTests @@ -160,21 +158,21 @@ public class BookmarkNavigationViewTests
var activeModel = vm.DockWorkspace.ActiveDecompilerTab;
activeModel.Should().NotBeNull("navigation must surface a decompiler tab");
// Wait until the fresh preview's view exists and its one-shot highlight has registered, then
// assert without any further delay: the adorner self-dismisses after an ~800 ms lifetime, so a
// fixed-length pump on a loaded CI runner can outlast it and observe an empty collection.
// Wait until the fresh preview's view exists and its one-shot highlight has played. The
// adorner itself self-dismisses after an ~800 ms lifetime and a stalled CI dispatcher can
// register and dismiss it inside a single pump, so polling the renderer collection can miss
// the entire play; LastHighlightPlayedLine is the view's persistent record of it.
DecompilerTextView? ActiveView() => window.GetVisualDescendants().OfType<DecompilerTextView>()
.FirstOrDefault(v => ReferenceEquals(v.DataContext, activeModel));
await Waiters.WaitForAsync(() => ActiveView()?.Editor.TextArea.TextView.BackgroundRenderers
.OfType<LineHighlightAdorner>().Any() == true);
await Waiters.WaitForAsync(() => ActiveView()?.LastHighlightPlayedLine != null);
var activeView = ActiveView()!;
int targetLine = activeView.GetLineForBookmark(bookmark) ?? -1;
targetLine.Should().BeGreaterThan(1, "the fresh preview shows System.String with the bookmarked line below the top");
activeView.Editor.TextArea.Caret.Line.Should().Be(targetLine,
"opening a fresh preview for a frozen-tab navigation must still scroll to the bookmark");
activeView.Editor.TextArea.TextView.BackgroundRenderers.OfType<LineHighlightAdorner>()
.Should().ContainSingle("the destination line must be highlighted in the fresh preview");
activeView.LastHighlightPlayedLine.Should().Be(targetLine,
"the destination line must be highlighted in the fresh preview");
}
// Regression: a bookmark re-anchors by token/IL offset, so a decompiler-setting change that

8
ILSpy/TextView/DecompilerTextView.axaml.cs

@ -843,6 +843,13 @@ namespace ICSharpCode.ILSpy.TextView @@ -843,6 +843,13 @@ namespace ICSharpCode.ILSpy.TextView
return false;
}
// The last line the one-shot navigation highlight was played on in this view, or null when
// none has played yet. The adorner itself self-dismisses after its ~800 ms lifetime, so an
// observer polling the renderer collection can miss the entire play when the dispatcher
// stalls (a headless test on a loaded CI runner); this record is the persistent evidence
// that the highlight ran, and where.
internal int? LastHighlightPlayedLine { get; private set; }
void ScrollToLine(int line, Bookmarks.BookmarkViewState? viewState = null)
{
var document = Editor.Document;
@ -862,6 +869,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -862,6 +869,7 @@ namespace ICSharpCode.ILSpy.TextView
RestoreBookmarkFoldings(viewState);
CenterLineInView(document, line);
LineHighlightAdorner.DisplayLineHighlight(Editor.TextArea, line);
LastHighlightPlayedLine = line;
bookmarkMargin?.PulseLine(line);
}, DispatcherPriority.Background);
}

Loading…
Cancel
Save