mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
The BAML tests were orphaned: in no solution, no CI, and missing their ICSharpCode.BamlDecompiler reference. Revive them split by platform, mirroring ILSpy.Tests / ILSpy.Tests.Windows: ILSpy.BamlDecompiler.Tests (net11.0) holds platform-agnostic tests - including a regression test that decompiles with the WPF assemblies hidden, covering the missing-assembly fix - and ILSpy.BamlDecompiler.Tests.Windows (net11.0-windows) holds the WPF round-trip cases that compile XAML into BAML. Single-TFM projects keep normal lock files; the Windows one declares all RIDs so its lock is generatable and verifiable on any host. Wire the cross-platform project into ILSpy.sln, ILSpy.XPlat.slnf and ILSpy.Desktop.slnf so it builds and runs on the Linux/macOS legs; the Windows project stays in ILSpy.sln (its tests execute in the Windows leg). The Windows job also archives that assembly, whose embedded BAML lets the decompiler be exercised against real BAML on a host without WPF. Assisted-by: Claude:claude-opus-4-8:Claude Codepull/3881/head
47 changed files with 5331 additions and 1261 deletions
@ -1,3 +1,3 @@ |
|||||||
<UserControl x:Class="ILSpy.BamlDecompiler.Tests.Cases.Issue3318" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cases="clr-namespace:ILSpy.BamlDecompiler.Tests.Cases;assembly=ILSpy.BamlDecompiler.Tests"> |
<UserControl x:Class="ILSpy.BamlDecompiler.Tests.Cases.Issue3318" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cases="clr-namespace:ILSpy.BamlDecompiler.Tests.Cases;assembly=ILSpy.BamlDecompiler.Tests.Windows"> |
||||||
<Grid Name="Root" x:FieldModifier="public" /> |
<Grid Name="Root" x:FieldModifier="public" /> |
||||||
</UserControl> |
</UserControl> |
||||||
@ -0,0 +1,137 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||||
|
|
||||||
|
<PropertyGroup> |
||||||
|
<IsWindowsX64 Condition="$([MSBuild]::IsOsPlatform('Windows')) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == X64">true</IsWindowsX64> |
||||||
|
<IsWindowsARM64 Condition="$([MSBuild]::IsOsPlatform('Windows')) And $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) == ARM64">true</IsWindowsARM64> |
||||||
|
</PropertyGroup> |
||||||
|
|
||||||
|
<PropertyGroup> |
||||||
|
<!-- The BAML round-trip cases compile WPF XAML into this assembly, so the project is |
||||||
|
Windows-bound (built and run only in the Windows CI leg via ILSpy.sln). EnableWindowsTargeting |
||||||
|
lets the package graph restore on non-Windows hosts, so the lock file can be generated and |
||||||
|
verified anywhere even though the tests themselves only execute on Windows. --> |
||||||
|
<TargetFramework>net11.0-windows</TargetFramework> |
||||||
|
<EnableWindowsTargeting>true</EnableWindowsTargeting> |
||||||
|
<UseWpf>true</UseWpf> |
||||||
|
|
||||||
|
<!-- RID-complete lock so the committed packages.lock.json matches regardless of the host that |
||||||
|
restores it; the concrete RuntimeIdentifier gives BamlTestRunner the bin\...\<rid> layout |
||||||
|
it walks up from to find the .xaml sources. --> |
||||||
|
<RuntimeIdentifiers>win-x64;win-arm64;linux-x64;osx-arm64</RuntimeIdentifiers> |
||||||
|
<RuntimeIdentifier Condition="$(IsWindowsX64) == true">win-x64</RuntimeIdentifier> |
||||||
|
<RuntimeIdentifier Condition="$(IsWindowsARM64) == true">win-arm64</RuntimeIdentifier> |
||||||
|
|
||||||
|
<IsPackable>false</IsPackable> |
||||||
|
<OutputType>Exe</OutputType> |
||||||
|
|
||||||
|
<EnableDefaultItems>false</EnableDefaultItems> |
||||||
|
|
||||||
|
<ExtrasEnableDefaultPageItems>false</ExtrasEnableDefaultPageItems> |
||||||
|
<ExtrasEnableDefaultResourceItems>false</ExtrasEnableDefaultResourceItems> |
||||||
|
|
||||||
|
<AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects> |
||||||
|
</PropertyGroup> |
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<DebugSymbols>true</DebugSymbols> |
||||||
|
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow> |
||||||
|
</PropertyGroup> |
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> |
||||||
|
<DebugType>pdbonly</DebugType> |
||||||
|
<DebugSymbols>true</DebugSymbols> |
||||||
|
</PropertyGroup> |
||||||
|
|
||||||
|
<ItemGroup> |
||||||
|
<PackageReference Include="NUnit" /> |
||||||
|
<PackageReference Include="NUnit3TestAdapter" /> |
||||||
|
<PackageReference Include="AwesomeAssertions" /> |
||||||
|
<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" /> |
||||||
|
<PackageReference Include="Microsoft.Testing.Extensions.VSTestBridge" /> |
||||||
|
<PackageReference Include="coverlet.MTP" /> |
||||||
|
</ItemGroup> |
||||||
|
|
||||||
|
<ItemGroup> |
||||||
|
<ProjectReference Include="..\ICSharpCode.Decompiler.Tests\ICSharpCode.Decompiler.Tests.csproj" /> |
||||||
|
<ProjectReference Include="..\ICSharpCode.BamlDecompiler\ICSharpCode.BamlDecompiler.csproj" /> |
||||||
|
</ItemGroup> |
||||||
|
|
||||||
|
<ItemGroup> |
||||||
|
<Compile Include="BamlSecurityTests.cs" /> |
||||||
|
<Compile Include="BamlTestRunner.cs" /> |
||||||
|
<Compile Include="Cases\AttachedEvent.xaml.cs"> |
||||||
|
<DependentUpon>AttachedEvent.xaml</DependentUpon> |
||||||
|
</Compile> |
||||||
|
<Compile Include="Cases\CustomControl.cs" /> |
||||||
|
<Compile Include="Cases\Issue1547.xaml.cs" /> |
||||||
|
<Compile Include="Cases\Issue2097.xaml.cs" /> |
||||||
|
<Compile Include="Cases\Issue2116.xaml.cs" /> |
||||||
|
<Compile Include="Cases\Issue3318.xaml.cs" /> |
||||||
|
<Compile Include="Cases\MyControl.xaml.cs"> |
||||||
|
<DependentUpon>MyControl.xaml</DependentUpon> |
||||||
|
</Compile> |
||||||
|
<Compile Include="Cases\ReadonlyProperty.xaml.cs" /> |
||||||
|
<Compile Include="Cases\Resources.xaml.cs"> |
||||||
|
<DependentUpon>Resources.xaml</DependentUpon> |
||||||
|
</Compile> |
||||||
|
<Compile Include="Cases\Simple.xaml.cs"> |
||||||
|
<DependentUpon>Simple.xaml</DependentUpon> |
||||||
|
</Compile> |
||||||
|
<Compile Include="Cases\SimpleNames.xaml.cs"> |
||||||
|
<DependentUpon>SimpleNames.xaml</DependentUpon> |
||||||
|
</Compile> |
||||||
|
<Compile Include="Mocks\AvalonDock.cs" /> |
||||||
|
</ItemGroup> |
||||||
|
|
||||||
|
<ItemGroup> |
||||||
|
<Page Include="Cases\AttachedEvent.xaml" /> |
||||||
|
<Page Include="Cases\AvalonDockBrushes.xaml" /> |
||||||
|
<Page Include="Cases\AvalonDockCommon.xaml" /> |
||||||
|
<Page Include="Cases\EscapeSequence.xaml"> |
||||||
|
<SubType>Designer</SubType> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue1435.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue1546.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue1547.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue2052.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue2097.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue2116.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue3318.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Issue775.xaml"> |
||||||
|
<SubType>Designer</SubType> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\MarkupExtension.xaml" /> |
||||||
|
<Page Include="Cases\MyControl.xaml" /> |
||||||
|
<Page Include="Cases\NamespacePrefix.xaml" /> |
||||||
|
<Page Include="Cases\ReadonlyProperty.xaml"> |
||||||
|
<Generator>MSBuild:Compile</Generator> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\Resources.xaml" /> |
||||||
|
<Page Include="Cases\Simple.xaml"> |
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
||||||
|
</Page> |
||||||
|
<Page Include="Cases\SimpleDictionary.xaml" /> |
||||||
|
<Page Include="Cases\SimpleNames.xaml" /> |
||||||
|
<Page Include="Cases\SimplePropertyElement.xaml" /> |
||||||
|
<Page Include="Cases\Dictionary1.xaml" /> |
||||||
|
<Page Include="Cases\Issue445.xaml" /> |
||||||
|
</ItemGroup> |
||||||
|
|
||||||
|
</Project> |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,137 @@ |
|||||||
|
// Copyright (c) 2026 Siegfried Pammer
|
||||||
|
//
|
||||||
|
// 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; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Reflection.PortableExecutable; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using ICSharpCode.BamlDecompiler; |
||||||
|
using ICSharpCode.BamlDecompiler.Baml; |
||||||
|
using ICSharpCode.Decompiler.Metadata; |
||||||
|
using ICSharpCode.Decompiler.TypeSystem; |
||||||
|
|
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ILSpy.BamlDecompiler.Tests |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the BAML decompiler survives when the well-known WPF assemblies cannot be
|
||||||
|
/// resolved - the situation on any machine without WPF installed (Linux/macOS). Previously
|
||||||
|
/// <see cref="KnownThings"/> threw because it could not find PresentationFramework et al.;
|
||||||
|
/// now those assemblies are substituted by synthetic stand-ins.
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture] |
||||||
|
public class MissingReferencesTests |
||||||
|
{ |
||||||
|
const string PresentationXmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An assembly resolver that hides the WPF assemblies, reproducing the "no WPF available"
|
||||||
|
/// environment deterministically on every platform (on Windows the real assemblies would
|
||||||
|
/// otherwise be found in the runtime pack).
|
||||||
|
/// </summary>
|
||||||
|
sealed class WpfHidingResolver : IAssemblyResolver |
||||||
|
{ |
||||||
|
static readonly HashSet<string> hidden = new(StringComparer.OrdinalIgnoreCase) { |
||||||
|
"WindowsBase", "PresentationCore", "PresentationFramework", "PresentationUI", "System.Xaml" |
||||||
|
}; |
||||||
|
|
||||||
|
readonly IAssemblyResolver inner; |
||||||
|
|
||||||
|
public WpfHidingResolver(IAssemblyResolver inner) => this.inner = inner; |
||||||
|
|
||||||
|
public MetadataFile Resolve(IAssemblyReference reference) |
||||||
|
=> hidden.Contains(reference.Name) ? null : inner.Resolve(reference); |
||||||
|
|
||||||
|
public MetadataFile ResolveModule(MetadataFile mainModule, string moduleName) |
||||||
|
=> inner.ResolveModule(mainModule, moduleName); |
||||||
|
|
||||||
|
public Task<MetadataFile> ResolveAsync(IAssemblyReference reference) |
||||||
|
=> hidden.Contains(reference.Name) ? Task.FromResult<MetadataFile>(null) : inner.ResolveAsync(reference); |
||||||
|
|
||||||
|
public Task<MetadataFile> ResolveModuleAsync(MetadataFile mainModule, string moduleName) |
||||||
|
=> inner.ResolveModuleAsync(mainModule, moduleName); |
||||||
|
} |
||||||
|
|
||||||
|
static BamlDecompilerTypeSystem CreateTypeSystemWithoutWpf() |
||||||
|
{ |
||||||
|
// This test assembly is a managed module that does not itself reference WPF; combined
|
||||||
|
// with the WPF-hiding resolver, none of the well-known WPF assemblies resolve.
|
||||||
|
var location = typeof(MissingReferencesTests).Assembly.Location; |
||||||
|
// PrefetchEntireImage reads the whole image into memory, so the file stream is only
|
||||||
|
// needed while the PEFile is being constructed and can be closed right afterwards.
|
||||||
|
using var stream = new FileStream(location, FileMode.Open, FileAccess.Read); |
||||||
|
var file = new PEFile(location, stream, streamOptions: PEStreamOptions.PrefetchEntireImage); |
||||||
|
var resolver = new WpfHidingResolver(new UniversalAssemblyResolver(location, throwOnError: false, |
||||||
|
file.DetectTargetFrameworkId(), file.DetectRuntimePack())); |
||||||
|
return new BamlDecompilerTypeSystem(file, resolver); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void KnownThings_DoesNotThrow_WhenWpfAssembliesAreUnavailable() |
||||||
|
{ |
||||||
|
var typeSystem = CreateTypeSystemWithoutWpf(); |
||||||
|
Assert.DoesNotThrow(() => new KnownThings(typeSystem)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void KnownType_ResolvesToSyntheticDefinition_WithCorrectIdentity() |
||||||
|
{ |
||||||
|
var knownThings = new KnownThings(CreateTypeSystemWithoutWpf()); |
||||||
|
|
||||||
|
var button = knownThings.Types(KnownTypes.Button); |
||||||
|
|
||||||
|
Assert.Multiple(() => { |
||||||
|
Assert.That(button.Namespace, Is.EqualTo("System.Windows.Controls")); |
||||||
|
Assert.That(button.Name, Is.EqualTo("Button")); |
||||||
|
Assert.That(button.ParentModule.AssemblyName, Is.EqualTo("PresentationFramework")); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void SyntheticModule_IsBounded_ReturnsNullForUnseededType() |
||||||
|
{ |
||||||
|
var knownThings = new KnownThings(CreateTypeSystemWithoutWpf()); |
||||||
|
var module = knownThings.Types(KnownTypes.Button).ParentModule; |
||||||
|
|
||||||
|
// A type the decompiler never seeded must stay unresolved, so that references to
|
||||||
|
// non-well-known WPF types keep degrading to UnknownType exactly as before.
|
||||||
|
var unseeded = module.GetTypeDefinition( |
||||||
|
new TopLevelTypeName("System.Windows.Controls", "ThisTypeDoesNotExist")); |
||||||
|
|
||||||
|
Assert.That(unseeded, Is.Null); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void SyntheticModule_ExposesPresentationXmlnsDefinition() |
||||||
|
{ |
||||||
|
var knownThings = new KnownThings(CreateTypeSystemWithoutWpf()); |
||||||
|
var module = knownThings.Types(KnownTypes.Button).ParentModule; |
||||||
|
|
||||||
|
var controlsMapping = module.GetAssemblyAttributes() |
||||||
|
.Where(a => a.AttributeType.FullName == "System.Windows.Markup.XmlnsDefinitionAttribute") |
||||||
|
.FirstOrDefault(a => (string)a.FixedArguments[1].Value == "System.Windows.Controls"); |
||||||
|
|
||||||
|
Assert.That(controlsMapping, Is.Not.Null, "expected an XmlnsDefinition for System.Windows.Controls"); |
||||||
|
Assert.That((string)controlsMapping.FixedArguments[0].Value, Is.EqualTo(PresentationXmlns)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue