Skip to content

Commit 43cdbf2

Browse files
authored
Merge pull request #14142 from michaelnebel/csharp/dotnetunittests
C#: Re-factor Dotnet.cs to enable unit testing.
2 parents 45484c7 + a3da11a commit 43cdbf2

File tree

12 files changed

+372
-85
lines changed

12 files changed

+372
-85
lines changed

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public sealed class DependencyManager : IDisposable
2727
private int conflictedReferences = 0;
2828
private readonly IDependencyOptions options;
2929
private readonly DirectoryInfo sourceDir;
30-
private readonly DotNet dotnet;
30+
private readonly IDotNet dotnet;
3131
private readonly FileContent fileContent;
3232
private readonly TemporaryDirectory packageDirectory;
3333
private readonly TemporaryDirectory tempWorkingDirectory;
@@ -47,7 +47,7 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
4747

4848
try
4949
{
50-
this.dotnet = new DotNet(options, progressMonitor);
50+
this.dotnet = DotNet.Make(options, progressMonitor);
5151
}
5252
catch
5353
{

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs

Lines changed: 17 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics;
43
using System.IO;
54
using System.Linq;
65
using System.Text.RegularExpressions;
7-
using Semmle.Util;
86

97
namespace Semmle.Extraction.CSharp.DependencyFetching
108
{
@@ -13,63 +11,30 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
1311
/// </summary>
1412
internal partial class DotNet : IDotNet
1513
{
14+
private readonly IDotNetCliInvoker dotnetCliInvoker;
1615
private readonly ProgressMonitor progressMonitor;
17-
private readonly string dotnet;
1816

19-
public DotNet(IDependencyOptions options, ProgressMonitor progressMonitor)
17+
private DotNet(IDotNetCliInvoker dotnetCliInvoker, ProgressMonitor progressMonitor)
2018
{
2119
this.progressMonitor = progressMonitor;
22-
this.dotnet = Path.Combine(options.DotNetPath ?? string.Empty, "dotnet");
20+
this.dotnetCliInvoker = dotnetCliInvoker;
2321
Info();
2422
}
2523

26-
private void Info()
27-
{
28-
// TODO: make sure the below `dotnet` version is matching the one specified in global.json
29-
var res = RunCommand("--info");
30-
if (!res)
31-
{
32-
throw new Exception($"{dotnet} --info failed.");
33-
}
34-
}
24+
private DotNet(IDependencyOptions options, ProgressMonitor progressMonitor) : this(new DotNetCliInvoker(progressMonitor, Path.Combine(options.DotNetPath ?? string.Empty, "dotnet")), progressMonitor) { }
3525

36-
private ProcessStartInfo MakeDotnetStartInfo(string args, bool redirectStandardOutput)
37-
{
38-
var startInfo = new ProcessStartInfo(dotnet, args)
39-
{
40-
UseShellExecute = false,
41-
RedirectStandardOutput = redirectStandardOutput
42-
};
43-
// Set the .NET CLI language to English to avoid localized output.
44-
startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"] = "en";
45-
return startInfo;
46-
}
26+
internal static IDotNet Make(IDotNetCliInvoker dotnetCliInvoker, ProgressMonitor progressMonitor) => new DotNet(dotnetCliInvoker, progressMonitor);
4727

48-
private bool RunCommand(string args)
49-
{
50-
progressMonitor.RunningProcess($"{dotnet} {args}");
51-
using var proc = Process.Start(MakeDotnetStartInfo(args, redirectStandardOutput: false));
52-
proc?.WaitForExit();
53-
var exitCode = proc?.ExitCode ?? -1;
54-
if (exitCode != 0)
55-
{
56-
progressMonitor.CommandFailed(dotnet, args, exitCode);
57-
return false;
58-
}
59-
return true;
60-
}
28+
public static IDotNet Make(IDependencyOptions options, ProgressMonitor progressMonitor) => new DotNet(options, progressMonitor);
6129

62-
private bool RunCommand(string args, out IList<string> output)
30+
private void Info()
6331
{
64-
progressMonitor.RunningProcess($"{dotnet} {args}");
65-
var pi = MakeDotnetStartInfo(args, redirectStandardOutput: true);
66-
var exitCode = pi.ReadOutput(out output);
67-
if (exitCode != 0)
32+
// TODO: make sure the below `dotnet` version is matching the one specified in global.json
33+
var res = dotnetCliInvoker.RunCommand("--info");
34+
if (!res)
6835
{
69-
progressMonitor.CommandFailed(dotnet, args, exitCode);
70-
return false;
36+
throw new Exception($"{dotnetCliInvoker.Exec} --info failed.");
7137
}
72-
return true;
7338
}
7439

7540
private static string GetRestoreArgs(string projectOrSolutionFile, string packageDirectory) =>
@@ -82,7 +47,7 @@ public bool RestoreProjectToDirectory(string projectFile, string packageDirector
8247
{
8348
args += $" --configfile \"{pathToNugetConfig}\"";
8449
}
85-
var success = RunCommand(args, out var output);
50+
var success = dotnetCliInvoker.RunCommand(args, out var output);
8651
stdout = string.Join("\n", output);
8752
return success;
8853
}
@@ -91,7 +56,7 @@ public bool RestoreSolutionToDirectory(string solutionFile, string packageDirect
9156
{
9257
var args = GetRestoreArgs(solutionFile, packageDirectory);
9358
args += " --verbosity normal";
94-
if (RunCommand(args, out var output))
59+
if (dotnetCliInvoker.RunCommand(args, out var output))
9560
{
9661
var regex = RestoreProjectRegex();
9762
projects = output
@@ -108,13 +73,13 @@ public bool RestoreSolutionToDirectory(string solutionFile, string packageDirect
10873
public bool New(string folder)
10974
{
11075
var args = $"new console --no-restore --output \"{folder}\"";
111-
return RunCommand(args);
76+
return dotnetCliInvoker.RunCommand(args);
11277
}
11378

11479
public bool AddPackage(string folder, string package)
11580
{
11681
var args = $"add \"{folder}\" package \"{package}\" --no-restore";
117-
return RunCommand(args);
82+
return dotnetCliInvoker.RunCommand(args);
11883
}
11984

12085
public IList<string> GetListedRuntimes() => GetListed("--list-runtimes", "runtime");
@@ -123,7 +88,7 @@ public bool AddPackage(string folder, string package)
12388

12489
private IList<string> GetListed(string args, string artifact)
12590
{
126-
if (RunCommand(args, out var artifacts))
91+
if (dotnetCliInvoker.RunCommand(args, out var artifacts))
12792
{
12893
progressMonitor.LogInfo($"Found {artifact}s: {string.Join("\n", artifacts)}");
12994
return artifacts;
@@ -134,7 +99,7 @@ private IList<string> GetListed(string args, string artifact)
13499
public bool Exec(string execArgs)
135100
{
136101
var args = $"exec {execArgs}";
137-
return RunCommand(args);
102+
return dotnetCliInvoker.RunCommand(args);
138103
}
139104

140105
[GeneratedRegex("Restored\\s+(.+\\.csproj)", RegexOptions.Compiled)]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
using Semmle.Util;
4+
5+
namespace Semmle.Extraction.CSharp.DependencyFetching
6+
{
7+
/// <summary>
8+
/// Low level utilities to run the "dotnet" command.
9+
/// </summary>
10+
internal sealed class DotNetCliInvoker : IDotNetCliInvoker
11+
{
12+
private readonly ProgressMonitor progressMonitor;
13+
14+
public string Exec { get; }
15+
16+
public DotNetCliInvoker(ProgressMonitor progressMonitor, string exec)
17+
{
18+
this.progressMonitor = progressMonitor;
19+
this.Exec = exec;
20+
}
21+
22+
private ProcessStartInfo MakeDotnetStartInfo(string args, bool redirectStandardOutput)
23+
{
24+
var startInfo = new ProcessStartInfo(Exec, args)
25+
{
26+
UseShellExecute = false,
27+
RedirectStandardOutput = redirectStandardOutput
28+
};
29+
// Set the .NET CLI language to English to avoid localized output.
30+
startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"] = "en";
31+
return startInfo;
32+
}
33+
34+
private bool RunCommandAux(string args, bool redirectStandardOutput, out IList<string> output)
35+
{
36+
progressMonitor.RunningProcess($"{Exec} {args}");
37+
var pi = MakeDotnetStartInfo(args, redirectStandardOutput);
38+
var exitCode = pi.ReadOutput(out output);
39+
if (exitCode != 0)
40+
{
41+
progressMonitor.CommandFailed(Exec, args, exitCode);
42+
return false;
43+
}
44+
return true;
45+
}
46+
47+
public bool RunCommand(string args) =>
48+
RunCommandAux(args, redirectStandardOutput: false, out _);
49+
50+
public bool RunCommand(string args, out IList<string> output) =>
51+
RunCommandAux(args, redirectStandardOutput: true, out output);
52+
}
53+
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotnetVersion.cs renamed to csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetVersion.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Semmle.Extraction.CSharp.DependencyFetching
55
{
6-
internal record DotnetVersion : IComparable<DotnetVersion>
6+
internal record DotNetVersion : IComparable<DotNetVersion>
77
{
88
private readonly string dir;
99
private readonly Version version;
@@ -48,7 +48,7 @@ public string? FullPathReferenceAssemblies
4848
}
4949

5050

51-
public DotnetVersion(string dir, string version, string preReleaseVersionType, string preReleaseVersion)
51+
public DotNetVersion(string dir, string version, string preReleaseVersionType, string preReleaseVersion)
5252
{
5353
this.dir = dir;
5454
this.version = Version.Parse(version);
@@ -59,7 +59,7 @@ public DotnetVersion(string dir, string version, string preReleaseVersionType, s
5959
}
6060
}
6161

62-
public int CompareTo(DotnetVersion? other)
62+
public int CompareTo(DotNetVersion? other)
6363
{
6464
var c = version.CompareTo(other?.version);
6565
if (c == 0 && IsPreRelease)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Collections.Generic;
2+
3+
namespace Semmle.Extraction.CSharp.DependencyFetching
4+
{
5+
internal interface IDotNetCliInvoker
6+
{
7+
/// <summary>
8+
/// The name of the dotnet executable.
9+
/// </summary>
10+
string Exec { get; }
11+
12+
/// <summary>
13+
/// Execute `dotnet <args>` and return true if the command succeeded, otherwise false.
14+
/// </summary>
15+
bool RunCommand(string args);
16+
17+
/// <summary>
18+
/// Execute `dotnet <args>` and return true if the command succeeded, otherwise false.
19+
/// The output of the command is returned in `output`.
20+
/// </summary>
21+
bool RunCommand(string args, out IList<string> output);
22+
}
23+
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/Razor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
88
{
99
internal class Razor
1010
{
11-
private readonly DotnetVersion sdk;
11+
private readonly DotNetVersion sdk;
1212
private readonly ProgressMonitor progressMonitor;
13-
private readonly DotNet dotNet;
13+
private readonly IDotNet dotNet;
1414
private readonly string sourceGeneratorFolder;
1515
private readonly string cscPath;
1616

17-
public Razor(DotnetVersion sdk, DotNet dotNet, ProgressMonitor progressMonitor)
17+
public Razor(DotNetVersion sdk, IDotNet dotNet, ProgressMonitor progressMonitor)
1818
{
1919
this.sdk = sdk;
2020
this.progressMonitor = progressMonitor;

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/Runtime.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ internal partial class Runtime
1717
private const string aspNetCoreApp = "Microsoft.AspNetCore.App";
1818

1919
private readonly IDotNet dotNet;
20-
private readonly Lazy<Dictionary<string, DotnetVersion>> newestRuntimes;
21-
private Dictionary<string, DotnetVersion> NewestRuntimes => newestRuntimes.Value;
20+
private readonly Lazy<Dictionary<string, DotNetVersion>> newestRuntimes;
21+
private Dictionary<string, DotNetVersion> NewestRuntimes => newestRuntimes.Value;
2222
private static string ExecutingRuntime => RuntimeEnvironment.GetRuntimeDirectory();
2323

2424
public Runtime(IDotNet dotNet)
@@ -36,17 +36,17 @@ public Runtime(IDotNet dotNet)
3636
/// It is assume that the format of a listed runtime is something like:
3737
/// Microsoft.NETCore.App 7.0.2 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
3838
/// </summary>
39-
private static Dictionary<string, DotnetVersion> ParseRuntimes(IList<string> listed)
39+
private static Dictionary<string, DotNetVersion> ParseRuntimes(IList<string> listed)
4040
{
4141
// Parse listed runtimes.
42-
var runtimes = new Dictionary<string, DotnetVersion>();
42+
var runtimes = new Dictionary<string, DotNetVersion>();
4343
var regex = RuntimeRegex();
4444
listed.ForEach(r =>
4545
{
4646
var match = regex.Match(r);
4747
if (match.Success)
4848
{
49-
runtimes.AddOrUpdateToLatest(match.Groups[1].Value, new DotnetVersion(match.Groups[6].Value, match.Groups[2].Value, match.Groups[4].Value, match.Groups[5].Value));
49+
runtimes.AddOrUpdateToLatest(match.Groups[1].Value, new DotNetVersion(match.Groups[6].Value, match.Groups[2].Value, match.Groups[4].Value, match.Groups[5].Value));
5050
}
5151
});
5252

@@ -56,7 +56,7 @@ private static Dictionary<string, DotnetVersion> ParseRuntimes(IList<string> lis
5656
/// <summary>
5757
/// Returns a dictionary mapping runtimes to their newest version.
5858
/// </summary>
59-
internal Dictionary<string, DotnetVersion> GetNewestRuntimes()
59+
internal Dictionary<string, DotNetVersion> GetNewestRuntimes()
6060
{
6161
var listed = dotNet.GetListedRuntimes();
6262
return ParseRuntimes(listed);

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/Sdk.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ internal partial class Sdk
1414
[GeneratedRegex(@"^(\d+\.\d+\.\d+)(-([a-z]+)\.(\d+\.\d+\.\d+))?\s\[(.+)\]$")]
1515
private static partial Regex SdkRegex();
1616

17-
private static HashSet<DotnetVersion> ParseSdks(IList<string> listed)
17+
private static HashSet<DotNetVersion> ParseSdks(IList<string> listed)
1818
{
19-
var sdks = new HashSet<DotnetVersion>();
19+
var sdks = new HashSet<DotNetVersion>();
2020
var regex = SdkRegex();
2121
listed.ForEach(r =>
2222
{
2323
var match = regex.Match(r);
2424
if (match.Success)
2525
{
26-
sdks.Add(new DotnetVersion(match.Groups[5].Value, match.Groups[1].Value, match.Groups[3].Value, match.Groups[4].Value));
26+
sdks.Add(new DotNetVersion(match.Groups[5].Value, match.Groups[1].Value, match.Groups[3].Value, match.Groups[4].Value));
2727
}
2828
});
2929

3030
return sdks;
3131
}
3232

33-
public DotnetVersion? GetNewestSdk()
33+
public DotNetVersion? GetNewestSdk()
3434
{
3535
var listed = dotNet.GetListedSdks();
3636
var sdks = ParseSdks(listed);

0 commit comments

Comments
 (0)