Browse Source

Remove UI tests that only restate the code they cover

These fixtures were written while porting to Avalonia, as an author's own
verification step rather than as coverage: reflection asserting that a type
derives from its base and that a property has the type it is declared with;
literals (MinHeight 29, Padding 3, MaxWidth 900) copied out of the .axaml
beside them; a property override asserted only so pane descendants stay
reachable from tests. None of them can fail except when someone deliberately
edits the line they mirror, and then they fail as a chore.

StartupPerfTests keeps its two [Explicit] benchmarks, which print per-phase
timings worth reading. The third was a wall-clock assertion (8 CoreLib copies
must settle in under 15s) that ran in CI, where a shared runner decides the
verdict; as [Explicit] it would be strictly dominated by the 200-assembly
benchmark it was derived from, so it goes.

Two fixtures are trimmed rather than deleted, because their kernel is real:
XmlDocLoader's ref-pack fallback has no other test in the repo, and the
MenuIcon metadata rasterisation was dropped once during the port already.
Both now assert that without booting MainWindow to reach it.

This is worth about two seconds - it buys reviewers less to read, not CI
less to do.

Assisted-by: Claude:claude-opus-5:Claude Code
pull/3980/head
Siegfried Pammer 1 month ago committed by Siegfried Pammer
parent
commit
76afcae7d0
  1. 17
      ILSpy.Tests/Bookmarks/BookmarksPaneStructureTests.cs
  2. 51
      ILSpy.Tests/Diagnostics/StartupPerfTests.cs
  3. 62
      ILSpy.Tests/Docking/ContentPageHierarchyTests.cs
  4. 29
      ILSpy.Tests/Editor/DocumentationRendererTooltipWidthTests.cs
  5. 65
      ILSpy.Tests/Editor/XmlDocumentationTests.cs
  6. 32
      ILSpy.Tests/MainWindow/MainMenuIconTests.cs
  7. 47
      ILSpy.Tests/MainWindow/ToolPaneDeferredContentTests.cs

17
ILSpy.Tests/Bookmarks/BookmarksPaneStructureTests.cs

@ -26,7 +26,6 @@ using AwesomeAssertions; @@ -26,7 +26,6 @@ using AwesomeAssertions;
using ICSharpCode.ILSpy.Bookmarks;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.Views.Controls;
using NUnit.Framework;
@ -35,22 +34,6 @@ namespace ICSharpCode.ILSpy.Tests.Bookmarks; @@ -35,22 +34,6 @@ namespace ICSharpCode.ILSpy.Tests.Bookmarks;
[TestFixture]
public class BookmarksPaneStructureTests
{
[AvaloniaTest]
public void Toolbar_uses_main_toolbar_chrome_and_button_content()
{
var pane = new BookmarksPane();
var toolbarBorder = pane.FindControl<Border>("ToolbarBorder")!;
var toolbarRoot = pane.FindControl<StackPanel>("ToolbarRoot")!;
toolbarBorder.BorderThickness.Should().Be(new Avalonia.Thickness(0, 0, 0, 1));
toolbarBorder.MinHeight.Should().Be(29);
toolbarBorder.Padding.Should().Be(new Avalonia.Thickness(3));
toolbarRoot.Children.OfType<Separator>().Should().HaveCount(2);
toolbarRoot.Children.OfType<Button>().Should().AllSatisfy(button => {
button.Content.Should().BeOfType<GrayscaleAwareImage>();
});
}
[AvaloniaTest]
public void Module_column_shows_module_name_with_full_path_tooltip()
{

51
ILSpy.Tests/Diagnostics/StartupPerfTests.cs

@ -148,57 +148,6 @@ public class StartupPerfTests @@ -148,57 +148,6 @@ public class StartupPerfTests
}
}
/// <summary>
/// CI-runnable variant of <see cref="BindTree_With_Large_AssemblyList_Reports_Phase_Timings"/>.
/// Same shape but with a much smaller assembly count (8 vs 200) so it can run inside the
/// regular suite — catches order-of-magnitude regressions in the open + load pipeline
/// without the 30+ second cost of the full benchmark. NOT [Explicit] on purpose.
/// </summary>
[AvaloniaTest]
public async Task BindTree_With_Small_AssemblyList_Settles_In_Reasonable_Time()
{
const int Copies = 8;
var tempDir = Path.Combine(Path.GetTempPath(), "ILSpy.PerfTest.CI", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
try
{
var sourcePath = typeof(object).Assembly.Location;
var clones = new string[Copies];
for (int i = 0; i < Copies; i++)
{
clones[i] = Path.Combine(tempDir, $"copy{i:D2}.dll");
File.Copy(sourcePath, clones[i]);
}
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var baselineCount = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Length;
var swTotal = Stopwatch.StartNew();
foreach (var path in clones)
vm.AssemblyTreeModel.AssemblyList!.OpenAssembly(path);
await Waiters.WaitForAsync(
() => vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Length >= baselineCount + Copies,
timeout: TimeSpan.FromSeconds(60));
swTotal.Stop();
// Relaxed CI threshold — 8 assemblies should never take more than 15s, even on a
// shared CI runner. A 10× regression of the open-and-settle pipeline trips this.
swTotal.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(15),
"opening + settling 8 LoadedAssembly entries should complete in well under 15s");
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Length
.Should().BeGreaterThanOrEqualTo(baselineCount + Copies);
}
finally
{
try
{ Directory.Delete(tempDir, recursive: true); }
catch { /* cleanup must never fail */ }
}
}
const int ResponsivenessAssemblyCount = 200;
[Explicit("Perf benchmark — emits dispatcher-latency stats for manual inspection")]

62
ILSpy.Tests/Docking/ContentPageHierarchyTests.cs

@ -1,62 +0,0 @@ @@ -1,62 +0,0 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using AwesomeAssertions;
using ICSharpCode.ILSpy.ViewModels;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests;
/// <summary>
/// Locks the single inner-content hierarchy: every viewmodel that may occupy a
/// <see cref="ContentTabPage"/>'s Content slot derives from the one <see cref="ContentPageModel"/>
/// base, and <c>ContentTabPage.Content</c> is typed to it (not <c>object</c>). Guards against a
/// future content type sneaking back in as a bare ObservableObject or the slot weakening to object.
/// </summary>
[TestFixture]
public class ContentPageHierarchyTests
{
[Test]
public void All_Four_Content_Types_Derive_From_ContentPageModel()
{
typeof(ICSharpCode.ILSpy.TextView.DecompilerTabPageModel).Should().BeAssignableTo<ContentPageModel>();
typeof(MetadataTablePageModel).Should().BeAssignableTo<ContentPageModel>();
typeof(ICSharpCode.ILSpy.Compare.CompareTabPageModel).Should().BeAssignableTo<ContentPageModel>();
typeof(ICSharpCode.ILSpy.Options.OptionsPageModel).Should().BeAssignableTo<ContentPageModel>(
"OptionsPageModel must join the content hierarchy, not stay a bare ObservableObject");
}
[Test]
public void ContentTabPage_Content_Is_Typed_To_ContentPageModel()
{
typeof(ContentTabPage).GetProperty(nameof(ContentTabPage.Content))!.PropertyType
.Should().Be(typeof(ContentPageModel), "the Content slot must be strongly typed, not object");
}
[Test]
public void IsStaticContent_Is_A_Single_Inherited_Member()
{
// One IsStaticContent, declared on the base -- not duck-typed across unrelated classes.
typeof(ContentPageModel).GetProperty(nameof(ContentPageModel.IsStaticContent))
.Should().NotBeNull("IsStaticContent lives on ContentPageModel");
typeof(ICSharpCode.ILSpy.Options.OptionsPageModel).GetProperty("IsStaticContent")!.DeclaringType
.Should().Be(typeof(ContentPageModel), "OptionsPageModel must inherit IsStaticContent, not redeclare it");
}
}

29
ILSpy.Tests/Editor/DocumentationRendererTooltipWidthTests.cs

@ -37,14 +37,11 @@ using NUnit.Framework; @@ -37,14 +37,11 @@ using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.TextView;
/// <summary>
/// Pins the rendering shape of method-signature tooltips so a long signature isn't
/// clipped horizontally. Two failure modes the original implementation suffered from:
/// (1) the signature <see cref="SelectableTextBlock"/> was created with
/// <see cref="TextWrapping.NoWrap"/>, so text past the popup's MaxWidth was simply cut
/// off (no horizontal scrolling because the outer ScrollViewer disables it);
/// (2) the popup MaxWidth defaulted to a snug 600px, narrower than most real C#
/// generic-method signatures. WPF parity is wrap-friendly, so we mirror that — these
/// tests fail if either property regresses.
/// Pins the rendering shape of method-signature tooltips so a long signature isn't clipped
/// horizontally: the signature <see cref="SelectableTextBlock"/> must wrap. Created with
/// <see cref="TextWrapping.NoWrap"/>, text past the popup's MaxWidth is simply cut off,
/// because the outer ScrollViewer disables horizontal scrolling. WPF parity is
/// wrap-friendly, so we mirror that.
/// </summary>
[TestFixture]
public class DocumentationRendererTooltipWidthTests
@ -78,20 +75,4 @@ public class DocumentationRendererTooltipWidthTests @@ -78,20 +75,4 @@ public class DocumentationRendererTooltipWidthTests
"the signature must wrap inside the popup — without wrapping a long signature is "
+ "clipped at the outer MaxWidth because the ScrollViewer disables horizontal scrolling");
}
[AvaloniaTest]
public void CreateView_Default_MaxWidth_Is_Generous_Enough_For_Typical_Signatures()
{
// 600 was too narrow — generic methods + ref-struct parameters routinely exceed it.
// Pin the bumped default so a future tweak doesn't silently shrink it back.
var renderer = new DocumentationRenderer(
new CSharpAmbience(),
new FontFamily("Consolas, Menlo, Monospace"),
12);
var view = (Border)renderer.CreateView();
view.MaxWidth.Should().BeGreaterThanOrEqualTo(900,
"the popup's outer MaxWidth must be wide enough that most realistic method "
+ "signatures fit without aggressive wrapping");
}
}

65
ILSpy.Tests/Editor/XmlDocumentationTests.cs

@ -16,68 +16,47 @@ @@ -16,68 +16,47 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpy.Views;
using ICSharpCode.ILSpyX;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.TextView;
/// <summary>
/// Diagnoses whether the XML-documentation lookup that backs the decompiler-view hover
/// tooltip actually surfaces non-empty docs for a well-documented system method. The
/// renderer + wiring (<c>DocumentationRenderer</c>,
/// <c>DecompilerTextView.BuildHoverContent</c>, <c>AppendXmlDocumentation</c>) are
/// already in place; this test verifies the underlying
/// <see cref="ICSharpCode.Decompiler.Documentation.XmlDocLoader.LoadDocumentation"/>
/// path produces a real doc string for at least one ubiquitous CoreLib method.
/// The XML-documentation lookup behind the decompiler view's hover tooltip. On modern .NET
/// the entity's metadata-token-bearing assembly (System.Private.CoreLib.dll) is not the one
/// whose XML carries its docs (System.Runtime.xml), so <see cref="XmlDocLoader"/> falls back
/// to the parallel ref pack - <c>&lt;dotnet&gt;/packs/Microsoft.NETCore.App.Ref/&lt;version&gt;/ref/&lt;tfm&gt;/*.xml</c>
/// - and aggregates every XML there into one provider. Without that fallback every tooltip
/// over a BCL member renders empty.
/// </summary>
[TestFixture]
public class XmlDocumentationTests
{
[AvaloniaTest]
public async Task XmlDocLoader_Surfaces_Documentation_For_CoreLib_String_Concat()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
// The ID string the decompiler produces for the two-argument overload; using the literal
// keeps the test off the type system, which is not what is under test here.
const string StringConcatId = "M:System.String.Concat(System.String,System.String)";
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(coreLibName, "System", "System.String");
stringNode.IsExpanded = true;
var concatNode = stringNode.Children.OfType<MethodTreeNode>()
.First(m => m.MethodDefinition.Name == "Concat");
var concat = concatNode.MethodDefinition;
Assert.That(concat, Is.Not.Null);
Assert.That(concat!.ParentModule, Is.Not.Null);
Assert.That(concat.ParentModule!.MetadataFile, Is.Not.Null);
[Test]
public void XmlDocLoader_Surfaces_Documentation_For_CoreLib_String_Concat()
{
var coreLib = new AssemblyList().OpenAssembly(typeof(object).Assembly.Location)
.GetMetadataFileOrNull();
coreLib.Should().NotBeNull();
// XmlDocLoader's modern-.NET fallback (added in the shared decompiler library) walks
// the parallel ref pack — <dotnet>/packs/Microsoft.NETCore.App.Ref/<version>/ref/<tfm>/*.xml
// — and aggregates every XML there into a single provider, since each entity's
// metadata-token-bearing assembly (System.Private.CoreLib.dll) differs from the one
// whose XML carries its docs (System.Runtime.xml).
var provider = XmlDocLoader.LoadDocumentation(concat.ParentModule.MetadataFile!);
var provider = XmlDocLoader.LoadDocumentation(coreLib!);
((object?)provider).Should().NotBeNull(
"XmlDocLoader's modern-.NET ref-pack fallback must locate XMLs for the test-host runtime layout");
"the ref-pack fallback must locate the XMLs for the test-host runtime layout");
var documentation = provider!.GetDocumentation(StringConcatId);
var documentation = provider!.GetDocumentation(concat.GetIdString());
documentation.Should().NotBeNullOrEmpty(
"System.String.Concat is one of the most-documented methods in CoreLib — the hover tooltip would be empty without this");
"System.String.Concat is one of the most-documented methods in CoreLib - the hover "
+ "tooltip would be empty without this");
documentation.Should().Contain("<summary",
"the raw documentation string must include the <summary> tag the renderer parses");
}

32
ILSpy.Tests/MainWindow/MenuIconWiringProbe.cs → ILSpy.Tests/MainWindow/MainMenuIconTests.cs

@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
@ -35,48 +34,23 @@ using NUnit.Framework; @@ -35,48 +34,23 @@ using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests;
[TestFixture]
public class MenuIconWiringProbe
public class MainMenuIconTests
{
[AvaloniaTest]
public void Known_Menu_Items_With_MenuIcon_Metadata_Get_Icon_Populated()
public void A_Menu_Item_Declaring_MenuIcon_Metadata_Gets_Its_Icon_Rasterised()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var nativeMenu = NativeMenu.GetMenu(window)
?? throw new InvalidOperationException("MainMenu.Attach should have set NativeMenu on the window");
var leaves = new List<(string Path, bool HasIcon)>();
Collect(nativeMenu, "", leaves);
var withIcon = leaves.Where(l => l.HasIcon).Select(l => l.Path).ToList();
// Spot-check: File -> Open (which has MenuIcon="Images/Open" in MEF metadata).
var fileMenu = nativeMenu.Items.OfType<NativeMenuItem>()
.Single(m => string.Equals(m.Header, Resources._File, StringComparison.Ordinal));
var openItem = fileMenu.Menu!.Items.OfType<NativeMenuItem>()
.Single(m => string.Equals(m.Header, Resources._Open, StringComparison.Ordinal));
openItem.Icon.Should().NotBeNull(
"File > Open declares MenuIcon=\"Images/Open\" in its [ExportMainMenuCommand]; the menu builder "
+ "must rasterise that into NativeMenuItem.Icon.");
// Sanity: at least 5 leaves with icons (we have ~12+ MEF declarations with MenuIcon).
// On failure, name the items that DID get an icon so the regression is diagnosable without
// dumping the whole menu on every (passing) run.
withIcon.Count.Should().BeGreaterThanOrEqualTo(5,
$"at least 5 main-menu items have MenuIcon metadata; found {withIcon.Count} of {leaves.Count} "
+ $"leaves with an icon [{string.Join(", ", withIcon)}]");
}
static void Collect(NativeMenu menu, string parentPath, List<(string Path, bool HasIcon)> leaves)
{
foreach (var element in menu.Items)
{
if (element is not NativeMenuItem item)
continue;
var path = string.IsNullOrEmpty(parentPath) ? (item.Header ?? "<unnamed>") : $"{parentPath} > {item.Header}";
if (item.Menu is { Items.Count: > 0 } sub)
Collect(sub, path, leaves);
else
leaves.Add((path, item.Icon != null));
}
}
}

47
ILSpy.Tests/MainWindow/ToolPaneDeferredContentTests.cs

@ -1,47 +0,0 @@ @@ -1,47 +0,0 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using AwesomeAssertions;
using Dock.Controls.DeferredContentControl;
using ICSharpCode.ILSpy.ViewModels;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests;
/// <summary>
/// Regression guard: <see cref="ToolPaneModel"/> opts out of Dock's deferred content
/// presentation so panes (search, analyzers, debug steps, etc.) materialise their views
/// eagerly. Without this, tests can't reach descendants of a pane until it's
/// focus-activated by the user — and the search-pane's startup tasks (assembly index
/// scan, etc.) wouldn't kick off until first activation.
/// </summary>
[TestFixture]
public class ToolPaneDeferredContentTests
{
[Test]
public void ToolPaneModel_Opts_Out_Of_Dock_Deferred_Content_Presentation()
{
var sentinel = new TestToolPane();
((IDeferredContentPresentation)sentinel).DeferContentPresentation.Should().BeFalse();
}
sealed class TestToolPane : ToolPaneModel { }
}
Loading…
Cancel
Save