diff --git a/cake/constants.cake b/cake/constants.cake index 6d4e35650..5729afd89 100644 --- a/cake/constants.cake +++ b/cake/constants.cake @@ -33,10 +33,10 @@ var ENGINE_TESTS_PROJECT = SOURCE_DIR + "NUnitEngine/nunit.engine.tests/nunit.en var ENGINE_CORE_TESTS_PROJECT = SOURCE_DIR + "NUnitEngine/nunit.engine.core.tests/nunit.engine.core.tests.csproj"; var CONSOLE_PROJECT = SOURCE_DIR + "NUnitConsole/nunit-console/nunit-console.csproj"; var CONSOLE_TESTS_PROJECT = SOURCE_DIR + "NUnitConsole/nunit-console.tests/nunit-console.tests.csproj"; -var MOCK_ASSEMBLY_PROJECT = SOURCE_DIR + "NUnitEngine/mock-assembly/mock-assembly.csproj"; -var MOCK_ASSEMBLY_X86_PROJECT = SOURCE_DIR + "NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj"; -var MOCK_ASSEMBLY_NUNIT4_PROJECT = SOURCE_DIR + "NUnitEngine/mock-assembly-nunit4/mock-assembly-nunit4.csproj"; -var NOTEST_PROJECT = SOURCE_DIR + "NUnitEngine/notest-assembly/notest-assembly.csproj"; +var MOCK_ASSEMBLY_PROJECT = SOURCE_DIR + "TestData/mock-assembly/mock-assembly.csproj"; +var MOCK_ASSEMBLY_X86_PROJECT = SOURCE_DIR + "TestData/mock-assembly-x86/mock-assembly-x86.csproj"; +var MOCK_ASSEMBLY_NUNIT4_PROJECT = SOURCE_DIR + "TestData/mock-assembly-nunit4/mock-assembly-nunit4.csproj"; +var NOTEST_PROJECT = SOURCE_DIR + "TestData/notest-assembly/notest-assembly.csproj"; // Console Runner var NET20_CONSOLE = BIN_DIR + "net20/nunit-console.exe"; var NET60_CONSOLE = BIN_DIR + "net6.0/nunit-console.dll"; diff --git a/src/NUnitEngine/nunit-agent/Program.cs b/src/NUnitEngine/nunit-agent/Program.cs index fa68708d5..953c66482 100644 --- a/src/NUnitEngine/nunit-agent/Program.cs +++ b/src/NUnitEngine/nunit-agent/Program.cs @@ -5,15 +5,34 @@ using System.IO; using System.Runtime.InteropServices; using System.Security; +using Microsoft.Win32; using NUnit.Common; using NUnit.Engine; using NUnit.Engine.Agents; using NUnit.Engine.Internal; +#if NETFRAMEWORK +using RuntimeInformation = NUnit.Engine.Internal.Backports.RuntimeInformation; +#endif + namespace NUnit.Agent { public class NUnitTestAgent { + static readonly string CURRENT_RUNTIME = RuntimeInformation.FrameworkDescription; + const string AGENT_RUNTIME = +#if NET6_0 + ".NET 6.0"; +#elif NET5_0 + ".NET 5.0"; +#elif NETCOREAPP3_1 + ".NET Core 3.1"; +#elif NET40 + ".NET 4.0"; +#elif NET20 + ".NET 2.0"; +#endif + static Guid AgentId; static string AgencyUrl; static Process AgencyProcess; @@ -63,21 +82,14 @@ public static void Main(string[] args) InternalTrace.Initialize(Path.Combine(workDirectory, logName), traceLevel); log = InternalTrace.GetLogger(typeof(NUnitTestAgent)); - log.Info("Agent process {0} starting", pid); + log.Info($"Agent process {pid} starting"); + log.Info($"Running {AGENT_RUNTIME} agent under {CURRENT_RUNTIME}"); if (debugArgPassed) TryLaunchDebugger(); LocateAgencyProcess(agencyPid); -#if NETCOREAPP3_1 - log.Info($"Running .NET Core 3.1 agent under {RuntimeInformation.FrameworkDescription}"); -#elif NET40 - log.Info($"Running .NET 4.0 agent under {RuntimeFramework.CurrentFramework.DisplayName}"); -#elif NET20 - log.Info($"Running .NET 2.0 agent under {RuntimeFramework.CurrentFramework.DisplayName}"); -#endif - log.Info("Starting RemoteTestAgent"); Agent = new RemoteTestAgent(AgentId); Agent.Transport = diff --git a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs index 0e48ebe6d..0f9b9cbd9 100644 --- a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs @@ -11,6 +11,12 @@ namespace NUnit.Engine /// public interface IRuntimeFrameworkService { + /// + /// Gets a RuntimeFramework instance representing the runtime under + /// which the code is currently running. + /// + IRuntimeFramework CurrentFramework { get; } + /// /// Returns true if the runtime framework represented by /// the string passed as an argument is available. diff --git a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs index 4a5a6121b..e8ca54e31 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionAssemblyTests.cs @@ -1,62 +1,54 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; -using System.Reflection; +using System.IO; using NUnit.Engine.Extensibility; using NUnit.Framework; -namespace NUnit.Engine.Tests.Extensibility +#if NETFRAMEWORK +using FrameworkName = NUnit.Engine.Compatibility.FrameworkName; +#else +using FrameworkName = System.Runtime.Versioning.FrameworkName; +#endif + +namespace NUnit.Engine.Extensibility { + // TODO: This should actually give us 3.5 + [TestFixture("../net35/mock-assembly.dll", FrameworkIdentifiers.NetFramework, "2.0")] + [TestFixture("../netcoreapp2.1/mock-assembly.dll", FrameworkIdentifiers.NetCoreApp, "2.1")] + [TestFixture("../netcoreapp3.1/mock-assembly.dll", FrameworkIdentifiers.NetCoreApp, "3.1")] + [TestFixture("../net5.0/mock-assembly.dll", FrameworkIdentifiers.NetCoreApp, "5.0")] + [TestFixture("../net6.0/mock-assembly.dll", FrameworkIdentifiers.NetCoreApp, "6.0")] public class ExtensionAssemblyTests { - private static readonly Assembly THIS_ASSEMBLY = Assembly.GetExecutingAssembly(); - private static readonly string THIS_ASSEMBLY_PATH = THIS_ASSEMBLY.Location; - private static readonly string THIS_ASSEMBLY_FULL_NAME = THIS_ASSEMBLY.GetName().FullName; - private static readonly string THIS_ASSEMBLY_NAME = THIS_ASSEMBLY.GetName().Name; - private static readonly Version THIS_ASSEMBLY_VERSION = THIS_ASSEMBLY.GetName().Version; - + private string _assemblyPath; + private string _assemblyFileName; + private FrameworkName _expectedTargetRuntime; private ExtensionAssembly _ea; - [OneTimeSetUp] - public void CreateExtensionAssemblies() - { - _ea = new ExtensionAssembly(THIS_ASSEMBLY_PATH, false); - } - - [Test] - public void AssemblyDefinition() + public ExtensionAssemblyTests(string assemblyPath, string expectedRuntime, string expectedVersion) { - Assert.That(_ea.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_FULL_NAME)); + _assemblyPath = assemblyPath; + _assemblyFileName = Path.GetFileNameWithoutExtension(assemblyPath); + _expectedTargetRuntime = new FrameworkName(expectedRuntime, new Version(expectedVersion)); } - [Test] - public void MainModule() + [OneTimeSetUp] + public void CreateExtensionAssemblies() { - Assert.That(_ea.MainModule.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_FULL_NAME)); + _ea = new ExtensionAssembly(_assemblyPath, false); } [Test] public void AssemblyName() { - Assert.That(_ea.AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(_ea.AssemblyName, Is.EqualTo(_assemblyFileName)); } - [Test] - public void AssemblyVersion() - { - Assert.That(_ea.AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); - } - -#if NETFRAMEWORK [Test] public void TargetFramework() { - Assert.Multiple(() => - { - Assert.That(_ea.TargetFramework, Has.Property(nameof(RuntimeFramework.Runtime)).EqualTo(RuntimeType.Any)); - Assert.That(_ea.TargetFramework, Has.Property(nameof(RuntimeFramework.FrameworkVersion)).EqualTo(new Version(2, 0))); - }); + Assert.That(_ea.TargetRuntime, Is.EqualTo(_expectedTargetRuntime)); } -#endif } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionSelectorTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionSelectorTests.cs index 2d381c799..8fb4802a9 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionSelectorTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Extensibility/ExtensionSelectorTests.cs @@ -4,6 +4,7 @@ #if NETFRAMEWORK using System; using NSubstitute; +using NUnit.Engine.Compatibility; using NUnit.Engine.Extensibility; using NUnit.Framework; @@ -112,7 +113,7 @@ private static IExtensionAssembly MockExtension(string assemblyName = "Extension sub.AssemblyName.Returns(assemblyName); sub.AssemblyVersion.Returns(assemblyVersion ?? new Version(1, 0)); targetFramework = targetFramework ?? new Version(2, 0); - sub.TargetFramework.Returns(new RuntimeFramework(RuntimeType.Any, targetFramework)); + sub.TargetRuntime.Returns(new FrameworkName(FrameworkIdentifiers.NetFramework, targetFramework)); sub.FromWildCard.Returns(fromWildcard); return sub; } diff --git a/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs b/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs deleted file mode 100644 index cbf6398c2..000000000 --- a/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETFRAMEWORK -using System; -using System.Collections.Generic; -using NUnit.Framework; - -namespace NUnit.Engine -{ - [TestFixture] - public class RuntimeFrameworkTests - { - static RuntimeType currentRuntime = - Type.GetType("Mono.Runtime", false) != null - ? RuntimeType.Mono - : RuntimeType.Net; - - [Test] - public void CanGetCurrentFramework() - { - RuntimeFramework framework = RuntimeFramework.CurrentFramework; - - Assert.That(framework.Runtime, Is.EqualTo(currentRuntime)); - Assert.That(framework.ClrVersion, Is.EqualTo(Environment.Version)); - } - - [Test] - public void CurrentFrameworkHasBuildSpecified() - { - Assert.That(RuntimeFramework.CurrentFramework.ClrVersion.Build, Is.GreaterThan(0)); - } - - [Test] - public void CurrentFrameworkMustBeAvailable() - { - var current = RuntimeFramework.CurrentFramework; - Console.WriteLine("Current framework is {0} ({1})", current.DisplayName, current.Id); - Assert.That(current.IsAvailable, "{0} not available", current); - } - - [Test] - public void AvailableFrameworksList() - { - RuntimeFramework[] available = RuntimeFramework.AvailableFrameworks; - Assert.That(RuntimeFramework.AvailableFrameworks.Length, Is.GreaterThan(0) ); - foreach (var framework in RuntimeFramework.AvailableFrameworks) - Console.WriteLine("Available: {0}", framework.DisplayName); - } - - [Test] - public void AvailableFrameworksList_IncludesCurrentFramework() - { - foreach (var framework in RuntimeFramework.AvailableFrameworks) - if (RuntimeFramework.CurrentFramework.Supports(framework)) - return; - - Assert.Fail("CurrentFramework not listed as available"); - } - - [Test] - public void AvailableFrameworksList_ContainsNoDuplicates() - { - var names = new List(); - foreach (var framework in RuntimeFramework.AvailableFrameworks) - names.Add(framework.DisplayName); - Assert.That(names, Is.Unique); - } - - [TestCaseSource(nameof(frameworkData))] - public void CanCreateUsingFrameworkVersion(FrameworkData data) - { - RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); - Assert.That(framework.Runtime, Is.EqualTo(data.runtime)); - Assert.That(framework.FrameworkVersion, Is.EqualTo(data.frameworkVersion)); - Assert.That(framework.ClrVersion, Is.EqualTo(data.clrVersion)); - } - - [TestCaseSource(nameof(frameworkData))] - public void CanCreateUsingClrVersion(FrameworkData data) - { - // Versions 3.x and 4.5 or higher can't be created using CLR version - Assume.That(data.frameworkVersion.Major != 3); - Assume.That(data.frameworkVersion.Major != 4 || data.frameworkVersion.Minor == 0); - - - RuntimeFramework framework = new RuntimeFramework(data.runtime, data.clrVersion); - Assert.That(framework.Runtime, Is.EqualTo(data.runtime)); - Assert.That(framework.FrameworkVersion, Is.EqualTo(data.frameworkVersion)); - Assert.That(framework.ClrVersion, Is.EqualTo(data.clrVersion)); - } - - [TestCaseSource(nameof(frameworkData))] - public void CanParseRuntimeFramework(FrameworkData data) - { - RuntimeFramework framework = RuntimeFramework.Parse(data.representation); - Assert.That(framework.Runtime, Is.EqualTo(data.runtime)); - Assert.That(framework.ClrVersion, Is.EqualTo(data.clrVersion)); - } - - [TestCaseSource(nameof(frameworkData))] - public void CanDisplayFrameworkAsString(FrameworkData data) - { - RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); - Assert.That(framework.ToString(), Is.EqualTo(data.representation)); - Assert.That(framework.DisplayName, Is.EqualTo(data.displayName)); - } - - [TestCaseSource(nameof(matchData))] - public bool CanMatchRuntimes(RuntimeFramework f1, RuntimeFramework f2) - { - return f1.Supports(f2); - } - - [TestCaseSource(nameof(CanLoadData))] - public bool CanLoad(RuntimeFramework f1, RuntimeFramework f2) - { - return f1.CanLoad(f2); - } - -#pragma warning disable 414 - static TestCaseData[] matchData = new TestCaseData[] { - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(3,5)), - new RuntimeFramework(RuntimeType.Net, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0)), - new RuntimeFramework(RuntimeType.Net, new Version(3,5))) - .Returns(false), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(3,5)), - new RuntimeFramework(RuntimeType.Net, new Version(3,5))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0)), - new RuntimeFramework(RuntimeType.Net, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0)), - new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727)), - new RuntimeFramework(RuntimeType.Net, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727)), - new RuntimeFramework(RuntimeType.Net, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0)), - new RuntimeFramework(RuntimeType.Mono, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Mono, new Version(2,0)), - new RuntimeFramework(RuntimeType.Net, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(4,0)), - new RuntimeFramework(RuntimeType.Mono, new Version(4,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Mono, new Version(4,0)), - new RuntimeFramework(RuntimeType.Net, new Version(4,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0)), - new RuntimeFramework(RuntimeType.Net, new Version(1,1))) - .Returns(false), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727)), - new RuntimeFramework(RuntimeType.Net, new Version(2,0,40607))) - .Returns(false), - new TestCaseData( - new RuntimeFramework(RuntimeType.Mono, new Version(1,1)), // non-existent version but it works - new RuntimeFramework(RuntimeType.Mono, new Version(1,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Mono, new Version(2,0)), - new RuntimeFramework(RuntimeType.Any, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Any, new Version(2,0)), - new RuntimeFramework(RuntimeType.Mono, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Any, new Version(2,0)), - new RuntimeFramework(RuntimeType.Any, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Any, new Version(2,0)), - new RuntimeFramework(RuntimeType.Any, new Version(4,0))) - .Returns(false), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, RuntimeFramework.DefaultVersion), - new RuntimeFramework(RuntimeType.Net, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0)), - new RuntimeFramework(RuntimeType.Net, RuntimeFramework.DefaultVersion)) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Any, RuntimeFramework.DefaultVersion), - new RuntimeFramework(RuntimeType.Net, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Net, new Version(2,0)), - new RuntimeFramework(RuntimeType.Any, RuntimeFramework.DefaultVersion)) - .Returns(true) - }; - - private static readonly TestCaseData[] CanLoadData = { - new TestCaseData( - new RuntimeFramework(RuntimeType.Any, new Version(2,0)), - new RuntimeFramework(RuntimeType.Any, new Version(2,0))) - .Returns(true), - new TestCaseData( - new RuntimeFramework(RuntimeType.Any, new Version(2,0)), - new RuntimeFramework(RuntimeType.Any, new Version(4,0))) - .Returns(false), - new TestCaseData( - new RuntimeFramework(RuntimeType.Any, new Version(4,0)), - new RuntimeFramework(RuntimeType.Any, new Version(2,0))) - .Returns(true) - }; -#pragma warning restore 414 - - public struct FrameworkData - { - public RuntimeType runtime; - public Version frameworkVersion; - public Version clrVersion; - public string representation; - public string displayName; - - public FrameworkData(RuntimeType runtime, Version frameworkVersion, Version clrVersion, - string representation, string displayName) - { - this.runtime = runtime; - this.frameworkVersion = frameworkVersion; - this.clrVersion = clrVersion; - this.representation = representation; - this.displayName = displayName; - } - - public override string ToString() - { - return string.Format("<{0}-{1}>", this.runtime, this.frameworkVersion); - } - } - -#pragma warning disable 414 - static FrameworkData[] frameworkData = new FrameworkData[] { - new FrameworkData(RuntimeType.Net, new Version(1,0), new Version(1,0,3705), "net-1.0", ".NET 1.0"), - new FrameworkData(RuntimeType.Net, new Version(1,1), new Version(1,1,4322), "net-1.1", ".NET 1.1"), - new FrameworkData(RuntimeType.Net, new Version(2,0), new Version(2,0,50727), "net-2.0", ".NET 2.0"), - new FrameworkData(RuntimeType.Net, new Version(3,0), new Version(2,0,50727), "net-3.0", ".NET 3.0"), - new FrameworkData(RuntimeType.Net, new Version(3,5), new Version(2,0,50727), "net-3.5", ".NET 3.5"), - new FrameworkData(RuntimeType.Net, new Version(4,0), new Version(4,0,30319), "net-4.0", ".NET 4.0"), - new FrameworkData(RuntimeType.Net, new Version(4,5), new Version(4,0,30319), "net-4.5", ".NET 4.5"), - new FrameworkData(RuntimeType.Net, new Version(4,5,1), new Version(4,0,30319), "net-4.5.1", ".NET 4.5.1"), - new FrameworkData(RuntimeType.Net, new Version(4,5,2), new Version(4,0,30319), "net-4.5.2", ".NET 4.5.2"), - new FrameworkData(RuntimeType.Net, new Version(4,6), new Version(4,0,30319), "net-4.6", ".NET 4.6"), - new FrameworkData(RuntimeType.Net, new Version(4,6,1), new Version(4,0,30319), "net-4.6.1", ".NET 4.6.1"), - new FrameworkData(RuntimeType.Net, new Version(4,6,2), new Version(4,0,30319), "net-4.6.2", ".NET 4.6.2"), - new FrameworkData(RuntimeType.Net, new Version(4,7), new Version(4,0,30319), "net-4.7", ".NET 4.7"), - new FrameworkData(RuntimeType.Net, new Version(4,7,1), new Version(4,0,30319), "net-4.7.1", ".NET 4.7.1"), - new FrameworkData(RuntimeType.Net, new Version(4,7,2), new Version(4,0,30319), "net-4.7.2", ".NET 4.7.2"), - new FrameworkData(RuntimeType.Net, new Version(4,8), new Version(4,0,30319), "net-4.8", ".NET 4.8"), - new FrameworkData(RuntimeType.Net, RuntimeFramework.DefaultVersion, RuntimeFramework.DefaultVersion, "net", ".NET"), - new FrameworkData(RuntimeType.Mono, new Version(1,0), new Version(1,1,4322), "mono-1.0", "Mono 1.0"), - new FrameworkData(RuntimeType.Mono, new Version(2,0), new Version(2,0,50727), "mono-2.0", "Mono 2.0"), - new FrameworkData(RuntimeType.Mono, new Version(3,5), new Version(2,0,50727), "mono-3.5", "Mono 3.5"), - new FrameworkData(RuntimeType.Mono, new Version(4,0), new Version(4,0,30319), "mono-4.0", "Mono 4.0"), - new FrameworkData(RuntimeType.Mono, RuntimeFramework.DefaultVersion, RuntimeFramework.DefaultVersion, "mono", "Mono"), - new FrameworkData(RuntimeType.Any, new Version(1,1), new Version(1,1,4322), "v1.1", "v1.1"), - new FrameworkData(RuntimeType.Any, new Version(2,0), new Version(2,0,50727), "v2.0", "v2.0"), - new FrameworkData(RuntimeType.Any, new Version(3,5), new Version(2,0,50727), "v3.5", "v3.5"), - new FrameworkData(RuntimeType.Any, new Version(4,0), new Version(4,0,30319), "v4.0", "v4.0"), - new FrameworkData(RuntimeType.Any, RuntimeFramework.DefaultVersion, RuntimeFramework.DefaultVersion, "any", "Any") - }; -#pragma warning restore 414 - } -} -#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs index ff614bca4..29251a975 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionAssembly.cs @@ -1,60 +1,57 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt using System; +using System.Collections.Generic; using System.IO; +using System.Runtime.Versioning; using Mono.Cecil; -using NUnit.Engine.Internal; +#if NETFRAMEWORK +using NUnit.Engine.Compatibility; +#endif namespace NUnit.Engine.Extensibility { internal class ExtensionAssembly : IExtensionAssembly, IDisposable { + private AssemblyDefinition _assemblyDefinition; + public ExtensionAssembly(string filePath, bool fromWildCard) { FilePath = filePath; FromWildCard = fromWildCard; - Assembly = GetAssemblyDefinition(); - } - - public string FilePath { get; } - public bool FromWildCard { get; } - public AssemblyDefinition Assembly { get; } - public string AssemblyName - { - get { return Assembly.Name.Name; } - } - - public Version AssemblyVersion - { - get { return Assembly.Name.Version; } - } - - public ModuleDefinition MainModule - { - get { return Assembly.MainModule; } - } - -#if NETFRAMEWORK - public RuntimeFramework TargetFramework - { - get { return new RuntimeFramework(RuntimeType.Any, Assembly.GetRuntimeVersion()); } - } -#endif - - private AssemblyDefinition GetAssemblyDefinition() - { var resolver = new DefaultAssemblyResolver(); resolver.AddSearchDirectory(Path.GetDirectoryName(FilePath)); resolver.AddSearchDirectory(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)); var parameters = new ReaderParameters { AssemblyResolver = resolver }; - return AssemblyDefinition.ReadAssembly(FilePath, parameters); + _assemblyDefinition = AssemblyDefinition.ReadAssembly(FilePath, parameters); } - public void Dispose() + public string FilePath { get; } + public bool FromWildCard { get; } + public string AssemblyName => _assemblyDefinition.Name.Name; + public Version AssemblyVersion => _assemblyDefinition.Name.Version; + public IEnumerable GetTypes() => _assemblyDefinition.MainModule.GetTypes(); + + public FrameworkName TargetRuntime { - Assembly?.Dispose(); + get + { + string frameworkName = _assemblyDefinition.GetFrameworkName(); + + if (frameworkName != null) + return new FrameworkName(frameworkName); + + // We rely on TargetRuntime being available for all assemblies later than .NET 2.0 + var runtimeVersion = _assemblyDefinition.GetRuntimeVersion(); + var frameworkVersion = new Version(runtimeVersion.Major, runtimeVersion.Minor); + return new FrameworkName(FrameworkIdentifiers.NetFramework, frameworkVersion); + } } + + + + public void Dispose() => _assemblyDefinition?.Dispose(); } } diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionManager.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionManager.cs index 7bae84f55..f68756529 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionManager.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionManager.cs @@ -416,23 +416,25 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) IRuntimeFramework assemblyTargetFramework = null; #if NETFRAMEWORK - var currentFramework = RuntimeFramework.CurrentFramework; - assemblyTargetFramework = assembly.TargetFramework; - if (!currentFramework.CanLoad(assemblyTargetFramework)) + // Use special properties provided by our backport of RuntimeInformation + Version currentVersion = RuntimeInformation.FrameworkVersion; + var frameworkName = assembly.TargetRuntime; + + if (frameworkName.Identifier != FrameworkIdentifiers.NetFramework || frameworkName.Version > currentVersion) { if (!assembly.FromWildCard) { - throw new NUnitEngineException($"Extension {assembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available."); + throw new NUnitEngineException($"Extension {assembly.FilePath} targets {frameworkName}, which is not available."); } else { - log.Info($"Assembly {assembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available. Assembly found via wildcard."); + log.Info($"Assembly {assembly.FilePath} targets {frameworkName}, which is not available. Assembly found via wildcard."); return; } } #endif - foreach (var type in assembly.MainModule.GetTypes()) + foreach (var type in assembly.GetTypes()) { CustomAttribute extensionAttr = type.GetAttribute("NUnit.Engine.Extensibility.ExtensionAttribute"); diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionSelector.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionSelector.cs index a008f39ae..85d80e4f4 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionSelector.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/ExtensionSelector.cs @@ -41,8 +41,8 @@ public static bool IsBetterVersionOf(this IExtensionAssembly first, IExtensionAs #if NETFRAMEWORK //Look at target runtime - var firstTargetRuntime = first.TargetFramework.FrameworkVersion; - var secondTargetRuntime = second.TargetFramework.FrameworkVersion; + var firstTargetRuntime = first.TargetRuntime.Version; + var secondTargetRuntime = second.TargetRuntime.Version; if (firstTargetRuntime > secondTargetRuntime) return true; diff --git a/src/NUnitEngine/nunit.engine.core/Extensibility/IExtensionAssembly.cs b/src/NUnitEngine/nunit.engine.core/Extensibility/IExtensionAssembly.cs index acbc91d39..1d5962bce 100644 --- a/src/NUnitEngine/nunit.engine.core/Extensibility/IExtensionAssembly.cs +++ b/src/NUnitEngine/nunit.engine.core/Extensibility/IExtensionAssembly.cs @@ -2,6 +2,12 @@ using System; +#if NETFRAMEWORK +using FrameworkName = NUnit.Engine.Compatibility.FrameworkName; +#else +using FrameworkName = System.Runtime.Versioning.FrameworkName; +#endif + namespace NUnit.Engine.Extensibility { internal interface IExtensionAssembly @@ -9,8 +15,6 @@ internal interface IExtensionAssembly bool FromWildCard { get; } string AssemblyName { get; } Version AssemblyVersion { get; } -#if NETFRAMEWORK - RuntimeFramework TargetFramework { get; } -#endif + FrameworkName TargetRuntime { get; } } } diff --git a/src/NUnitEngine/nunit.engine.core/FrameworkIdentifers.cs b/src/NUnitEngine/nunit.engine.core/FrameworkIdentifers.cs new file mode 100644 index 000000000..23101ec09 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/FrameworkIdentifers.cs @@ -0,0 +1,11 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +namespace NUnit.Engine +{ + public static class FrameworkIdentifiers + { + public const string NetFramework = ".NETFramework"; + public const string NetCoreApp = ".NETCoreApp"; + public const string NetStandard = ".NETStandard"; + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Internal/Backports/RuntimeInformation.cs b/src/NUnitEngine/nunit.engine.core/Internal/Backports/RuntimeInformation.cs new file mode 100644 index 000000000..cd3dedf3d --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Internal/Backports/RuntimeInformation.cs @@ -0,0 +1,108 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.IO; +using Microsoft.Win32; + +namespace NUnit.Engine.Internal.Backports +{ + /// + /// Partially replaces System.Runtime.InteropServices.RuntimeInformation + /// under the .NET Framework. Only FrameworkDescription is implemented. + /// + public static class RuntimeInformation + { + static RuntimeInformation() + { + bool isMono = Type.GetType("Mono.Runtime", false) != null; + + Version version = new Version(Environment.Version.Major, Environment.Version.Minor); + if (isMono) + { + FrameworkName = "Mono"; + + switch (version.Major) + { + case 1: + version = new Version(1, 0); + break; + case 2: + version = new Version(3, 5); + break; + } + } + else /* We must be on Windows, so registry is available */ + { + FrameworkName = ".NET Framework"; + + switch (version.Major) + { + case 2: + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework"); + if (key != null) + { + string installRoot = key.GetValue("InstallRoot") as string; + if (installRoot != null) + { + if (Directory.Exists(System.IO.Path.Combine(installRoot, "v3.5"))) + { + version = new Version(3, 5); + } + else if (Directory.Exists(System.IO.Path.Combine(installRoot, "v3.0"))) + { + version = new Version(3, 0); + } + } + } + break; + case 4: + key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"); + if (key != null) + { + version = new Version(4, 5); + int release = (int)key.GetValue("Release", 0); + foreach (var entry in ReleaseTable) + if (release >= entry.Release) + version = entry.Version; + } + break; + } + } + + FrameworkVersion = version; + } + + public static string FrameworkName { get; } + public static Version FrameworkVersion { get; } + public static string FrameworkDescription => $"{FrameworkName} {FrameworkVersion}"; + + + private struct MinimumRelease + { + public readonly int Release; + public readonly Version Version; + + public MinimumRelease(int release, Version version) + { + Release = release; + Version = version; + } + } + + private static readonly MinimumRelease[] ReleaseTable = new MinimumRelease[] + { + new MinimumRelease(378389, new Version(4, 5)), + new MinimumRelease(378675, new Version(4, 5, 1)), + new MinimumRelease(379893, new Version(4, 5, 2)), + new MinimumRelease(393295, new Version(4, 6)), + new MinimumRelease(394254, new Version(4, 6, 1)), + new MinimumRelease(394802, new Version(4, 6, 2)), + new MinimumRelease(460798, new Version(4, 7)), + new MinimumRelease(461308, new Version(4, 7, 1)), + new MinimumRelease(461808, new Version(4, 7, 2)), + new MinimumRelease(528040, new Version(4, 8)) + }; + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs deleted file mode 100644 index 33fc7116b..000000000 --- a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs +++ /dev/null @@ -1,746 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETFRAMEWORK -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using Microsoft.Win32; -using NUnit.Engine.Internal.RuntimeFrameworks; - -namespace NUnit.Engine -{ - /// - /// RuntimeFramework represents a particular version - /// of a common language runtime implementation. - /// - [Serializable] - public sealed class RuntimeFramework : IRuntimeFramework - { - // TODO: RuntimeFramework was originally created for use in - // a single-threaded environment. The introduction of parallel - // execution and especially parallel loading of tests has - // exposed some weaknesses. - // - // Ideally, we should remove all knowledge of the environment - // from RuntimeFramework. An instance of RuntimeFramework does - // not need to know, for example, if it is available on the - // current system. In the present architecture, that's really - // the job of the RuntimeFrameworkService. Other functions - // may actually belong in TestAgency. - // - // All the static properties of RuntimeFramework need to be - // examined for thread-safety, particularly CurrentFramework - // and AvailableFrameworks. The latter caused a problem with - // parallel loading, which has been fixed for now through a - // hack added to RuntimeFrameworkService. We may be able to - // move all this functionality to services, eliminating the - // use of public static properties here. - - /// - /// DefaultVersion is an empty Version, used to indicate that - /// NUnit should select the CLR version to use for the test. - /// - public static readonly Version DefaultVersion = new Version(0, 0); - - #region IRuntimeFramework Implementation - - /// - /// Gets the unique Id for this runtime, such as "net-4.5" - /// - public string Id - { - get - { - if (FrameworkVersion == DefaultVersion) - { - return Runtime.ToString().ToLower(); - } - else - { - string vstring = FrameworkVersion.ToString(); - if (Runtime == RuntimeType.Any) - return "v" + vstring; - else - return Runtime.ToString().ToLower() + "-" + vstring; - } - } - } - - /// - /// The type of this runtime framework - /// - public RuntimeType Runtime { get; private set; } - - /// - /// The framework version for this runtime framework - /// - public Version FrameworkVersion { get; private set; } - - /// - /// The CLR version for this runtime framework - /// - public Version ClrVersion { get; private set; } - - /// - /// The Profile for this framework, where relevant. - /// May be null and will have different sets of - /// values for each Runtime. - /// - public string Profile { get; private set; } - - /// - /// Returns the Display name for this framework - /// - public string DisplayName { get; private set; } - - #endregion - - private static RuntimeFramework _currentFramework; - private static List _availableFrameworks; - - private static readonly string DEFAULT_WINDOWS_MONO_DIR = - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Mono"); - - /// - /// Construct from a runtime type and version. If the version has - /// two parts, it is taken as a framework version. If it has three - /// or more, it is taken as a CLR version. In either case, the other - /// version is deduced based on the runtime type and provided version. - /// - /// The runtime type of the framework - /// The version of the framework - public RuntimeFramework(RuntimeType runtime, Version version) - : this(runtime, version, null) - { - } - - /// - /// Construct from a runtime type, version and profile. The version - /// may be either a framework version or a CLR version. If a CLR - /// version is provided, we try to deduce the framework version but - /// this may not always be successful, in which case a version of - /// 0.0 is used. - /// - /// The runtime type of the framework. - /// The version of the framework. - /// The profile of the framework. Null if unspecified. - public RuntimeFramework(RuntimeType runtime, Version version, string profile) - { - Runtime = runtime; - FrameworkVersion = ClrVersion = version; - - // Version 0.0 means any version so we can't deduce anything - if (version != DefaultVersion) - { - if (IsFrameworkVersion(version)) - ClrVersion = GetClrVersionForFramework(version); - else - FrameworkVersion = GetFrameworkVersionForClr(version); - } - - Profile = profile; - - DisplayName = GetDefaultDisplayName(runtime, FrameworkVersion, profile); - } - - private bool IsFrameworkVersion(Version v) - { - // All known framework versions have either two components or - // three. If three, then the Build is currently less than 3. - return v.Build < 3 && v.Revision == -1; - } - - private Version GetClrVersionForFramework(Version frameworkVersion) - { - switch (Runtime) - { - case RuntimeType.Net: - case RuntimeType.Any: - switch (frameworkVersion.Major) - { - case 1: - switch (frameworkVersion.Minor) - { - case 0: - return new Version(1, 0, 3705); - case 1: - return new Version(1, 1, 4322); - } - break; - case 2: - case 3: - return new Version(2, 0, 50727); - case 4: - return new Version(4, 0, 30319); - } - break; - case RuntimeType.Mono: - switch (frameworkVersion.Major) - { - case 1: - return new Version(1, 1, 4322); - case 2: - case 3: - return new Version(2, 0, 50727); - case 4: - return new Version(4, 0, 30319); - } - break; - case RuntimeType.NetCore: - switch (frameworkVersion.Major) - { - case 1: - case 2: - // For pre-3.0 versions of .NET Core, Environment.Version returns 4.0.30319.42000 - return new Version(4, 0, 30319); - case 3: - return new Version(3, 1, 10); - case 5: - return new Version(5, 0, 1); - case 6: - return new Version(6, 0, 0); - } - break; - } - - throw new ArgumentException("Unknown framework version " + frameworkVersion.ToString(), "version"); - } - - private Version GetFrameworkVersionForClr(Version clrVersion) - { - return Runtime == RuntimeType.Mono && clrVersion.Major == 1 - ? new Version(1, 0) - : new Version(clrVersion.Major, clrVersion.Minor); - } - - /// - /// Static method to return a RuntimeFramework object - /// for the framework that is currently in use. - /// - public static RuntimeFramework CurrentFramework - { - get - { - if (_currentFramework == null) - { - Type monoRuntimeType = Type.GetType("Mono.Runtime", false); - bool isMono = monoRuntimeType != null; - - RuntimeType runtime = isMono - ? RuntimeType.Mono - : RuntimeType.Net; - - int major = Environment.Version.Major; - int minor = Environment.Version.Minor; - - if (isMono) - { - switch (major) - { - case 1: - minor = 0; - break; - case 2: - major = 3; - minor = 5; - break; - } - } - else /* It's windows */ - if (major == 2) - { - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework"); - if (key != null) - { - string installRoot = key.GetValue("InstallRoot") as string; - if (installRoot != null) - { - if (Directory.Exists(Path.Combine(installRoot, "v3.5"))) - { - major = 3; - minor = 5; - } - else if (Directory.Exists(Path.Combine(installRoot, "v3.0"))) - { - major = 3; - minor = 0; - } - } - } - } - else if (major == 4 && Type.GetType("System.Reflection.AssemblyMetadataAttribute") != null) - { - minor = 5; - } - - _currentFramework = new RuntimeFramework(runtime, new Version(major, minor)); - _currentFramework.ClrVersion = Environment.Version; - - if (isMono) - { - if (MonoPrefix == null) - MonoPrefix = GetMonoPrefixFromAssembly(monoRuntimeType.Assembly); - - MethodInfo getDisplayNameMethod = monoRuntimeType.GetMethod( - "GetDisplayName", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding); - if (getDisplayNameMethod != null) - { - string displayName = (string)getDisplayNameMethod.Invoke(null, new object[0]); - - int space = displayName.IndexOf(' '); - if (space >= 3) // Minimum length of a version - { - string version = displayName.Substring(0, space); - displayName = "Mono " + version; - if (MonoVersion == null) - MonoVersion = new Version(version); - } - else - displayName = "Mono " + displayName; - - _currentFramework.DisplayName = displayName; - } - } - } - - return _currentFramework; - } - } - - /// - /// Gets an array of all available frameworks - /// - public static RuntimeFramework[] AvailableFrameworks - { - get - { - if (_availableFrameworks == null) - FindAvailableFrameworks(); - - return _availableFrameworks.ToArray(); - } - } - - /// - /// The version of Mono in use or null if no Mono runtime - /// is available on this machine. - /// - /// The mono version. - private static Version MonoVersion { get; set; } - - /// - /// The install directory where the version of mono in - /// use is located. Null if no Mono runtime is present. - /// - private static string MonoPrefix { get; set; } - - /// - /// The path to the mono executable, based on the - /// Mono prefix if available. Otherwise, uses "mono", - /// to invoke a script of that name. - /// - public static string MonoExePath - { - get - { - return MonoPrefix != null && Environment.OSVersion.Platform == PlatformID.Win32NT - ? Path.Combine(MonoPrefix, "bin/mono.exe") - : "mono"; - } - } - - /// - /// Returns true if the current RuntimeFramework is available. - /// In the current implementation, only Mono and Microsoft .NET - /// are supported. - /// - /// True if it's available, false if not - public bool IsAvailable - { - get - { - foreach (RuntimeFramework framework in AvailableFrameworks) - if (framework.Supports(this)) - return true; - - return false; - } - } - - /// - /// Parses a string representing a RuntimeFramework. - /// The string may be just a RuntimeType name or just - /// a Version or a hyphenated RuntimeType-Version or - /// a Version prefixed by 'v'. - /// - /// - /// - public static RuntimeFramework Parse(string s) - { - RuntimeType runtime = RuntimeType.Any; - Version version = DefaultVersion; - - string[] parts = s.Split(new char[] { '-' }); - if (parts.Length == 2) - { - runtime = (RuntimeType)System.Enum.Parse(typeof(RuntimeType), parts[0], true); - string vstring = parts[1]; - if (vstring != "") - version = new Version(vstring); - } - else if (char.ToLower(s[0]) == 'v') - { - version = new Version(s.Substring(1)); - } - else if (IsRuntimeTypeName(s)) - { - runtime = (RuntimeType)System.Enum.Parse(typeof(RuntimeType), s, true); - } - else - { - version = new Version(s); - } - - return new RuntimeFramework(runtime, version); - } - - public static bool TryParse(string s, out RuntimeFramework runtimeFramework) - { - try - { - runtimeFramework = Parse(s); - return true; - } - catch - { - runtimeFramework = null; - return false; - } - } - - /// - /// Overridden to return the short name of the framework - /// - /// - public override string ToString() - { - return this.Id; - } - - /// - /// Returns true if the current framework matches the - /// one supplied as an argument. Both the RuntimeType - /// and the version must match. - /// - /// Two RuntimeTypes match if they are equal, if either one - /// is RuntimeType.Any or if one is RuntimeType.Net and - /// the other is RuntimeType.Mono. - /// - /// Two versions match if all specified version components - /// are equal. Negative (i.e. unspecified) version - /// components are ignored. - /// - /// The RuntimeFramework to be matched. - /// true on match, otherwise false - public bool Supports(RuntimeFramework target) - { - if (!this.Supports(target.Runtime)) - return false; - - if (FrameworkVersion == DefaultVersion || target.FrameworkVersion == DefaultVersion) - return true; - - return VersionsMatch(this.ClrVersion, target.ClrVersion) - && this.FrameworkVersion.Major >= target.FrameworkVersion.Major - && this.FrameworkVersion.Minor >= target.FrameworkVersion.Minor; - } - - private bool Supports(RuntimeType targetRuntime) - { - if (this.Runtime == targetRuntime) - return true; - - if (this.Runtime == RuntimeType.Any || targetRuntime == RuntimeType.Any) - return true; - - if (this.Runtime == RuntimeType.Net && targetRuntime == RuntimeType.Mono) - return true; - - if (this.Runtime == RuntimeType.Mono && targetRuntime == RuntimeType.Net) - return true; - - return false; - } - - public bool CanLoad(IRuntimeFramework requested) - { - return FrameworkVersion >= requested.FrameworkVersion; - } - - private static bool IsRuntimeTypeName(string name) - { - foreach (string item in Enum.GetNames(typeof(RuntimeType))) - if (item.ToLower() == name.ToLower()) - return true; - - return false; - } - - private static string GetDefaultDisplayName(RuntimeType runtime, Version version, string profile) - { - string displayName; - - if (version == DefaultVersion) - displayName = GetRuntimeDisplayName(runtime); - else if (runtime == RuntimeType.Any) - displayName = "v" + version.ToString(); - else - displayName = GetRuntimeDisplayName(runtime) + " " + version.ToString(); - - if (!string.IsNullOrEmpty(profile) && profile != "Full") - displayName += " - " + profile; - - return displayName; - } - - private static string GetRuntimeDisplayName(RuntimeType runtime) - { - switch (runtime) - { - case RuntimeType.Net: - return ".NET"; - case RuntimeType.NetCore: - return ".NETCore"; - default: - return runtime.ToString(); - } - } - - private static bool VersionsMatch(Version v1, Version v2) - { - return v1.Major == v2.Major && - v1.Minor == v2.Minor && - (v1.Build < 0 || v2.Build < 0 || v1.Build == v2.Build) && - (v1.Revision < 0 || v2.Revision < 0 || v1.Revision == v2.Revision); - } - - private static string GetMonoPrefixFromAssembly(Assembly assembly) - { - string prefix = assembly.Location; - - // In all normal mono installations, there will be sufficient - // levels to complete the four iterations. But just in case - // files have been copied to some non-standard place, we check. - for (int i = 0; i < 4; i++) - { - string dir = Path.GetDirectoryName(prefix); - if (string.IsNullOrEmpty(dir)) break; - - prefix = dir; - } - - return prefix; - } - - private static void FindAvailableFrameworks() - { - _availableFrameworks = new List(); - - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - _availableFrameworks.AddRange(DotNetFrameworkLocator.FindDotNetFrameworks()); - - FindDefaultMonoFramework(); - FindDotNetCoreFrameworks(); - } - - private static void FindDefaultMonoFramework() - { - if (CurrentFramework.Runtime == RuntimeType.Mono) - UseCurrentMonoFramework(); - else - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - FindBestMonoFrameworkOnWindows(); - } - - private static void UseCurrentMonoFramework() - { - Debug.Assert(CurrentFramework.Runtime == RuntimeType.Mono && MonoPrefix != null && MonoVersion != null); - - // Multiple profiles are no longer supported with Mono 4.0 - if (MonoVersion.Major < 4 && FindAllMonoProfiles() > 0) - return; - - // If Mono 4.0+ or no profiles found, just use current runtime - _availableFrameworks.Add(RuntimeFramework.CurrentFramework); - } - - private static void FindBestMonoFrameworkOnWindows() - { - // First, look for recent frameworks that use the Software\Mono Key - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Mono"); - - if (key != null && (int)key.GetValue("Installed", 0) == 1) - { - string version = key.GetValue("Version") as string; - MonoPrefix = key.GetValue("SdkInstallRoot") as string; - - if (version != null) - { - MonoVersion = new Version(version); - AddMonoFramework(new Version(4, 5), null); - return; - } - } - - // Some later 3.x Mono releases didn't use the registry - // so check in standard location for them. - if (Directory.Exists(DEFAULT_WINDOWS_MONO_DIR)) - { - MonoPrefix = DEFAULT_WINDOWS_MONO_DIR; - AddMonoFramework(new Version(4, 5), null); - return; - } - - // Look in the Software\Novell key for older versions - key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); - if (key != null) - { - string version = key.GetValue("DefaultCLR") as string; - if (version != null) - { - RegistryKey subKey = key.OpenSubKey(version); - if (subKey != null) - { - MonoPrefix = subKey.GetValue("SdkInstallRoot") as string; - MonoVersion = new Version(version); - - FindAllMonoProfiles(); - } - } - } - } - - private static int FindAllMonoProfiles() - { - int count = 0; - - if (MonoPrefix != null) - { - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/1.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(1, 1, 4322), "1.0"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/2.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(2, 0), "2.0"); - count++; - } - - if (Directory.Exists(Path.Combine(MonoPrefix, "lib/mono/3.5"))) - { - AddMonoFramework(new Version(3, 5), "3.5"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(4, 0), "4.0"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.5/mscorlib.dll"))) - { - AddMonoFramework(new Version(4, 5), "4.5"); - count++; - } - } - - return count; - } - - private static void AddMonoFramework(Version frameworkVersion, string profile) - { - var framework = new RuntimeFramework(RuntimeType.Mono, frameworkVersion) - { - Profile = profile, - DisplayName = MonoVersion != null - ? "Mono " + MonoVersion.ToString() + " - " + profile + " Profile" - : "Mono - " + profile + " Profile" - }; - - _availableFrameworks.Add(framework); - } - - private static void FindDotNetCoreFrameworks() - { - const string WINDOWS_INSTALL_DIR = "C:\\Program Files\\dotnet\\"; - const string LINUX_INSTALL_DIR = "/usr/shared/dotnet/"; - string INSTALL_DIR = Path.DirectorySeparatorChar == '\\' - ? WINDOWS_INSTALL_DIR - : LINUX_INSTALL_DIR; - - if (!Directory.Exists(INSTALL_DIR)) - return; - if (!File.Exists(Path.Combine(INSTALL_DIR, "dotnet.exe"))) - return; - - string runtimeDir = Path.Combine(INSTALL_DIR, Path.Combine("shared", "Microsoft.NETCore.App")); - if (!Directory.Exists(runtimeDir)) - return; - - var dirList = new DirectoryInfo(runtimeDir).GetDirectories(); - var dirNames = new List(); - foreach (var dir in dirList) - dirNames.Add(dir.Name); - var runtimes = GetNetCoreRuntimesFromDirectoryNames(dirNames); - - _availableFrameworks.AddRange(runtimes); - } - - // Deal with oddly named directories, which may sometimes appear when previews are installed - internal static IList GetNetCoreRuntimesFromDirectoryNames(IEnumerable dirNames) - { - const string VERSION_CHARS = ".0123456789"; - var runtimes = new List(); - - foreach (string dirName in dirNames) - { - int len = 0; - foreach (char c in dirName) - { - if (VERSION_CHARS.IndexOf(c) >= 0) - len++; - else - break; - } - - if (len == 0) - continue; - - Version fullVersion = null; - try - { - fullVersion = new Version(dirName.Substring(0, len)); - } - catch - { - continue; - } - - var newVersion = new Version(fullVersion.Major, fullVersion.Minor); - int count = runtimes.Count; - if (count > 0 && runtimes[count - 1].FrameworkVersion == newVersion) - continue; - - runtimes.Add(new RuntimeFramework(RuntimeType.NetCore, newVersion)); - } - - return runtimes; - } - } -} -#endif diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeType.cs b/src/NUnitEngine/nunit.engine.core/RuntimeType.cs deleted file mode 100644 index 0594b6641..000000000 --- a/src/NUnitEngine/nunit.engine.core/RuntimeType.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.Engine -{ - /// - /// Enumeration identifying a common language - /// runtime implementation. - /// - public enum RuntimeType - { - /// Any supported runtime framework - Any, - /// Microsoft .NET Framework - Net, - /// Mono - Mono, - /// .NET Core - NetCore - } -} diff --git a/src/NUnitEngine/nunit.engine.tests/RuntimeFrameworkTests.cs b/src/NUnitEngine/nunit.engine.tests/RuntimeFrameworkTests.cs new file mode 100644 index 000000000..d91f62b82 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.tests/RuntimeFrameworkTests.cs @@ -0,0 +1,160 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace NUnit.Engine +{ + [TestFixture] + public class RuntimeFrameworkTests + { + [TestCaseSource(nameof(frameworkData))] + public void CanCreateUsingFrameworkVersion(FrameworkData data) + { + RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); + Assert.That(framework.Runtime, Is.EqualTo(data.runtime)); + Assert.That(framework.FrameworkVersion, Is.EqualTo(data.frameworkVersion)); + Assert.That(framework.ClrVersion, Is.EqualTo(data.clrVersion)); + } + + [TestCaseSource(nameof(frameworkData))] + public void CanParseRuntimeFramework(FrameworkData data) + { + RuntimeFramework framework = RuntimeFramework.Parse(data.representation); + Assert.That(framework.Runtime, Is.EqualTo(data.runtime)); + Assert.That(framework.ClrVersion, Is.EqualTo(data.clrVersion)); + } + + [TestCaseSource(nameof(frameworkData))] + public void CanDisplayFrameworkAsString(FrameworkData data) + { + RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); + Assert.That(framework.ToString(), Is.EqualTo(data.representation)); + Assert.That(framework.DisplayName, Is.EqualTo(data.displayName)); + } + + [TestCaseSource(nameof(matchData))] + public bool CanMatchRuntimes(RuntimeFramework f1, RuntimeFramework f2) + { + return f1.Supports(f2); + } + + [TestCaseSource(nameof(CanLoadData))] + public bool CanLoad(RuntimeFramework f1, RuntimeFramework f2) + { + return f1.CanLoad(f2); + } + +#pragma warning disable 414 + static TestCaseData[] matchData = new TestCaseData[] { + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(3,5)), + new RuntimeFramework(Runtime.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(2,0)), + new RuntimeFramework(Runtime.Net, new Version(3,5))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(3,5)), + new RuntimeFramework(Runtime.Net, new Version(3,5))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(2,0)), + new RuntimeFramework(Runtime.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(2,0)), + new RuntimeFramework(Runtime.Mono, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Mono, new Version(2,0)), + new RuntimeFramework(Runtime.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(4,0)), + new RuntimeFramework(Runtime.Mono, new Version(4,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Mono, new Version(4,0)), + new RuntimeFramework(Runtime.Net, new Version(4,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(2,0)), + new RuntimeFramework(Runtime.Net, new Version(1,1))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(Runtime.Mono, new Version(1,1)), // non-existent version but it works + new RuntimeFramework(Runtime.Mono, new Version(1,0))) + .Returns(true), + }; + + private static readonly TestCaseData[] CanLoadData = { + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(2,0)), + new RuntimeFramework(Runtime.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(2,0)), + new RuntimeFramework(Runtime.Net, new Version(4,0))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(Runtime.Net, new Version(4,0)), + new RuntimeFramework(Runtime.Net, new Version(2,0))) + .Returns(true) + }; +#pragma warning restore 414 + + public struct FrameworkData + { + public Runtime runtime; + public Version frameworkVersion; + public Version clrVersion; + public string representation; + public string displayName; + + public FrameworkData(Runtime runtime, Version frameworkVersion, Version clrVersion, + string representation, string displayName) + { + this.runtime = runtime; + this.frameworkVersion = frameworkVersion; + this.clrVersion = clrVersion; + this.representation = representation; + this.displayName = displayName; + } + + public override string ToString() + { + return string.Format("<{0}-{1}>", this.runtime, this.frameworkVersion); + } + } + +#pragma warning disable 414 + static FrameworkData[] frameworkData = new FrameworkData[] { + new FrameworkData(Runtime.Net, new Version(1,0), new Version(1,0,3705), "net-1.0", ".NET 1.0"), + new FrameworkData(Runtime.Net, new Version(1,1), new Version(1,1,4322), "net-1.1", ".NET 1.1"), + new FrameworkData(Runtime.Net, new Version(2,0), new Version(2,0,50727), "net-2.0", ".NET 2.0"), + new FrameworkData(Runtime.Net, new Version(3,0), new Version(2,0,50727), "net-3.0", ".NET 3.0"), + new FrameworkData(Runtime.Net, new Version(3,5), new Version(2,0,50727), "net-3.5", ".NET 3.5"), + new FrameworkData(Runtime.Net, new Version(4,0), new Version(4,0,30319), "net-4.0", ".NET 4.0"), + new FrameworkData(Runtime.Net, new Version(4,5), new Version(4,0,30319), "net-4.5", ".NET 4.5"), + new FrameworkData(Runtime.Net, new Version(4,5,1), new Version(4,0,30319), "net-4.5.1", ".NET 4.5.1"), + new FrameworkData(Runtime.Net, new Version(4,5,2), new Version(4,0,30319), "net-4.5.2", ".NET 4.5.2"), + new FrameworkData(Runtime.Net, new Version(4,6), new Version(4,0,30319), "net-4.6", ".NET 4.6"), + new FrameworkData(Runtime.Net, new Version(4,6,1), new Version(4,0,30319), "net-4.6.1", ".NET 4.6.1"), + new FrameworkData(Runtime.Net, new Version(4,6,2), new Version(4,0,30319), "net-4.6.2", ".NET 4.6.2"), + new FrameworkData(Runtime.Net, new Version(4,7), new Version(4,0,30319), "net-4.7", ".NET 4.7"), + new FrameworkData(Runtime.Net, new Version(4,7,1), new Version(4,0,30319), "net-4.7.1", ".NET 4.7.1"), + new FrameworkData(Runtime.Net, new Version(4,7,2), new Version(4,0,30319), "net-4.7.2", ".NET 4.7.2"), + new FrameworkData(Runtime.Net, new Version(4,8), new Version(4,0,30319), "net-4.8", ".NET 4.8"), + new FrameworkData(Runtime.Mono, new Version(1,0), new Version(1,1,4322), "mono-1.0", "Mono 1.0"), + new FrameworkData(Runtime.Mono, new Version(2,0), new Version(2,0,50727), "mono-2.0", "Mono 2.0"), + new FrameworkData(Runtime.Mono, new Version(3,5), new Version(2,0,50727), "mono-3.5", "Mono 3.5"), + new FrameworkData(Runtime.Mono, new Version(4,0), new Version(4,0,30319), "mono-4.0", "Mono 4.0"), + }; +#pragma warning restore 414 + } +} +#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs index b5b4ed323..2b0c9162a 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs @@ -72,11 +72,11 @@ public void DefaultValues(string framework) Assert.False(startInfo.LoadUserProfile, "LoadUserProfile"); var targetRuntime = RuntimeFramework.Parse(framework); - if (targetRuntime.Runtime == RuntimeType.Mono) + if (targetRuntime.Runtime == Runtime.Mono) { string monoOptions = "--runtime=v" + targetRuntime.ClrVersion.ToString(3); monoOptions += " --debug"; - Assert.That(startInfo.FileName, Is.EqualTo(RuntimeFramework.MonoExePath)); + Assert.That(startInfo.FileName, Is.EqualTo(RuntimeFrameworkService.MonoExePath)); Assert.That(startInfo.Arguments, Is.EqualTo( $"{monoOptions} \"{process.AgentExePath}\" {process.AgentArgs}")); } diff --git a/src/NUnitEngine/nunit.engine.tests/Services/FakeRuntimeService.cs b/src/NUnitEngine/nunit.engine.tests/Services/FakeRuntimeService.cs index 1cb3579d3..a5b745c71 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/FakeRuntimeService.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/FakeRuntimeService.cs @@ -1,9 +1,13 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using System.Collections.Generic; + namespace NUnit.Engine.Services { - public class FakeRuntimeService : FakeService, IRuntimeFrameworkService + public class FakeRuntimeService : FakeService, IRuntimeFrameworkService, IAvailableRuntimes { + public IRuntimeFramework CurrentFramework => throw new System.NotImplementedException(); + bool IRuntimeFrameworkService.IsAvailable(string framework) { return true; @@ -13,5 +17,7 @@ string IRuntimeFrameworkService.SelectRuntimeFramework(TestPackage package) { return string.Empty; } + + public IList AvailableRuntimes => throw new System.NotImplementedException(); } } diff --git a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs index c0f938387..12000790d 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs @@ -13,6 +13,16 @@ public class RuntimeFrameworkServiceTests { private RuntimeFrameworkService _runtimeService; + // We can do this because we currently only build under NETFRAMEWORK + private static Runtime _currentRuntime = + Type.GetType("Mono.Runtime", false) != null + ? Runtime.Mono + : Runtime.Net; + + // TODO: We cast IRuntimeFramework to RuntimeFramework in several + // places here. Ideally, we should deal with the interfaces but + // they would need to be changed to do that. For now, the casts are + // used since we may end up eliminating the RuntimeFramework class. [SetUp] public void CreateServiceContext() { @@ -47,6 +57,21 @@ public void SelectRuntimeFramework(string assemblyName, bool runAsX86) Assert.That(package.GetSetting("RunAsX86", false), Is.EqualTo(runAsX86)); } + [Test] + public void CanGetCurrentFramework() + { + var framework = _runtimeService.CurrentFramework as RuntimeFramework; + + Assert.That(framework.Runtime, Is.EqualTo(_currentRuntime)); + Assert.That(framework.ClrVersion, Is.EqualTo(Environment.Version)); + } + + [Test] + public void CurrentFrameworkHasBuildSpecified() + { + Assert.That(_runtimeService.CurrentFramework.ClrVersion.Build, Is.GreaterThan(0)); + } + [Test] public void AvailableFrameworks() { @@ -56,6 +81,34 @@ public void AvailableFrameworks() Console.WriteLine("Available: {0}", framework.DisplayName); } + [Test] + public void CurrentFrameworkMustBeAvailable() + { + var current = _runtimeService.CurrentFramework; + Console.WriteLine("Current framework is {0} ({1})", current.DisplayName, current.Id); + Assert.That(_runtimeService.IsAvailable(current.Id), "{0} not available", current); + } + + [Test] + public void AvailableFrameworksList_IncludesCurrentFramework() + { + var current = _runtimeService.CurrentFramework as RuntimeFramework; + foreach (var framework in _runtimeService.AvailableRuntimes) + if (current.Supports(framework as RuntimeFramework)) + return; + + Assert.Fail("CurrentFramework not listed as available"); + } + + [Test] + public void AvailableFrameworksList_ContainsNoDuplicates() + { + var names = new List(); + foreach (var framework in _runtimeService.AvailableRuntimes) + names.Add(framework.DisplayName); + Assert.That(names, Is.Unique); + } + [TestCase("mono", 2, 0, "net-4.0")] [TestCase("net", 2, 0, "net-4.0")] [TestCase("net", 3, 5, "net-4.0")] @@ -75,15 +128,15 @@ public void EngineOptionPreferredOverImageTarget(string framework, int majorVers public void RuntimeFrameworkIsSetForSubpackages() { //Runtime Service verifies that requested frameworks are available, therefore this test can only currently be run on platforms with both CLR v2 and v4 available - Assume.That(new RuntimeFramework(RuntimeType.Net, new Version("2.0.50727")), Has.Property(nameof(RuntimeFramework.IsAvailable)).True); - Assume.That(new RuntimeFramework(RuntimeType.Net, new Version("4.0.30319")), Has.Property(nameof(RuntimeFramework.IsAvailable)).True); + Assume.That(_runtimeService.IsAvailable("net-2.0")); + Assume.That(_runtimeService.IsAvailable("net-4.0")); var topLevelPackage = new TestPackage(new [] {"a.dll", "b.dll"}); var net20Package = topLevelPackage.SubPackages[0]; - net20Package.Settings.Add(InternalEnginePackageSettings.ImageRuntimeVersion, new Version("2.0.50727")); + net20Package.Settings.Add(InternalEnginePackageSettings.ImageRuntimeVersion, new Version("2.0")); var net40Package = topLevelPackage.SubPackages[1]; - net40Package.Settings.Add(InternalEnginePackageSettings.ImageRuntimeVersion, new Version("4.0.30319")); + net40Package.Settings.Add(InternalEnginePackageSettings.ImageRuntimeVersion, new Version("4.0")); _runtimeService.SelectRuntimeFramework(topLevelPackage); diff --git a/src/NUnitEngine/nunit.engine/Runtime.cs b/src/NUnitEngine/nunit.engine/Runtime.cs new file mode 100644 index 000000000..899c37559 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Runtime.cs @@ -0,0 +1,160 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +namespace NUnit.Engine +{ + /// + /// Runtime class represents a specific Runtime, which may be + /// available in one or more versions. To define new Runtimes, + /// add a new member to the RuntimeType enum and then update + /// the SetProperties method in this class. + /// + public abstract class Runtime + { + #region Static Properties and Methods + + // NOTE: The following are the only instances, which should + // ever exist, since the nested classes are private. + + /// Microsoft .NET Framework + public static Runtime Net { get; } = new NetFrameworkRuntime(); + + /// Mono + public static Runtime Mono { get; } = new MonoRuntime(); + + /// NetCore + public static Runtime NetCore { get; } = new NetCoreRuntime(); + + public static Runtime Parse(string s) + { + switch (s.ToLower()) + { + case "net": + return Runtime.Net; + case "mono": + return Runtime.Mono; + case "netcore": + return Runtime.NetCore; + default: + throw new NUnitEngineException($"Invalid runtime specified: {s}"); + } + } + + public static Runtime FromFrameworkIdentifier(string s) + { + switch (s) + { + case FrameworkIdentifiers.NetFramework: + return Runtime.Net; + case FrameworkIdentifiers.NetCoreApp: + return Runtime.NetCore; + case FrameworkIdentifiers.NetStandard: + throw new NUnitEngineException( + "Test assemblies must target a specific platform, rather than .NETStandard."); + } + + throw new NUnitEngineException("Unrecognized Target Framework Identifier: " + s); + } + + #endregion + + #region Absract Properties and Methods + + public abstract string DisplayName { get; } + + public abstract string FrameworkIdentifier { get; } + + public abstract bool Matches(Runtime targetRuntime); + + public abstract Version GetClrVersionForFramework(Version frameworkVersion); + + #endregion + + #region Nested Runtime Classes + + private class NetFrameworkRuntime : Runtime + { + public override string DisplayName => ".NET"; + public override string FrameworkIdentifier => FrameworkIdentifiers.NetFramework; + + public override string ToString() => "Net"; + public override bool Matches(Runtime targetRuntime) => targetRuntime is NetFrameworkRuntime; + + public override Version GetClrVersionForFramework(Version frameworkVersion) + { + switch (frameworkVersion.Major) + { + case 1: + switch (frameworkVersion.Minor) + { + case 0: + return new Version(1, 0, 3705); + case 1: + return new Version(1, 1, 4322); + } + break; + case 2: + case 3: + return new Version(2, 0, 50727); + case 4: + return new Version(4, 0, 30319); + } + + throw new ArgumentException($"Unknown version for .NET Framework: {frameworkVersion}", "version"); + } + } + + private class MonoRuntime : NetFrameworkRuntime + { + public override string DisplayName => "Mono"; + + public override string ToString() => "Mono"; + + public override Version GetClrVersionForFramework(Version frameworkVersion) + { + switch (frameworkVersion.Major) + { + case 1: + return new Version(1, 1, 4322); + case 2: + case 3: + return new Version(2, 0, 50727); + case 4: + return new Version(4, 0, 30319); + } + + throw new ArgumentException($"Unknown version for Mono runtime: {frameworkVersion}", "version"); + } + } + + private class NetCoreRuntime : Runtime + { + public override string DisplayName => ".NETCore"; + public override string FrameworkIdentifier => FrameworkIdentifiers.NetCoreApp; + + public override string ToString() => "NetCore"; + public override bool Matches(Runtime targetRuntime) => targetRuntime is NetCoreRuntime; + + public override Version GetClrVersionForFramework(Version frameworkVersion) + { + switch(frameworkVersion.Major) + { + case 1: + case 2: + return new Version(4, 0, 30319); + case 3: + return new Version(3, 1, 10); + case 5: + return new Version(5, 0, 1); + case 6: + return new Version(6, 0, 0); + } + + throw new ArgumentException($"Unknown .NET Core version: {frameworkVersion}", "version"); + } + } + + #endregion + } +} diff --git a/src/NUnitEngine/nunit.engine/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine/RuntimeFramework.cs new file mode 100644 index 000000000..040a5db82 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/RuntimeFramework.cs @@ -0,0 +1,220 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using Microsoft.Win32; +using NUnit.Common; +using NUnit.Engine.Compatibility; + +namespace NUnit.Engine +{ + /// + /// RuntimeFramework represents a particular version + /// of a common language runtime implementation. + /// + [Serializable] + public sealed class RuntimeFramework : IRuntimeFramework + { + #region Constructors + + /// + /// Construct from a Runtime and Version. + /// + /// A Runtime instance + /// The Version of the framework + public RuntimeFramework(Runtime runtime, Version version) + : this(runtime, version, null) + { + } + + /// + /// Construct from a Runtime, Version and profile. + /// + /// A Runtime instance. + /// The Version of the framework. + /// A string representing the profile of the framework. Null if unspecified. + public RuntimeFramework(Runtime runtime, Version version, string profile) + { + Guard.ArgumentNotNull(runtime, nameof(runtime)); + Guard.ArgumentValid(IsValidFrameworkVersion(version), $"{version} is not a valid framework version", nameof(version)); + + Runtime = runtime; + FrameworkVersion = ClrVersion = version; + ClrVersion = runtime.GetClrVersionForFramework(version); + + Profile = profile; + + DisplayName = GetDefaultDisplayName(runtime, FrameworkVersion, profile); + + FrameworkName = new FrameworkName(runtime.FrameworkIdentifier, FrameworkVersion); + } + + private bool IsValidFrameworkVersion(Version v) + { + // All known framework versions have either two components or + // three. If three, then the Build is currently less than 3. + return v.Major > 0 && v.Minor >= 0 && v.Build < 3 && v.Revision == -1; + } + + #endregion + + #region IRuntimeFramework Implementation + + /// + /// Gets the unique Id for this runtime, such as "net-4.5" + /// + public string Id => Runtime.ToString().ToLower() + "-" + FrameworkVersion.ToString(); + + public FrameworkName FrameworkName { get; } + + /// + /// The type of this runtime framework + /// + public Runtime Runtime { get; } + + /// + /// The framework version for this runtime framework + /// + public Version FrameworkVersion { get; private set; } + + /// + /// The CLR version for this runtime framework + /// + public Version ClrVersion { get; set; } + + /// + /// The Profile for this framework, where relevant. + /// May be null and will have different sets of + /// values for each Runtime. + /// + public string Profile { get; private set; } + + /// + /// Returns the Display name for this framework + /// + public string DisplayName { get; set; } + + #endregion + + /// + /// Parses a string representing a RuntimeFramework. + /// The string may be just a RuntimeType name or just + /// a Version or a hyphenated RuntimeType-Version or + /// a Version prefixed by 'v'. + /// + /// + /// + public static RuntimeFramework Parse(string s) + { + Guard.ArgumentNotNullOrEmpty(s, nameof(s)); + + string[] parts = s.Split(new char[] { '-' }); + Guard.ArgumentValid(parts.Length == 2 && parts[0].Length > 0 && parts[1].Length > 0, "RuntimeFramework id not in correct format", nameof(s)); + + var runtime = Runtime.Parse(parts[0]); + var version = new Version(parts[1]); + return new RuntimeFramework(runtime, version); + } + + public static bool TryParse(string s, out RuntimeFramework runtimeFramework) + { + try + { + runtimeFramework = Parse(s); + return true; + } + catch + { + runtimeFramework = null; + return false; + } + } + + public static RuntimeFramework FromFrameworkName(string frameworkName) + { + return FromFrameworkName(new FrameworkName(frameworkName)); + } + + public static RuntimeFramework FromFrameworkName(FrameworkName frameworkName) + { + return new RuntimeFramework(Runtime.FromFrameworkIdentifier(frameworkName.Identifier), frameworkName.Version, frameworkName.Profile); + } + + /// + /// Overridden to return the short name of the framework + /// + /// + public override string ToString() => Id; + + /// + /// Returns true if the current framework matches the + /// one supplied as an argument. Both the RuntimeType + /// and the version must match. + /// + /// Two RuntimeTypes match if they are equal, if either one + /// is RuntimeType.Any or if one is RuntimeType.Net and + /// the other is RuntimeType.Mono. + /// + /// Two versions match if all specified version components + /// are equal. Negative (i.e. unspecified) version + /// components are ignored. + /// + /// The RuntimeFramework to be matched. + /// true on match, otherwise false + public bool Supports(RuntimeFramework target) + { + if (!Runtime.Matches(target.Runtime)) + return false; + + return VersionsMatch(this.ClrVersion, target.ClrVersion) + && this.FrameworkVersion.Major >= target.FrameworkVersion.Major + && this.FrameworkVersion.Minor >= target.FrameworkVersion.Minor; + } + + public bool CanLoad(IRuntimeFramework requested) + { + return FrameworkVersion >= requested.FrameworkVersion; + } + + private static string GetDefaultDisplayName(Runtime runtime, Version version, string profile) + { + string displayName = $"{runtime.DisplayName} {version}"; + + if (!string.IsNullOrEmpty(profile) && profile != "Full") + displayName += " - " + profile; + + return displayName; + } + + private static bool VersionsMatch(Version v1, Version v2) + { + return v1.Major == v2.Major && + v1.Minor == v2.Minor && + (v1.Build < 0 || v2.Build < 0 || v1.Build == v2.Build) && + (v1.Revision < 0 || v2.Revision < 0 || v1.Revision == v2.Revision); + } + + private static string GetMonoPrefixFromAssembly(Assembly assembly) + { + string prefix = assembly.Location; + + // In all normal mono installations, there will be sufficient + // levels to complete the four iterations. But just in case + // files have been copied to some non-standard place, we check. + for (int i = 0; i < 4; i++) + { + string dir = Path.GetDirectoryName(prefix); + if (string.IsNullOrEmpty(dir)) break; + + prefix = dir; + } + + return prefix; + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index dcfd0f568..91cbce7f8 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -27,7 +27,7 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) bool loadUserProfile = package.GetSetting(EnginePackageSettings.LoadUserProfile, false); string workDirectory = package.GetSetting(EnginePackageSettings.WorkDirectory, string.Empty); - string agencyUrl = TargetRuntime.Runtime == RuntimeType.NetCore ? agency.TcpEndPoint : agency.RemotingUrl; + string agencyUrl = TargetRuntime.Runtime == Runtime.NetCore ? agency.TcpEndPoint : agency.RemotingUrl; AgentArgs = new StringBuilder($"{agentId} {agencyUrl} --pid={Process.GetCurrentProcess().Id}"); // Set options that need to be in effect before the package @@ -48,20 +48,20 @@ public AgentProcess(TestAgency agency, TestPackage package, Guid agentId) StartInfo.WorkingDirectory = Environment.CurrentDirectory; EnableRaisingEvents = true; - if (TargetRuntime.Runtime == RuntimeType.Mono) + if (TargetRuntime.Runtime == Runtime.Mono) { - StartInfo.FileName = RuntimeFramework.MonoExePath; + StartInfo.FileName = RuntimeFrameworkService.MonoExePath; string monoOptions = "--runtime=v" + TargetRuntime.ClrVersion.ToString(3); monoOptions += " --debug"; StartInfo.Arguments = string.Format("{0} \"{1}\" {2}", monoOptions, AgentExePath, AgentArgs); } - else if (TargetRuntime.Runtime == RuntimeType.Net) + else if (TargetRuntime.Runtime == Runtime.Net) { StartInfo.FileName = AgentExePath; StartInfo.Arguments = AgentArgs.ToString(); StartInfo.LoadUserProfile = loadUserProfile; } - else if (TargetRuntime.Runtime == RuntimeType.NetCore) + else if (TargetRuntime.Runtime == Runtime.NetCore) { StartInfo.FileName = "dotnet"; StartInfo.Arguments = $"{AgentExePath} {AgentArgs}"; @@ -117,15 +117,14 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re string agentName; string agentExtension; int major = targetRuntime.FrameworkVersion.Major; - switch (targetRuntime.Runtime) + switch (targetRuntime.Runtime.FrameworkIdentifier) { - case RuntimeType.Net: - case RuntimeType.Mono: + case FrameworkIdentifiers.NetFramework: runtimeDir = major >= 4 ? "net40" : "net20"; agentName = requires32Bit ? "nunit-agent-x86" : "nunit-agent"; agentExtension = ".exe"; break; - case RuntimeType.NetCore: + case FrameworkIdentifiers.NetCoreApp: runtimeDir = major >= 6 ? "net6.0" : major == 5 ? "net5.0" : "netcoreapp3.1"; agentName = "nunit-agent"; agentExtension = ".dll"; diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index bd5cecf99..633b0566b 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using Mono.Cecil; using NUnit.Common; using NUnit.Engine.Internal; @@ -13,22 +14,34 @@ namespace NUnit.Engine.Services { + using Microsoft.Win32; + using RuntimeLocators; + public class RuntimeFrameworkService : Service, IRuntimeFrameworkService, IAvailableRuntimes { static readonly Logger log = InternalTrace.GetLogger(typeof(RuntimeFrameworkService)); - // HACK: This line forces RuntimeFramework to initialize the static property - // AvailableFrameworks before it is accessed by multiple threads. See comment - // on RuntimeFramework class for a more detailled explanation. - static readonly RuntimeFramework[] _availableRuntimes = RuntimeFramework.AvailableFrameworks; + private List _availableRuntimes = new List(); + + /// + /// Gets a RuntimeFramework instance representing the runtime under + /// which the code is currently running. + /// + public IRuntimeFramework CurrentFramework { get; private set; } + + private static string MonoPrefix; + + /// + /// The path to the mono executable, if we are running on Mono. + /// + public static string MonoExePath => MonoPrefix != null && Environment.OSVersion.Platform == PlatformID.Win32NT + ? Path.Combine(MonoPrefix, "bin/mono.exe") + : "mono"; /// /// Gets a list of available runtimes. /// - public IList AvailableRuntimes - { - get { return _availableRuntimes; } - } + public IList AvailableRuntimes => _availableRuntimes.ToArray(); /// /// Returns true if the runtime framework represented by @@ -43,7 +56,7 @@ public bool IsAvailable(string name) if (!RuntimeFramework.TryParse(name, out RuntimeFramework requestedFramework)) throw new NUnitEngineException("Invalid or unknown framework requested: " + name); - foreach (var framework in RuntimeFramework.AvailableFrameworks) + foreach (var framework in _availableRuntimes) if (FrameworksMatch(requestedFramework, framework)) return true; @@ -69,15 +82,15 @@ private static bool FrameworksMatch(RuntimeFramework requested, RuntimeFramework (requestedVersion.Revision < 0 || availableVersion.Revision < 0 || requestedVersion.Revision == availableVersion.Revision); } - private static bool RuntimesMatch(RuntimeType requested, RuntimeType available) + private static bool RuntimesMatch(Runtime requested, Runtime available) { - if (requested == available || requested == RuntimeType.Any) + if (requested == available) return true; - if (requested == RuntimeType.Net && available == RuntimeType.Mono) + if (requested == Runtime.Net && available == Runtime.Mono) return true; - if (requested == RuntimeType.Mono && available == RuntimeType.Net) + if (requested == Runtime.Mono && available == Runtime.Net) return true; return false; @@ -108,7 +121,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) } // Examine the provided settings - RuntimeFramework currentFramework = RuntimeFramework.CurrentFramework; + IRuntimeFramework currentFramework = CurrentFramework; log.Debug("Current framework is " + currentFramework); string frameworkSetting = package.GetSetting(EnginePackageSettings.RequestedRuntimeFramework, ""); @@ -131,14 +144,15 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) log.Debug($"No specific framework requested for {package.Name}"); string imageTargetFrameworkNameSetting = package.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, ""); - RuntimeType targetRuntime; + Runtime targetRuntime; Version targetVersion; if (string.IsNullOrEmpty(imageTargetFrameworkNameSetting)) { // Assume .NET Framework - targetRuntime = currentFramework.Runtime; - targetVersion = package.GetSetting(InternalEnginePackageSettings.ImageRuntimeVersion, new Version(2, 0)); + targetRuntime = Runtime.Net; + var trialVersion = package.GetSetting(InternalEnginePackageSettings.ImageRuntimeVersion, new Version(2, 0)); + targetVersion = new Version(trialVersion.Major, trialVersion.Minor); } else { @@ -147,10 +161,10 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) switch (frameworkName.Identifier) { case ".NETFramework": - targetRuntime = RuntimeType.Net; + targetRuntime = Runtime.Net; break; case ".NETCoreApp": - targetRuntime = RuntimeType.NetCore; + targetRuntime = Runtime.NetCore; break; default: throw new NUnitEngineException("Unsupported Target Framework: " + imageTargetFrameworkNameSetting); @@ -159,7 +173,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) targetVersion = frameworkName.Version; } - if (!new RuntimeFramework(targetRuntime, targetVersion).IsAvailable) + if (!IsAvailable(new RuntimeFramework(targetRuntime, targetVersion).Id)) { log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); if (targetVersion < currentFramework.FrameworkVersion) @@ -173,6 +187,21 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) return targetFramework; } + public override void StartService() + { + try + { + SetCurrentFramework(); + FindAvailableRuntimes(); + } + catch + { + Status = ServiceStatus.Error; + throw; + } + + Status = ServiceStatus.Started; + } /// /// Returns the best available framework that matches a target framework. @@ -194,6 +223,118 @@ public RuntimeFramework GetBestAvailableFramework(RuntimeFramework target) return result; } + private void SetCurrentFramework() + { + Type monoRuntimeType = Type.GetType("Mono.Runtime", false); + bool isMono = monoRuntimeType != null; + + Runtime runtime = isMono + ? Runtime.Mono + : Runtime.Net; + + int major = Environment.Version.Major; + int minor = Environment.Version.Minor; + + if (isMono) + { + switch (major) + { + case 1: + minor = 0; + break; + case 2: + major = 3; + minor = 5; + break; + } + } + else /* It's windows */ + if (major == 2) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework"); + if (key != null) + { + string installRoot = key.GetValue("InstallRoot") as string; + if (installRoot != null) + { + if (Directory.Exists(Path.Combine(installRoot, "v3.5"))) + { + major = 3; + minor = 5; + } + else if (Directory.Exists(Path.Combine(installRoot, "v3.0"))) + { + major = 3; + minor = 0; + } + } + } + } + else if (major == 4 && Type.GetType("System.Reflection.AssemblyMetadataAttribute") != null) + { + minor = 5; + } + + var currentFramework = new RuntimeFramework(runtime, new Version(major, minor)) + { + ClrVersion = Environment.Version + }; + + if (isMono) + { + MonoPrefix = GetMonoPrefixFromAssembly(monoRuntimeType.Assembly); + + MethodInfo getDisplayNameMethod = monoRuntimeType.GetMethod( + "GetDisplayName", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding); + if (getDisplayNameMethod != null) + { + string displayName = (string)getDisplayNameMethod.Invoke(null, new object[0]); + + int space = displayName.IndexOf(' '); + if (space >= 3) // Minimum length of a version + { + string version = displayName.Substring(0, space); + displayName = "Mono " + version; + } + else + displayName = "Mono " + displayName; + + currentFramework.DisplayName = displayName; + } + } + + CurrentFramework = currentFramework; + } + + private static string GetMonoPrefixFromAssembly(Assembly assembly) + { + string prefix = assembly.Location; + + // In all normal mono installations, there will be sufficient + // levels to complete the four iterations. But just in case + // files have been copied to some non-standard place, we check. + for (int i = 0; i < 4; i++) + { + string dir = Path.GetDirectoryName(prefix); + if (string.IsNullOrEmpty(dir)) break; + + prefix = dir; + } + + return prefix; + } + + private void FindAvailableRuntimes() + { + _availableRuntimes = new List(); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + _availableRuntimes.AddRange(NetFxRuntimeLocator.FindRuntimes()); + + //FindDefaultMonoFramework(); + _availableRuntimes.AddRange(NetCoreRuntimeLocator.FindRuntimes()); + } + /// /// Use Mono.Cecil to get information about all assemblies and /// apply it to the package using special internal keywords. diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs new file mode 100644 index 000000000..da9a919d6 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs @@ -0,0 +1,137 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK && NYI +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class MonoRuntimeLocator + { + //public static IEnumerable FindRuntimes() + //{ + // var current = RuntimeFramework.CurrentFramework; + // if (current.Runtime == Runtime.Mono) + // { + // yield return current; + // } + // else + // if (Environment.OSVersion.Platform == PlatformID.Win32NT) + // FindBestMonoFrameworkOnWindows(); + //} + + //private static void UseCurrentMonoFramework() + //{ + // Debug.Assert(CurrentFramework.Runtime == Runtime.Mono && MonoPrefix != null && MonoVersion != null); + + // // Multiple profiles are no longer supported with Mono 4.0 + // if (RuntimeFramework.MonoVersion.Major < 4 && FindAllMonoProfiles() > 0) + // return; + + // // If Mono 4.0+ or no profiles found, just use current runtime + // _availableFrameworks.Add(RuntimeFramework.CurrentFramework); + //} + + //private static void FindBestMonoFrameworkOnWindows() + //{ + // // First, look for recent frameworks that use the Software\Mono Key + // RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Mono"); + + // if (key != null && (int)key.GetValue("Installed", 0) == 1) + // { + // string version = key.GetValue("Version") as string; + // MonoPrefix = key.GetValue("SdkInstallRoot") as string; + + // if (version != null) + // { + // MonoVersion = new Version(version); + // AddMonoFramework(new Version(4, 5), null); + // return; + // } + // } + + // // Some later 3.x Mono releases didn't use the registry + // // so check in standard location for them. + // if (Directory.Exists(DEFAULT_WINDOWS_MONO_DIR)) + // { + // MonoPrefix = DEFAULT_WINDOWS_MONO_DIR; + // AddMonoFramework(new Version(4, 5), null); + // return; + // } + + // // Look in the Software\Novell key for older versions + // key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); + // if (key != null) + // { + // string version = key.GetValue("DefaultCLR") as string; + // if (version != null) + // { + // RegistryKey subKey = key.OpenSubKey(version); + // if (subKey != null) + // { + // MonoPrefix = subKey.GetValue("SdkInstallRoot") as string; + // MonoVersion = new Version(version); + + // FindAllMonoProfiles(); + // } + // } + // } + //} + + //private static int FindAllMonoProfiles() + //{ + // int count = 0; + + // if (MonoPrefix != null) + // { + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/1.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(1, 1, 4322), "1.0"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/2.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(2, 0), "2.0"); + // count++; + // } + + // if (Directory.Exists(Path.Combine(MonoPrefix, "lib/mono/3.5"))) + // { + // AddMonoFramework(new Version(3, 5), "3.5"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(4, 0), "4.0"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.5/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(4, 5), "4.5"); + // count++; + // } + // } + + // return count; + //} + + //private static void AddMonoFramework(Version frameworkVersion, string profile) + //{ + // var framework = new RuntimeFramework(Runtime.Mono, frameworkVersion) + // { + // Profile = profile, + // DisplayName = MonoVersion != null + // ? "Mono " + MonoVersion.ToString() + " - " + profile + " Profile" + // : "Mono - " + profile + " Profile" + // }; + + // _availableFrameworks.Add(framework); + //} + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs new file mode 100644 index 000000000..070d21b3a --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -0,0 +1,77 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using System.IO; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class NetCoreRuntimeLocator + { + public static IEnumerable FindRuntimes() + { + const string WINDOWS_INSTALL_DIR = "C:\\Program Files\\dotnet\\"; + const string LINUX_INSTALL_DIR = "/usr/shared/dotnet/"; + string INSTALL_DIR = Path.DirectorySeparatorChar == '\\' + ? WINDOWS_INSTALL_DIR + : LINUX_INSTALL_DIR; + string runtimeDir = Path.Combine(INSTALL_DIR, Path.Combine("shared", "Microsoft.NETCore.App")); + + if (Directory.Exists(INSTALL_DIR) && + File.Exists(Path.Combine(INSTALL_DIR, "dotnet.exe")) && + Directory.Exists(runtimeDir)) + { + var dirList = new DirectoryInfo(runtimeDir).GetDirectories(); + var dirNames = new List(); + foreach (var dir in dirList) + dirNames.Add(dir.Name); + + foreach (var runtime in GetNetCoreRuntimesFromDirectoryNames(dirNames)) + yield return runtime; + } + } + + // Deal with oddly named directories, which may sometimes appear when previews are installed + internal static IList GetNetCoreRuntimesFromDirectoryNames(IEnumerable dirNames) + { + const string VERSION_CHARS = ".0123456789"; + var runtimes = new List(); + + foreach (string dirName in dirNames) + { + int len = 0; + foreach (char c in dirName) + { + if (VERSION_CHARS.IndexOf(c) >= 0) + len++; + else + break; + } + + if (len == 0) + continue; + + Version fullVersion = null; + try + { + fullVersion = new Version(dirName.Substring(0, len)); + } + catch + { + continue; + } + + var newVersion = new Version(fullVersion.Major, fullVersion.Minor); + int count = runtimes.Count; + if (count > 0 && runtimes[count - 1].FrameworkVersion == newVersion) + continue; + + runtimes.Add(new RuntimeFramework(Runtime.NetCore, newVersion)); + } + + return runtimes; + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine.core/Internal/RuntimeFrameworks/DotNetFrameworkLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs similarity index 90% rename from src/NUnitEngine/nunit.engine.core/Internal/RuntimeFrameworks/DotNetFrameworkLocator.cs rename to src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs index 92560e38a..1c7c6cd2a 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/RuntimeFrameworks/DotNetFrameworkLocator.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs @@ -5,14 +5,14 @@ using System.Collections.Generic; using Microsoft.Win32; -namespace NUnit.Engine.Internal.RuntimeFrameworks +namespace NUnit.Engine.Services.RuntimeLocators { - internal static class DotNetFrameworkLocator + public static class NetFxRuntimeLocator { // Note: this method cannot be generalized past V4, because (a) it has // specific code for detecting .NET 4.5 and (b) we don't know what // microsoft will do in the future - public static IEnumerable FindDotNetFrameworks() + public static IEnumerable FindRuntimes() { // Handle Version 1.0, using a different registry key foreach (var framework in FindExtremelyOldDotNetFrameworkVersions()) @@ -39,7 +39,7 @@ public static IEnumerable FindDotNetFrameworks() else if (CheckInstallDword(versionKey)) { // Versions 1.1, 2.0, 3.0 and 3.5 are possible here - yield return new RuntimeFramework(RuntimeType.Net, new Version(name.Substring(1, 3))); + yield return new RuntimeFramework(Runtime.Net, new Version(name.Substring(1, 3))); } } } @@ -53,7 +53,7 @@ private static IEnumerable FindExtremelyOldDotNetFrameworkVers yield break; foreach (var build in key.GetValueNames()) - yield return new RuntimeFramework(RuntimeType.Net, new Version("1.0." + build)); + yield return new RuntimeFramework(Runtime.Net, new Version("1.0." + build)); } private struct MinimumRelease @@ -91,12 +91,12 @@ private static IEnumerable FindDotNetFourFrameworkVersions(Reg if (CheckInstallDword(profileKey)) { - yield return new RuntimeFramework(RuntimeType.Net, new Version(4, 0), profile); + yield return new RuntimeFramework(Runtime.Net, new Version(4, 0), profile); var release = (int)profileKey.GetValue("Release", 0); foreach (var entry in ReleaseTable) if (release >= entry.Release) - yield return new RuntimeFramework(RuntimeType.Net, entry.Version); + yield return new RuntimeFramework(Runtime.Net, entry.Version); yield break; //If full profile found don't check for client profile diff --git a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs index 4f0591c19..42daca7c6 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs @@ -29,6 +29,7 @@ public partial class TestAgency : ITestAgency, IService private readonly AgentStore _agentStore = new AgentStore(); private IRuntimeFrameworkService _runtimeService; + private IAvailableRuntimes _availableRuntimeService; // Transports used for various target runtimes private TestAgencyRemotingTransport _remotingTransport; // .NET Framework @@ -62,7 +63,7 @@ public ITestAgent GetAgent(TestPackage package) if (!_runtimeService.IsAvailable(targetRuntime.Id)) { string msg = $"The {targetRuntime} framework is not available.\r\nAvailable frameworks:"; - foreach (var runtime in RuntimeFramework.AvailableFrameworks) + foreach (var runtime in _availableRuntimeService.AvailableRuntimes) msg += $" {runtime}"; throw new ArgumentException(msg); } @@ -179,14 +180,19 @@ public void StopService() public void StartService() { _runtimeService = ServiceContext.GetService(); - if (_runtimeService == null) + _availableRuntimeService = ServiceContext.GetService(); + + if (_runtimeService == null || _availableRuntimeService == null) + { Status = ServiceStatus.Error; - else - try - { + return; + } + + try + { _remotingTransport.Start(); - _tcpTransport.Start(); - Status = ServiceStatus.Started; + _tcpTransport.Start(); + Status = ServiceStatus.Started; } catch {