diff --git a/docs/feature-overview.md b/docs/feature-overview.md
index b5369b23e..f3df03240 100644
--- a/docs/feature-overview.md
+++ b/docs/feature-overview.md
@@ -15,5 +15,5 @@
| Pip (Python) |
- setup.py
- requirements.txt
- *setup=distutils.core.run_setup({setup.py}); setup.install_requires*
- dist package METADATA file
| - Python 2 or Python 3
- Internet connection
| ❌ | ✔ |
| Poetry (Python) | - poetry.lock
| - | ✔ | ❌ |
| Ruby | | - | ❌ | ✔ |
-| Cargo | - cargo.lock (v1, v2)
- cargo.toml
| - | ✔ (dev-dependencies in cargo.toml) | ✔ |
+| Cargo | | - | ❌ | ✔ |
diff --git a/src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustCrateV2DetectorTelemetryRecord.cs b/src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustCrateDetectorTelemetryRecord.cs
similarity index 52%
rename from src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustCrateV2DetectorTelemetryRecord.cs
rename to src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustCrateDetectorTelemetryRecord.cs
index 47b48584f..55b66ad71 100644
--- a/src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustCrateV2DetectorTelemetryRecord.cs
+++ b/src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustCrateDetectorTelemetryRecord.cs
@@ -1,8 +1,8 @@
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
{
- public class RustCrateV2DetectorTelemetryRecord : BaseDetectionTelemetryRecord
+ public class RustCrateDetectorTelemetryRecord : BaseDetectionTelemetryRecord
{
- public override string RecordName => "RustCrateV2MalformedDependencies";
+ public override string RecordName => "RustCrateMalformedDependencies";
public string PackageInfo { get; set; }
diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoPackage.cs b/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoPackage.cs
index bf8b19cc2..c3b46e7e2 100644
--- a/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoPackage.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoPackage.cs
@@ -28,16 +28,20 @@ public class CargoPackage
// Manually added some casing handling
public override bool Equals(object obj)
{
- var package = obj as CargoPackage;
- return package != null && this.name.Equals(package.name) && this.version.Equals(package.version, StringComparison.OrdinalIgnoreCase);
+ return obj is CargoPackage package &&
+ string.Equals(this.name, package.name) &&
+ string.Equals(this.version, package.version, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.source, package.source) &&
+ string.Equals(this.checksum, package.checksum);
}
public override int GetHashCode()
{
- var hashCode = -2143789899;
- hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.name);
- hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.version.ToLowerInvariant());
- return hashCode;
+ return HashCode.Combine(
+ EqualityComparer.Default.GetHashCode(this.name),
+ EqualityComparer.Default.GetHashCode(this.version.ToLowerInvariant()),
+ EqualityComparer.Default.GetHashCode(this.source),
+ EqualityComparer.Default.GetHashCode(this.checksum));
}
}
}
diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs
index 7b1bd4cb5..db853dba5 100644
--- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs
@@ -3,7 +3,9 @@
using System.Composition;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using Microsoft.ComponentDetection.Common.Telemetry.Records;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
@@ -15,13 +17,41 @@ namespace Microsoft.ComponentDetection.Detectors.Rust
[Export(typeof(IComponentDetector))]
public class RustCrateDetector : FileComponentDetector
{
+ private const string CargoLockSearchPattern = "Cargo.lock";
+
+ //// PkgName[ Version][ (Source)]
+ private static readonly Regex DependencyFormatRegex = new Regex(
+ @"^(?[^ ]+)(?: (?[^ ]+))?(?: \((?[^()]*)\))?$",
+ RegexOptions.Compiled);
+
+ private static bool ParseDependency(string dependency, out string packageName, out string version, out string source)
+ {
+ var match = DependencyFormatRegex.Match(dependency);
+ var packageNameMatch = match.Groups["packageName"];
+ var versionMatch = match.Groups["version"];
+ var sourceMatch = match.Groups["source"];
+
+ packageName = packageNameMatch.Success ? packageNameMatch.Value : null;
+ version = versionMatch.Success ? versionMatch.Value : null;
+ source = sourceMatch.Success ? sourceMatch.Value : null;
+
+ if (source == string.Empty)
+ {
+ source = null;
+ }
+
+ return match.Success;
+ }
+
+ private static bool IsLocalPackage(CargoPackage package) => package.source == null;
+
public override string Id => "RustCrateDetector";
- public override IList SearchPatterns => new List { RustCrateUtilities.CargoLockSearchPattern };
+ public override IList SearchPatterns => new List { CargoLockSearchPattern };
public override IEnumerable SupportedComponentTypes => new[] { ComponentType.Cargo };
- public override int Version { get; } = 7;
+ public override int Version { get; } = 8;
public override IEnumerable Categories => new List { "Rust" };
@@ -39,61 +69,172 @@ protected override Task OnFileFound(ProcessRequest processRequest, IDictionary(reader.ReadToEnd(), options: options);
- // This makes sure we're only trying to parse Cargo.lock v1 formats
- if (cargoLock.Metadata == null)
+ var seenAsDependency = new HashSet();
+
+ // Pass 1: Create typed components and allow lookup by name.
+ var packagesByName = new Dictionary>();
+ if (cargoLock.Package != null)
{
- this.Logger.LogInfo($"Cargo.lock file at {cargoLockFile.Location} contains no metadata section so we're parsing it as the v2 format. The v1 detector will not process it.");
- return Task.CompletedTask;
+ foreach (var cargoPackage in cargoLock.Package)
+ {
+ // Get or create the list of packages with this name
+ if (!packagesByName.TryGetValue(cargoPackage.name, out var packageList))
+ {
+ // First package with this name
+ packageList = new List<(CargoPackage, CargoComponent)>();
+ packagesByName.Add(cargoPackage.name, packageList);
+ }
+ else if (packageList.Any(p => p.package.Equals(cargoPackage)))
+ {
+ // Ignore duplicate packages
+ continue;
+ }
+
+ // Create a node for each non-local package to allow adding dependencies later.
+ CargoComponent cargoComponent = null;
+ if (!IsLocalPackage(cargoPackage))
+ {
+ cargoComponent = new CargoComponent(cargoPackage.name, cargoPackage.version);
+ singleFileComponentRecorder.RegisterUsage(new DetectedComponent(cargoComponent));
+ }
+
+ // Add the package/component pair to the list
+ packageList.Add((cargoPackage, cargoComponent));
+ }
+
+ // Pass 2: Register dependencies.
+ foreach (var packageList in packagesByName.Values)
+ {
+ // Get the parent package and component
+ foreach (var (parentPackage, parentComponent) in packageList)
+ {
+ if (parentPackage.dependencies == null)
+ {
+ // This package has no dependency edges to contribute.
+ continue;
+ }
+
+ // Process each dependency
+ foreach (var dependency in parentPackage.dependencies)
+ {
+ ProcessDependency(cargoLockFile, singleFileComponentRecorder, seenAsDependency, packagesByName, parentPackage, parentComponent, dependency);
+ }
+ }
+ }
+
+ // Pass 3: Conservatively mark packages we found no dependency to as roots
+ foreach (var packageList in packagesByName.Values)
+ {
+ // Get the package and component.
+ foreach (var (package, component) in packageList)
+ {
+ if (!IsLocalPackage(package) && !seenAsDependency.Contains(package))
+ {
+ var detectedComponent = new DetectedComponent(component);
+ singleFileComponentRecorder.RegisterUsage(detectedComponent, isExplicitReferencedDependency: true);
+ }
+ }
+ }
}
+ }
+ catch (Exception e)
+ {
+ // If something went wrong, just ignore the file
+ this.Logger.LogFailedReadingFile(cargoLockFile.Location, e);
+ }
- var lockFileInfo = new FileInfo(cargoLockFile.Location);
- var cargoTomlComponentStream = this.ComponentStreamEnumerableFactory.GetComponentStreams(lockFileInfo.Directory, new List { RustCrateUtilities.CargoTomlSearchPattern }, (name, directoryName) => false, recursivelyScanDirectories: false);
+ return Task.CompletedTask;
+ }
- var cargoDependencyData = RustCrateUtilities.ExtractRootDependencyAndWorkspaceSpecifications(cargoTomlComponentStream, singleFileComponentRecorder);
+ private void ProcessDependency(
+ IComponentStream cargoLockFile,
+ ISingleFileComponentRecorder singleFileComponentRecorder,
+ HashSet seenAsDependency,
+ Dictionary> packagesByName,
+ CargoPackage parentPackage,
+ CargoComponent parentComponent,
+ string dependency)
+ {
+ try
+ {
+ // Extract the information from the dependency (name with optional version and source)
+ if (!ParseDependency(dependency, out var childName, out var childVersion, out var childSource))
+ {
+ // Could not parse the dependency string
+ throw new FormatException($"Failed to parse dependency '{dependency}'");
+ }
- // If workspaces have been defined in the root cargo.toml file, scan for specified cargo.toml manifests
- var numWorkspaceComponentStreams = 0;
- var expectedWorkspaceTomlCount = cargoDependencyData.CargoWorkspaces.Count;
- if (expectedWorkspaceTomlCount > 0)
+ if (!packagesByName.TryGetValue(childName, out var candidatePackages))
{
- var rootCargoTomlLocation = Path.Combine(lockFileInfo.DirectoryName, "Cargo.toml");
-
- var cargoTomlWorkspaceComponentStreams = this.ComponentStreamEnumerableFactory.GetComponentStreams(
- lockFileInfo.Directory,
- new List { RustCrateUtilities.CargoTomlSearchPattern },
- RustCrateUtilities.BuildExcludeDirectoryPredicateFromWorkspaces(lockFileInfo, cargoDependencyData.CargoWorkspaces, cargoDependencyData.CargoWorkspaceExclusions),
- recursivelyScanDirectories: true)
- .Where(x => !x.Location.Equals(rootCargoTomlLocation)); // The root directory needs to be included in directoriesToScan, but should not be reprocessed
- numWorkspaceComponentStreams = cargoTomlWorkspaceComponentStreams.Count();
-
- // Now that the non-root files have been located, add their dependencies
- RustCrateUtilities.ExtractDependencySpecifications(cargoTomlWorkspaceComponentStreams, singleFileComponentRecorder, cargoDependencyData.NonDevDependencies, cargoDependencyData.DevDependencies);
+ throw new FormatException($"Could not find any package named '{childName}' for depenency string '{dependency}'");
}
- // Even though we can't read the file streams, we still have the enumerable!
- if (!cargoTomlComponentStream.Any() || cargoTomlComponentStream.Count() > 1)
+ // Search through the list of candidates to find a match (note that version and source are optional).
+ CargoPackage childPackage = null;
+ CargoComponent childComponent = null;
+ foreach (var (candidatePackage, candidateComponent) in candidatePackages)
{
- this.Logger.LogWarning($"We are expecting exactly 1 accompanying Cargo.toml file next to the cargo.lock file found at {cargoLockFile.Location}");
- return Task.CompletedTask;
+ if (childVersion != null && candidatePackage.version != childVersion)
+ {
+ // This does not have the requested version
+ continue;
+ }
+
+ if (childSource != null && candidatePackage.source != childSource)
+ {
+ // This does not have the requested source
+ continue;
+ }
+
+ if (childPackage != null)
+ {
+ throw new FormatException($"Found multiple matching packages for dependency string '{dependency}'");
+ }
+
+ // We have found the requested package.
+ childPackage = candidatePackage;
+ childComponent = candidateComponent;
}
- // If there is a mismatch between the number of expected and found workspaces, exit
- if (expectedWorkspaceTomlCount > numWorkspaceComponentStreams)
+ if (childPackage == null)
{
- this.Logger.LogWarning($"We are expecting at least {expectedWorkspaceTomlCount} accompanying Cargo.toml file(s) from workspaces outside of the root directory {lockFileInfo.DirectoryName}, but found {numWorkspaceComponentStreams}");
- return Task.CompletedTask;
+ throw new FormatException($"Could not find matching package for dependency string '{dependency}'");
}
- var cargoPackages = cargoLock.Package.ToHashSet();
- RustCrateUtilities.BuildGraph(cargoPackages, cargoDependencyData.NonDevDependencies, cargoDependencyData.DevDependencies, singleFileComponentRecorder);
+ if (IsLocalPackage(childPackage))
+ {
+ if (!IsLocalPackage(parentPackage))
+ {
+ throw new FormatException($"In package with source '{parentComponent.Id}' found non-source dependency string: '{dependency}'");
+ }
+
+ // This is a dependency between packages without source
+ return;
+ }
+
+ var detectedComponent = new DetectedComponent(childComponent);
+ seenAsDependency.Add(childPackage);
+
+ if (IsLocalPackage(parentPackage))
+ {
+ // We are adding a root edge (from a local package)
+ singleFileComponentRecorder.RegisterUsage(detectedComponent, isExplicitReferencedDependency: true);
+ }
+ else
+ {
+ // we are adding an edge within the graph
+ singleFileComponentRecorder.RegisterUsage(detectedComponent, isExplicitReferencedDependency: false, parentComponentId: parentComponent.Id);
+ }
}
catch (Exception e)
{
- // If something went wrong, just ignore the file
+ using var record = new RustCrateDetectorTelemetryRecord();
+
+ record.PackageInfo = $"{parentPackage.name}, {parentPackage.version}, {parentPackage.source}";
+ record.Dependencies = dependency;
+
this.Logger.LogFailedReadingFile(cargoLockFile.Location, e);
}
-
- return Task.CompletedTask;
}
}
}
diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateUtilities.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateUtilities.cs
deleted file mode 100644
index a501061f7..000000000
--- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateUtilities.cs
+++ /dev/null
@@ -1,492 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using DotNet.Globbing;
-using Microsoft.ComponentDetection.Common.Telemetry.Records;
-using Microsoft.ComponentDetection.Contracts;
-using Microsoft.ComponentDetection.Contracts.TypedComponent;
-using Microsoft.ComponentDetection.Detectors.Rust.Contracts;
-using Semver;
-using Tomlyn;
-using Tomlyn.Model;
-
-namespace Microsoft.ComponentDetection.Detectors.Rust
-{
- public class RustCrateUtilities
- {
- public const string CargoTomlSearchPattern = "Cargo.toml";
- public const string CargoLockSearchPattern = "Cargo.lock";
-
- public static string[] NonDevDependencyKeys => new string[] { "dependencies", "build-dependencies" };
-
- public static string[] DevDependencyKeys => new string[] { "dev-dependencies" };
-
- private const string Pattern = @"([^ ]+) ([^ ]+) \(([^()]*)\)"; // PkgName Version Source
- private static readonly Regex DependencyFormatRegex = new Regex(Pattern, RegexOptions.Compiled);
-
- private const string WorkspaceKey = "workspace";
-
- private const string WorkspaceMemberKey = "members";
-
- private const string WorkspaceExcludeKey = "exclude";
-
- public static DependencySpecification GenerateDependencySpecifications(TomlTable cargoToml, IEnumerable tomlDependencyKeys)
- {
- var dependencySpecifications = new DependencySpecification();
- var dependencyLocations = GetDependencies(cargoToml, tomlDependencyKeys);
- foreach (var dependencies in dependencyLocations)
- {
- foreach (var dependency in dependencies.Keys)
- {
- string versionSpecifier = string.Empty;
- if (dependencies.TryGetValue(dependency, out var value))
- {
- if (value is string valueAsString)
- {
- versionSpecifier = valueAsString;
- }
- else if ((value is TomlTable versionTable) && versionTable.TryGetValue("version", out var versionValue) && versionValue is string versionValueAsSring && (versionValueAsSring != "0.0.0"))
- {
- // We have a valid version that doesn't indicate 'internal' like 0.0.0 does.
- versionSpecifier = versionValueAsSring;
- }
- else if ((value is TomlTable pathTable) && pathTable.TryGetValue("path", out var pathValue))
- {
- // If this is a workspace dependency specification that specifies a component by path reference, skip adding it directly here.
- // Example: kubos-app = { path = "../../apis/app-api/rust" }
- continue;
- }
- else
- {
- return null;
- }
- }
-
- // If the dependency is renamed, use the actual name of the package:
- // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml
- string dependencyName;
- if (dependencies.TryGetValue(dependency, out var dependencyValue) && dependencyValue is TomlTable tomlTable && tomlTable.TryGetValue("package", out var packageValue) && packageValue is string packageValueAsString)
- {
- dependencyName = packageValueAsString;
- }
- else
- {
- dependencyName = dependency;
- }
-
- dependencySpecifications.Add(dependencyName, versionSpecifier);
- }
- }
-
- return dependencySpecifications;
- }
-
- public static IEnumerable ConvertCargoLockV2PackagesToV1(CargoLock cargoLock)
- {
- var packageMap = new Dictionary>();
- cargoLock.Package.ToList().ForEach(package =>
- {
- if (!packageMap.TryGetValue(package.name, out var packageList))
- {
- packageMap[package.name] = new List() { package };
- }
- else
- {
- packageList.Add(package);
- }
- });
-
- return cargoLock.Package.Select(package =>
- {
- if (package.dependencies == null)
- {
- return package;
- }
-
- try
- {
- // We're just formatting the v2 dependencies in the v1 way
- package.dependencies = package.dependencies
- .Select(dep =>
- {
- // parts[0] => name
- // parts[1] => version
- var parts = dep.Split(' ');
-
- // Using v1 format, nothing to change
- if (string.IsNullOrEmpty(dep) || parts.Length == 3)
- {
- return dep;
- }
-
- // Below 2 cases use v2 format
- else if (parts.Length == 1)
- {
- // There should only be 1 package in packageMap with this name since we don't specify a version
- // We want this to throw if we find more than 1 package because it means there is ambiguity about which package is being used
- var mappedPackage = packageMap[parts[0]].Single();
-
- return MakeDependencyStringFromPackage(mappedPackage);
- }
- else if (parts.Length == 2)
- {
- // Search for the package name + version
- // Throws if more than 1 for same reason as above
- var mappedPackage = packageMap[parts[0]].Where(subPkg => subPkg.version == parts[1]).Single();
-
- return MakeDependencyStringFromPackage(mappedPackage);
- }
-
- throw new FormatException($"Did not expect the dependency string {dep} to have more than 3 parts");
- }).ToArray();
- }
- catch
- {
- using var record = new RustCrateV2DetectorTelemetryRecord();
-
- record.PackageInfo = $"{package.name}, {package.version}, {package.source}";
- record.Dependencies = string.Join(',', package.dependencies);
- }
-
- return package;
- });
- }
-
- public static string MakeDependencyStringFromPackage(CargoPackage package)
- {
- return $"{package.name} {package.version} ({package.source})";
- }
-
- ///
- /// Given the project root Cargo.toml file, extract any workspaces specified and any root dependencies.
- ///
- /// A stream representing the root cargo.toml file.
- /// The component recorder which will have workspace toml files added as related.
- ///
- /// A CargoDependencyData containing populated lists of CargoWorkspaces that will be included from search, CargoWorkspaceExclusions that will be excluded from search,
- /// a list of non-development dependencies, and a list of development dependencies.
- ///
- public static CargoDependencyData ExtractRootDependencyAndWorkspaceSpecifications(IEnumerable cargoTomlComponentStream, ISingleFileComponentRecorder singleFileComponentRecorder)
- {
- var cargoDependencyData = new CargoDependencyData();
-
- // The file handle is disposed if you call .First() on cargoTomlComponentStream
- // Since multiple Cargo.toml files for 1 Cargo.lock file obviously doesn't make sense
- // We break at the end of this loop
- foreach (var cargoTomlFile in cargoTomlComponentStream)
- {
- var reader = new StreamReader(cargoTomlFile.Stream);
- var cargoToml = Toml.ToModel(reader.ReadToEnd());
-
- singleFileComponentRecorder.AddAdditionalRelatedFile(cargoTomlFile.Location);
-
- // Extract the workspaces present, if any
- if (cargoToml.TryGetValue(WorkspaceKey, out var value) && value is TomlTable workspaces)
- {
- if (workspaces.TryGetValue(WorkspaceMemberKey, out var workspaceMembers))
- {
- if (workspaceMembers is TomlArray workspaceMembersArray)
- {
- cargoDependencyData.CargoWorkspaces.UnionWith(workspaceMembersArray.Select(i => i.ToString()));
- }
- else
- {
- throw new InvalidRustTomlFileException($"In accompanying Cargo.toml file expected {WorkspaceMemberKey} within {WorkspaceKey} to be of type Array, but found {workspaceMembers?.GetType()}");
- }
- }
-
- if (workspaces.TryGetValue(WorkspaceExcludeKey, out var workspaceExclusions))
- {
- if (workspaceExclusions is TomlArray workspaceExclusionsArray)
- {
- cargoDependencyData.CargoWorkspaceExclusions.UnionWith(workspaceExclusionsArray.Select(i => i.ToString()));
- }
- else
- {
- throw new InvalidRustTomlFileException($"In accompanying Cargo.toml file expected {WorkspaceExcludeKey} within {WorkspaceKey} to be of type Array, but found {workspaceExclusions?.GetType()}");
- }
- }
- }
-
- GenerateDependencies(cargoToml, cargoDependencyData.NonDevDependencies, cargoDependencyData.DevDependencies);
-
- break;
- }
-
- return cargoDependencyData;
- }
-
- ///
- /// Generate a predicate which will be used to exclude directories which should not contain cargo.toml files.
- ///
- /// The FileInfo for the cargo.lock file found in the root directory.
- /// A list of relative folder paths to include in search.
- /// A list of relative folder paths to exclude from search.
- /// Returns predicate which will be used to exclude directories.
- public static ExcludeDirectoryPredicate BuildExcludeDirectoryPredicateFromWorkspaces(FileInfo rootLockFileInfo, HashSet definedWorkspaces, HashSet definedExclusions)
- {
- var workspaceGlobs = BuildGlobMatchingFromWorkspaces(rootLockFileInfo, definedWorkspaces);
-
- // Since the paths come in as relative, make them fully qualified
- var fullyQualifiedExclusions = definedExclusions.Select(x => Path.Combine(rootLockFileInfo.DirectoryName, x)).ToHashSet();
-
- // The predicate will be evaluated with the current directory name to search and the full path of its parent. Return true when it should be excluded from search.
- return (ReadOnlySpan nameOfDirectoryToConsider, ReadOnlySpan pathOfParentOfDirectoryToConsider) =>
- {
- var currentPath = Path.Combine(pathOfParentOfDirectoryToConsider.ToString(), nameOfDirectoryToConsider.ToString());
-
- return !workspaceGlobs.Values.Any(x => x.IsMatch(currentPath)) || fullyQualifiedExclusions.Contains(currentPath);
- };
- }
-
- ///
- /// Given a set of Cargo.toml files, extract development and non-development dependency lists for each.
- ///
- /// A list of streams representing cargo workspaces.
- /// The component recorder which will have workspace toml files added as related.
- /// Current list of non-development dependencies.
- /// Current list of development dependencies.
- public static void ExtractDependencySpecifications(IEnumerable cargoTomlComponentStreams, ISingleFileComponentRecorder singleFileComponentRecorder, IList nonDevDependencySpecifications, IList devDependencySpecifications)
- {
- // The file handles within cargoTomlComponentStreams will be disposed after enumeration
- // This method is only used in non root toml extraction, so the whole list should be iterated
- foreach (var cargoTomlFile in cargoTomlComponentStreams)
- {
- var reader = new StreamReader(cargoTomlFile.Stream);
- var cargoToml = Toml.ToModel(reader.ReadToEnd());
-
- singleFileComponentRecorder.AddAdditionalRelatedFile(cargoTomlFile.Location);
-
- GenerateDependencies(cargoToml, nonDevDependencySpecifications, devDependencySpecifications);
- }
- }
-
- public static void BuildGraph(HashSet cargoPackages, IList nonDevDependencies, IList devDependencies, ISingleFileComponentRecorder singleFileComponentRecorder)
- {
- // Get all root components that are not dev dependencies
- // This is a bug:
- // Say Cargo.toml defined async ^1.0 as a dependency
- // Say Cargo.lock has async 1.0.0 and async 1.0.2
- // Both will be marked as root and there's no way to tell which one is "real"
- IList nonDevRoots = cargoPackages
- .Where(detectedComponent => IsCargoPackageInDependencySpecifications(detectedComponent, nonDevDependencies))
- .ToList();
-
- // Get all roots that are dev deps
- IList devRoots = cargoPackages
- .Where(detectedComponent => IsCargoPackageInDependencySpecifications(detectedComponent, devDependencies))
- .ToList();
-
- var packagesDict = cargoPackages.ToDictionary(cargoPackage => new CargoComponent(cargoPackage.name, cargoPackage.version).Id);
-
- FollowRoots(packagesDict, devRoots, singleFileComponentRecorder, true);
- FollowRoots(packagesDict, nonDevRoots, singleFileComponentRecorder, false);
- }
-
- ///
- /// Extract development and non-development dependency lists from a given TomlTable.
- ///
- /// The TomlTable representing a whole cargo.toml file.
- /// Current list of non-development dependencies.
- /// Current list of development dependencies.
- private static void GenerateDependencies(TomlTable cargoToml, IList nonDevDependencySpecifications, IList devDependencySpecifications)
- {
- var dependencySpecification = GenerateDependencySpecifications(cargoToml, NonDevDependencyKeys);
- var devDependencySpecification = GenerateDependencySpecifications(cargoToml, DevDependencyKeys);
-
- // If null, this indicates the toml is an internal file that should not be tracked as a component.
- if (dependencySpecification != null)
- {
- nonDevDependencySpecifications.Add(dependencySpecification);
- }
-
- if (devDependencySpecification != null)
- {
- devDependencySpecifications.Add(devDependencySpecification);
- }
- }
-
- ///
- /// Generates a list of Glob compatible Cargo workspace directories which will be searched. See https://docs.rs/glob/0.3.0/glob/struct.Pattern.html for glob patterns.
- ///
- /// The FileInfo for the cargo.lock file found in the root directory.
- /// A list of relative folder paths to include in search.
- /// Dictionary with worspace paths and their corresponding Globs.
- private static Dictionary BuildGlobMatchingFromWorkspaces(FileInfo rootLockFileInfo, HashSet definedWorkspaces)
- {
- var directoryGlobs = new Dictionary
- {
- { rootLockFileInfo.DirectoryName, Glob.Parse(rootLockFileInfo.DirectoryName) },
- };
-
- // For the given workspaces, add their paths to search list
- foreach (var workspace in definedWorkspaces)
- {
- var currentPath = rootLockFileInfo.DirectoryName;
- var directoryPathParts = workspace.Split('/');
-
- // When multiple levels of subdirectory are present, each directory parent must be added or the directory will not be reached
- // For example, ROOT/test-space/first-test/src/Cargo.toml requires the following directories be matched:
- // ROOT/test-space, ROOT/test-space/first-test, ROOT/test-space/first-test, ROOT/test-space/first-test/src
- // Each directory is matched explicitly instead of performing a StartsWith due to the potential of Glob character matching
- foreach (var pathPart in directoryPathParts)
- {
- currentPath = Path.Combine(currentPath, pathPart);
- directoryGlobs[currentPath] = Glob.Parse(currentPath);
- }
- }
-
- return directoryGlobs;
- }
-
- private static void FollowRoots(Dictionary packagesDict, IList roots, ISingleFileComponentRecorder singleFileComponentRecorder, bool isDevDependencies)
- {
- var componentQueue = new Queue<(string, CargoPackage)>();
- roots.ToList().ForEach(devRootDetectedComponent => componentQueue.Enqueue((null, devRootDetectedComponent)));
-
- var visited = new HashSet();
-
- // All of these components will be dev deps
- while (componentQueue.Count > 0)
- {
- var (parentId, currentPackage) = componentQueue.Dequeue();
- var currentComponent = CargoPackageToCargoComponent(currentPackage);
-
- if (visited.Contains(currentComponent.Id))
- {
- continue;
- }
-
- var isRootComponent = string.IsNullOrEmpty(parentId);
- if (isRootComponent)
- {
- AddOrUpdateDetectedComponent(singleFileComponentRecorder, currentComponent, isDevDependencies, isExplicitReferencedDependency: true);
- }
- else
- {
- AddOrUpdateDetectedComponent(singleFileComponentRecorder, currentComponent, isDevDependencies, parentComponentId: parentId);
- }
-
- visited.Add(currentComponent.Id);
-
- if (currentPackage.dependencies != null && currentPackage.dependencies.Any())
- {
- foreach (var dependency in currentPackage.dependencies)
- {
- var regexMatch = DependencyFormatRegex.Match(dependency);
- if (regexMatch.Success)
- {
- if (SemVersion.TryParse(regexMatch.Groups[2].Value, out var sv))
- {
- var name = regexMatch.Groups[1].Value;
- var version = sv.ToString();
- var source = regexMatch.Groups[3].Value;
-
- packagesDict.TryGetValue(new CargoComponent(name, version).Id, out var dependencyPackage);
-
- componentQueue.Enqueue((currentComponent.Id, dependencyPackage));
- }
- else
- {
- throw new FormatException($"Could not parse {regexMatch.Groups[2].Value} into a valid Semver");
- }
- }
- else
- {
- throw new FormatException("Could not parse: " + dependency);
- }
- }
- }
- }
- }
-
- private static DetectedComponent AddOrUpdateDetectedComponent(
- ISingleFileComponentRecorder singleFileComponentRecorder,
- TypedComponent component,
- bool isDevDependency,
- string parentComponentId = null,
- bool isExplicitReferencedDependency = false)
- {
- var newComponent = new DetectedComponent(component);
- singleFileComponentRecorder.RegisterUsage(newComponent, isExplicitReferencedDependency, parentComponentId: parentComponentId, isDevelopmentDependency: isDevDependency);
- var recordedComponent = singleFileComponentRecorder.GetComponent(newComponent.Component.Id);
- recordedComponent.DevelopmentDependency &= isDevDependency;
-
- return recordedComponent;
- }
-
- private static CargoPackage DependencyStringToCargoPackage(string depString)
- {
- var regexMatch = DependencyFormatRegex.Match(depString);
- if (regexMatch.Success)
- {
- if (SemVersion.TryParse(regexMatch.Groups[2].Value, out var sv))
- {
- var dependencyPackage = new CargoPackage
- {
- name = regexMatch.Groups[1].Value,
- version = sv.ToString(),
- source = regexMatch.Groups[3].Value,
- };
- return dependencyPackage;
- }
-
- throw new FormatException($"Could not parse {regexMatch.Groups[2].Value} into a valid Semver");
- }
-
- throw new FormatException("Could not parse: " + depString);
- }
-
- private static bool IsCargoPackageInDependencySpecifications(CargoPackage cargoPackage, IList dependencySpecifications)
- {
- return dependencySpecifications
- .Where(dependencySpecification => dependencySpecification.MatchesPackage(cargoPackage))
- .Any();
- }
-
- private static TypedComponent CargoPackageToCargoComponent(CargoPackage cargoPackage)
- {
- return new CargoComponent(cargoPackage.name, cargoPackage.version);
- }
-
- private static IEnumerable GetDependencies(TomlTable cargoToml, IEnumerable tomlDependencyKeys)
- {
- const string targetKey = "target";
- var dependencies = new List();
-
- foreach (var tomlDependencyKey in tomlDependencyKeys)
- {
- if (cargoToml.ContainsKey(tomlDependencyKey))
- {
- var newDependencyKey = cargoToml[tomlDependencyKey] as TomlTable;
- dependencies.Add(newDependencyKey);
- }
- }
-
- if (cargoToml.ContainsKey(targetKey))
- {
- var configs = cargoToml[targetKey] as TomlTable;
- foreach (var config in configs)
- {
- var properties = configs[config.Key] as TomlTable;
- foreach (var propertyKey in properties.Keys)
- {
- var isRelevantKey = tomlDependencyKeys.Any(dependencyKey =>
- string.Equals(propertyKey, dependencyKey, StringComparison.InvariantCultureIgnoreCase));
-
- if (isRelevantKey)
- {
- var newDependencyKey = properties[propertyKey] as TomlTable;
- dependencies.Add(newDependencyKey);
- }
- }
- }
- }
-
- return dependencies;
- }
- }
-}
diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateV2Detector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateV2Detector.cs
deleted file mode 100644
index dd529ea44..000000000
--- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateV2Detector.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Composition;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.ComponentDetection.Contracts;
-using Microsoft.ComponentDetection.Contracts.Internal;
-using Microsoft.ComponentDetection.Contracts.TypedComponent;
-using Microsoft.ComponentDetection.Detectors.Rust.Contracts;
-using Tomlyn;
-
-namespace Microsoft.ComponentDetection.Detectors.Rust
-{
- [Export(typeof(IComponentDetector))]
- public class RustCrateV2Detector : FileComponentDetector
- {
- public override string Id => "RustCrateV2Detector";
-
- public override IList SearchPatterns => new List { RustCrateUtilities.CargoLockSearchPattern };
-
- public override IEnumerable SupportedComponentTypes => new[] { ComponentType.Cargo };
-
- public override int Version { get; } = 6;
-
- public override IEnumerable Categories => new List { "Rust" };
-
- protected override Task OnFileFound(ProcessRequest processRequest, IDictionary detectorArgs)
- {
- var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder;
- var cargoLockFile = processRequest.ComponentStream;
- var options = new TomlModelOptions
- {
- IgnoreMissingProperties = true,
- };
- try
- {
- var reader = new StreamReader(cargoLockFile.Stream);
- var cargoLock = Toml.ToModel(reader.ReadToEnd(), options: options);
-
- // This makes sure we're only trying to parse Cargo.lock v2 formats
- if (cargoLock.Metadata != null)
- {
- this.Logger.LogInfo($"Cargo.lock file at {cargoLockFile.Location} contains a metadata section so we're parsing it as the v1 format. The v2 detector will no process it.");
- return Task.CompletedTask;
- }
-
- var lockFileInfo = new FileInfo(cargoLockFile.Location);
- var cargoTomlComponentStream = this.ComponentStreamEnumerableFactory.GetComponentStreams(lockFileInfo.Directory, new List { RustCrateUtilities.CargoTomlSearchPattern }, (name, directoryName) => false, recursivelyScanDirectories: false);
-
- var cargoDependencyData = RustCrateUtilities.ExtractRootDependencyAndWorkspaceSpecifications(cargoTomlComponentStream, singleFileComponentRecorder);
-
- // If workspaces have been defined in the root cargo.toml file, scan for specified cargo.toml manifests
- var numWorkspaceComponentStreams = 0;
- var expectedWorkspaceTomlCount = cargoDependencyData.CargoWorkspaces.Count;
- if (expectedWorkspaceTomlCount > 0)
- {
- var rootCargoTomlLocation = Path.Combine(lockFileInfo.DirectoryName, "Cargo.toml");
-
- var cargoTomlWorkspaceComponentStreams = this.ComponentStreamEnumerableFactory.GetComponentStreams(
- lockFileInfo.Directory,
- new List { RustCrateUtilities.CargoTomlSearchPattern },
- RustCrateUtilities.BuildExcludeDirectoryPredicateFromWorkspaces(lockFileInfo, cargoDependencyData.CargoWorkspaces, cargoDependencyData.CargoWorkspaceExclusions),
- recursivelyScanDirectories: true)
- .Where(x => !x.Location.Equals(rootCargoTomlLocation)); // The root directory needs to be included in directoriesToScan, but should not be reprocessed
- numWorkspaceComponentStreams = cargoTomlWorkspaceComponentStreams.Count();
-
- // Now that the non-root files have been located, add their dependencies
- RustCrateUtilities.ExtractDependencySpecifications(cargoTomlWorkspaceComponentStreams, singleFileComponentRecorder, cargoDependencyData.NonDevDependencies, cargoDependencyData.DevDependencies);
- }
-
- // Even though we can't read the file streams, we still have the enumerable!
- if (!cargoTomlComponentStream.Any() || cargoTomlComponentStream.Count() > 1)
- {
- this.Logger.LogWarning($"We are expecting exactly 1 accompanying Cargo.toml file next to the cargo.lock file found at {cargoLockFile.Location}");
- return Task.CompletedTask;
- }
-
- // If there is a mismatch between the number of expected and found workspaces, exit
- if (expectedWorkspaceTomlCount > numWorkspaceComponentStreams)
- {
- this.Logger.LogWarning($"We are expecting at least {expectedWorkspaceTomlCount} accompanying Cargo.toml file(s) from workspaces outside of the root directory {lockFileInfo.DirectoryName}, but found {numWorkspaceComponentStreams}");
- return Task.CompletedTask;
- }
-
- var cargoPackages = RustCrateUtilities.ConvertCargoLockV2PackagesToV1(cargoLock).ToHashSet();
- RustCrateUtilities.BuildGraph(cargoPackages, cargoDependencyData.NonDevDependencies, cargoDependencyData.DevDependencies, singleFileComponentRecorder);
- }
- catch (Exception e)
- {
- // If something went wrong, just ignore the file
- this.Logger.LogFailedReadingFile(cargoLockFile.Location, e);
- }
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCrateDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/RustCrateDetectorTests.cs
index d5ba17b1e..5bc2ea216 100644
--- a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCrateDetectorTests.cs
+++ b/test/Microsoft.ComponentDetection.Detectors.Tests/RustCrateDetectorTests.cs
@@ -20,13 +20,11 @@ namespace Microsoft.ComponentDetection.Detectors.Tests
public class RustCrateDetectorTests
{
private DetectorTestUtility detectorTestUtility;
- private DetectorTestUtility detectorV2TestUtility;
[TestInitialize]
public void TestInitialize()
{
this.detectorTestUtility = DetectorTestUtilityCreator.Create();
- this.detectorV2TestUtility = DetectorTestUtilityCreator.Create();
}
[TestMethod]
@@ -34,7 +32,6 @@ public async Task TestGraphIsCorrect()
{
var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", this.testCargoLockString)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -46,60 +43,29 @@ public async Task TestGraphIsCorrect()
var rootComponents = new List
{
"my_dependency 1.0.0 - Cargo",
-
- // Note: my_other_dependency isn't here because we don't capture local deps
"other_dependency 0.4.0 - Cargo",
+ "my_dev_dependency 1.0.0 - Cargo",
};
rootComponents.ForEach(rootComponentId => graph.IsComponentExplicitlyReferenced(rootComponentId).Should().BeTrue());
- // Verify explicitly referenced dev roots
- var rootDevComponents = new List { "my_dev_dependency 1.0.0 - Cargo" };
-
- rootDevComponents.ForEach(rootDevComponentId => graph.IsComponentExplicitlyReferenced(rootDevComponentId).Should().BeTrue());
-
// Verify dependencies for my_dependency
graph.GetDependenciesForComponent("my_dependency 1.0.0 - Cargo").Should().BeEmpty();
// Verify dependencies for other_dependency
- var other_dependencyDependencies = new List { "other_dependency_dependency 0.1.12-alpha.6 - Cargo" };
+ var other_dependencyDependencies = new List {
+ "other_dependency_dependency 0.1.12-alpha.6 - Cargo",
+ };
graph.GetDependenciesForComponent("other_dependency 0.4.0 - Cargo").Should().BeEquivalentTo(other_dependencyDependencies);
// Verify dependencies for my_dev_dependency
- var my_dev_dependencyDependencies = new List { "other_dependency_dependency 0.1.12-alpha.6 - Cargo", "dev_dependency_dependency 0.2.23 - Cargo" };
-
- graph.GetDependenciesForComponent("my_dev_dependency 1.0.0 - Cargo").Should().BeEquivalentTo(my_dev_dependencyDependencies);
- }
-
- [TestMethod]
- public async Task TestRequirePairForComponents()
- {
- var cargoDefinitionPairsMatrix = new List<(string, string)>
- {
- (null, this.testCargoTomlString),
- (this.testCargoLockString, null),
- (null, null),
+ var my_dev_dependencyDependencies = new List {
+ "other_dependency_dependency 0.1.12-alpha.6 - Cargo",
+ "dev_dependency_dependency 0.2.23 - Cargo",
};
- foreach (var cargoDefinitionPairs in cargoDefinitionPairsMatrix)
- {
- if (cargoDefinitionPairs.Item1 != null)
- {
- this.detectorTestUtility.WithFile("Cargo.lock", cargoDefinitionPairs.Item1);
- }
-
- if (cargoDefinitionPairs.Item2 != null)
- {
- this.detectorTestUtility.WithFile("Cargo.toml", cargoDefinitionPairs.Item2, new List { "Cargo.toml" });
- }
-
- var (result, componentRecorder) = await this.detectorTestUtility.ExecuteDetector();
-
- Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
-
- componentRecorder.GetDetectedComponents().Count().Should().Be(0);
- }
+ graph.GetDependenciesForComponent("my_dev_dependency 1.0.0 - Cargo").Should().BeEquivalentTo(my_dev_dependencyDependencies);
}
[TestMethod]
@@ -108,30 +74,16 @@ public async Task TestSupportsCargoV1AndV2DefinitionPairs()
var componentRecorder = new ComponentRecorder();
var request = new ScanRequest(new DirectoryInfo(Path.GetTempPath()), null, null, new Dictionary(), null, componentRecorder);
- var (result1, _) = await this.detectorTestUtility
+ var (result, _) = await this.detectorTestUtility
/* v1 files */
.WithFile("Cargo.lock", this.testCargoLockString)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
/* v2 files */
.WithFile("Cargo.lock", this.testCargoLockV2String, fileLocation: Path.Join(Path.GetTempPath(), "v2", "Cargo.lock"))
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" }, fileLocation: Path.Join(Path.GetTempPath(), "v2", "Cargo.toml"))
/* so we can reuse the component recorder */
.WithScanRequest(request)
.ExecuteDetector();
- var (result2, _) = await this.detectorV2TestUtility
- /* v1 files */
- .WithFile("Cargo.lock", this.testCargoLockString)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
- /* v2 files */
- .WithFile("Cargo.lock", this.testCargoLockV2String, fileLocation: Path.Join(Path.GetTempPath(), "v2", "Cargo.lock"))
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" }, fileLocation: Path.Join(Path.GetTempPath(), "v2", "Cargo.toml"))
- /* so we can reuse the component recorder */
- .WithScanRequest(request)
- .ExecuteDetector();
-
- Assert.AreEqual(ProcessingResultCode.Success, result1.ResultCode);
- Assert.AreEqual(ProcessingResultCode.Success, result2.ResultCode);
+ Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
var componentGraphs = componentRecorder.GetDependencyGraphsByLocation();
@@ -143,9 +95,7 @@ public async Task TestSupportsMultipleCargoV1DefinitionPairs()
{
var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", this.testCargoLockString)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
.WithFile("Cargo.lock", this.testCargoLockString, fileLocation: Path.Join(Path.GetTempPath(), "sub-path", "Cargo.lock"))
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" }, fileLocation: Path.Join(Path.GetTempPath(), "sub-path", "Cargo.toml"))
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -159,18 +109,16 @@ public async Task TestSupportsMultipleCargoV1DefinitionPairs()
graph1.GetComponents().Should().BeEquivalentTo(graph2.GetComponents()); // The graphs should have detected the same components
- // 4 file locations are expected. 2 for each Cargo.lock and Cargo.toml pair
- componentRecorder.ForAllComponents(x => Enumerable.Count(x.AllFileLocations).Should().Be(4));
+ // Two Cargo.lock files
+ componentRecorder.ForAllComponents(x => Enumerable.Count(x.AllFileLocations).Should().Be(2));
}
[TestMethod]
public async Task TestSupportsMultipleCargoV2DefinitionPairs()
{
- var (result, componentRecorder) = await this.detectorV2TestUtility
+ var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", this.testCargoLockV2String)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
.WithFile("Cargo.lock", this.testCargoLockV2String, fileLocation: Path.Join(Path.GetTempPath(), "sub-path", "Cargo.lock"))
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" }, fileLocation: Path.Join(Path.GetTempPath(), "sub-path", "Cargo.toml"))
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -184,8 +132,8 @@ public async Task TestSupportsMultipleCargoV2DefinitionPairs()
graph1.GetComponents().Should().BeEquivalentTo(graph2.GetComponents()); // The graphs should have detected the same components
- // 4 file locations are expected. 2 for each Cargo.lock and Cargo.toml pair
- componentRecorder.ForAllComponents(x => x.AllFileLocations.Count().Should().Be(4));
+ // Two Cargo.lock files
+ componentRecorder.ForAllComponents(x => x.AllFileLocations.Count().Should().Be(2));
}
[TestMethod]
@@ -193,7 +141,6 @@ public async Task TestRustDetector()
{
var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", this.testCargoLockString)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -209,16 +156,6 @@ public async Task TestRustDetector()
{ "one_more_dev_dep", "1.0.0" },
};
- IDictionary packageIsDevDependency = new Dictionary()
- {
- { "my_dependency 1.0.0 - Cargo", false },
- { "other_dependency 0.4.0 - Cargo", false },
- { "other_dependency_dependency 0.1.12-alpha.6 - Cargo", false },
- { "my_dev_dependency 1.0.0 - Cargo", true },
- { "dev_dependency_dependency 0.2.23 - Cargo", true },
- { "one_more_dev_dep 1.0.0 - Cargo", true },
- };
-
IDictionary> packageDependencyRoots = new Dictionary>()
{
{ "my_dependency", new HashSet() { "my_dependency" } },
@@ -238,9 +175,6 @@ public async Task TestRustDetector()
// Verify version
Assert.AreEqual(packageVersions[packageName], (discoveredComponent.Component as CargoComponent).Version);
- // Verify dev dependency flag
- componentRecorder.GetEffectiveDevDependencyValue(discoveredComponent.Component.Id).Should().Be(packageIsDevDependency[discoveredComponent.Component.Id]);
-
var dependencyRoots = new HashSet();
componentRecorder.AssertAllExplicitlyReferencedComponents(
@@ -261,9 +195,8 @@ public async Task TestRustDetector()
[TestMethod]
public async Task TestRustV2Detector()
{
- var (result, componentRecorder) = await this.detectorV2TestUtility
+ var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", this.testCargoLockV2String)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -280,17 +213,6 @@ public async Task TestRustV2Detector()
"same_package 2.0.0",
};
- IDictionary packageIsDevDependency = new Dictionary()
- {
- { "my_dependency 1.0.0 - Cargo", false },
- { "other_dependency 0.4.0 - Cargo", false },
- { "other_dependency_dependency 0.1.12-alpha.6 - Cargo", false },
- { "my_dev_dependency 1.0.0 - Cargo", true },
- { "dev_dependency_dependency 0.2.23 - Cargo", true },
- { "same_package 1.0.0 - Cargo", false },
- { "same_package 2.0.0 - Cargo", true },
- };
-
IDictionary> packageDependencyRoots = new Dictionary>()
{
{ "my_dependency 1.0.0", new HashSet() { "my_dependency 1.0.0" } },
@@ -311,9 +233,6 @@ public async Task TestRustV2Detector()
// Verify version
Assert.IsTrue(packageVersions.Contains(componentKey));
- // Verify dev dependency flag
- componentRecorder.GetEffectiveDevDependencyValue(discoveredComponent.Component.Id).Should().Be(packageIsDevDependency[discoveredComponent.Component.Id]);
-
componentRecorder.AssertAllExplicitlyReferencedComponents(
discoveredComponent.Component.Id,
packageDependencyRoots[componentKey].Select(expectedRoot =>
@@ -329,30 +248,6 @@ public async Task TestRustV2Detector()
}
}
- [TestMethod]
- public async Task TestRustV2Detector_DoesNotRunV1Format()
- {
- var (result, componentRecorder) = await this.detectorV2TestUtility
- .WithFile("Cargo.lock", this.testCargoLockString)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
- .ExecuteDetector();
-
- Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
- Assert.AreEqual(0, componentRecorder.GetDetectedComponents().Count());
- }
-
- [TestMethod]
- public async Task TestRustV1Detector_DoesNotRunV2Format()
- {
- var (result, componentRecorder) = await this.detectorTestUtility
- .WithFile("Cargo.lock", this.testCargoLockV2String)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
- .ExecuteDetector();
-
- Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
- Assert.AreEqual(0, componentRecorder.GetDetectedComponents().Count());
- }
-
[TestMethod]
public async Task TestRustV2Detector_DuplicatePackage()
{
@@ -425,9 +320,8 @@ public async Task TestRustV2Detector_DuplicatePackage()
source = ""registry+https://github.com/rust-lang/crates.io-index""
";
- var (result, componentRecorder) = await this.detectorV2TestUtility
+ var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", testCargoLock)
- .WithFile("Cargo.toml", this.testCargoTomlString, new List { "Cargo.toml" })
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -439,20 +333,16 @@ public async Task TestRustV2Detector_DuplicatePackage()
var rootComponents = new List
{
"my_dependency 1.0.0 - Cargo",
-
- // Note: my_other_dependency isn't here because we don't capture local deps
+ "my_dev_dependency 1.0.0 - Cargo",
"other_dependency 0.4.0 - Cargo",
};
rootComponents.ForEach(rootComponentId => graph.IsComponentExplicitlyReferenced(rootComponentId).Should().BeTrue());
- // Verify explicitly referenced dev roots
- var rootDevComponents = new List { "my_dev_dependency 1.0.0 - Cargo" };
-
- rootDevComponents.ForEach(rootDevComponentId => graph.IsComponentExplicitlyReferenced(rootDevComponentId).Should().BeTrue());
-
// Verify dependencies for my_dependency
- var my_dependencyDependencies = new List { "same_package 1.0.0 - Cargo" };
+ var my_dependencyDependencies = new List {
+ "same_package 1.0.0 - Cargo",
+ };
graph.GetDependenciesForComponent("my_dependency 1.0.0 - Cargo").Should().BeEquivalentTo(my_dependencyDependencies);
@@ -462,7 +352,10 @@ public async Task TestRustV2Detector_DuplicatePackage()
graph.GetDependenciesForComponent("other_dependency 0.4.0 - Cargo").Should().BeEquivalentTo(other_dependencyDependencies);
// Verify dependencies for my_dev_dependency
- var my_dev_dependencyDependencies = new List { "other_dependency_dependency 0.1.12-alpha.6 - Cargo", "dev_dependency_dependency 0.2.23 - Cargo" };
+ var my_dev_dependencyDependencies = new List {
+ "other_dependency_dependency 0.1.12-alpha.6 - Cargo",
+ "dev_dependency_dependency 0.2.23 - Cargo",
+ };
graph.GetDependenciesForComponent("my_dev_dependency 1.0.0 - Cargo").Should().BeEquivalentTo(my_dev_dependencyDependencies);
}
@@ -470,16 +363,14 @@ public async Task TestRustV2Detector_DuplicatePackage()
[TestMethod]
public async Task TestRustDetector_SupportEmptySource()
{
- var testTomlString = @"
-[package]
+ var testLockString = @"
+[[package]]
name = ""my_test_package""
version = ""1.2.3""
-authors = [""example@example.com>""]
+dependencies = [
+ ""my_dependency""
+]
-[dependencies]
-my_dependency = ""1.0""
-";
- var testLockString = @"
[[package]]
name = ""my_dependency""
version = ""1.0.0""
@@ -493,9 +384,8 @@ public async Task TestRustDetector_SupportEmptySource()
version = ""0.1.12-alpha.6""
source = ""registry+https://github.com/rust-lang/crates.io-index""
";
- var (result, componentRecorder) = await this.detectorV2TestUtility
+ var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", testLockString)
- .WithFile("Cargo.toml", testTomlString, new List { "Cargo.toml" })
.ExecuteDetector();
result.ResultCode.Should().Be(ProcessingResultCode.Success);
@@ -514,86 +404,21 @@ public async Task TestRustDetector_SupportEmptySource()
}
[TestMethod]
- public async Task TestRustV1Detector_WorkspacesWithTopLevelDependencies()
+ public async Task TestRustDetector_V1WorkspacesWithTopLevelDependencies()
{
- var (result, componentRecorder) = await this.detectorTestUtility
- .WithFile("Cargo.lock", string.Concat(this.testWorkspaceLockBaseDependency, this.testWorkspaceLockV1NoBaseString))
- .WithFile("Cargo.toml", string.Concat(this.testWorkspaceTomlBaseDependency, this.testWorkspacesBaseTomlString), new List { "Cargo.toml" })
- .WithFile("Cargo.toml", this.testWorkspace1TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work", "Cargo.toml"))
- .WithFile("Cargo.toml", this.testWorkspace2TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work2", "Cargo.toml"))
- .ExecuteDetector();
-
- Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
- Assert.AreEqual(7, componentRecorder.GetDetectedComponents().Count());
-
- var packageVersions = new List()
- {
- "dev_dependency_dependency 0.2.23",
- "one_more_dev_dep 1.0.0",
- "other_dependency 0.4.0",
- "other_dependency_dependency 0.1.12-alpha.6",
- "my_dependency 1.0.0",
- "same_package 1.0.0",
- "test_package 2.0.0",
- };
-
- IDictionary packageIsDevDependency = new Dictionary()
- {
- { "dev_dependency_dependency 0.2.23 - Cargo", true },
- { "one_more_dev_dep 1.0.0 - Cargo", true },
- { "other_dependency 0.4.0 - Cargo", false },
- { "other_dependency_dependency 0.1.12-alpha.6 - Cargo", false },
- { "my_dependency 1.0.0 - Cargo", false },
- { "same_package 1.0.0 - Cargo", false },
- { "test_package 2.0.0 - Cargo", false },
- };
-
- IDictionary> packageDependencyRoots = new Dictionary>()
- {
- { "dev_dependency_dependency 0.2.23", new HashSet() { "dev_dependency_dependency 0.2.23" } },
- { "one_more_dev_dep 1.0.0", new HashSet() { "dev_dependency_dependency 0.2.23" } },
- { "other_dependency 0.4.0", new HashSet() { "other_dependency 0.4.0" } },
- { "other_dependency_dependency 0.1.12-alpha.6", new HashSet() { "other_dependency 0.4.0" } },
- { "my_dependency 1.0.0", new HashSet() { "my_dependency 1.0.0" } },
- { "same_package 1.0.0", new HashSet() { "my_dependency 1.0.0" } },
- { "test_package 2.0.0", new HashSet() { "test_package 2.0.0" } },
- };
-
- ISet componentNames = new HashSet();
- foreach (var discoveredComponent in componentRecorder.GetDetectedComponents())
- {
- var component = discoveredComponent.Component as CargoComponent;
- var componentKey = $"{component.Name} {component.Version}";
-
- // Verify version
- Assert.IsTrue(packageVersions.Contains(componentKey));
-
- // Verify dev dependency flag
- componentRecorder.GetEffectiveDevDependencyValue(discoveredComponent.Component.Id).Should().Be(packageIsDevDependency[discoveredComponent.Component.Id]);
-
- componentRecorder.AssertAllExplicitlyReferencedComponents(
- discoveredComponent.Component.Id,
- packageDependencyRoots[componentKey].Select(expectedRoot =>
- new Func(parentComponent => $"{parentComponent.Name} {parentComponent.Version}" == expectedRoot)).ToArray());
-
- componentNames.Add(componentKey);
- }
-
- // Verify all packages were detected
- foreach (var expectedPackage in packageVersions)
- {
- Assert.IsTrue(componentNames.Contains(expectedPackage));
- }
+ await TestRustDetector_WorkspacesWithTopLevelDependencies(this.testWorkspaceLockV1NoBaseString);
}
[TestMethod]
- public async Task TestRustV2Detector_WorkspacesWithTopLevelDependencies()
+ public async Task TestRustDetector_V2WorkspacesWithTopLevelDependencies()
{
- var (result, componentRecorder) = await this.detectorV2TestUtility
- .WithFile("Cargo.lock", string.Concat(this.testWorkspaceLockBaseDependency, this.testWorkspaceLockV2NoBaseString))
- .WithFile("Cargo.toml", string.Concat(this.testWorkspaceTomlBaseDependency, this.testWorkspacesBaseTomlString), new List { "Cargo.toml" })
- .WithFile("Cargo.toml", this.testWorkspace1TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work", "Cargo.toml"))
- .WithFile("Cargo.toml", this.testWorkspace2TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work2", "Cargo.toml"))
+ await TestRustDetector_WorkspacesWithTopLevelDependencies(this.testWorkspaceLockV2NoBaseString);
+ }
+
+ private async Task TestRustDetector_WorkspacesWithTopLevelDependencies(string lockFile)
+ {
+ var (result, componentRecorder) = await this.detectorTestUtility
+ .WithFile("Cargo.lock", string.Concat(testWorkspaceLockBaseDependency, lockFile))
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -610,17 +435,6 @@ public async Task TestRustV2Detector_WorkspacesWithTopLevelDependencies()
"test_package 2.0.0",
};
- IDictionary packageIsDevDependency = new Dictionary()
- {
- { "dev_dependency_dependency 0.2.23 - Cargo", true },
- { "one_more_dev_dep 1.0.0 - Cargo", true },
- { "other_dependency 0.4.0 - Cargo", false },
- { "other_dependency_dependency 0.1.12-alpha.6 - Cargo", false },
- { "my_dependency 1.0.0 - Cargo", false },
- { "same_package 1.0.0 - Cargo", false },
- { "test_package 2.0.0 - Cargo", false },
- };
-
IDictionary> packageDependencyRoots = new Dictionary>()
{
{ "dev_dependency_dependency 0.2.23", new HashSet() { "dev_dependency_dependency 0.2.23" } },
@@ -641,9 +455,6 @@ public async Task TestRustV2Detector_WorkspacesWithTopLevelDependencies()
// Verify version
Assert.IsTrue(packageVersions.Contains(componentKey));
- // Verify dev dependency flag
- componentRecorder.GetEffectiveDevDependencyValue(discoveredComponent.Component.Id).Should().Be(packageIsDevDependency[discoveredComponent.Component.Id]);
-
componentRecorder.AssertAllExplicitlyReferencedComponents(
discoveredComponent.Component.Id,
packageDependencyRoots[componentKey].Select(expectedRoot =>
@@ -660,62 +471,43 @@ public async Task TestRustV2Detector_WorkspacesWithTopLevelDependencies()
}
[TestMethod]
- public async Task TestRustV1Detector_WorkspacesNoTopLevelDependencies()
+ public async Task TestRustDetector_V1WorkspacesNoTopLevelDependencies()
{
- var (result, componentRecorder) = await this.detectorTestUtility
- .WithFile("Cargo.lock", this.testWorkspaceLockV1NoBaseString)
- .WithFile("Cargo.toml", this.testWorkspacesBaseTomlString, new List { "Cargo.toml" })
- .WithFile("Cargo.toml", this.testWorkspace1TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work", "Cargo.toml"))
- .WithFile("Cargo.toml", this.testWorkspace2TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work2", "Cargo.toml"))
- .ExecuteDetector();
-
- Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
- Assert.AreEqual(6, componentRecorder.GetDetectedComponents().Count());
+ await TestRustDetector_WorkspacesNoTopLevelDependencies(this.testWorkspaceLockV1NoBaseString);
}
[TestMethod]
- public async Task TestRustV2Detector_WorkspacesNoTopLevelDependencies()
+ public async Task TestRustDetector_V2WorkspacesNoTopLevelDependencies()
{
- var (result, componentRecorder) = await this.detectorV2TestUtility
- .WithFile("Cargo.lock", this.testWorkspaceLockV2NoBaseString)
- .WithFile("Cargo.toml", this.testWorkspacesBaseTomlString, new List { "Cargo.toml" })
- .WithFile("Cargo.toml", this.testWorkspace1TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work", "Cargo.toml"))
- .WithFile("Cargo.toml", this.testWorkspace2TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "test-work2", "Cargo.toml"))
- .ExecuteDetector();
-
- Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
- Assert.AreEqual(6, componentRecorder.GetDetectedComponents().Count());
+ await TestRustDetector_WorkspacesNoTopLevelDependencies(this.testWorkspaceLockV2NoBaseString);
}
- [TestMethod]
- public async Task TestRustV1Detector_WorkspacesWithSubDirectories()
+ private async Task TestRustDetector_WorkspacesNoTopLevelDependencies(string lockFile)
{
var (result, componentRecorder) = await this.detectorTestUtility
- .WithFile("Cargo.lock", this.testWorkspaceLockV1NoBaseString)
- .WithFile("Cargo.toml", this.testWorkspacesSubdirectoryTomlString, new List { "Cargo.toml" })
- .WithFile("Cargo.toml", this.testWorkspace1TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "sub//test-work", "Cargo.toml"))
- .WithFile("Cargo.toml", this.testWorkspace2TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "sub2//test//test-work2", "Cargo.toml"))
+ .WithFile("Cargo.lock", lockFile)
.ExecuteDetector();
- var componentGraphs = componentRecorder.GetDependencyGraphsByLocation();
-
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
Assert.AreEqual(6, componentRecorder.GetDetectedComponents().Count());
+ }
- Assert.AreEqual(1, componentGraphs.Count); // Only 1 cargo.lock is specified with multiple sub-directories of .toml
-
- // A root Cargo.lock, Cargo.toml, and the 2 workspace Cargo.tomls should be registered
- componentRecorder.ForAllComponents(x => x.AllFileLocations.Count().Should().Be(4));
+ [TestMethod]
+ public async Task TestRustDetector_V1WorkspacesWithSubDirectories()
+ {
+ await TestRustDetector_WorkspacesWithSubDirectories(testWorkspaceLockV1NoBaseString);
}
[TestMethod]
- public async Task TestRustV2Detector_WorkspacesWithSubDirectories()
+ public async Task TestRustDetector_V2WorkspacesWithSubDirectories()
{
- var (result, componentRecorder) = await this.detectorV2TestUtility
- .WithFile("Cargo.lock", this.testWorkspaceLockV2NoBaseString)
- .WithFile("Cargo.toml", this.testWorkspacesSubdirectoryTomlString, new List { "Cargo.toml" })
- .WithFile("Cargo.toml", this.testWorkspace1TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "sub//test-work", "Cargo.toml"))
- .WithFile("Cargo.toml", this.testWorkspace2TomlString, new List { "Cargo.toml" }, fileLocation: Path.Combine(Path.GetTempPath(), "sub2//test//test-work2", "Cargo.toml"))
+ await TestRustDetector_WorkspacesWithSubDirectories(this.testWorkspaceLockV2NoBaseString);
+ }
+
+ private async Task TestRustDetector_WorkspacesWithSubDirectories(string lockFile)
+ {
+ var (result, componentRecorder) = await this.detectorTestUtility
+ .WithFile("Cargo.lock", lockFile)
.ExecuteDetector();
var componentGraphs = componentRecorder.GetDependencyGraphsByLocation();
@@ -723,24 +515,15 @@ public async Task TestRustV2Detector_WorkspacesWithSubDirectories()
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
Assert.AreEqual(6, componentRecorder.GetDetectedComponents().Count());
- Assert.AreEqual(1, componentGraphs.Count); // Only 1 cargo.lock is specified with multiple sub-directories of .toml
+ Assert.AreEqual(1, componentGraphs.Count); // Only 1 Cargo.lock is specified
- // A root Cargo.lock, Cargo.toml, and the 2 workspace Cargo.tomls should be registered
- componentRecorder.ForAllComponents(x => x.AllFileLocations.Count().Should().Be(4));
+ // A root Cargo.lock
+ componentRecorder.ForAllComponents(x => x.AllFileLocations.Count().Should().Be(1));
}
[TestMethod]
public async Task TestRustDetector_UnequalButSemverCompatibleRoot()
{
- var testTomlString = @"
-[package]
-name = ""test""
-version = ""0.1.0""
-edition = ""2021""
-
-[dependencies]
-c-ares = ""7.1.0""
-";
var testLockString = @"
[[package]]
name = ""c-ares""
@@ -764,9 +547,8 @@ public async Task TestRustDetector_UnequalButSemverCompatibleRoot()
""c-ares"",
]
";
- var (result, componentRecorder) = await this.detectorV2TestUtility
+ var (result, componentRecorder) = await this.detectorTestUtility
.WithFile("Cargo.lock", testLockString)
- .WithFile("Cargo.toml", testTomlString, new List { "Cargo.toml" })
.ExecuteDetector();
Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
@@ -788,84 +570,78 @@ public async Task TestRustDetector_UnequalButSemverCompatibleRoot()
}
[TestMethod]
- public async Task TestRustDetector_RenamedDependency()
+ public async Task TestRustDetector_GitDependency()
{
- var testTomlString = @"
-[package]
-name = ""my_test_package""
-version = ""1.2.3""
-authors = [""example@example.com>""]
-
-[dependencies]
-foo_dependency = { package = ""my_dependency"", version = ""1.0.0""}
-";
var testLockString = @"
[[package]]
-name = ""my_dependency""
-version = ""1.0.0""
-source = ""registry+https://github.com/rust-lang/crates.io-index""
+name = ""my_git_dep_test""
+version = ""0.1.0""
+dependencies = [
+ ""my_git_dep"",
+]
+
+[[package]]
+name = ""my_git_dep""
+version = ""0.1.0""
+source = ""git+https://github.com/microsoft/component-detection/?branch=main#abcdabcdabcdabcdabcdbacdbacdbacdabcdabcd""
";
- var (result, componentRecorder) = await this.detectorV2TestUtility
- .WithFile("Cargo.lock", testLockString)
- .WithFile("Cargo.toml", testTomlString, new List { "Cargo.toml" })
- .ExecuteDetector();
+ var (result, componentRecorder) = await this.detectorTestUtility
+ .WithFile("Cargo.lock", testLockString)
+ .ExecuteDetector();
- result.ResultCode.Should().Be(ProcessingResultCode.Success);
+ Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
+ Assert.AreEqual(1, componentRecorder.GetDetectedComponents().Count());
var dependencyGraphs = componentRecorder.GetDependencyGraphsByLocation();
dependencyGraphs.Count.Should().Be(1);
var dependencyGraph = dependencyGraphs.Single().Value;
- var foundComponents = dependencyGraph.GetComponents();
- foundComponents.Count().Should().Be(1);
-
- var rootComponents = new List
- {
- "my_dependency 1.0.0 - Cargo",
- };
- rootComponents.ForEach(rootComponentId => dependencyGraph.IsComponentExplicitlyReferenced(rootComponentId).Should().BeTrue());
+ dependencyGraph.Contains("my_git_dep 0.1.0 - Cargo").Should().BeTrue();
}
[TestMethod]
- public async Task TestRustDetector_TargetSpecificDependencies()
+ public async Task TestRustDetector_MultipleRegistries()
{
- var (result, componentRecorder) = await this.detectorV2TestUtility
- .WithFile("Cargo.lock", this.testTargetSpecificDependenciesLockString)
- .WithFile("Cargo.toml", this.testTargetSpecificDependenciesTomlString, new List { "Cargo.toml" })
- .ExecuteDetector();
+ var testLockString = @"
+[[package]]
+name = ""registrytest""
+version = ""0.1.0""
+dependencies = [
+ ""common_name 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"",
+ ""common_name 0.2.0 (registry+sparse+https://other.registry/index/)"",
+]
- Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
- Assert.AreEqual(3, componentRecorder.GetDetectedComponents().Count());
+[[package]]
+name = ""common_name""
+version = ""0.2.0""
+source = ""registry+https://github.com/rust-lang/crates.io-index""
- componentRecorder.GetComponent("my_dependency 1.0.0 - Cargo").Should().NotBeNull();
- componentRecorder.GetComponent("winhttp 0.4.0 - Cargo").Should().NotBeNull();
+[[package]]
+name = ""common_name""
+version = ""0.2.0""
+source = ""registry+sparse+https://other.registry/index/""
+";
+ var (result, componentRecorder) = await detectorTestUtility
+ .WithFile("Cargo.lock", testLockString)
+ .ExecuteDetector();
- var openssl = componentRecorder.GetComponent("openssl 1.0.1 - Cargo");
- openssl.Should().NotBeNull();
- componentRecorder.GetEffectiveDevDependencyValue(openssl.Id).Value.Should().BeTrue();
- }
+ Assert.AreEqual(ProcessingResultCode.Success, result.ResultCode);
+ // If registries have identity, this should be 2
+ Assert.AreEqual(1, componentRecorder.GetDetectedComponents().Count());
- ///
- /// (my_dependency, 1.0, root)
- /// (my_other_dependency, 0.1.0, root)
- /// (other_dependency, 0.4, root) -> (other_dependency_dependency, 0.1.12-alpha.6)
- /// (my_dev_dependency, 1.0, root, dev) -> (other_dependency_dependency, 0.1.12-alpha.6)
- /// -> (dev_dependency_dependency, 0.2.23, dev) -> (one_more_dev_dep, 1.0.0, dev).
- ///
- private readonly string testCargoTomlString = @"
-[package]
-name = ""my_test_package""
-version = ""1.2.3""
-authors = [""example@example.com>""]
+ var dependencyGraphs = componentRecorder.GetDependencyGraphsByLocation();
+ dependencyGraphs.Count.Should().Be(1);
-[dependencies]
-my_dependency = ""1.0""
-my_other_package = { path = ""../my_other_package_path"", version = ""0.1.0"" }
-other_dependency = { version = ""0.4"" }
+ var dependencyGraph = dependencyGraphs.Single().Value;
-[dev-dependencies]
-my_dev_dependency = ""1.0""
-";
+ // If registries have identity, we should have two entries here
+ var componentIds = new List
+ {
+ "common_name 0.2.0 - Cargo",
+ };
+
+ componentIds.ForEach(componentId => dependencyGraph.Contains(componentId).Should().BeTrue());
+ }
private readonly string testCargoLockString = @"
[[package]]
@@ -987,20 +763,6 @@ public async Task TestRustDetector_TargetSpecificDependencies()
name = ""same_package""
version = ""2.0.0""
source = ""registry+https://github.com/rust-lang/crates.io-index""
-";
-
- private readonly string testWorkspacesBaseTomlString = @"[workspace]
-members = [
- ""test-work"",
- ""test-work2"",
-]
-";
-
- private readonly string testWorkspacesSubdirectoryTomlString = @"[workspace]
-members = [
- ""sub/test-work"",
- ""sub2/test/test-work2"",
-]
";
private readonly string testWorkspaceLockV1NoBaseString = @"[[package]]
@@ -1090,57 +852,6 @@ public async Task TestRustDetector_TargetSpecificDependencies()
name = ""test_package""
version = ""2.0.0""
source = ""registry+https://github.com/rust-lang/crates.io-index""
-";
-
- private readonly string testWorkspaceTomlBaseDependency = @"
-[dependencies]
-test_package = ""2.0.0""
-";
-
- private readonly string testWorkspace1TomlString = @"
-[dependencies]
-my_dependency = ""1.0.0""
-
-[dev-dependencies]
-dev_dependency_dependency = ""0.2.23""
-";
-
- private readonly string testWorkspace2TomlString = @"
-[dependencies]
-other_dependency = ""0.4.0""
-";
-
- private readonly string testTargetSpecificDependenciesTomlString = @"
-[package]
-name = ""my_test_package""
-version = ""1.2.3""
-authors = [""example@example.com>""]
-
-[dependencies]
-my_dependency = ""1.0""
-
-[target.'cfg(windows)'.dependencies]
-winhttp = ""0.4.0""
-
-[target.'cfg(unix)'.dev-dependencies]
-openssl = ""1.0.1""
-";
-
- private readonly string testTargetSpecificDependenciesLockString = @"
-[[package]]
-name = ""my_dependency""
-version = ""1.0.0""
-source = ""registry+https://github.com/rust-lang/crates.io-index""
-
-[[package]]
-name = ""winhttp""
-version = ""0.4.0""
-source = ""registry+https://github.com/rust-lang/crates.io-index""
-
-[[package]]
-name = ""openssl""
-version = ""1.0.1""
-source = ""registry+https://github.com/rust-lang/crates.io-index""
";
}
}