From 467aca72951590b6422034069316b62ce987dbba Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 14 Jun 2023 09:05:45 +0200 Subject: [PATCH] Add runtime trait for skipping test based on .NET using a Mono or CoreCLR runtime. --- Turkey.Tests/ProgramTest.cs | 16 +++++---- Turkey.Tests/TestParserTest.cs | 9 ++++- Turkey/DotNet.cs | 65 ++++++++++++++++++++++++++++++---- Turkey/Program.cs | 8 +++-- Turkey/TestRunner.cs | 5 ++- Turkey/XUnitTest.cs | 4 +-- 6 files changed, 88 insertions(+), 19 deletions(-) diff --git a/Turkey.Tests/ProgramTest.cs b/Turkey.Tests/ProgramTest.cs index a64aa55..ab34ed3 100644 --- a/Turkey.Tests/ProgramTest.cs +++ b/Turkey.Tests/ProgramTest.cs @@ -55,29 +55,33 @@ public static IEnumerable SystemTraits_MemberData() string expectedArch = $"arch={OSArchitectureName}"; // default traits. - yield return new object[] { runtimeVersion, sdkVersion, Array.Empty(), Array.Empty(), CombineTraits() }; + yield return new object[] { runtimeVersion, sdkVersion, Array.Empty(), false, Array.Empty(), CombineTraits() }; + + // 'runtime=mono' + yield return new object[] { runtimeVersion, sdkVersion, Array.Empty(), true, Array.Empty(), CombineTraits(isMonoRuntime: true) }; // 'os=..' and 'rid=...' are added for the platform rids. - yield return new object[] { runtimeVersion, sdkVersion, new[] { "linux-x64", "fedora.37-x64", "linux-musl-x64" }, Array.Empty(), + yield return new object[] { runtimeVersion, sdkVersion, new[] { "linux-x64", "fedora.37-x64", "linux-musl-x64" }, false, Array.Empty(), CombineTraits(new[] { "os=linux", "os=fedora.37", "os=linux-musl", "rid=linux-x64", "rid=fedora.37-x64", "rid=linux-musl-x64" } ) }; // additional traits are added. - yield return new object[] { runtimeVersion, sdkVersion, Array.Empty(), new[] { "blue", "green" }, + yield return new object[] { runtimeVersion, sdkVersion, Array.Empty(), false, new[] { "blue", "green" }, CombineTraits(new[] { "blue", "green" } ) }; - string[] CombineTraits(string[] expectedAdditionalTraits = null) + string[] CombineTraits(string[] expectedAdditionalTraits = null, bool isMonoRuntime = false) => expectedVersionTraits .Concat(new[] { expectedArch }) + .Concat(isMonoRuntime ? new[] { "runtime=mono" } : new[] { "runtime=coreclr" }) .Concat(expectedAdditionalTraits ?? Array.Empty()) .ToArray(); } [Theory] [MemberData(nameof(SystemTraits_MemberData))] - public void SystemTraits(Version runtimeVersion, Version sdkVersion, string[] rids, string[] additionalTraits, string[] expectedTraits) + public void SystemTraits(Version runtimeVersion, Version sdkVersion, string[] rids, bool isMonoRuntime, string[] additionalTraits, string[] expectedTraits) { - IReadOnlySet systemTraits = Program.CreateTraits(runtimeVersion, sdkVersion, new List(rids), additionalTraits); + IReadOnlySet systemTraits = Program.CreateTraits(runtimeVersion, sdkVersion, new List(rids), isMonoRuntime, additionalTraits); Assert.Equal(expectedTraits.OrderBy(s => s), systemTraits.OrderBy(s => s)); } diff --git a/Turkey.Tests/TestParserTest.cs b/Turkey.Tests/TestParserTest.cs index 1345dd4..27f44af 100644 --- a/Turkey.Tests/TestParserTest.cs +++ b/Turkey.Tests/TestParserTest.cs @@ -13,7 +13,7 @@ public class TestParserTests public void DisabledTestShouldBeSkipped() { TestParser parser = new TestParser(); - SystemUnderTest system = new SystemUnderTest(null, null, null, null, null); + SystemUnderTest system = new SystemUnderTest(null, null, null, null, null, null); TestDescriptor test = new TestDescriptor() { Enabled = false, @@ -40,6 +40,7 @@ public void TestShouldBeRunForSameOrHigherVersions(string version, bool expected { TestParser parser = new TestParser(); SystemUnderTest system = new SystemUnderTest( + dotnet: null, runtimeVersion: Version.Parse(version), sdkVersion: null, platformIds: new List(), @@ -76,6 +77,7 @@ public void VersionSpecificTestShouldBeRunForSameMajorMinorVersion(string versio { TestParser parser = new TestParser(); SystemUnderTest system = new SystemUnderTest( + dotnet: null, runtimeVersion: Version.Parse(version), sdkVersion: null, platformIds: new List(), @@ -114,6 +116,7 @@ public void VersionSpecificTestWithWildcardShouldBeRunForSameMajorVersion(string { TestParser parser = new TestParser(); SystemUnderTest system = new SystemUnderTest( + dotnet: null, runtimeVersion: Version.Parse(version), sdkVersion: null, platformIds: new List(), @@ -137,6 +140,7 @@ public void MissingIgnoredRIDsIsOkay() { TestParser parser = new TestParser(); SystemUnderTest system = new SystemUnderTest( + dotnet: null, runtimeVersion: Version.Parse("2.1"), sdkVersion: null, platformIds: new string[] { "linux" }.ToList(), @@ -166,6 +170,7 @@ public void TestShouldNotRunOnIgnoredPlatforms(string[] currentPlatforms, string { TestParser parser = new TestParser(); SystemUnderTest system = new SystemUnderTest( + dotnet: null, runtimeVersion: Version.Parse("2.1"), sdkVersion: null, platformIds: currentPlatforms.ToList(), @@ -192,6 +197,7 @@ public void SdkTestsShouldRunOnlyWithSdk(string sdkVersion, bool requiresSdk, bo { TestParser parser = new TestParser(); SystemUnderTest system = new SystemUnderTest( + dotnet: null, runtimeVersion: Version.Parse("3.1"), sdkVersion: Version.Parse(sdkVersion), platformIds: new List(), @@ -250,6 +256,7 @@ public void SkipTestForTraits(string[] systemTraits, string[] skipWhen, bool exp TestParser parser = new TestParser(); SystemUnderTest system = new SystemUnderTest( + dotnet: null, runtimeVersion: Version.Parse("3.1"), sdkVersion: null, platformIds: null, diff --git a/Turkey/DotNet.cs b/Turkey/DotNet.cs index 98a36ca..20b4c0f 100644 --- a/Turkey/DotNet.cs +++ b/Turkey/DotNet.cs @@ -10,13 +10,29 @@ namespace Turkey { public class DotNet { + private string _dotnetPath; + + public DotNet() + { + _dotnetPath = FindProgramInPath("dotnet"); + if (_dotnetPath is not null) + { + // resolve link target. + _dotnetPath = new FileInfo(_dotnetPath).ResolveLinkTarget(returnFinalTarget: true)?.FullName ?? _dotnetPath; + } + } + + private string DotnetFileName => _dotnetPath ?? throw new FileNotFoundException("dotnet"); + + private string DotnetRoot => Path.GetDirectoryName(DotnetFileName); + public List RuntimeVersions { get { ProcessStartInfo startInfo = new ProcessStartInfo() { - FileName = "dotnet", + FileName = DotnetFileName, RedirectStandardOutput = true, RedirectStandardError = true, Arguments = "--list-runtimes", @@ -45,13 +61,16 @@ public Version LatestRuntimeVersion } } + public bool IsMonoRuntime(Version runtimeVersion) + => IsMonoRuntime(DotnetRoot, runtimeVersion); + public List SdkVersions { get { ProcessStartInfo startInfo = new ProcessStartInfo() { - FileName = "dotnet", + FileName = DotnetFileName, RedirectStandardOutput = true, RedirectStandardError = true, Arguments = "--list-sdks", @@ -79,7 +98,7 @@ public Version LatestSdkVersion } } - public static Task BuildAsync(DirectoryInfo workingDirectory, IReadOnlyDictionary environment, Action logger, CancellationToken token) + public Task BuildAsync(DirectoryInfo workingDirectory, IReadOnlyDictionary environment, Action logger, CancellationToken token) { var arguments = new string[] { @@ -91,18 +110,18 @@ public static Task BuildAsync(DirectoryInfo workingDirectory, IReadOnlyDict return RunDotNetCommandAsync(workingDirectory, arguments, environment, logger, token); } - public static Task RunAsync(DirectoryInfo workingDirectory, IReadOnlyDictionary environment, Action logger, CancellationToken token) + public Task RunAsync(DirectoryInfo workingDirectory, IReadOnlyDictionary environment, Action logger, CancellationToken token) => RunDotNetCommandAsync(workingDirectory, new string[] { "run", "--no-restore", "--no-build"} , environment, logger, token); - public static Task TestAsync(DirectoryInfo workingDirectory, IReadOnlyDictionary environment, Action logger, CancellationToken token) + public Task TestAsync(DirectoryInfo workingDirectory, IReadOnlyDictionary environment, Action logger, CancellationToken token) => RunDotNetCommandAsync(workingDirectory, new string[] { "test", "--no-restore", "--no-build"} , environment, logger, token); - private static async Task RunDotNetCommandAsync(DirectoryInfo workingDirectory, string[] commands, IReadOnlyDictionary environment, Action logger, CancellationToken token) + private async Task RunDotNetCommandAsync(DirectoryInfo workingDirectory, string[] commands, IReadOnlyDictionary environment, Action logger, CancellationToken token) { var arguments = string.Join(" ", commands); ProcessStartInfo startInfo = new ProcessStartInfo() { - FileName = "dotnet", + FileName = DotnetFileName, Arguments = arguments, WorkingDirectory = workingDirectory.FullName, RedirectStandardOutput = true, @@ -117,5 +136,37 @@ private static async Task RunDotNetCommandAsync(DirectoryInfo workingDirect return await ProcessRunner.RunAsync(startInfo, logger, token); } + + private static bool IsMonoRuntime(string dotnetRoot, Version version) + { + string[] runtimeDirectories = Directory.GetDirectories(Path.Combine(dotnetRoot, "shared", "Microsoft.NETCore.App")) + .Where(dir => Version.Parse(Path.GetFileName(dir)) == version) + .ToArray(); + if (runtimeDirectories.Length == 0) + { + throw new DirectoryNotFoundException($"No runtime directory for {version} found in {dotnetRoot}."); + } + + if (runtimeDirectories.Length > 1) + { + throw new DirectoryNotFoundException($"Multiple runtime directories found for {version} in {dotnetRoot}."); + } + + string runtimeDir = runtimeDirectories[0]; + return File.Exists(Path.Combine(runtimeDir, "mono-gc.h")); + } + + private static string? FindProgramInPath(string program) + { + string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(':', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); + foreach (string p in paths) + { + if (Path.Combine(p, program) is var filename && File.Exists(filename)) + { + return filename; + } + } + return null; + } } } diff --git a/Turkey/Program.cs b/Turkey/Program.cs index 8dd58c6..d9baa84 100644 --- a/Turkey/Program.cs +++ b/Turkey/Program.cs @@ -101,10 +101,11 @@ public static async Task Run(string testRoot, var sanitizer = new EnvironmentVariableSanitizer(); var envVars = sanitizer.SanitizeCurrentEnvironmentVariables(); - var traits = CreateTraits(dotnet.LatestRuntimeVersion, dotnet.LatestSdkVersion, platformIds, trait); + var traits = CreateTraits(dotnet.LatestRuntimeVersion, dotnet.LatestSdkVersion, platformIds, dotnet.IsMonoRuntime(dotnet.LatestRuntimeVersion), trait); Console.WriteLine($"Tests matching these traits will be skipped: {string.Join(", ", traits.OrderBy(s => s))}."); SystemUnderTest system = new SystemUnderTest( + dotnet, runtimeVersion: dotnet.LatestRuntimeVersion, sdkVersion: dotnet.LatestSdkVersion, platformIds: platformIds, @@ -195,7 +196,7 @@ public static async Task GenerateNuGetConfigIfNeededAsync(string additio return null; } - public static IReadOnlySet CreateTraits(Version runtimeVersion, Version sdkVersion, List rids, IEnumerable additionalTraits) + public static IReadOnlySet CreateTraits(Version runtimeVersion, Version sdkVersion, List rids, bool isMonoRuntime, IEnumerable additionalTraits) { var traits = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -217,6 +218,9 @@ public static IReadOnlySet CreateTraits(Version runtimeVersion, Version string arch = RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(); traits.Add($"arch={arch}"); + // Add 'runtime=' trait. + traits.Add($"runtime={(isMonoRuntime ? "mono" : "coreclr")}"); + // Add additional traits. foreach (var skipTrait in additionalTraits) { diff --git a/Turkey/TestRunner.cs b/Turkey/TestRunner.cs index efdbcce..b5cf194 100644 --- a/Turkey/TestRunner.cs +++ b/Turkey/TestRunner.cs @@ -24,13 +24,16 @@ public class SystemUnderTest public List CurrentPlatformIds { get; } public IReadOnlyDictionary EnvironmentVariables; public IReadOnlySet Traits { get; } + public DotNet Dotnet { get; } - public SystemUnderTest(Version runtimeVersion, + public SystemUnderTest(DotNet dotnet, + Version runtimeVersion, Version sdkVersion, List platformIds, IReadOnlyDictionary environmentVariables, IReadOnlySet traits) { + Dotnet = dotnet; RuntimeVersion = runtimeVersion; SdkVersion = sdkVersion; CurrentPlatformIds = platformIds ?? new List(); diff --git a/Turkey/XUnitTest.cs b/Turkey/XUnitTest.cs index 5bdd729..1834f82 100644 --- a/Turkey/XUnitTest.cs +++ b/Turkey/XUnitTest.cs @@ -24,9 +24,9 @@ protected override async Task InternalRunAsync(Action logger private Task BuildProjectAsync(Action logger, CancellationToken token) - => DotNet.BuildAsync(Directory, SystemUnderTest.EnvironmentVariables, logger, token); + => SystemUnderTest.Dotnet.BuildAsync(Directory, SystemUnderTest.EnvironmentVariables, logger, token); private Task RunTestProjectAsync(Action logger, CancellationToken token) - => DotNet.TestAsync(Directory, SystemUnderTest.EnvironmentVariables, logger, token); + => SystemUnderTest.Dotnet.TestAsync(Directory, SystemUnderTest.EnvironmentVariables, logger, token); } }