diff --git a/tests/BenchmarkDotNet.IntegrationTests/MonoAotLlvmTests.cs b/tests/BenchmarkDotNet.IntegrationTests/MonoAotLlvmTests.cs new file mode 100644 index 0000000000..df5988eb5d --- /dev/null +++ b/tests/BenchmarkDotNet.IntegrationTests/MonoAotLlvmTests.cs @@ -0,0 +1,111 @@ +using System; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.IntegrationTests.Diagnosers; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Portability; +using BenchmarkDotNet.Tests.Loggers; +using BenchmarkDotNet.Tests.XUnit; +using BenchmarkDotNet.Toolchains.DotNetCli; +using BenchmarkDotNet.Toolchains.MonoAotLLVM; +using Xunit; +using Xunit.Abstractions; + +namespace BenchmarkDotNet.IntegrationTests +{ + /// + /// Running these tests requires building the Mono AOT runtime from dotnet/runtime. + /// The toolchain isn't available as a standalone workload yet. + /// + /// Setup instructions: + /// + /// 1. Clone https://github.com/dotnet/runtime + /// + /// 2. Build the mono runtime with libraries: + /// Windows: .\build.cmd -subset mono+libs -c Release + /// Linux: ./build.sh -subset mono+libs -c Release + /// macOS: ./build.sh -subset mono+libs -c Release + /// + /// 3. Set MONOAOTLLVM_COMPILER_PATH to the built compiler: + /// Windows: set MONOAOTLLVM_COMPILER_PATH=C:\path\to\runtime\artifacts\bin\mono\windows.x64.Release\mono-sgen.exe + /// Unix: export MONOAOTLLVM_COMPILER_PATH=/path/to/runtime/artifacts/bin/mono/linux.x64.Release/mono-sgen + /// + /// The runtime pack is at: artifacts/bin/microsoft.netcore.app.runtime.[os]-x64/Release/ + /// + public class MonoAotLlvmTests(ITestOutputHelper output) : BenchmarkTestExecutor(output) + { + private const string AotCompilerPathEnvVar = "MONOAOTLLVM_COMPILER_PATH"; + + private static string GetAotCompilerPath() + => Environment.GetEnvironmentVariable(AotCompilerPathEnvVar); + + private ManualConfig GetConfig(MonoAotCompilerMode mode = MonoAotCompilerMode.llvm) + { + var aotCompilerPath = GetAotCompilerPath(); + var logger = new OutputLogger(Output); + var netCoreAppSettings = new NetCoreAppSettings( + "net8.0", + null, + "MonoAOTLLVM", + aotCompilerPath: aotCompilerPath, + aotCompilerMode: mode); + + return ManualConfig.CreateEmpty() + .AddLogger(logger) + .AddJob(Job.Dry + .WithRuntime(new MonoAotLLVMRuntime( + new System.IO.FileInfo(aotCompilerPath), + mode, + "net8.0")) + .WithToolchain(MonoAotLLVMToolChain.From(netCoreAppSettings))) + .WithBuildTimeout(TimeSpan.FromMinutes(5)) + .WithOption(ConfigOptions.GenerateMSBuildBinLog, true); + } + + [FactEnvSpecific("Requires Mono AOT LLVM toolchain from dotnet/runtime build", EnvRequirement.MonoAotLlvmToolchain)] + public void MonoAotLlvmIsSupported() + { + CanExecute(GetConfig()); + } + + [FactEnvSpecific("Requires Mono AOT LLVM toolchain from dotnet/runtime build", EnvRequirement.MonoAotLlvmToolchain)] + public void MonoAotLlvmSupportsInProcessDiagnosers() + { + try + { + var diagnoser = new MockInProcessDiagnoser1(BenchmarkDotNet.Diagnosers.RunMode.NoOverhead); + var config = GetConfig().AddDiagnoser(diagnoser); + + CanExecute(config); + + Assert.Equal([diagnoser.ExpectedResult], diagnoser.Results.Values); + Assert.Equal([diagnoser.ExpectedResult], BaseMockInProcessDiagnoser.s_completedResults.Select(t => t.result)); + } + finally + { + BaseMockInProcessDiagnoser.s_completedResults.Clear(); + } + } + + [FactEnvSpecific("Requires Mono AOT LLVM toolchain from dotnet/runtime build", EnvRequirement.MonoAotLlvmToolchain)] + public void MonoAotLlvmMiniModeIsSupported() + { + CanExecute(GetConfig(MonoAotCompilerMode.mini)); + } + + public class MonoAotLlvmBenchmark + { + [Benchmark] + public void Check() + { + if (!RuntimeInformation.IsMono) + throw new Exception("Expected Mono runtime"); + + if (!RuntimeInformation.IsAot) + throw new Exception("Expected AOT mode"); + } + } + } +} diff --git a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs index e9e546bd1e..b458584c22 100644 --- a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs +++ b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs @@ -9,5 +9,6 @@ public enum EnvRequirement FullFrameworkOnly, NonFullFramework, DotNetCoreOnly, - NeedsPrivilegedProcess -} \ No newline at end of file + NeedsPrivilegedProcess, + MonoAotLlvmToolchain +} diff --git a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs index f00a7bc0da..a464f095c0 100644 --- a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs +++ b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Environments; using System; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Principal; @@ -9,6 +10,8 @@ namespace BenchmarkDotNet.Tests.XUnit; public static class EnvRequirementChecker { + private const string MonoAotCompilerPathEnvVar = "MONOAOTLLVM_COMPILER_PATH"; + public static string? GetSkip(params EnvRequirement[] requirements) => requirements.Select(GetSkip).FirstOrDefault(skip => skip != null); internal static string? GetSkip(EnvRequirement requirement) => requirement switch @@ -21,6 +24,7 @@ public static class EnvRequirementChecker EnvRequirement.NonFullFramework => !BdnRuntimeInformation.IsFullFramework ? null : "Non-Full .NET Framework test", EnvRequirement.DotNetCoreOnly => BdnRuntimeInformation.IsNetCore ? null : ".NET/.NET Core-only test", EnvRequirement.NeedsPrivilegedProcess => IsPrivilegedProcess() ? null : "Needs authorization to perform security-relevant functions", + EnvRequirement.MonoAotLlvmToolchain => IsMonoAotLlvmAvailable() ? null : $"Mono AOT LLVM toolchain not found. Set {MonoAotCompilerPathEnvVar} to the mono-sgen binary path", _ => throw new ArgumentOutOfRangeException(nameof(requirement), requirement, "Unknown value") }; @@ -36,4 +40,10 @@ private static bool IsPrivilegedProcess() private static bool IsArm() => BdnRuntimeInformation.GetCurrentPlatform() is Platform.Arm64 or Platform.Arm or Platform.Armv6; -} \ No newline at end of file + + private static bool IsMonoAotLlvmAvailable() + { + var compilerPath = Environment.GetEnvironmentVariable(MonoAotCompilerPathEnvVar); + return !string.IsNullOrEmpty(compilerPath) && File.Exists(compilerPath); + } +}