Skip to content

Commit b7a793f

Browse files
committed
Handle prerelease dotnet runtimes correctly
1 parent 7200e37 commit b7a793f

File tree

3 files changed

+267
-139
lines changed

3 files changed

+267
-139
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt
2+
3+
using System;
4+
using System.IO;
5+
using System.Collections.Generic;
6+
using NUnit.Framework;
7+
using System.Linq;
8+
9+
namespace NUnit.Engine
10+
{
11+
public static class DotNetHelperTests
12+
{
13+
[Test]
14+
public static void CanGetInstallDirectory([Values] bool x86)
15+
{
16+
string path = DotNet.GetInstallDirectory(x86);
17+
Assert.That(Directory.Exists(path));
18+
Assert.That(File.Exists(Path.Combine(path, OS.IsWindows ? "dotnet.exe" : "dotnet")));
19+
}
20+
21+
[Test]
22+
public static void CanGetExecutable([Values] bool x86)
23+
{
24+
string path = DotNet.GetDotnetExecutable(x86);
25+
Assert.That(File.Exists(path));
26+
Assert.That(Path.GetFileName(path), Is.EqualTo(OS.IsWindows ? "dotnet.exe" : "dotnet"));
27+
}
28+
29+
[Test]
30+
public static void CanIssueDotNetCommand([Values] bool x86)
31+
{
32+
var output = DotNet.DotnetCommand("--help", x86);
33+
Assert.That(output.Count(), Is.GreaterThan(0));
34+
}
35+
36+
[TestCaseSource(nameof(RuntimeCases))]
37+
public static void CanParseInputLine(string line, string name, string packageVersion, string path,
38+
bool isPreRelease, Version version, string suffix)
39+
{
40+
DotNet.RuntimeInfo runtime = DotNet.RuntimeInfo.Parse(line);
41+
Assert.That(runtime.Name, Is.EqualTo(name));
42+
Assert.That(runtime.PackageVersion, Is.EqualTo(packageVersion));
43+
Assert.That(runtime.Path, Is.EqualTo(path));
44+
Assert.That(runtime.IsPreRelease, Is.EqualTo(isPreRelease));
45+
Assert.That(runtime.Version, Is.EqualTo(version));
46+
Assert.That(runtime.PreReleaseSuffix, Is.EqualTo(suffix));
47+
}
48+
49+
static TestCaseData[] RuntimeCases = [
50+
new TestCaseData("Microsoft.NETCore.App 8.0.22 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]",
51+
"Microsoft.NETCore.App", "8.0.22", "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App",
52+
false, new Version(8,0,22), null),
53+
new TestCaseData("Microsoft.WindowsDesktop.App 9.0.11 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]",
54+
"Microsoft.WindowsDesktop.App", "9.0.11", "C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App",
55+
false, new Version(9,0,11), null),
56+
new TestCaseData("Microsoft.AspNetCore.App 7.0.20 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]",
57+
"Microsoft.AspNetCore.App", "7.0.20", "C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App",
58+
false, new Version(7,0,20), null),
59+
new TestCaseData("Microsoft.AspNetCore.App 9.0.0-rc.2.24474.3 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]",
60+
"Microsoft.AspNetCore.App", "9.0.0-rc.2.24474.3", "C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App",
61+
true, new Version(9,0,0), "rc.2.24474.3")];
62+
63+
[TestCase("Microsoft.NETCore.App", "8.0.0", "8.0.22")]
64+
[TestCase("Microsoft.NETCore.App", "8.0.0.0", "8.0.22")]
65+
[TestCase("Microsoft.NETCore.App", "8.0.0.100", "8.0.22")]
66+
[TestCase("Microsoft.NETCore.App", "8.0.100", "9.0.11")]
67+
[TestCase("Microsoft.AspNetCore.App", "5.0.0", "8.0.22")] // Rather than 8.0.2
68+
[TestCase("Microsoft.AspNetCore.App", "7.0.0", "8.0.22")] // Rather than 8.0.2
69+
[TestCase("Microsoft.AspNetCore.App", "8.0.0", "8.0.22")] // Rather than 8.0.2
70+
[TestCase("Microsoft.WindowsDesktop.App", "9.0.0", "9.0.11")] // Rather than the pre-release version
71+
[TestCase("Microsoft.WindowsDesktop.App", "10.0.0", "10.0.0-rc.2.25502.107")]
72+
public static void FindBestRuntimeTests(string runtimeName, string targetVersion, string expectedVersion)
73+
{
74+
var availableRuntimes = SimulatedListRuntimesOutput.Where(r => r.Name == runtimeName);
75+
Assert.That(DotNet.FindBestRuntime(new Version(targetVersion), availableRuntimes, out DotNet.RuntimeInfo bestRuntime));
76+
Assert.That(bestRuntime, Is.Not.Null);
77+
Assert.That(bestRuntime.PackageVersion, Is.EqualTo(expectedVersion));
78+
}
79+
80+
static DotNet.RuntimeInfo[] SimulatedListRuntimesOutput = [
81+
DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"),
82+
DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 8.0.22 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"),
83+
DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 9.0.0-rc.2.24474.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"),
84+
DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 9.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"),
85+
DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 10.0.0-rc.2.25502.107 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"),
86+
DotNet.RuntimeInfo.Parse(@"Microsoft.AspNetCore.App 10.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]"),
87+
DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 8.0.22 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"),
88+
DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 9.0.0-rc.2.24473.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"),
89+
DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 9.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"),
90+
DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 10.0.0-rc.2.25502.107 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"),
91+
DotNet.RuntimeInfo.Parse(@"Microsoft.NETCore.App 10.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]"),
92+
DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 8.0.22 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]"),
93+
DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 9.0.0-rc.2.24474.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]"),
94+
DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 9.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]"),
95+
DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 10.0.0-rc.2.25502.107 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]") ];
96+
//DotNet.RuntimeInfo.Parse(@"Microsoft.WindowsDesktop.App 10.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]") ];
97+
}
98+
}

src/NUnitEngine/nunit.engine.core/DotNetHelper.cs

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Diagnostics;
77
using System.IO;
88
using System.Linq;
9+
using System.Reflection;
10+
using System.Xml.Linq;
911

1012
namespace NUnit.Engine
1113
{
@@ -36,18 +38,43 @@ public static class DotNet
3638

3739
public class RuntimeInfo
3840
{
39-
public string Name;
40-
public Version Version;
41-
public string Path;
41+
public string Name { get; }
42+
public string PackageVersion { get; }
43+
public string Path { get; }
4244

43-
public RuntimeInfo(string name, string version, string path)
44-
: this(name, new Version(version), path) { }
45+
public bool IsPreRelease { get; }
46+
public Version Version { get; }
47+
public string PreReleaseSuffix { get; }
4548

46-
public RuntimeInfo(string name, Version version, string path)
49+
50+
public RuntimeInfo(string name, string packageVersion, string path)
4751
{
4852
Name = name;
49-
Version = version;
53+
PackageVersion = packageVersion;
5054
Path = path;
55+
56+
int dash = PackageVersion.IndexOf('-');
57+
IsPreRelease = dash > 0;
58+
59+
if (IsPreRelease)
60+
{
61+
Version = new Version(PackageVersion.Substring(0, dash));
62+
PreReleaseSuffix = PackageVersion.Substring(dash + 1);
63+
}
64+
else
65+
Version = new Version(packageVersion);
66+
}
67+
68+
/// <summary>
69+
/// Parses a single line from the --list-runtimes display to create
70+
/// an instance of DotNet.RuntimeInfo.
71+
/// </summary>
72+
/// <param name="line">Line from execution of dotnet --list-runtimes</param>
73+
/// <returns>A DotNet.RuntimeInfo</returns>
74+
public static RuntimeInfo Parse(string line)
75+
{
76+
string[] parts = line.Trim().Split([' '], 3);
77+
return new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']']));
5178
}
5279
}
5380

@@ -64,24 +91,43 @@ public static string GetInstallDirectory(bool x86) => x86
6491
/// </summary>
6592
/// <param name="x86">Flag indicating whether the X86 architecture is needed</param>
6693
/// <returns></returns>
67-
public static string GetDotnetExecutable(bool x86) => Path.Combine(GetInstallDirectory(x86), OS.IsWindows ? "dotnet.exe" : "dotnet");
94+
public static string GetDotnetExecutable(bool x86) =>
95+
Path.Combine(GetInstallDirectory(x86), OS.IsWindows ? "dotnet.exe" : "dotnet");
6896

6997
public static IEnumerable<RuntimeInfo> GetRuntimes(string name, bool x86)
7098
{
7199
var runtimes = x86 ? _x86Runtimes.Value : _x64Runtimes.Value;
72100
return runtimes.Where(r => r.Name == name);
73101
}
74102

75-
private static IEnumerable<RuntimeInfo> GetAllRuntimes(bool x86)
103+
public static bool FindBestRuntime(Version targetVersion, string name, bool x86, out RuntimeInfo bestRuntime) =>
104+
FindBestRuntime(targetVersion, GetRuntimes(name, x86), out bestRuntime);
105+
106+
// Separate internal method for testing
107+
internal static bool FindBestRuntime(Version targetVersion, IEnumerable<RuntimeInfo> availableRuntimes, out RuntimeInfo bestRuntime)
76108
{
77-
foreach (string line in DotnetCommand("--list-runtimes", x86: x86))
109+
bestRuntime = null;
110+
111+
if (targetVersion is null)
112+
return false;
113+
114+
foreach (var candidate in availableRuntimes)
78115
{
79-
string[] parts = line.Trim().Split([' '], 3);
80-
yield return new RuntimeInfo(parts[0], parts[1], parts[2].Trim(['[', ']']));
116+
if (candidate.Version >= targetVersion)
117+
if (bestRuntime is null || candidate.Version.Major == bestRuntime.Version.Major)
118+
bestRuntime = candidate;
81119
}
120+
121+
return bestRuntime is not null;
122+
}
123+
124+
private static IEnumerable<RuntimeInfo> GetAllRuntimes(bool x86)
125+
{
126+
foreach (string line in DotnetCommand("--list-runtimes", x86: x86))
127+
yield return RuntimeInfo.Parse(line);
82128
}
83129

84-
private static IEnumerable<string> DotnetCommand(string arguments, bool x86 = false)
130+
internal static IEnumerable<string> DotnetCommand(string arguments, bool x86 = false)
85131
{
86132
var process = new Process
87133
{

0 commit comments

Comments
 (0)