// 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 System.Linq;
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.Metadata;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Metadata;
///
/// Pins the typed-children breakdown of : CodeView
/// + PdbChecksum entries get specialised tree nodes that surface their fields, other
/// entry types fall back to a generic node with a hex-dump Decompile. CoreLib's
/// PE always ships at least a CodeView entry plus a Reproducible entry, which lets us
/// validate both the typed and the fallback paths against a real assembly.
///
[TestFixture]
public class DebugDirectoryChildNodesTests
{
[AvaloniaTest]
public async Task DebugDirectoryTreeNode_Surfaces_CodeViewTreeNode_For_The_CodeView_Entry()
{
var debugDir = await GetDebugDirectoryForCoreLib();
// CoreLib ships at least one CodeView entry (private + public PDB descriptions
// can produce multiple); the contract is "every CodeView entry surfaces as a
// typed child", not a specific count.
debugDir.Children.OfType().Should().NotBeEmpty(
"every System.Private.CoreLib build ships at least one CodeView debug-directory entry");
}
[AvaloniaTest]
public async Task DebugDirectoryTreeNode_Surfaces_PdbChecksumTreeNode_For_PdbChecksum_Entries()
{
var debugDir = await GetDebugDirectoryForCoreLib();
debugDir.Children.OfType().Should().NotBeEmpty(
"modern CoreLib builds ship one or more PdbChecksum debug-directory entries");
var sum = debugDir.Children.OfType().First();
sum.AlgorithmName.Should().NotBeNullOrEmpty();
sum.Checksum.Should().NotBeNullOrEmpty();
}
[AvaloniaTest]
public async Task DebugDirectoryTreeNode_Falls_Back_To_Generic_Entry_For_Unspecialised_Types()
{
// Reproducible entries are zero-payload and have no specialised viewer; they should
// still surface as DebugDirectoryEntryTreeNode children so the user can see them in
// the tree alongside the typed entries.
var debugDir = await GetDebugDirectoryForCoreLib();
var generic = debugDir.Children.OfType()
.Where(n => n.GetType() == typeof(DebugDirectoryEntryTreeNode))
.ToList();
generic.Should().NotBeEmpty("expected at least one fallback entry (e.g. Reproducible)");
}
[AvaloniaTest]
public async Task Every_Entry_In_The_PE_Debug_Directory_Becomes_A_Child_Node()
{
// Sanity check: no entry is silently dropped. The child count must match the raw
// directory's entry count exactly (modulo embedded-PDB BadImageFormat skips, which
// don't happen on healthy CoreLib).
var (debugDir, peEntryCount) = await GetDebugDirectoryAndRawCountForCoreLib();
debugDir.Children.Count.Should().Be(peEntryCount);
}
// --- helpers ---------------------------------------------------------------------------
static async Task GetDebugDirectoryForCoreLib()
{
var (_, vm) = await TestHarness.BootAsync();
var debugDir = vm.AssemblyTreeModel.FindCoreLib()
.GetChild()
.GetChild();
debugDir.EnsureLazyChildren();
return debugDir;
}
static async Task<(DebugDirectoryTreeNode node, int rawCount)> GetDebugDirectoryAndRawCountForCoreLib()
{
var (_, vm) = await TestHarness.BootAsync();
var assemblyNode = vm.AssemblyTreeModel.FindCoreLib();
var module = (ICSharpCode.Decompiler.Metadata.PEFile)assemblyNode.LoadedAssembly.GetMetadataFileOrNull()!;
int rawCount = module.Reader.ReadDebugDirectory().Length;
var debugDir = assemblyNode
.GetChild()
.GetChild();
debugDir.EnsureLazyChildren();
return (debugDir, rawCount);
}
}