Skip to content

Commit f00b6e2

Browse files
committed
C#: Re-factor Dotnet to enable unit testing.
1 parent c45ca72 commit f00b6e2

File tree

3 files changed

+98
-52
lines changed

3 files changed

+98
-52
lines changed

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

Lines changed: 14 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,27 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
1311
/// </summary>
1412
internal partial class DotNet : IDotNet
1513
{
14+
private readonly IDotnetCommand dotnet;
1615
private readonly ProgressMonitor progressMonitor;
17-
private readonly string dotnet;
1816

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

24+
public DotNet(IDependencyOptions options, ProgressMonitor progressMonitor) : this(new DotnetCommand(progressMonitor, Path.Combine(options.DotNetPath ?? string.Empty, "dotnet")), progressMonitor) { }
25+
26+
2627
private void Info()
2728
{
2829
// TODO: make sure the below `dotnet` version is matching the one specified in global.json
29-
var res = RunCommand("--info");
30+
var res = dotnet.RunCommand("--info");
3031
if (!res)
3132
{
32-
throw new Exception($"{dotnet} --info failed.");
33-
}
34-
}
35-
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-
}
47-
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-
}
61-
62-
private bool RunCommand(string args, out IList<string> output)
63-
{
64-
progressMonitor.RunningProcess($"{dotnet} {args}");
65-
var pi = MakeDotnetStartInfo(args, redirectStandardOutput: true);
66-
var exitCode = pi.ReadOutput(out output);
67-
if (exitCode != 0)
68-
{
69-
progressMonitor.CommandFailed(dotnet, args, exitCode);
70-
return false;
33+
throw new Exception($"{dotnet.Exec} --info failed.");
7134
}
72-
return true;
7335
}
7436

7537
private static string GetRestoreArgs(string projectOrSolutionFile, string packageDirectory) =>
@@ -82,7 +44,7 @@ public bool RestoreProjectToDirectory(string projectFile, string packageDirector
8244
{
8345
args += $" --configfile \"{pathToNugetConfig}\"";
8446
}
85-
var success = RunCommand(args, out var output);
47+
var success = dotnet.RunCommand(args, out var output);
8648
stdout = string.Join("\n", output);
8749
return success;
8850
}
@@ -91,7 +53,7 @@ public bool RestoreSolutionToDirectory(string solutionFile, string packageDirect
9153
{
9254
var args = GetRestoreArgs(solutionFile, packageDirectory);
9355
args += " --verbosity normal";
94-
if (RunCommand(args, out var output))
56+
if (dotnet.RunCommand(args, out var output))
9557
{
9658
var regex = RestoreProjectRegex();
9759
projects = output
@@ -108,13 +70,13 @@ public bool RestoreSolutionToDirectory(string solutionFile, string packageDirect
10870
public bool New(string folder)
10971
{
11072
var args = $"new console --no-restore --output \"{folder}\"";
111-
return RunCommand(args);
73+
return dotnet.RunCommand(args);
11274
}
11375

11476
public bool AddPackage(string folder, string package)
11577
{
11678
var args = $"add \"{folder}\" package \"{package}\" --no-restore";
117-
return RunCommand(args);
79+
return dotnet.RunCommand(args);
11880
}
11981

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

12486
private IList<string> GetListed(string args, string artifact)
12587
{
126-
if (RunCommand(args, out var artifacts))
88+
if (dotnet.RunCommand(args, out var artifacts))
12789
{
12890
progressMonitor.LogInfo($"Found {artifact}s: {string.Join("\n", artifacts)}");
12991
return artifacts;
@@ -134,7 +96,7 @@ private IList<string> GetListed(string args, string artifact)
13496
public bool Exec(string execArgs)
13597
{
13698
var args = $"exec {execArgs}";
137-
return RunCommand(args);
99+
return dotnet.RunCommand(args);
138100
}
139101

140102
[GeneratedRegex("Restored\\s+(.+\\.csproj)", RegexOptions.Compiled)]
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 DotnetCommand : IDotnetCommand
11+
{
12+
private readonly ProgressMonitor progressMonitor;
13+
14+
public string Exec { get; }
15+
16+
public DotnetCommand(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+
public bool RunCommand(string args)
35+
{
36+
progressMonitor.RunningProcess($"{Exec} {args}");
37+
using var proc = Process.Start(MakeDotnetStartInfo(args, redirectStandardOutput: false));
38+
proc?.WaitForExit();
39+
var exitCode = proc?.ExitCode ?? -1;
40+
if (exitCode != 0)
41+
{
42+
progressMonitor.CommandFailed(Exec, args, exitCode);
43+
return false;
44+
}
45+
return true;
46+
}
47+
48+
public bool RunCommand(string args, out IList<string> output)
49+
{
50+
progressMonitor.RunningProcess($"{Exec} {args}");
51+
var pi = MakeDotnetStartInfo(args, redirectStandardOutput: true);
52+
var exitCode = pi.ReadOutput(out output);
53+
if (exitCode != 0)
54+
{
55+
progressMonitor.CommandFailed(Exec, args, exitCode);
56+
return false;
57+
}
58+
return true;
59+
}
60+
}
61+
}
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 IDotnetCommand
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+
}

0 commit comments

Comments
 (0)