Browse Source

Build ICSharpCode.Decompiler.Tests + the format hook cross-platform

Decompiler.Tests targeted net11.0-windows only because
Microsoft.DiaSymReader.Native is Win-only and Tester.cs hardcoded ilasm.exe
/ ildasm.exe filenames. Both are narrow problems: gate the Native package
to Windows, replace the ".exe" suffix with an OS-aware helper, and drop
the -windows TFM. The Microsoft.NETCore.IL{,D}Asm metapackages' RID graph
already ships ilasm/ildasm for linux + osx via per-RID runtime sub-packages
when RuntimeIdentifier resolves dynamically, so the tool just lands in the
test output dir with the right suffix on every host.

Windows-only test fixtures (RoundtripAssembly + the RunWithTestRunner,
SignAssembly, FindMSBuild call sites) gate via Assert.Ignore +
[Platform("Win")] so they skip on non-Windows instead of failing. CA1416
joins NoWarn -- the analyzer can't see the runtime guards, and the test
inputs (Console.CapsLock, Registry access) are decompilation targets, not
infrastructure.

BuildTools/pre-commit learns to find dotnet-format on non-Windows (XDG
basedir + no .exe suffix) so the hook can run wherever the build now does.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 1 month ago
parent
commit
1fb4acfc3d
  1. 31
      BuildTools/pre-commit
  2. 40
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  3. 18
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  4. 2
      ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
  5. 2
      ILSpy.Tests/Analyzers/MemberImplementsInterfaceAnalyzerTests.cs
  6. 2
      ILSpy.Tests/Analyzers/MethodUsesAnalyzerTests.cs
  7. 2
      ILSpy.Tests/Analyzers/TypeUsedByAnalyzerTests.cs

31
BuildTools/pre-commit

@ -1,26 +1,45 @@ @@ -1,26 +1,45 @@
#!/bin/sh
#
# To enable this hook, copy/symlink this file to ".git/hooks/pre-commit".
# mklink .git\hooks\pre-commit ..\..\BuildTools\pre-commit
# Windows: mklink .git\hooks\pre-commit ..\..\BuildTools\pre-commit
# Linux/Mac: ln -s ../../BuildTools/pre-commit .git/hooks/pre-commit
set -eu
DOTNET_FORMAT_VERSION=10.0.100-rtm.25531.102
DOTNET_FORMAT_SOURCE="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10-transport/nuget/v3/index.json"
DOTNET_PATH="$LOCALAPPDATA/ICSharpCode/ILSpy/dotnet-format-$DOTNET_FORMAT_VERSION"
# Per-OS tool cache location + binary suffix. Windows uses %LOCALAPPDATA% and a .exe
# suffix; Unix-likes follow the XDG basedir spec (falling back to ~/.local/share) and
# drop the suffix. Keeping the tool installed under the same logical path on both
# platforms means a single repo clone can host hook installs from either OS.
case "$(uname -s 2>/dev/null)" in
MINGW*|MSYS*|CYGWIN*|Windows*)
DOTNET_CACHE_ROOT="$LOCALAPPDATA"
EXE_SUFFIX=".exe"
;;
*)
DOTNET_CACHE_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}"
EXE_SUFFIX=""
;;
esac
DOTNET_PATH="$DOTNET_CACHE_ROOT/ICSharpCode/ILSpy/dotnet-format-$DOTNET_FORMAT_VERSION"
if [ ! -d "$DOTNET_PATH" ]; then
echo "Downloading dotnet-format $DOTNET_FORMAT_VERSION..."
dotnet tool install --tool-path "$DOTNET_PATH" dotnet-format --version "$DOTNET_FORMAT_VERSION" --add-source "$DOTNET_FORMAT_SOURCE"
fi
"$DOTNET_PATH/dotnet-format.exe" --version
DOTNET_FORMAT="$DOTNET_PATH/dotnet-format$EXE_SUFFIX"
"$DOTNET_FORMAT" --version
if [ "${1:-}" = "--format" ]; then
# called via format.bat
"$DOTNET_PATH/dotnet-format.exe" whitespace --no-restore --verbosity detailed ILSpy.sln
"$DOTNET_FORMAT" whitespace --no-restore --verbosity detailed ILSpy.sln
elif git diff --quiet --ignore-submodules; then
"$DOTNET_PATH/dotnet-format.exe" whitespace --no-restore --verbosity detailed ILSpy.sln
"$DOTNET_FORMAT" whitespace --no-restore --verbosity detailed ILSpy.sln
git add -u -- \*\*.cs
else
echo Partial commit: only verifying formatting
exec "$DOTNET_PATH/dotnet-format.exe" whitespace --verify-no-changes --no-restore --verbosity detailed ILSpy.sln
exec "$DOTNET_FORMAT" whitespace --verify-no-changes --no-restore --verbosity detailed ILSpy.sln
fi

40
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -109,6 +109,15 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -109,6 +109,15 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
static readonly VsWhereToolset vswhereToolset;
internal static readonly RefAssembliesToolset RefAssembliesToolset;
// ilasm/ildasm ship as `ilasm[.exe]` inside the Microsoft.NETCore.IL{,D}Asm
// per-RID runtime packages, picked up by NuGet's RID graph from the project's
// RuntimeIdentifier. The umbrella package drops the binary alongside the test
// assembly, but only Windows uses the .exe suffix.
static readonly string ExecutableExtension = OperatingSystem.IsWindows() ? ".exe" : "";
static string ResolveToolPath(string toolName) =>
Path.Combine(TesterPath, toolName + ExecutableExtension);
static Tester()
{
TesterPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location);
@ -145,18 +154,23 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -145,18 +154,23 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
await RefAssembliesToolset.Fetch("9.0.0", sourcePath: "ref/net9.0").ConfigureAwait(false);
await RefAssembliesToolset.Fetch(CurrentNetCoreRefAsmVersion, sourcePath: $"ref/net{CurrentNetCoreVersion}").ConfigureAwait(false);
// The self-contained TestRunner builds only target Windows RIDs; the fixtures that
// consume them (UseTestRunner) are themselves gated to Windows at runtime.
if (OperatingSystem.IsWindows())
{
#if DEBUG
await BuildTestRunner("win-x86", "Debug").ConfigureAwait(false);
await BuildTestRunner("win-x64", "Debug").ConfigureAwait(false);
await BuildTestRunner("win-x86", "Debug").ConfigureAwait(false);
await BuildTestRunner("win-x64", "Debug").ConfigureAwait(false);
#else
await BuildTestRunner("win-x86", "Release").ConfigureAwait(false);
await BuildTestRunner("win-x64", "Release").ConfigureAwait(false);
await BuildTestRunner("win-x86", "Release").ConfigureAwait(false);
await BuildTestRunner("win-x64", "Release").ConfigureAwait(false);
#endif
}
}
static async Task BuildTestRunner(string runtime, string config)
{
await Cli.Wrap("dotnet.exe")
await Cli.Wrap("dotnet")
.WithArguments(new[] { "build", Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj"), "-r", runtime, "-c", config, "--self-contained" })
.ExecuteAsync();
}
@ -166,13 +180,13 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -166,13 +180,13 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
string ilasmPath;
if (options.HasFlag(AssemblerOptions.UseLegacyAssembler))
{
if (!OperatingSystem.IsWindows())
Assert.Ignore("UseLegacyAssembler requires the .NET Framework ilasm.exe, which only exists on Windows.");
ilasmPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), @"Microsoft.NET\Framework\v4.0.30319\ilasm.exe");
}
else
{
ilasmPath = Path.Combine(
Path.GetDirectoryName(typeof(Tester).Assembly.Location),
"ilasm.exe");
ilasmPath = ResolveToolPath("ilasm");
}
string outputFile = Path.Combine(Path.GetDirectoryName(sourceFileName), Path.GetFileNameWithoutExtension(sourceFileName));
string otherOptions = " ";
@ -246,9 +260,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -246,9 +260,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
return outputFile;
}
string ildasmPath = Path.Combine(
Path.GetDirectoryName(typeof(Tester).Assembly.Location),
"ildasm.exe");
string ildasmPath = ResolveToolPath("ildasm");
var command = Cli.Wrap(ildasmPath)
.WithArguments($"/utf8 /out=\"{outputFile}\" \"{sourceFileName}\"")
@ -869,6 +881,8 @@ namespace System.Runtime.CompilerServices @@ -869,6 +881,8 @@ namespace System.Runtime.CompilerServices
public static async Task<(int ExitCode, string Output, string Error)> RunWithTestRunner(string assemblyFileName, bool force32Bit)
{
if (!OperatingSystem.IsWindows())
Assert.Ignore("RunWithTestRunner uses a self-contained Windows test-runner build (win-x86/win-x64); not available on this platform.");
string testRunner = Path.Combine(testRunnerBasePath, force32Bit ? "win-x86" : "win-x64", "ICSharpCode.Decompiler.TestRunner.exe");
var command = Cli.Wrap(testRunner)
.WithArguments(assemblyFileName)
@ -1023,6 +1037,8 @@ namespace System.Runtime.CompilerServices @@ -1023,6 +1037,8 @@ namespace System.Runtime.CompilerServices
public static async Task SignAssembly(string assemblyPath, string keyFilePath)
{
if (!OperatingSystem.IsWindows())
Assert.Ignore("SignAssembly uses sn.exe from the Windows .NET Framework SDK; not available on this platform.");
string snPath = SdkUtility.GetSdkPath("sn.exe");
var command = Cli.Wrap(snPath)
@ -1044,6 +1060,8 @@ namespace System.Runtime.CompilerServices @@ -1044,6 +1060,8 @@ namespace System.Runtime.CompilerServices
public static async Task<string> FindMSBuild()
{
if (!OperatingSystem.IsWindows())
Assert.Ignore("FindMSBuild uses vswhere.exe to locate Visual Studio's MSBuild; not available on this platform.");
string path = vswhereToolset.GetVsWhere();
var result = await Cli.Wrap(path)

18
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -2,15 +2,9 @@ @@ -2,15 +2,9 @@
<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>
<TargetFramework>net11.0-windows</TargetFramework>
<TargetFramework>net11.0</TargetFramework>
<LangVersion>preview</LangVersion>
<RuntimeIdentifier Condition="$(IsWindowsX64) == true">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="$(IsWindowsARM64) == true">win-arm64</RuntimeIdentifier>
<RuntimeIdentifier>$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)</RuntimeIdentifier>
<IsPackable>false</IsPackable>
@ -21,7 +15,11 @@ @@ -21,7 +15,11 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<!-- NU1902/1903 are "Package 'X' has a known security vulnerability". In our tests, we don't care. -->
<NoWarn>$(NoWarn);1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981;NU1902;NU1903</NoWarn>
<!-- CA1416 fires because the TFM dropped its -windows suffix: Windows-only helpers (SdkUtility,
Tester.SignAssembly/FindMSBuild/RunWithTestRunner) and Windows-only test inputs (Console.CapsLock
in Async/Switch fixtures) are reachable from non-Windows in principle, but gated by
OperatingSystem.IsWindows() + Assert.Ignore at runtime. The analyzer can't see that. -->
<NoWarn>$(NoWarn);1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981;NU1902;NU1903;CA1416</NoWarn>
<DefineConstants>ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120;CS130</DefineConstants>
<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
@ -65,7 +63,7 @@ @@ -65,7 +63,7 @@
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic" />
<PackageReference Include="Microsoft.DiaSymReader.Converter.Xml" />
<PackageReference Include="Microsoft.DiaSymReader" />
<PackageReference Include="Microsoft.DiaSymReader.Native" />
<PackageReference Include="Microsoft.DiaSymReader.Native" Condition="$([MSBuild]::IsOsPlatform('Windows'))" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="coverlet.MTP" />
<PackageReference Include="NUnit" />

2
ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs

@ -36,7 +36,7 @@ using NUnit.Framework; @@ -36,7 +36,7 @@ using NUnit.Framework;
namespace ICSharpCode.Decompiler.Roundtrip
{
[TestFixture, Parallelizable(ParallelScope.All)]
[TestFixture, Parallelizable(ParallelScope.All), Platform("Win")]
public class RoundtripAssembly
{
public static readonly string TestDir = Path.GetFullPath(Path.Combine(Tester.TestCasePath, "../../ILSpy-tests"));

2
ILSpy.Tests/Analyzers/MemberImplementsInterfaceAnalyzerTests.cs

@ -34,7 +34,7 @@ using NUnit.Framework; @@ -34,7 +34,7 @@ using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Analyzers
{
[TestFixture, Parallelizable(ParallelScope.All)]
[TestFixture]
public class MemberImplementsInterfaceAnalyzerTests
{
static readonly SymbolKind[] ValidSymbolKinds = { SymbolKind.Event, SymbolKind.Indexer, SymbolKind.Method, SymbolKind.Property };

2
ILSpy.Tests/Analyzers/MethodUsesAnalyzerTests.cs

@ -14,7 +14,7 @@ using NUnit.Framework; @@ -14,7 +14,7 @@ using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Analyzers
{
[TestFixture, Parallelizable(ParallelScope.All)]
[TestFixture]
public class MethodUsesAnalyzerTests
{
AssemblyList assemblyList;

2
ILSpy.Tests/Analyzers/TypeUsedByAnalyzerTests.cs

@ -27,7 +27,7 @@ using NUnit.Framework; @@ -27,7 +27,7 @@ using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Analyzers
{
[TestFixture, Parallelizable(ParallelScope.All)]
[TestFixture]
public class TypeUsedByAnalyzerTests
{
AssemblyList assemblyList;

Loading…
Cancel
Save