Revert "Move Go With Replace Detector to the Standard Go Detector" (#1198)

* Revert "go detector (#1194)"

This reverts commit 39058c5561.

* Update ServiceCollectionExtensions.cs

* Update ServiceCollectionExtensions.cs
This commit is contained in:
Amitla Vannikumar 2024-07-10 14:18:24 -07:00 коммит произвёл GitHub
Родитель 683f30ee44
Коммит 0707d99b36
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 1101 добавлений и 91 удалений

Просмотреть файл

@ -50,7 +50,7 @@ public class GoComponentDetector : FileComponentDetector
public override IEnumerable<ComponentType> SupportedComponentTypes { get; } = new[] { ComponentType.Go };
public override int Version => 8;
public override int Version => 7;
protected override Task<IObservable<ProcessRequest>> OnPrepareDetectionAsync(
IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs)
@ -282,7 +282,6 @@ public class GoComponentDetector : FileComponentDetector
GoGraphTelemetryRecord goGraphTelemetryRecord)
{
using var reader = new StreamReader(file.Stream);
var startString = "require ";
// There can be multiple require( ) sections in go 1.17+. loop over all of them.
while (!reader.EndOfStream)
@ -298,9 +297,9 @@ public class GoComponentDetector : FileComponentDetector
// In go >= 1.17, direct dependencies are listed as "require x/y v1.2.3", and transitive dependencies
// are listed in the require () section
if (line.StartsWith(startString))
if (line.StartsWith("require "))
{
this.TryRegisterDependencyFromModLine(line[startString.Length..], singleFileComponentRecorder);
this.TryRegisterDependencyFromModLine(line[8..], singleFileComponentRecorder);
}
line = await reader.ReadLineAsync();
@ -444,15 +443,7 @@ public class GoComponentDetector : FileComponentDetector
continue;
}
GoComponent goComponent;
if (dependency.Replace != null)
{
goComponent = new GoComponent(dependency.Replace.Path, dependency.Replace.Version);
}
else
{
goComponent = new GoComponent(dependency.Path, dependency.Version);
}
var goComponent = new GoComponent(dependency.Path, dependency.Version);
if (dependency.Indirect)
{
@ -492,7 +483,5 @@ public class GoComponentDetector : FileComponentDetector
public string Version { get; set; }
public bool Indirect { get; set; }
public GoBuildModule Replace { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,498 @@
namespace Microsoft.ComponentDetection.Detectors.Go;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ComponentDetection.Common;
using Microsoft.ComponentDetection.Common.Telemetry.Records;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
public class GoComponentWithReplaceDetector : FileComponentDetector, IExperimentalDetector
{
private static readonly Regex GoSumRegex = new(
@"(?<name>.*)\s+(?<version>.*?)(/go\.mod)?\s+(?<hash>.*)",
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
private readonly HashSet<string> projectRoots = new();
private readonly ICommandLineInvocationService commandLineInvocationService;
private readonly IEnvironmentVariableService envVarService;
public GoComponentWithReplaceDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
ICommandLineInvocationService commandLineInvocationService,
IEnvironmentVariableService envVarService,
ILogger<GoComponentWithReplaceDetector> logger)
{
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.commandLineInvocationService = commandLineInvocationService;
this.envVarService = envVarService;
this.Logger = logger;
}
public override string Id => "GoWithReplace";
public override IEnumerable<string> Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.GoMod) };
public override IList<string> SearchPatterns { get; } = new List<string> { "go.mod", "go.sum" };
public override IEnumerable<ComponentType> SupportedComponentTypes { get; } = new[] { ComponentType.Go };
public override int Version => 1;
protected override Task<IObservable<ProcessRequest>> OnPrepareDetectionAsync(
IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs)
{
// Filter out any go.sum process requests if the adjacent go.mod file is present and has a go version >= 1.17
var goModProcessRequests = processRequests.Where(processRequest =>
{
if (Path.GetFileName(processRequest.ComponentStream.Location) != "go.sum")
{
return true;
}
var goModFile = this.FindAdjacentGoModComponentStreams(processRequest).FirstOrDefault();
if (goModFile == null)
{
this.Logger.LogDebug(
"go.sum file found without an adjacent go.mod file. Location: {Location}",
processRequest.ComponentStream.Location);
return true;
}
// parse the go.mod file to get the go version
using var reader = new StreamReader(goModFile.Stream);
var goModFileContents = reader.ReadToEnd();
goModFile.Stream.Dispose();
return this.CheckGoModVersion(goModFileContents, processRequest, goModFile);
});
return Task.FromResult(goModProcessRequests);
}
private IEnumerable<ComponentStream> FindAdjacentGoModComponentStreams(ProcessRequest processRequest) =>
this.ComponentStreamEnumerableFactory.GetComponentStreams(
new FileInfo(processRequest.ComponentStream.Location).Directory,
new[] { "go.mod" },
(_, _) => false,
false)
.Select(x =>
{
// The stream will be disposed at the end of this method, so we need to copy it to a new stream.
var memoryStream = new MemoryStream();
x.Stream.CopyTo(memoryStream);
memoryStream.Position = 0;
return new ComponentStream
{
Stream = memoryStream,
Location = x.Location,
Pattern = x.Pattern,
};
});
private bool CheckGoModVersion(string goModFileContents, ProcessRequest processRequest, ComponentStream goModFile)
{
var goVersionMatch = Regex.Match(goModFileContents, @"go\s(?<version>\d+\.\d+)");
if (!goVersionMatch.Success)
{
this.Logger.LogDebug(
"go.sum file found with an adjacent go.mod file that does not contain a go version. Location: {Location}",
processRequest.ComponentStream.Location);
return true;
}
var goVersion = goVersionMatch.Groups["version"].Value;
if (System.Version.TryParse(goVersion, out var version))
{
if (version < new Version(1, 17))
{
this.Logger.LogWarning(
"go.mod file at {GoModLocation} does not have a go version >= 1.17. Scanning this go.sum file: {GoSumLocation} which may lead to over reporting components",
goModFile.Location,
processRequest.ComponentStream.Location);
return true;
}
this.Logger.LogInformation(
"go.sum file found with an adjacent go.mod file that has a go version >= 1.17. Will not scan this go.sum file. Location: {Location}",
processRequest.ComponentStream.Location);
return false;
}
this.Logger.LogWarning(
"go.sum file found with an adjacent go.mod file that has an invalid go version. Scanning both for components. Location: {Location}",
processRequest.ComponentStream.Location);
return true;
}
protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default)
{
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder;
var file = processRequest.ComponentStream;
var projectRootDirectory = Directory.GetParent(file.Location);
if (this.projectRoots.Any(path => projectRootDirectory.FullName.StartsWith(path)))
{
return;
}
using var record = new GoGraphTelemetryRecord();
record.WasGoCliDisabled = false;
record.WasGoFallbackStrategyUsed = false;
var wasGoCliScanSuccessful = false;
try
{
if (!this.IsGoCliManuallyDisabled())
{
wasGoCliScanSuccessful = await this.UseGoCliToScanAsync(file.Location, singleFileComponentRecorder, record);
}
else
{
record.WasGoCliDisabled = true;
this.Logger.LogInformation("Go cli scan was manually disabled, fallback strategy performed." +
" More info: https://github.com/microsoft/component-detection/blob/main/docs/detectors/go.md#fallback-detection-strategy");
}
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Failed to detect components using go cli. Location: {Location}", file.Location);
}
finally
{
if (wasGoCliScanSuccessful)
{
this.projectRoots.Add(projectRootDirectory.FullName);
}
else
{
record.WasGoFallbackStrategyUsed = true;
var fileExtension = Path.GetExtension(file.Location).ToUpperInvariant();
switch (fileExtension)
{
case ".MOD":
{
this.Logger.LogDebug("Found Go.mod: {Location}", file.Location);
await this.ParseGoModFileAsync(singleFileComponentRecorder, file, record);
break;
}
case ".SUM":
{
this.Logger.LogDebug("Found Go.sum: {Location}", file.Location);
this.ParseGoSumFile(singleFileComponentRecorder, file);
break;
}
default:
{
throw new InvalidOperationException("Unexpected file type detected in go detector");
}
}
}
}
}
[SuppressMessage("Maintainability", "CA1508:Avoid dead conditional code", Justification = "False positive")]
private async Task<bool> UseGoCliToScanAsync(string location, ISingleFileComponentRecorder singleFileComponentRecorder, GoGraphTelemetryRecord record)
{
record.WasGraphSuccessful = false;
record.DidGoCliCommandFail = false;
var projectRootDirectory = Directory.GetParent(location);
record.ProjectRoot = projectRootDirectory.FullName;
var isGoAvailable = await this.commandLineInvocationService.CanCommandBeLocatedAsync("go", null, workingDirectory: projectRootDirectory, new[] { "version" });
record.IsGoAvailable = isGoAvailable;
if (!isGoAvailable)
{
this.Logger.LogInformation("Go CLI was not found in the system");
return false;
}
this.Logger.LogInformation("Go CLI was found in system and will be used to generate dependency graph. " +
"Detection time may be improved by activating fallback strategy (https://github.com/microsoft/component-detection/blob/main/docs/detectors/go.md#fallback-detection-strategy). " +
"But, it will introduce noise into the detected components.");
var goDependenciesProcess = await this.commandLineInvocationService.ExecuteCommandAsync("go", null, workingDirectory: projectRootDirectory, new[] { "list", "-mod=readonly", "-m", "-json", "all" });
if (goDependenciesProcess.ExitCode != 0)
{
this.Logger.LogError("Go CLI command \"go list -m -json all\" failed with error: {GoDependenciesProcessStdErr}", goDependenciesProcess.StdErr);
this.Logger.LogError("Go CLI could not get dependency build list at location: {Location}. Fallback go.sum/go.mod parsing will be used.", location);
record.DidGoCliCommandFail = true;
record.GoCliCommandError = goDependenciesProcess.StdErr;
return false;
}
this.RecordBuildDependencies(goDependenciesProcess.StdOut, singleFileComponentRecorder);
var generateGraphProcess = await this.commandLineInvocationService.ExecuteCommandAsync("go", null, workingDirectory: projectRootDirectory, new List<string> { "mod", "graph" }.ToArray());
if (generateGraphProcess.ExitCode == 0)
{
this.PopulateDependencyGraph(generateGraphProcess.StdOut, singleFileComponentRecorder);
record.WasGraphSuccessful = true;
}
return true;
}
private void TryRegisterDependencyFromModLine(string line, ISingleFileComponentRecorder singleFileComponentRecorder)
{
if (line.Trim().StartsWith("//"))
{
// this is a comment line, ignore it
return;
}
if (this.TryToCreateGoComponentFromModLine(line, out var goComponent))
{
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(goComponent));
}
else
{
var lineTrim = line.Trim();
this.Logger.LogWarning("Line could not be parsed for component [{LineTrim}]", lineTrim);
singleFileComponentRecorder.RegisterPackageParseFailure(lineTrim);
}
}
private async Task ParseGoModFileAsync(
ISingleFileComponentRecorder singleFileComponentRecorder,
IComponentStream file,
GoGraphTelemetryRecord goGraphTelemetryRecord)
{
using var reader = new StreamReader(file.Stream);
var startString = "require ";
// There can be multiple require( ) sections in go 1.17+. loop over all of them.
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
while (line != null && !line.StartsWith("require ("))
{
if (line.StartsWith("go "))
{
goGraphTelemetryRecord.GoModVersion = line[3..].Trim();
}
// In go >= 1.17, direct dependencies are listed as "require x/y v1.2.3", and transitive dependencies
// are listed in the require () section
if (line.StartsWith(startString))
{
this.TryRegisterDependencyFromModLine(line[startString.Length..], singleFileComponentRecorder);
}
line = await reader.ReadLineAsync();
}
// Stopping at the first ) restrict the detection to only the require section.
while ((line = await reader.ReadLineAsync()) != null && !line.EndsWith(")"))
{
this.TryRegisterDependencyFromModLine(line, singleFileComponentRecorder);
}
}
}
private bool TryToCreateGoComponentFromModLine(string line, out GoComponent goComponent)
{
var lineComponents = Regex.Split(line.Trim(), @"\s+");
if (lineComponents.Length < 2)
{
goComponent = null;
return false;
}
var name = lineComponents[0];
var version = lineComponents[1];
goComponent = new GoComponent(name, version);
return true;
}
// For more information about the format of the go.sum file
// visit https://golang.org/cmd/go/#hdr-Module_authentication_using_go_sum
private void ParseGoSumFile(
ISingleFileComponentRecorder singleFileComponentRecorder,
IComponentStream file)
{
using var reader = new StreamReader(file.Stream);
string line;
while ((line = reader.ReadLine()) != null)
{
if (this.TryToCreateGoComponentFromSumLine(line, out var goComponent))
{
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(goComponent));
}
else
{
var lineTrim = line.Trim();
this.Logger.LogWarning("Line could not be parsed for component [{LineTrim}]", lineTrim);
singleFileComponentRecorder.RegisterPackageParseFailure(lineTrim);
}
}
}
private bool TryToCreateGoComponentFromSumLine(string line, out GoComponent goComponent)
{
var m = GoSumRegex.Match(line);
if (m.Success)
{
goComponent = new GoComponent(m.Groups["name"].Value, m.Groups["version"].Value, m.Groups["hash"].Value);
return true;
}
goComponent = null;
return false;
}
/// <summary>
/// This command only adds edges between parent and child components, it does not add nor remove any entries from the existing build list.
/// </summary>
private void PopulateDependencyGraph(string goGraphOutput, ISingleFileComponentRecorder componentRecorder)
{
// Yes, go always returns \n even on Windows
var graphRelationships = goGraphOutput.Split('\n');
foreach (var relationship in graphRelationships)
{
var components = relationship.Split(' ');
if (components.Length != 2)
{
if (string.IsNullOrWhiteSpace(relationship))
{
// normally the last line is an empty string
continue;
}
this.Logger.LogWarning("Unexpected relationship output from go mod graph: {Relationship}", relationship);
continue;
}
var isParentParsed = this.TryCreateGoComponentFromRelationshipPart(components[0], out var parentComponent);
var isChildParsed = this.TryCreateGoComponentFromRelationshipPart(components[1], out var childComponent);
if (!isParentParsed)
{
// These are explicit dependencies, we already have those recorded
continue;
}
if (isChildParsed)
{
if (this.IsModuleInBuildList(componentRecorder, parentComponent) && this.IsModuleInBuildList(componentRecorder, childComponent))
{
componentRecorder.RegisterUsage(new DetectedComponent(childComponent), parentComponentId: parentComponent.Id);
}
}
else
{
this.Logger.LogWarning("Failed to parse components from relationship string {Relationship}", relationship);
componentRecorder.RegisterPackageParseFailure(relationship);
}
}
}
private bool IsModuleInBuildList(ISingleFileComponentRecorder singleFileComponentRecorder, GoComponent component)
{
return singleFileComponentRecorder.GetComponent(component.Id) != null;
}
private void RecordBuildDependencies(string goListOutput, ISingleFileComponentRecorder singleFileComponentRecorder)
{
var goBuildModules = new List<GoBuildModule>();
var reader = new JsonTextReader(new StringReader(goListOutput))
{
SupportMultipleContent = true,
};
while (reader.Read())
{
var serializer = new JsonSerializer();
var buildModule = serializer.Deserialize<GoBuildModule>(reader);
goBuildModules.Add(buildModule);
}
foreach (var dependency in goBuildModules)
{
if (dependency.Main)
{
// main is the entry point module (superfluous as we already have the file location)
continue;
}
GoComponent goComponent;
if (dependency.Replace != null)
{
goComponent = new GoComponent(dependency.Replace.Path, dependency.Replace.Version);
}
else
{
goComponent = new GoComponent(dependency.Path, dependency.Version);
}
if (dependency.Indirect)
{
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(goComponent));
}
else
{
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(goComponent), isExplicitReferencedDependency: true);
}
}
}
private bool TryCreateGoComponentFromRelationshipPart(string relationship, out GoComponent goComponent)
{
var componentParts = relationship.Split('@');
if (componentParts.Length != 2)
{
goComponent = null;
return false;
}
goComponent = new GoComponent(componentParts[0], componentParts[1]);
return true;
}
private bool IsGoCliManuallyDisabled()
{
return this.envVarService.IsEnvironmentVariableValueTrue("DisableGoCliScan");
}
private class GoBuildModule
{
public string Path { get; set; }
public bool Main { get; set; }
public string Version { get; set; }
public bool Indirect { get; set; }
public GoBuildModule Replace { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,18 @@
namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Detectors.Go;
/// <summary>
/// Validating the <see cref="GoComponentWithReplaceDetector"/>.
/// </summary>
public class GoDetectorReplaceExperiment : IExperimentConfiguration
{
public string Name => "GoWithReplace";
public bool IsInControlGroup(IComponentDetector componentDetector) => componentDetector is GoComponentDetector;
public bool IsInExperimentGroup(IComponentDetector componentDetector) => componentDetector is GoComponentWithReplaceDetector;
public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => true;
}

Просмотреть файл

@ -63,6 +63,7 @@ public static class ServiceCollectionExtensions
services.AddSingleton<IExperimentConfiguration, SimplePipExperiment>();
services.AddSingleton<IExperimentConfiguration, RustCliDetectorExperiment>();
services.AddSingleton<IExperimentConfiguration, VcpkgExperiment>();
services.AddSingleton<IExperimentConfiguration, GoDetectorReplaceExperiment>();
// Detectors
// CocoaPods
@ -79,6 +80,7 @@ public static class ServiceCollectionExtensions
// Go
services.AddSingleton<IComponentDetector, GoComponentDetector>();
services.AddSingleton<IComponentDetector, GoComponentWithReplaceDetector>();
// Gradle
services.AddSingleton<IComponentDetector, GradleComponentDetector>();

Просмотреть файл

@ -368,82 +368,6 @@ replace (
await this.TestGoSumDetectorWithValidFile_ReturnsSuccessfullyAsync();
}
[TestMethod]
public async Task TestGoDetector_GoGraphReplaceAsync()
{
var buildDependencies = @"{
""Path"": ""some-package"",
""Version"": ""v1.2.3"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11"",
""Replace"": {
""Path"": ""some-package"",
""Version"": ""v1.2.4"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11"",
}
}" + "\n" + @"{
""Path"": ""test"",
""Version"": ""v2.0.0"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""other"",
""Version"": ""v1.2.0"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""a"",
""Version"": ""v1.5.0"",
""Time"": ""2020-05-19T17:02:07Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}";
var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 a@v1.5.0";
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(true);
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "list", "-mod=readonly", "-m", "-json", "all" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = buildDependencies,
});
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "mod", "graph" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = goGraph,
});
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", string.Empty)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(4);
detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "other v1.2.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.4 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "a v1.5.0 - Go");
}
[TestMethod]
public async Task TestGoDetector_GoGraphHappyPathAsync()
{

Просмотреть файл

@ -0,0 +1,579 @@
namespace Microsoft.ComponentDetection.Detectors.Tests;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Detectors.Go;
using Microsoft.ComponentDetection.TestsUtilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestClass]
[TestCategory("Governance/All")]
[TestCategory("Governance/ComponentDetection")]
public class GoComponentWithReplaceDetectorTests : BaseDetectorTest<GoComponentWithReplaceDetector>
{
private readonly Mock<ICommandLineInvocationService> commandLineMock;
private readonly Mock<IEnvironmentVariableService> envVarService;
public GoComponentWithReplaceDetectorTests()
{
this.commandLineMock = new Mock<ICommandLineInvocationService>();
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(false);
this.DetectorTestUtility.AddServiceMock(this.commandLineMock);
this.envVarService = new Mock<IEnvironmentVariableService>();
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(true);
this.DetectorTestUtility.AddServiceMock(this.envVarService);
}
[TestMethod]
public async Task TestGoModDetectorWithValidFile_ReturnsSuccessfullyAsync()
{
var goMod =
@"module github.com/Azure/azure-storage-blob-go
require (
github.com/Azure/azure-pipeline-go v0.2.1
github.com/kr/pretty v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
github.com/dgrijalva/jwt-go v3.2.0+incompatible
)";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", goMod)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(4);
var discoveredComponents = detectedComponents.ToArray();
discoveredComponents.Where(component => component.Component.Id == "github.com/Azure/azure-pipeline-go v0.2.1 - Go").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "github.com/dgrijalva/jwt-go v3.2.0+incompatible - Go").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 - Go").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "github.com/kr/pretty v0.1.0 - Go").Should().ContainSingle();
}
[TestMethod]
public async Task TestGoModDetector_CommentsOnFile_CommentsAreIgnoredAsync()
{
var goMod =
@"module github.com/Azure/azure-storage-blob-go
require (
// comment
github.com/kr/pretty v0.1.0 // indirect
)";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", goMod)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().ContainSingle("there is only one component definition on the file");
var discoveredComponents = detectedComponents.ToArray();
discoveredComponents.Where(component => component.Component.Id == "github.com/kr/pretty v0.1.0 - Go").Should().ContainSingle();
}
[TestMethod]
public async Task TestGoSumDetectorWithValidFile_ReturnsSuccessfullyAsync()
{
var goSum =
@"
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
)";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.sum", goSum)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(6);
var typedComponents = detectedComponents.Select(d => d.Component).ToList();
typedComponents.Should().Contain(
new GoComponent("github.com/golang/mock", "v1.1.1", "h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A="));
typedComponents.Should().Contain(
new GoComponent("github.com/golang/mock", "v1.2.0", "h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A="));
typedComponents.Should().Contain(
new GoComponent("github.com/golang/protobuf", "v0.0.0-20161109072736-4bd1920723d7", "h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U="));
typedComponents.Should().Contain(
new GoComponent("github.com/golang/protobuf", "v1.2.0", "h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U="));
typedComponents.Should().Contain(
new GoComponent("github.com/golang/protobuf", "v1.3.1", "h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg="));
typedComponents.Should().Contain(
new GoComponent("github.com/golang/protobuf", "v1.3.2", "h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs="));
}
[TestMethod]
public async Task TestGoModDetector_MultipleSpaces_ReturnsSuccessfullyAsync()
{
var goMod =
@"module github.com/Azure/azure-storage-blob-go
require (
github.com/Azure/azure-pipeline-go v0.2.1
github.com/kr/pretty v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
github.com/dgrijalva/jwt-go v3.2.0+incompatible
)";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", goMod)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(4);
var discoveredComponents = detectedComponents.ToArray();
discoveredComponents.Where(component => component.Component.Id == "github.com/Azure/azure-pipeline-go v0.2.1 - Go").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "github.com/dgrijalva/jwt-go v3.2.0+incompatible - Go").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 - Go").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "github.com/kr/pretty v0.1.0 - Go").Should().ContainSingle();
}
[TestMethod]
public async Task TestGoModDetector_ComponentsWithMultipleLocations_ReturnsSuccessfullyAsync()
{
var goMod1 =
@"module github.com/Azure/azure-storage-blob-go
require (
github.com/Azure/azure-pipeline-go v0.2.1
github.com/kr/pretty v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
github.com/Azure/go-autorest v10.15.2+incompatible
)";
var goMod2 =
@"module github.com/Azure/azure-storage-blob-go
require (
github.com/Azure/azure-pipeline-go v0.2.1
github.com/kr/pretty v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
github.com/Azure/go-autorest v10.15.2+incompatible
)";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", goMod1)
.WithFile("go.mod", goMod2, fileLocation: Path.Join(Path.GetTempPath(), "another-location", "go.mod"))
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
componentRecorder.GetDetectedComponents().Count().Should().Be(4);
var dependencyGraphs = componentRecorder.GetDependencyGraphsByLocation();
dependencyGraphs.Keys.Should().HaveCount(2);
var firstGraph = dependencyGraphs.Values.First();
var secondGraph = dependencyGraphs.Values.Skip(1).First();
firstGraph.GetComponents().Should().BeEquivalentTo(secondGraph.GetComponents());
}
[TestMethod]
public async Task TestGoModDetectorInvalidFiles_DoesNotFailAsync()
{
var invalidGoMod =
@" #/bin/sh
lorem ipsum
four score and seven bugs ago
$#26^#25%4";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", invalidGoMod)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
componentRecorder.GetDetectedComponents().Count().Should().Be(0);
}
[TestMethod]
public async Task TestGoModDetector_SkipsGoSumFilesAsync()
{
var goMod =
@"module contoso.com/greetings
go 1.18
require github.com/go-sql-driver/mysql v1.7.1 // indirect";
var goSum =
@"github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", goMod)
.WithFile("go.mod", goMod, new[] { "go.mod" })
.WithFile("go.sum", goSum)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
componentRecorder.GetDetectedComponents().Should().ContainSingle();
var component = componentRecorder.GetDetectedComponents().First();
component.Component.Id.Should().Be("github.com/go-sql-driver/mysql v1.7.1 - Go");
}
[TestMethod]
public async Task TestGoModDetector_HandlesTwoRequiresSectionsAsync()
{
var goMod =
@"module microsoft/component-detection
go 1.18
require (
github.com/go-sql-driver/mysql v1.7.1
rsc.io/quote v1.5.2
)
require (
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
rsc.io/sampler v1.3.0 // indirect
)";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", goMod)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
componentRecorder.GetDetectedComponents().Should().HaveCount(4);
var expectedComponentIds = new[]
{
"github.com/go-sql-driver/mysql v1.7.1 - Go", "rsc.io/quote v1.5.2 - Go",
"golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c - Go", "rsc.io/sampler v1.3.0 - Go",
};
componentRecorder.GetDetectedComponents().Select(c => c.Component.Id).Should().BeEquivalentTo(expectedComponentIds);
}
[TestMethod]
public async Task TestGoSumDetection_TwoEntriesForTheSameComponent_ReturnsSuccessfullyAsync()
{
var goSum =
@"
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
)";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.sum", goSum)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().ContainSingle();
}
[TestMethod]
public async Task TestGoModDetector_DetectorOnlyDetectInsideRequireSectionAsync()
{
var goMod =
@"module github.com/Azure/azure-storage-blob-go
require (
github.com/Azure/azure-pipeline-go v0.2.1
github.com/kr/pretty v0.1.0 // indirect
)
replace (
github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible
github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d
)
";
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", goMod)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(2);
var discoveredComponents = detectedComponents.ToArray();
discoveredComponents.Where(component => component.Component.Id == "github.com/Azure/azure-pipeline-go v0.2.1 - Go").Should().ContainSingle();
discoveredComponents.Where(component => component.Component.Id == "github.com/kr/pretty v0.1.0 - Go").Should().ContainSingle();
}
[TestMethod]
public async Task TestGoDetector_GoCommandNotFoundAsync()
{
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(false);
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
await this.TestGoSumDetectorWithValidFile_ReturnsSuccessfullyAsync();
}
[TestMethod]
public async Task TestGoDetector_GoCommandThrowsAsync()
{
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(() => throw new InvalidOperationException("Some horrible error occured"));
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
await this.TestGoSumDetectorWithValidFile_ReturnsSuccessfullyAsync();
}
[TestMethod]
public async Task TestGoDetector_GoGraphCommandFailsAsync()
{
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(true);
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go mod graph", null, It.IsAny<DirectoryInfo>(), It.IsAny<string>()))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 1,
});
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
await this.TestGoSumDetectorWithValidFile_ReturnsSuccessfullyAsync();
}
[TestMethod]
public async Task TestGoDetector_GoGraphCommandThrowsAsync()
{
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(true);
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go mod graph", null, It.IsAny<DirectoryInfo>(), It.IsAny<string>()))
.ReturnsAsync(() => throw new InvalidOperationException("Some horrible error occured"));
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
await this.TestGoSumDetectorWithValidFile_ReturnsSuccessfullyAsync();
}
[TestMethod]
public async Task TestGoDetector_GoGraphReplaceAsync()
{
var buildDependencies = @"{
""Path"": ""some-package"",
""Version"": ""v1.2.3"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11"",
""Replace"": {
""Path"": ""some-package"",
""Version"": ""v1.2.4"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11"",
}
}" + "\n" + @"{
""Path"": ""test"",
""Version"": ""v2.0.0"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""other"",
""Version"": ""v1.2.0"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""a"",
""Version"": ""v1.5.0"",
""Time"": ""2020-05-19T17:02:07Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}";
var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 a@v1.5.0";
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(true);
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "list", "-mod=readonly", "-m", "-json", "all" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = buildDependencies,
});
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "mod", "graph" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = goGraph,
});
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", string.Empty)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(4);
detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "other v1.2.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.4 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "a v1.5.0 - Go");
}
[TestMethod]
public async Task TestGoDetector_GoGraphHappyPathAsync()
{
var buildDependencies = @"{
""Path"": ""some-package"",
""Version"": ""v1.2.3"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""test"",
""Version"": ""v2.0.0"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""other"",
""Version"": ""v1.2.0"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""a"",
""Version"": ""v1.5.0"",
""Time"": ""2020-05-19T17:02:07Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}";
var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 a@v1.5.0";
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(true);
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "list", "-mod=readonly", "-m", "-json", "all" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = buildDependencies,
});
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "mod", "graph" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = goGraph,
});
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", string.Empty)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(4);
detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "other v1.2.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.3 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go");
detectedComponents.Should().ContainSingle(component => component.Component.Id == "a v1.5.0 - Go");
}
[TestMethod]
public async Task TestGoDetector_GoGraphCyclicDependenciesAsync()
{
var buildDependencies = @"{
""Path"": ""github.com/prometheus/common"",
""Version"": ""v0.32.1"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""github.com/prometheus/client_golang"",
""Version"": ""v1.11.0"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}" + "\n" + @"{
""Path"": ""github.com/prometheus/client_golang"",
""Version"": ""v1.12.1"",
""Time"": ""2021-12-06T23:04:27Z"",
""Indirect"": true,
""GoMod"": ""C:\\test\\go.mod"",
""GoVersion"": ""1.11""
}";
var goGraph = @"
github.com/prometheus/common@v0.32.1 github.com/prometheus/client_golang@v1.11.0
github.com/prometheus/client_golang@v1.12.1 github.com/prometheus/common@v0.32.1";
this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()))
.ReturnsAsync(true);
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "list", "-mod=readonly", "-m", "-json", "all" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = buildDependencies,
});
this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny<DirectoryInfo>(), new[] { "mod", "graph" }))
.ReturnsAsync(new CommandLineExecutionResult
{
ExitCode = 0,
StdOut = goGraph,
});
this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false);
var (scanResult, componentRecorder) = await this.DetectorTestUtility
.WithFile("go.mod", string.Empty)
.ExecuteDetectorAsync();
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
var detectedComponents = componentRecorder.GetDetectedComponents();
detectedComponents.Should().HaveCount(3);
}
[TestMethod]
public async Task TestGoDetector_GoCliRequiresEnvVarToRunAsync()
{
await this.TestGoSumDetectorWithValidFile_ReturnsSuccessfullyAsync();
this.commandLineMock.Verify(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny<DirectoryInfo>(), It.IsAny<string[]>()), Times.Never);
}
}