diff --git a/docs/detectors/rust.md b/docs/detectors/rust.md new file mode 100644 index 000000000..6ed92945f --- /dev/null +++ b/docs/detectors/rust.md @@ -0,0 +1,86 @@ +## Overview + +The **RustSbomDetector** detects components in Rust projects that use Cargo as their build system. +It identifies all Cargo packages (crates) referenced within a repository, including both workspace-owned and external dependencies, and reports them under the `cargo` component type in the scan manifest. + +The detector’s implementation resides in `RustSbomDetector`. + +--- + +## SBOM Mode + +When Cargo-generated SBOMs are present, they serve as the **authoritative source of dependency information**. +SBOMs are produced by running Cargo with the unstable [SBOM feature](https://doc.rust-lang.org/cargo/reference/unstable.html#sbom), which emits a machine-readable dependency graph per build artifact. + +In this mode: + +- Every discovered `*.cargo-sbom.json` is parsed individually; **no skip logic** is applied. +- Each SBOM corresponds to a specific build artifact or package target, so all are processed to ensure complete coverage. +- Dependencies, including transitive ones, are read directly from SBOM entries. +- SBOMs are considered the most accurate reflection of the built dependency graph, with minimal risk of false positives or duplicate detections. + +--- + +## Fallback Mode + +If no Cargo SBOMs are found, the detector switches to **Fallback Mode**, which derives dependency information from the project’s manifests and lock files: + +1. The detector attempts to run `cargo metadata` on each discovered `Cargo.toml`. + - This produces a structured dependency graph for that manifest and its workspace members. +2. If the CLI is unavailable or fails, the detector falls back to parsing the corresponding `Cargo.lock` files directly. + +Because both `cargo metadata` and `Cargo.lock` describe potentially broader dependency sets than what is actually built, this mode can occasionally **over-report dependencies**, resulting in potential false positives compared to SBOM Mode. + +--- + +### Skip Optimizations (Applied in Fallback Mode) + +To improve performance in multi-package repositories, the detector avoids redundant processing by applying **skip logic**: + +- **Child TOMLs are skipped** if a parent `Cargo.toml` has already been processed and its `cargo metadata` output includes the child as a workspace member. +- **Lock files are skipped** if a `Cargo.toml` in the same directory has already been processed (since running `cargo metadata` produces equivalent results). + +These optimizations potentially reduce the number of redundant `cargo metadata` invocations without affecting detection completeness. + +--- + +## Mapping Components to Locations + +### Why + +Cargo SBOM files (`*.cargo-sbom.json`) describe dependencies as **build artifacts**, not as user-authored source definitions. +While they accurately represent what was built, they are not actionable for repository owners — fixing or updating a dependency typically happens by editing a `Cargo.toml`, not the generated SBOM. + +For this reason, Component Detection maps each detected dependency back to the **`Cargo.toml` file that introduced it**, ensuring that scan results point developers to the source manifest responsible for including the dependency. + +--- + +### Implementation + +To achieve accurate attribution between dependencies and their source manifests, the detector constructs a mapping between **packages** and their **owning `Cargo.toml` files**. +This process differs slightly between modes: + +| Mode | Mapping Source | Behavior | +|------|----------------|-----------| +| **SBOM Mode** | `cargo metadata` run on discovered TOMLs | Used to correlate each package from the SBOM with the manifest that owns it. | +| **SBOM Mode (CLI unavailable / failed)** | SBOM file itself | Packages are mapped to their corresponding `*.cargo-sbom.json` file as a fallback. | +| **Fallback Mode** | `cargo metadata` or lock file parsing | Packages are mapped directly to the TOML or LOCK file that defines or references them. | + +In all cases, the goal is to surface the **most relevant and editable source location** for each dependency. +This mapping enables future integrations — such as pull request comments or IDE feedback — to guide developers directly to the file where a dependency can be modified. + +--- + +## `DisableRustCliScan` Environment Variable + +The detector supports an optional environment variable named **`DisableRustCliScan`**, which controls whether the Cargo CLI (`cargo metadata`) is invoked during detection. + +When this variable is set to `true`, the detector **skips all Cargo CLI execution**, including metadata queries. +As a result: + +- In **SBOM Mode**, the detector will not run `cargo metadata` to build ownership mappings. + - Dependencies will instead be mapped directly to the corresponding `*.cargo-sbom.json` files. +- In **Fallback Mode**, the detector cannot rely on CLI-generated dependency graphs. + - It falls back to processing `Cargo.lock` and `Cargo.toml` files to infer dependencies and relationships. + +Because Cargo lock files and manifest parsing provide less contextual information than `cargo metadata`, disabling the CLI may reduce the precision of component-to-location mapping and can lead to **over-reporting**. diff --git a/src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustDetectionTelemetryRecord.cs b/src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustDetectionTelemetryRecord.cs new file mode 100644 index 000000000..00397eeb2 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Common/Telemetry/Records/RustDetectionTelemetryRecord.cs @@ -0,0 +1,28 @@ +namespace Microsoft.ComponentDetection.Common.Telemetry.Records; + +public class RustDetectionTelemetryRecord : BaseDetectionTelemetryRecord +{ + public override string RecordName => "RustDetection"; + + public string DetectionMode { get; set; } + + public int SkippedCargoTomlCount { get; set; } + + public int SkippedCargoLockCount { get; set; } + + public int TotalSkippedFiles { get; set; } + + public int ProcessedCargoTomlCount { get; set; } + + public int ProcessedCargoLockCount { get; set; } + + public int ProcessedSbomCount { get; set; } + + public int TotalProcessedFiles { get; set; } + + public int OwnershipMapPackageCount { get; set; } + + public bool OwnershipMapAvailable { get; set; } + + public string SkipRatio { get; set; } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs deleted file mode 100644 index 92d18f0b1..000000000 --- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs +++ /dev/null @@ -1,208 +0,0 @@ -namespace Microsoft.ComponentDetection.Detectors.Rust; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -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; - -/// -/// A Rust CLI detector that uses the cargo metadata command to detect Rust components. -/// -public class RustCliDetector : FileComponentDetector -{ - private readonly IRustCliParser cliParser; - private readonly IRustCargoLockParser cargoLockParser; - - /// - /// Initializes a new instance of the class. - /// - /// The component stream enumerable factory. - /// The walker factory. - /// The command line invocation service. - /// The environment variable reader service. - /// The logger. - /// Rust cli parser. - /// Rust cargo lock parser. - public RustCliDetector( - IComponentStreamEnumerableFactory componentStreamEnumerableFactory, - IObservableDirectoryWalkerFactory walkerFactory, - ICommandLineInvocationService cliService, - IEnvironmentVariableService envVarService, - ILogger logger, - IRustCliParser cliParser, - IRustCargoLockParser cargoLockParser) - { - this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory; - this.Scanner = walkerFactory; - this.Logger = logger; - this.cliParser = cliParser; - this.cargoLockParser = cargoLockParser; - } - - /// - public override string Id => "RustCli"; - - /// - public override IEnumerable Categories { get; } = ["Rust"]; - - /// - public override IEnumerable SupportedComponentTypes => [ComponentType.Cargo]; - - /// - public override int Version => 5; - - /// - public override IList SearchPatterns { get; } = ["Cargo.toml"]; - - /// - protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary detectorArgs, CancellationToken cancellationToken = default) - { - var componentStream = processRequest.ComponentStream; - this.Logger.LogInformation("Discovered Cargo.toml: {Location}", componentStream.Location); - - using var record = new RustGraphTelemetryRecord(); - record.CargoTomlLocation = processRequest.ComponentStream.Location; - - try - { - // Try to parse using cargo metadata command - var parseResult = await this.cliParser.ParseAsync( - componentStream, - processRequest.SingleFileComponentRecorder, - cancellationToken); - - if (parseResult.Success) - { - // CLI parsing succeeded - record.DidRustCliCommandFail = false; - record.WasRustFallbackStrategyUsed = false; - } - else - { - // CLI parsing failed - record.DidRustCliCommandFail = true; - record.RustCliCommandError = parseResult.ErrorMessage; - record.FallbackReason = parseResult.FailureReason; - - // Determine if we should use fallback based on the error - if (!string.IsNullOrEmpty(parseResult.ErrorMessage)) - { - record.WasRustFallbackStrategyUsed = ShouldFallbackFromError(parseResult.ErrorMessage); - } - else - { - // If there's no error message (e.g., manually disabled or cargo not found), use fallback - record.WasRustFallbackStrategyUsed = true; - } - } - } - catch (Exception e) - { - this.Logger.LogWarning(e, "Failed attempting to call `cargo` with file: {Location}", processRequest.ComponentStream.Location); - record.DidRustCliCommandFail = true; - record.RustCliCommandError = e.Message; - record.WasRustFallbackStrategyUsed = true; - record.FallbackReason = "InvalidOperationException"; - } - finally - { - if (record.WasRustFallbackStrategyUsed) - { - try - { - await this.ProcessCargoLockFallbackAsync(componentStream, processRequest.SingleFileComponentRecorder, record, cancellationToken); - } - catch (ArgumentException e) - { - this.Logger.LogWarning(e, "fallback failed for {Location}", processRequest.ComponentStream.Location); - record.DidRustCliCommandFail = true; - record.RustCliCommandError = e.Message; - record.WasRustFallbackStrategyUsed = true; - } - - this.AdditionalProperties.Add(("Rust Fallback", JsonConvert.SerializeObject(record))); - } - } - } - - private static bool ShouldFallbackFromError(string error) - { - if (error.Contains("current package believes it's in a workspace", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - return true; - } - - private IComponentStream FindCorrespondingCargoLock(IComponentStream cargoToml) - { - var cargoLockStream = this.ComponentStreamEnumerableFactory.GetComponentStreams( - new FileInfo(cargoToml.Location).Directory, - ["Cargo.lock"], - (name, directoryName) => false, - recursivelyScanDirectories: false).FirstOrDefault(); - - if (cargoLockStream == null) - { - return null; - } - - if (cargoLockStream.Stream.CanRead) - { - return cargoLockStream; - } - else - { - var fileStream = new FileStream(cargoLockStream.Location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - return new ComponentStream() - { - Location = cargoLockStream.Location, - Pattern = cargoLockStream.Pattern, - Stream = fileStream, - }; - } - } - - private async Task ProcessCargoLockFallbackAsync( - IComponentStream cargoTomlFile, - ISingleFileComponentRecorder singleFileComponentRecorder, - RustGraphTelemetryRecord record, - CancellationToken cancellationToken = default) - { - var cargoLockFileStream = this.FindCorrespondingCargoLock(cargoTomlFile); - if (cargoLockFileStream == null) - { - this.Logger.LogWarning("Fallback failed, could not find Cargo.lock file for {CargoTomlLocation}, skipping processing", cargoTomlFile.Location); - record.FallbackCargoLockFound = false; - return; - } - else - { - this.Logger.LogWarning("Falling back to cargo.lock processing using {CargoTomlLocation}", cargoLockFileStream.Location); - } - - record.FallbackCargoLockLocation = cargoLockFileStream.Location; - record.FallbackCargoLockFound = true; - - // Use RustCrateParser to parse the Cargo.lock file - var lockfileVersion = await this.cargoLockParser.ParseAsync( - cargoLockFileStream, - singleFileComponentRecorder, - cancellationToken); - - if (lockfileVersion.HasValue) - { - this.RecordLockfileVersion(lockfileVersion.Value); - } - } -} diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs deleted file mode 100644 index b8ddd08f7..000000000 --- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Microsoft.ComponentDetection.Detectors.Rust; - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.ComponentDetection.Contracts; -using Microsoft.ComponentDetection.Contracts.Internal; -using Microsoft.ComponentDetection.Contracts.TypedComponent; -using Microsoft.Extensions.Logging; - -public class RustCrateDetector : FileComponentDetector -{ - private const string CargoLockSearchPattern = "Cargo.lock"; - private readonly IRustCargoLockParser parser; - - public RustCrateDetector( - IComponentStreamEnumerableFactory componentStreamEnumerableFactory, - IObservableDirectoryWalkerFactory walkerFactory, - ILogger logger, - IRustCargoLockParser parser) - { - this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory; - this.Scanner = walkerFactory; - this.Logger = logger; - this.parser = parser; - } - - public override string Id => "RustCrateDetector"; - - public override IList SearchPatterns => [CargoLockSearchPattern]; - - public override IEnumerable SupportedComponentTypes => [ComponentType.Cargo]; - - public override int Version { get; } = 9; - - public override IEnumerable Categories => ["Rust"]; - - protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary detectorArgs, CancellationToken cancellationToken = default) - { - var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; - var cargoLockFile = processRequest.ComponentStream; - - var lockfileVersion = await this.parser.ParseAsync(cargoLockFile, singleFileComponentRecorder, cancellationToken); - - if (lockfileVersion.HasValue) - { - this.RecordLockfileVersion(lockfileVersion.Value); - } - } -} diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustSbomDetector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustSbomDetector.cs index d9a366e2e..96594ac03 100644 --- a/src/Microsoft.ComponentDetection.Detectors/rust/RustSbomDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/rust/RustSbomDetector.cs @@ -9,6 +9,7 @@ namespace Microsoft.ComponentDetection.Detectors.Rust; using System.Threading; using System.Threading.Tasks; using global::DotNet.Globbing; +using Microsoft.ComponentDetection.Common.Telemetry.Records; using Microsoft.ComponentDetection.Contracts; using Microsoft.ComponentDetection.Contracts.Internal; using Microsoft.ComponentDetection.Contracts.TypedComponent; @@ -19,7 +20,7 @@ namespace Microsoft.ComponentDetection.Detectors.Rust; /// /// A unified Rust detector that orchestrates SBOM, CLI, and Crate parsing. /// -public class RustSbomDetector : FileComponentDetector, IExperimentalDetector +public class RustSbomDetector : FileComponentDetector { private const string CargoTomlFileName = "Cargo.toml"; private const string CargoLockFileName = "Cargo.lock"; @@ -43,6 +44,14 @@ public class RustSbomDetector : FileComponentDetector, IExperimentalDetector private Dictionary manifestMetadataCache; private DetectionMode mode; + // Telemetry counters + private int skippedCargoTomlCount; + private int skippedCargoLockCount; + private int processedCargoTomlCount; + private int processedCargoLockCount; + private int processedSbomCount; + private int totalPackagesInOwnershipMap; + public RustSbomDetector( IComponentStreamEnumerableFactory componentStreamEnumerableFactory, IObservableDirectoryWalkerFactory walkerFactory, @@ -72,6 +81,14 @@ public RustSbomDetector( this.visitedDirs = new HashSet(this.pathComparer); this.visitedGlobRules = []; this.manifestMetadataCache = new Dictionary(this.pathComparer); + + // Initialize telemetry counters + this.skippedCargoTomlCount = 0; + this.skippedCargoLockCount = 0; + this.processedCargoTomlCount = 0; + this.processedCargoLockCount = 0; + this.processedSbomCount = 0; + this.totalPackagesInOwnershipMap = 0; } /// @@ -159,9 +176,11 @@ protected override async Task> OnPrepareDetectionAsy var ownership = await this.metadataContextBuilder.BuildPackageOwnershipMapAsync(tomlPaths, cancellationToken); this.ownershipMap = ownership.PackageToTomls; this.manifestMetadataCache = ownership.ManifestToMetadata; + this.totalPackagesInOwnershipMap = this.ownershipMap?.Count ?? 0; + this.Logger.LogInformation( "Loaded Rust ownership (packages: {PkgCount}) and metadata cache (manifests: {ManifestCount})", - this.ownershipMap?.Count ?? 0, + this.totalPackagesInOwnershipMap, this.manifestMetadataCache?.Count ?? 0); if (ownership.FailedManifests?.Count > 0) @@ -177,6 +196,7 @@ protected override async Task> OnPrepareDetectionAsy this.Logger.LogWarning(ex, "Failed to compute Rust ownership/metadata cache; proceeding without cache"); this.ownershipMap = null; this.manifestMetadataCache = null; + this.totalPackagesInOwnershipMap = 0; } } else @@ -184,6 +204,7 @@ protected override async Task> OnPrepareDetectionAsy this.Logger.LogInformation("No Cargo.toml files found; ownership and metadata cache unavailable"); this.ownershipMap = null; this.manifestMetadataCache = null; + this.totalPackagesInOwnershipMap = 0; } IEnumerable filteredRequests; @@ -251,12 +272,29 @@ protected override async Task OnFileFoundAsync( if (this.ShouldSkip(directory, fileKind, location)) { this.Logger.LogInformation("Skipping file due to skip rules: {Location}", normLocation); + + // Increment skip counters + switch (fileKind) + { + case FileKind.CargoToml: + Interlocked.Increment(ref this.skippedCargoTomlCount); + break; + case FileKind.CargoLock: + Interlocked.Increment(ref this.skippedCargoLockCount); + break; + case FileKind.CargoSbom: + break; + default: + break; + } + return; } if (this.mode == DetectionMode.SBOM_ONLY) { await this.ProcessSbomFileAsync(processRequest, cancellationToken); + Interlocked.Increment(ref this.processedSbomCount); } else { @@ -264,14 +302,45 @@ protected override async Task OnFileFoundAsync( if (fileKind == FileKind.CargoToml) { await this.ProcessCargoTomlAsync(processRequest, directory, cancellationToken); + Interlocked.Increment(ref this.processedCargoTomlCount); } else if (fileKind == FileKind.CargoLock) { await this.ProcessCargoLockAsync(processRequest, directory, cancellationToken); + Interlocked.Increment(ref this.processedCargoLockCount); } } } + /// + protected override Task OnDetectionFinishedAsync() + { + // Record telemetry using the using pattern + var totalSkippedFiles = this.skippedCargoTomlCount + this.skippedCargoLockCount; + var totalProcessedFiles = this.processedCargoTomlCount + this.processedCargoLockCount + this.processedSbomCount; + var totalFiles = totalSkippedFiles + totalProcessedFiles; + var skipRatio = totalFiles > 0 + ? $"{100.0 * totalSkippedFiles / totalFiles:0.00}%" + : "0.00%"; + + using var telemetryRecord = new RustDetectionTelemetryRecord + { + DetectionMode = this.mode.ToString(), + SkippedCargoTomlCount = this.skippedCargoTomlCount, + SkippedCargoLockCount = this.skippedCargoLockCount, + TotalSkippedFiles = totalSkippedFiles, + ProcessedCargoTomlCount = this.processedCargoTomlCount, + ProcessedCargoLockCount = this.processedCargoLockCount, + ProcessedSbomCount = this.processedSbomCount, + TotalProcessedFiles = totalProcessedFiles, + OwnershipMapPackageCount = this.totalPackagesInOwnershipMap, + OwnershipMapAvailable = this.ownershipMap != null, + SkipRatio = skipRatio, + }; + + return Task.CompletedTask; + } + /// /// Calculates the depth of a directory path by counting the number of directory separators. /// @@ -558,7 +627,7 @@ private async Task ProcessSbomFileAsync(ProcessRequest processRequest, Cancellat /// A cancellation token to observe while waiting for the task to complete. /// A task that represents the asynchronous operation. /// - /// This method delegates parsing to the which executes the 'cargo metadata' command. + /// This method delegates parsing to the which executes the 'cargo metadata' command. /// If the CLI parsing is successful, the method: /// /// Adds all local package directories found in the workspace to the visited directories set to prevent duplicate processing. @@ -609,7 +678,7 @@ private async Task ProcessCargoTomlAsync(ProcessRequest processRequest, string d /// /// This method performs the following steps: /// - /// Delegates parsing of the Cargo.lock file to the . + /// Delegates parsing of the Cargo.lock file to the . /// If parsing is successful and returns a lockfile version, records the version in telemetry. /// Checks if a corresponding Cargo.toml file exists in the same directory. /// If Cargo.toml exists, parses its workspace tables to extract member and exclude patterns. diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustSbomVsCliExperiment.cs b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustSbomVsCliExperiment.cs deleted file mode 100644 index 08ebb1b35..000000000 --- a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustSbomVsCliExperiment.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; - -using Microsoft.ComponentDetection.Contracts; -using Microsoft.ComponentDetection.Detectors.Rust; - -/// -/// Validating the Rust SBOM detector against the Rust CLI detector. -/// -public class RustSbomVsCliExperiment : IExperimentConfiguration -{ - /// - public string Name => "RustSbomVsCliExperiment"; - - /// - public bool IsInControlGroup(IComponentDetector componentDetector) => componentDetector is RustCliDetector; - - /// - public bool IsInExperimentGroup(IComponentDetector componentDetector) => componentDetector is RustSbomDetector; - - /// - public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => true; -} diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustSbomVsCrateExperiment.cs b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustSbomVsCrateExperiment.cs deleted file mode 100644 index 4801ff2f7..000000000 --- a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustSbomVsCrateExperiment.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; - -using Microsoft.ComponentDetection.Contracts; -using Microsoft.ComponentDetection.Detectors.Rust; - -/// -/// Validating the Rust SBOM detector against the Rust crate detector. -/// -public class RustSbomVsCrateExperiment : IExperimentConfiguration -{ - /// - public string Name => "RustSbomVsCrateExperiment"; - - /// - public bool IsInControlGroup(IComponentDetector componentDetector) => componentDetector is RustCrateDetector; - - /// - public bool IsInExperimentGroup(IComponentDetector componentDetector) => componentDetector is RustSbomDetector; - - /// - public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => true; -} diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs b/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs index 1e3806ff2..1ee0f776b 100644 --- a/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs +++ b/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs @@ -69,8 +69,6 @@ public static IServiceCollection AddComponentDetection(this IServiceCollection s services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); // Detectors @@ -139,8 +137,6 @@ public static IServiceCollection AddComponentDetection(this IServiceCollection s services.AddSingleton(); // Rust - services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); // SPDX diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCliDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/RustCliDetectorTests.cs deleted file mode 100644 index 2549c72f1..000000000 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCliDetectorTests.cs +++ /dev/null @@ -1,1246 +0,0 @@ -namespace Microsoft.ComponentDetection.Detectors.Tests; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.ComponentDetection.Common; -using Microsoft.ComponentDetection.Contracts; -using Microsoft.ComponentDetection.Contracts.TypedComponent; -using Microsoft.ComponentDetection.Detectors.Rust; -using Microsoft.ComponentDetection.TestsUtilities; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -[TestClass] -[TestCategory("Governance/All")] -[TestCategory("Governance/ComponentDetection")] -public class RustCliDetectorTests : BaseDetectorTest -{ - private readonly string mockMetadataV1 = @" -{ - ""packages"": [ - { - ""name"": ""registry-package-1"", - ""version"": ""1.0.1"", - ""id"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""license"": null, - ""license_file"": null, - ""description"": ""test registry package 1"", - ""source"": ""registry+https://test.com/registry-package-1"", - ""dependencies"": [ - { - ""name"": ""inner-dependency-1"", - ""source"": ""registry+registry+https://test.com/inner-dependency-1"", - ""req"": ""^0.3.0"", - ""kind"": ""dev"", - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null - } - ] - }, - { - ""name"": ""rust-test"", - ""version"": ""0.1.0"", - ""id"": ""rust-test 0.1.0 (path+file:///C:/test)"", - ""license"": null, - ""license_file"": null, - ""description"": null, - ""source"": null, - ""dependencies"": [ - { - ""name"": ""registry-package-1"", - ""source"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""req"": ""^1.0.1"", - ""kind"": null, - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null - }, - { - ""name"": ""rust-test-inner"", - ""source"": ""(path+file:///C:/test/rust-test-inner)"", - ""req"": ""*"", - ""kind"": null, - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null, - ""path"": ""C:\\test\\rust-test-inner"" - }, - { - ""name"": ""dev-dependency-1"", - ""source"": ""registry+https://test.com/dev-dependency-1"", - ""req"": ""^0.4.0"", - ""kind"": ""dev"", - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null - } - ] - }, - { - ""name"": ""rust-test-inner"", - ""version"": ""0.1.0"", - ""id"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""license"": null, - ""license_file"": null, - ""description"": null, - ""source"": null, - ""dependencies"": [] - }, - { - ""name"": ""dev-dependency-1"", - ""version"": ""0.4.0"", - ""id"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""license"": null, - ""license_file"": null, - ""description"": ""test dev dependency"", - ""source"": ""registry+https://github.com/rust-lang/crates.io-index"", - ""dependencies"": [] - } - ], - ""workspace_members"": [ - ""rust-test 0.1.0 (path+file:///C:/test)"" - ], - ""workspace_default_members"": [ - ""rust-test 0.1.0 (path+file:///C:/test)"" - ], - ""resolve"": { - ""nodes"": [ - { - ""id"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [ - ""default"", - ""std"" - ] - }, - { - ""id"": ""rust-test 0.1.0 (path+file:///C:/test)"", - ""dependencies"": [ - ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"" - ], - ""deps"": [ - { - ""name"": ""registry-package-1"", - ""pkg"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - }, - { - ""name"": ""cargo"", - ""pkg"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - }, - { - ""name"": ""dev-dependency-1"", - ""pkg"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""dep_kinds"": [ - { - ""kind"": ""dev"", - ""target"": null - } - ] - } - ], - ""features"": [] - }, - { - ""id"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [] - }, - { - ""id"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [] - } - ], - ""root"": ""rust-test 0.1.0 (path+file:///C:/test)"" - }, - ""target_directory"": ""C:\\test"", - ""version"": 1, - ""workspace_root"": ""C:\\test"", - ""metadata"": null -}"; - - private readonly string mockMetadataWithLicenses = @" -{ - ""packages"": [ - { - ""name"": ""registry-package-1"", - ""version"": ""1.0.1"", - ""id"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""license"": ""MIT"", - ""authors"": [ - ""Sample Author 1"", - ""Sample Author 2"" - ], - ""license_file"": null, - ""description"": ""test registry package 1"", - ""source"": ""registry+https://test.com/registry-package-1"", - ""dependencies"": [ - { - ""name"": ""inner-dependency-1"", - ""source"": ""registry+registry+https://test.com/inner-dependency-1"", - ""req"": ""^0.3.0"", - ""kind"": ""dev"", - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null - } - ] - }, - { - ""name"": ""rust-test"", - ""version"": ""0.1.0"", - ""id"": ""rust-test 0.1.0 (path+file:///C:/test)"", - ""license"": ""MIT"", - ""authors"": [ - ""Sample Author 1"", - ""Sample Author 2"" - ], - ""license_file"": null, - ""description"": null, - ""source"": null, - ""dependencies"": [ - { - ""name"": ""registry-package-1"", - ""source"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""req"": ""^1.0.1"", - ""kind"": null, - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null - }, - { - ""name"": ""rust-test-inner"", - ""source"": ""(path+file:///C:/test/rust-test-inner)"", - ""req"": ""*"", - ""kind"": null, - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null, - ""path"": ""C:\\test\\rust-test-inner"" - }, - { - ""name"": ""dev-dependency-1"", - ""source"": ""registry+https://test.com/dev-dependency-1"", - ""req"": ""^0.4.0"", - ""kind"": ""dev"", - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null - } - ] - }, - { - ""name"": ""rust-test-inner"", - ""version"": ""0.1.0"", - ""id"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""license"": ""MIT"", - ""authors"": [ - ""Sample Author 1"", - ""Sample Author 2"" - ], - ""license_file"": null, - ""description"": null, - ""source"": null, - ""dependencies"": [] - }, - { - ""name"": ""dev-dependency-1"", - ""version"": ""0.4.0"", - ""id"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""license"": ""MIT"", - ""authors"": [ - ""Sample Author 1"", - ""Sample Author 2"" - ], - ""license_file"": null, - ""description"": ""test dev dependency"", - ""source"": ""registry+https://github.com/rust-lang/crates.io-index"", - ""dependencies"": [] - } - ], - ""workspace_members"": [ - ""rust-test 0.1.0 (path+file:///C:/test)"" - ], - ""workspace_default_members"": [ - ""rust-test 0.1.0 (path+file:///C:/test)"" - ], - ""resolve"": { - ""nodes"": [ - { - ""id"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [ - ""default"", - ""std"" - ] - }, - { - ""id"": ""rust-test 0.1.0 (path+file:///C:/test)"", - ""dependencies"": [ - ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"" - ], - ""deps"": [ - { - ""name"": ""registry-package-1"", - ""pkg"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - }, - { - ""name"": ""cargo"", - ""pkg"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - }, - { - ""name"": ""dev-dependency-1"", - ""pkg"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""dep_kinds"": [ - { - ""kind"": ""dev"", - ""target"": null - } - ] - } - ], - ""features"": [] - }, - { - ""id"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [] - }, - { - ""id"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [] - } - ], - ""root"": ""rust-test 0.1.0 (path+file:///C:/test)"" - }, - ""target_directory"": ""C:\\test"", - ""version"": 1, - ""workspace_root"": ""C:\\test"", - ""metadata"": null -}"; - - private readonly string mockMetadataVirtualManifest = @" -{ - ""packages"": [ - { - ""name"": ""registry-package-1"", - ""version"": ""1.0.1"", - ""id"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""license"": ""MIT"", - ""authors"": [ - ""Sample Author 1"", - ""Sample Author 2"" - ], - ""license_file"": null, - ""description"": ""test registry package 1"", - ""source"": ""registry+https://test.com/registry-package-1"", - ""dependencies"": [ - { - ""name"": ""inner-dependency-1"", - ""source"": ""registry+registry+https://test.com/inner-dependency-1"", - ""req"": ""^0.3.0"", - ""kind"": ""dev"", - ""rename"": null, - ""optional"": false, - ""uses_default_features"": true, - ""features"": [], - ""target"": null, - ""registry"": null - } - ] - }, - { - ""name"": ""rust-test-inner"", - ""version"": ""0.1.0"", - ""id"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""license"": ""MIT"", - ""authors"": [ - ""Sample Author 1"", - ""Sample Author 2"" - ], - ""license_file"": null, - ""description"": null, - ""source"": null, - ""dependencies"": [] - }, - { - ""name"": ""dev-dependency-1"", - ""version"": ""0.4.0"", - ""id"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""license"": ""MIT"", - ""authors"": [ - ""Sample Author 1"", - ""Sample Author 2"" - ], - ""license_file"": null, - ""description"": ""test dev dependency"", - ""source"": ""registry+https://github.com/rust-lang/crates.io-index"", - ""dependencies"": [] - } - ], - ""workspace_members"": [ - ""rust-test 0.1.0 (path+file:///C:/test)"" - ], - ""workspace_default_members"": [ - ""rust-test 0.1.0 (path+file:///C:/test)"" - ], - ""resolve"": { - ""nodes"": [ - { - ""id"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [ - ""default"", - ""std"" - ] - }, - { - ""id"": ""rust-test-inner 0.1.0 (path+file:///C:/test/rust-test-inner)"", - ""dependencies"": [ - ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"" - ], - ""deps"": [ - { - ""name"": ""registry-package-1"", - ""pkg"": ""registry-package-1 1.0.1 (registry+https://test.com/registry-package-1)"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - }, - { - ""name"": ""dev-dependency-1"", - ""pkg"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""dep_kinds"": [ - { - ""kind"": ""dev"", - ""target"": null - } - ] - }], - ""features"": [] - }, - { - ""id"": ""dev-dependency-1 0.4.0 (registry+https://test.com/dev-dependency-1)"", - ""dependencies"": [], - ""deps"": [], - ""features"": [] - } - ], - ""root"": null - }, - ""target_directory"": ""C:\\test"", - ""version"": 1, - ""workspace_root"": ""C:\\test"", - ""metadata"": null -}"; - - private Mock mockCliService; - - private Mock mockEnvVarService; - - private Mock mockComponentStreamEnumerableFactory; - - private Mock> mockRustCliParserLogger; - - [TestInitialize] - public void InitMocks() - { - this.mockCliService = new Mock(); - this.DetectorTestUtility.AddServiceMock(this.mockCliService); - this.mockComponentStreamEnumerableFactory = new Mock(); - this.mockRustCliParserLogger = new Mock>(); - this.DetectorTestUtility.AddServiceMock(this.mockComponentStreamEnumerableFactory); - this.mockEnvVarService = new Mock(); - this.DetectorTestUtility.AddServiceMock(this.mockEnvVarService); - this.DetectorTestUtility.AddService(new RustCliParser(this.mockCliService.Object, this.mockEnvVarService.Object, new PathUtilityService(new Mock>().Object), this.mockRustCliParserLogger.Object)); - this.DetectorTestUtility.AddService(new RustCargoLockParser(new Mock>().Object)); - } - - [TestMethod] - public async Task RustCLiDetector_CommandCantBeLocatedSuccessAsync() - { - this.mockCliService - .Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())) - .ReturnsAsync(false); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().BeEmpty(); - } - - [TestMethod] - public async Task RustCliDetector_FailExecutingCommandSuccessAsync() - { - this.mockCliService - .Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())) - .ReturnsAsync(true); - this.mockCliService - .Setup(x => x.ExecuteCommandAsync("cargo", It.IsAny>(), It.IsAny())) - .ThrowsAsync(new InvalidOperationException()); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().BeEmpty(); - } - - [TestMethod] - public async Task RustCliDetector_RespectsFallBackVariableAsync() - { - var testCargoLockString = @" -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0"" -] - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dev_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", - ""dev_dependency_dependency 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)"", -]"; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny())) - .Throws(new InvalidOperationException()); - this.mockEnvVarService - .Setup(x => x.IsEnvironmentVariableValueTrue("DisableRustCliScan")) - .Returns(true); - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); - await writer.WriteAsync(testCargoLockString); - await writer.FlushAsync(); - stream.Position = 0; - this.mockComponentStreamEnumerableFactory.Setup(x => x.GetComponentStreams(It.IsAny(), new List { "Cargo.lock" }, It.IsAny(), false)) - .Returns([new ComponentStream() { Location = "Cargo.toml", Stream = stream }]); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(4); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("other_dependency_dependency 0.1.12-alpha.6 - Cargo", "my_dev_dependency 1.0.0 - Cargo", "my_dependency 1.0.0 - Cargo", "other_dependency 0.4.0 - Cargo"); - - var components = componentRecorder.GetDetectedComponents(); - - foreach (var component in components) - { - if (component.Component is CargoComponent cargoComponent) - { - cargoComponent.Author.Should().Be(null); - cargoComponent.License.Should().Be(null); - } - } - - return; - } - - [TestMethod] - public async Task RustCliDetector_HandlesNonZeroExitCodeAsync() - { - var cargoMetadata = this.mockMetadataV1; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata, ExitCode = -1 }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().BeEmpty(); - } - - [TestMethod] - public async Task RustCliDetector_RegistersCorrectRootDepsAsync() - { - var cargoMetadata = this.mockMetadataV1; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(2); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("registry-package-1 1.0.1 - Cargo", "dev-dependency-1 0.4.0 - Cargo"); - } - - [TestMethod] - public async Task RustCliDetector_NotInGraphAsync() - { - var cargoMetadata = @" -{ - ""packages"": [], - ""workspace_members"": [ - ""path+file:///home/justin/rust-test#rust-test@0.1.0"" - ], - ""resolve"": { - ""nodes"": [ - { - ""id"": ""path+file:///home/justin/rust-test#rust-test@0.1.0"", - ""dependencies"": [ - ""registry+https://github.com/rust-lang/crates.io-index#libc@0.2.147"" - ], - ""deps"": [ - { - ""name"": ""libc"", - ""pkg"": ""registry+https://github.com/rust-lang/crates.io-index#libc@0.2.147"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - } - ], - ""features"": [] - } - ], - ""root"": ""path+file:///home/justin/rust-test#rust-test@0.1.0"" - }, - ""target_directory"": ""/home/justin/rust-test/target"", - ""version"": 1, - ""workspace_root"": ""/home/justin/rust-test"", - ""metadata"": null -}"; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().BeEmpty(); - } - - [TestMethod] - public async Task RustCliDetector_InvalidNameAsync() - { - var cargoMetadata = @" -{ - ""packages"": [], - ""workspace_members"": [ - ""path+file:///home/justin/rust-test#rust-test@0.1.0"" - ], - ""resolve"": { - ""nodes"": [ - { - ""id"": ""path+file:///home/justin/rust-test#rust-test@0.1.0"", - ""dependencies"": [ - ""registry+https://github.com/rust-lang/crates.io-index#libc@0.2.147"" - ], - ""deps"": [ - { - ""name"": ""libc"", - ""pkg"": ""INVALID"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - } - ], - ""features"": [] - } - ], - ""root"": ""path+file:///home/justin/rust-test#rust-test@0.1.0"" - }, - ""target_directory"": ""/home/justin/rust-test/target"", - ""version"": 1, - ""workspace_root"": ""/home/justin/rust-test"", - ""metadata"": null -}"; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().BeEmpty(); - } - - [TestMethod] - public async Task RustCliDetector_ComponentContainsAuthorAndLicenseAsync() - { - var cargoMetadata = this.mockMetadataWithLicenses; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(2); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("registry-package-1 1.0.1 - Cargo", "dev-dependency-1 0.4.0 - Cargo"); - - var components = componentRecorder.GetDetectedComponents(); - - foreach (var component in components) - { - if (component.Component is CargoComponent cargoComponent) - { - cargoComponent.Author.Should().Be("Sample Author 1, Sample Author 2"); - cargoComponent.License.Should().Be("MIT"); - } - } - } - - [TestMethod] - public async Task RustCliDetector_AuthorAndLicenseNullAsync() - { - var cargoMetadata = this.mockMetadataV1; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(2); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("registry-package-1 1.0.1 - Cargo", "dev-dependency-1 0.4.0 - Cargo"); - - var components = componentRecorder.GetDetectedComponents(); - - foreach (var component in components) - { - if (component.Component is CargoComponent cargoComponent) - { - cargoComponent.Author.Should().Be(null); - cargoComponent.License.Should().Be(null); - } - } - } - - [TestMethod] - public async Task RustCliDetector_AuthorAndLicenseEmptyStringAsync() - { - var cargoMetadata = @" -{ - ""workspace_members"": [ - ""path+file:///home/justin/rust-test#rust-test@0.1.0"" - ], - ""resolve"": { - ""nodes"": [ - { - ""id"": ""registry+https://github.com/rust-lang/crates.io-index#libc@0.2.147"", - ""dependencies"": [], - ""deps"": [], - ""features"": [ - ""default"", - ""std"" - ] - }, - { - ""id"": ""path+file:///home/justin/rust-test#rust-test@0.1.0"", - ""dependencies"": [ - ""registry+https://github.com/rust-lang/crates.io-index#libc@0.2.147"" - ], - ""deps"": [ - { - ""name"": ""libc"", - ""pkg"": ""registry+https://github.com/rust-lang/crates.io-index#libc@0.2.147"", - ""dep_kinds"": [ - { - ""kind"": null, - ""target"": null - } - ] - } - ], - ""features"": [] - } - ], - ""root"": ""path+file:///home/justin/rust-test#rust-test@0.1.0"" - }, - ""packages"": [ - { - ""name"": ""libc"", - ""version"": ""0.2.147"", - ""id"": ""registry+https://github.com/rust-lang/crates.io-index#libc@0.2.147"", - ""license"": """", - ""license_file"": null, - ""description"": """", - ""source"": ""registry+https://github.com/rust-lang/crates.io-index"", - ""dependencies"": [], - ""targets"": [], - ""features"": {}, - ""manifest_path"": """", - ""metadata"": {}, - ""publish"": null, - ""authors"": [ - """" - ], - ""categories"": [], - ""keywords"": [], - ""readme"": ""README.md"", - ""repository"": ""https://github.com/tkaitchuck/ahash"", - ""homepage"": null, - ""documentation"": """", - ""edition"": ""00"", - ""links"": null, - ""default_run"": null, - ""rust_version"": null - }, - { - ""name"": ""rust-test"", - ""version"": ""0.1.0"", - ""id"": ""path+file:///home/justin/rust-test#rust-test@0.1.0"", - ""license"": """", - ""license_file"": null, - ""description"": ""A non-cryptographic hash function using AES-NI for high performance"", - ""source"": ""registry+https://github.com/rust-lang/crates.io-index"", - ""dependencies"": [], - ""targets"": [], - ""features"": {}, - ""manifest_path"": """", - ""metadata"": {}, - ""publish"": null, - ""authors"": [ - """" - ], - ""categories"": [], - ""keywords"": [], - ""readme"": ""README.md"", - ""repository"": ""https://github.com/tkaitchuck/ahash"", - ""homepage"": null, - ""documentation"": """", - ""edition"": ""000"", - ""links"": null, - ""default_run"": null, - ""rust_version"": null - } -], - ""target_directory"": ""/home/justin/rust-test/target"", - ""version"": 1, - ""workspace_root"": ""/home/justin/rust-test"", - ""metadata"": null -}"; - - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().ContainSingle(); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("libc 0.2.147 - Cargo"); - - var components = componentRecorder.GetDetectedComponents(); - - foreach (var component in components) - { - if (component.Component is CargoComponent cargoComponent) - { - cargoComponent.Author.Should().Be(null); - cargoComponent.License.Should().Be(null); - } - } - } - - [TestMethod] - public async Task RustCliDetector_VirtualManifestSuccessfullyProcessedAsync() - { - var cargoMetadata = this.mockMetadataVirtualManifest; - - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = cargoMetadata }); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(2); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("registry-package-1 1.0.1 - Cargo", "dev-dependency-1 0.4.0 - Cargo"); - - var components = componentRecorder.GetDetectedComponents(); - return; - } - - [TestMethod] - public async Task RustCliDetector_FallBackLogicFailsIfNoCargoLockFoundAsync() - { - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = string.Empty, ExitCode = -1 }); - - this.mockComponentStreamEnumerableFactory.Setup(x => x.GetComponentStreams(It.IsAny(), new List { "Cargo.lock" }, It.IsAny(), false)) - .Returns(Enumerable.Empty()); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().BeEmpty(); - } - - [TestMethod] - public async Task RustCliDetector_FallBackLogicTriggeredOnFailedCargoCommandAsync() - { - var testCargoLockString = @" -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0"" -] - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dev_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", - ""dev_dependency_dependency 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)"", -]"; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = null, ExitCode = -1 }); - - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); - await writer.WriteAsync(testCargoLockString); - await writer.FlushAsync(); - stream.Position = 0; - this.mockComponentStreamEnumerableFactory.Setup(x => x.GetComponentStreams(It.IsAny(), new List { "Cargo.lock" }, It.IsAny(), false)) - .Returns([new ComponentStream() { Location = "Cargo.toml", Stream = stream }]); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(4); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("other_dependency_dependency 0.1.12-alpha.6 - Cargo", "my_dev_dependency 1.0.0 - Cargo", "my_dependency 1.0.0 - Cargo", "other_dependency 0.4.0 - Cargo"); - - var components = componentRecorder.GetDetectedComponents(); - - foreach (var component in components) - { - if (component.Component is CargoComponent cargoComponent) - { - cargoComponent.Author.Should().Be(null); - cargoComponent.License.Should().Be(null); - } - } - - return; - } - - [TestMethod] - public async Task RustCliDetector_FallBackLogicTriggeredOnFailedProcessingAsync() - { - var testCargoLockString = @" -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0"" -] - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dev_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", - ""dev_dependency_dependency 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)"", -]"; - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny())) - .Throws(new InvalidOperationException()); - - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); - await writer.WriteAsync(testCargoLockString); - await writer.FlushAsync(); - stream.Position = 0; - this.mockComponentStreamEnumerableFactory.Setup(x => x.GetComponentStreams(It.IsAny(), new List { "Cargo.lock" }, It.IsAny(), false)) - .Returns([new ComponentStream() { Location = "Cargo.toml", Stream = stream }]); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(4); - - componentRecorder - .GetDetectedComponents() - .Select(x => x.Component.Id) - .Should() - .BeEquivalentTo("other_dependency_dependency 0.1.12-alpha.6 - Cargo", "my_dev_dependency 1.0.0 - Cargo", "my_dependency 1.0.0 - Cargo", "other_dependency 0.4.0 - Cargo"); - - var components = componentRecorder.GetDetectedComponents(); - - foreach (var component in components) - { - if (component.Component is CargoComponent cargoComponent) - { - cargoComponent.Author.Should().Be(null); - cargoComponent.License.Should().Be(null); - } - } - - return; - } - - [TestMethod] - public async Task RustCliDetector_FallBackLogicSkippedOnWorkspaceErrorAsync() - { - this.mockCliService.Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())).ReturnsAsync(true); - this.mockCliService.Setup(x => x.ExecuteCommandAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(new CommandLineExecutionResult { StdOut = null, StdErr = "current package believes it's in a workspace when it's not:", ExitCode = -1 }); - var testCargoLockString = @" -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0"" -] - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dev_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", - ""dev_dependency_dependency 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)"", -]"; - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); - await writer.WriteAsync(testCargoLockString); - await writer.FlushAsync(); - stream.Position = 0; - this.mockComponentStreamEnumerableFactory.Setup(x => x.GetComponentStreams(It.IsAny(), new List { "Cargo.lock" }, It.IsAny(), false)) - .Returns([new ComponentStream() { Location = "Cargo.toml", Stream = stream }]); - - var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.toml", string.Empty) - .ExecuteDetectorAsync(); - - scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().BeEmpty(); - - return; - } -} diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCrateDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/RustCrateDetectorTests.cs deleted file mode 100644 index c71cc4bd7..000000000 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCrateDetectorTests.cs +++ /dev/null @@ -1,898 +0,0 @@ -namespace Microsoft.ComponentDetection.Detectors.Tests; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.ComponentDetection.Common.DependencyGraph; -using Microsoft.ComponentDetection.Contracts; -using Microsoft.ComponentDetection.Contracts.TypedComponent; -using Microsoft.ComponentDetection.Detectors.Rust; -using Microsoft.ComponentDetection.Detectors.Tests.Utilities; -using Microsoft.ComponentDetection.TestsUtilities; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -[TestClass] -[TestCategory("Governance/All")] -[TestCategory("Governance/ComponentDetection")] -public class RustCrateDetectorTests : BaseDetectorTest -{ - private readonly string testCargoLockString = @" -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dev_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", - ""dev_dependency_dependency 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""dev_dependency_dependency"" -version = ""0.2.23"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""one_more_dev_dep 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"" -] - -[[package]] -name = ""one_more_dev_dep"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_other_package"" -version = ""1.0.0"" - -[[package]] -name = ""my_test_package"" -version = ""1.2.3"" -dependencies = [ - ""my_dependency 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"", - ""my_other_package 1.0.0"", - ""other_dependency 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)"", - ""my_dev_dependency 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[metadata] -"; - - private readonly string testCargoLockV2String = @" -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0"" -] - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dev_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", - ""dev_dependency_dependency 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""dev_dependency_dependency"" -version = ""0.2.23"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 2.0.0"" -] - -[[package]] -name = ""my_other_package"" -version = ""1.0.0"" - -[[package]] -name = ""my_test_package"" -version = ""1.2.3"" -dependencies = [ - ""my_dependency 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"", - ""my_other_package 1.0.0"", - ""other_dependency 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)"", - ""my_dev_dependency 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""same_package"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""same_package"" -version = ""2.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -"; - - private readonly string testWorkspaceLockV1NoBaseString = @"[[package]] -name = ""dev_dependency_dependency"" -version = ""0.2.23"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""one_more_dev_dep 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"" -] - -[[package]] -name = ""one_more_dev_dep"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"" -] - -[[package]] -name = ""same_package"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[metadata] -"; - - private readonly string testWorkspaceLockV2NoBaseString = @"[[package]] -name = ""dev_dependency_dependency"" -version = ""0.2.23"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""one_more_dev_dep 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"" -] - -[[package]] -name = ""one_more_dev_dep"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"" -] - -[[package]] -name = ""same_package"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -"; - - private readonly string testWorkspaceLockBaseDependency = @" -[[package]] -name = ""test_package"" -version = ""2.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -"; - - [TestInitialize] - public void TestInitialize() - { - this.DetectorTestUtility.AddService(new RustCargoLockParser(new Mock>().Object)); - } - - [TestMethod] - public async Task TestGraphIsCorrectAsync() - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", this.testCargoLockString) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(6); - - var graph = componentRecorder.GetDependencyGraphsByLocation().Values.First(); // There should only be 1 - - // Verify explicitly referenced roots - var rootComponents = new List - { - "my_dependency 1.0.0 - Cargo", - "other_dependency 0.4.0 - Cargo", - "my_dev_dependency 1.0.0 - Cargo", - }; - - rootComponents.ForEach(rootComponentId => graph.IsComponentExplicitlyReferenced(rootComponentId).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", - }; - - 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 TestSupportsCargoV1AndV2DefinitionPairsAsync() - { - var componentRecorder = new ComponentRecorder(); - var request = new ScanRequest(new DirectoryInfo(Path.GetTempPath()), null, null, new Dictionary(), null, componentRecorder); - - var (result, _) = await this.DetectorTestUtility - /* v1 files */ - .WithFile("Cargo.lock", this.testCargoLockString) - /* v2 files */ - .WithFile("Cargo.lock", this.testCargoLockV2String, fileLocation: Path.Join(Path.GetTempPath(), "v2", "Cargo.lock")) - /* so we can reuse the component recorder */ - .WithScanRequest(request) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - - var componentGraphs = componentRecorder.GetDependencyGraphsByLocation(); - - componentGraphs.Should().HaveCount(2); // 1 for each detector - } - - [TestMethod] - public async Task TestSupportsMultipleCargoV1DefinitionPairsAsync() - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", this.testCargoLockString) - .WithFile("Cargo.lock", this.testCargoLockString, fileLocation: Path.Join(Path.GetTempPath(), "sub-path", "Cargo.lock")) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - - var componentGraphs = componentRecorder.GetDependencyGraphsByLocation(); - - componentGraphs.Should().HaveCount(2); // 1 graph for each Cargo.lock - - var graph1 = componentGraphs.Values.First(); - var graph2 = componentGraphs.Values.Skip(1).First(); - - graph1.GetComponents().Should().BeEquivalentTo(graph2.GetComponents()); // The graphs should have detected the same components - - // Two Cargo.lock files - componentRecorder.ForAllComponents(x => x.AllFileLocations.Should().HaveCount(2)); - } - - [TestMethod] - public async Task TestSupportsMultipleCargoV2DefinitionPairsAsync() - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", this.testCargoLockV2String) - .WithFile("Cargo.lock", this.testCargoLockV2String, fileLocation: Path.Join(Path.GetTempPath(), "sub-path", "Cargo.lock")) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - - var componentGraphs = componentRecorder.GetDependencyGraphsByLocation(); - - componentGraphs.Should().HaveCount(2); // 1 graph for each Cargo.lock - - var graph1 = componentGraphs.Values.First(); - var graph2 = componentGraphs.Values.Skip(1).First(); - - graph1.GetComponents().Should().BeEquivalentTo(graph2.GetComponents()); // The graphs should have detected the same components - - // Two Cargo.lock files - componentRecorder.ForAllComponents(x => x.AllFileLocations.Should().HaveCount(2)); - } - - [TestMethod] - public async Task TestRustDetectorAsync() - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", this.testCargoLockString) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(6); - - IDictionary packageVersions = new Dictionary() - { - { "my_dependency", "1.0.0" }, - { "other_dependency", "0.4.0" }, - { "other_dependency_dependency", "0.1.12-alpha.6" }, - { "my_dev_dependency", "1.0.0" }, - { "dev_dependency_dependency", "0.2.23" }, - { "one_more_dev_dep", "1.0.0" }, - }; - - IDictionary> packageDependencyRoots = new Dictionary>() - { - { "my_dependency", new HashSet() { "my_dependency" } }, - { "other_dependency", new HashSet() { "other_dependency" } }, - { "other_dependency_dependency", new HashSet() { "other_dependency", "my_dev_dependency" } }, - { "my_dev_dependency", new HashSet() { "my_dev_dependency" } }, - { "dev_dependency_dependency", new HashSet() { "my_dev_dependency" } }, - { "one_more_dev_dep", new HashSet() { "my_dev_dependency" } }, - }; - - ISet componentNames = new HashSet(); - foreach (var discoveredComponent in componentRecorder.GetDetectedComponents()) - { - // Verify each package has the right information - var packageName = (discoveredComponent.Component as CargoComponent).Name; - - // Verify version - (discoveredComponent.Component as CargoComponent).Version.Should().Be(packageVersions[packageName]); - - var dependencyRoots = new HashSet(); - - componentRecorder.AssertAllExplicitlyReferencedComponents( - discoveredComponent.Component.Id, - packageDependencyRoots[packageName].Select(expectedRoot => - new Func(parentComponent => parentComponent.Name == expectedRoot)).ToArray()); - - componentNames.Add(packageName); - } - - // Verify all packages were detected - foreach (var expectedPackage in packageVersions.Keys) - { - componentNames.Should().Contain(expectedPackage); - } - } - - [TestMethod] - public async Task TestRustV2DetectorAsync() - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", this.testCargoLockV2String) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(7); - - var packageVersions = new List() - { - "my_dependency 1.0.0", - "other_dependency 0.4.0", - "other_dependency_dependency 0.1.12-alpha.6", - "my_dev_dependency 1.0.0", - "dev_dependency_dependency 0.2.23", - "same_package 1.0.0", - "same_package 2.0.0", - }; - - IDictionary> packageDependencyRoots = new Dictionary>() - { - { "my_dependency 1.0.0", new HashSet() { "my_dependency 1.0.0" } }, - { "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_dev_dependency 1.0.0" } }, - { "my_dev_dependency 1.0.0", new HashSet() { "my_dev_dependency 1.0.0" } }, - { "dev_dependency_dependency 0.2.23", new HashSet() { "my_dev_dependency 1.0.0" } }, - { "same_package 2.0.0", new HashSet() { "my_dev_dependency 1.0.0" } }, - { "same_package 1.0.0", new HashSet() { "my_dependency 1.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 - packageVersions.Should().Contain(componentKey); - - 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) - { - componentNames.Should().Contain(expectedPackage); - } - } - - [TestMethod] - public async Task TestRustV2Detector_DuplicatePackageAsync() - { - var testCargoLock = @" -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 1.0.0"" -] - -[[package]] -name = ""other_dependency"" -version = ""0.4.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""my_dev_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)"", - ""dev_dependency_dependency 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""dev_dependency_dependency"" -version = ""0.2.23"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""same_package 2.0.0"" -] - -[[package]] -name = ""my_other_package"" -version = ""1.0.0"" - -[[package]] -name = ""my_test_package"" -version = ""1.2.3"" -dependencies = [ - ""my_dependency 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"", - ""my_other_package 1.0.0"", - ""other_dependency 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)"", - ""my_dev_dependency 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)"", -] - -[[package]] -name = ""same_package"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""same_package"" -version = ""2.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -"; - - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", testCargoLock) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(7); - - var graph = componentRecorder.GetDependencyGraphsByLocation().Values.First(); // There should only be 1 - - // Verify explicitly referenced roots - var rootComponents = new List - { - "my_dependency 1.0.0 - Cargo", - "my_dev_dependency 1.0.0 - Cargo", - "other_dependency 0.4.0 - Cargo", - }; - - rootComponents.ForEach(rootComponentId => graph.IsComponentExplicitlyReferenced(rootComponentId).Should().BeTrue()); - - // Verify dependencies for my_dependency - var my_dependencyDependencies = new List - { - "same_package 1.0.0 - Cargo", - }; - - graph.GetDependenciesForComponent("my_dependency 1.0.0 - Cargo").Should().BeEquivalentTo(my_dependencyDependencies); - - // Verify dependencies for other_dependency - 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 TestRustDetector_SupportEmptySourceAsync() - { - var testLockString = @" -[[package]] -name = ""my_test_package"" -version = ""1.2.3"" -dependencies = [ - ""my_dependency"" -] - -[[package]] -name = ""my_dependency"" -version = ""1.0.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -dependencies = [ - ""other_dependency_dependency 0.1.12-alpha.6 ()"", -] - -[[package]] -name = ""other_dependency_dependency"" -version = ""0.1.12-alpha.6"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -"; - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", testLockString) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - - var dependencyGraphs = componentRecorder.GetDependencyGraphsByLocation(); - dependencyGraphs.Should().ContainSingle(); - - var dependencyGraph = dependencyGraphs.Single().Value; - var foundComponents = dependencyGraph.GetComponents(); - foundComponents.Should().HaveCount(2); - - componentRecorder.ForOneComponent("other_dependency_dependency 0.1.12-alpha.6 - Cargo", (grouping) => - { - grouping.ParentComponentIdsThatAreExplicitReferences.Should().BeEquivalentTo("my_dependency 1.0.0 - Cargo"); - }); - } - - [TestMethod] - public async Task TestRustDetector_V1WorkspacesWithTopLevelDependenciesAsync() - { - await this.TestRustDetector_WorkspacesWithTopLevelDependenciesAsync(this.testWorkspaceLockV1NoBaseString); - } - - [TestMethod] - public async Task TestRustDetector_V2WorkspacesWithTopLevelDependenciesAsync() - { - await this.TestRustDetector_WorkspacesWithTopLevelDependenciesAsync(this.testWorkspaceLockV2NoBaseString); - } - - private async Task TestRustDetector_WorkspacesWithTopLevelDependenciesAsync(string lockFile) - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", string.Concat(this.testWorkspaceLockBaseDependency, lockFile)) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(7); - - 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> 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 - packageVersions.Should().Contain(componentKey); - - 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) - { - componentNames.Should().Contain(expectedPackage); - } - } - - [TestMethod] - public async Task TestRustDetector_V1WorkspacesNoTopLevelDependenciesAsync() - { - await this.TestRustDetector_WorkspacesNoTopLevelDependenciesAsync(this.testWorkspaceLockV1NoBaseString); - } - - [TestMethod] - public async Task TestRustDetector_V2WorkspacesNoTopLevelDependenciesAsync() - { - await this.TestRustDetector_WorkspacesNoTopLevelDependenciesAsync(this.testWorkspaceLockV2NoBaseString); - } - - private async Task TestRustDetector_WorkspacesNoTopLevelDependenciesAsync(string lockFile) - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", lockFile) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(6); - } - - [TestMethod] - public async Task TestRustDetector_V1WorkspacesWithSubDirectoriesAsync() - { - await this.TestRustDetector_WorkspacesWithSubDirectoriesAsync(this.testWorkspaceLockV1NoBaseString); - } - - [TestMethod] - public async Task TestRustDetector_V2WorkspacesWithSubDirectoriesAsync() - { - await this.TestRustDetector_WorkspacesWithSubDirectoriesAsync(this.testWorkspaceLockV2NoBaseString); - } - - private async Task TestRustDetector_WorkspacesWithSubDirectoriesAsync(string lockFile) - { - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", lockFile) - .ExecuteDetectorAsync(); - - var componentGraphs = componentRecorder.GetDependencyGraphsByLocation(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(6); - - componentGraphs.Should().ContainSingle(); // Only 1 Cargo.lock is specified - - // A root Cargo.lock - componentRecorder.ForAllComponents(x => x.AllFileLocations.Should().ContainSingle()); - } - - [TestMethod] - public async Task TestRustDetector_UnequalButSemverCompatibleRootAsync() - { - var testLockString = @" -[[package]] -name = ""c-ares"" -version = ""7.5.2"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -checksum = ""a8554820e0b20a1b58b4626a3477fa4bccb1f8ee75c61ef547d50523a517126f"" -dependencies = [ - ""c-ares-sys"", -] - -[[package]] -name = ""c-ares-sys"" -version = ""5.3.3"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -checksum = ""067403b940b1320de22c347323f2cfd20b7c64b5709ab47928f5eb000e585fe0"" - -[[package]] -name = ""test"" -version = ""0.1.0"" -dependencies = [ - ""c-ares"", -] -"; - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", testLockString) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().HaveCount(2); - - var graph = componentRecorder.GetDependencyGraphsByLocation().Values.First(); // There should only be 1 - - // Verify explicitly referenced roots - var rootComponents = new List - { - "c-ares 7.5.2 - Cargo", - }; - - rootComponents.ForEach(rootComponentId => graph.IsComponentExplicitlyReferenced(rootComponentId).Should().BeTrue()); - - // Verify dependencies for other_dependency - var cAresDependencies = new List { "c-ares-sys 5.3.3 - Cargo" }; - graph.GetDependenciesForComponent("c-ares 7.5.2 - Cargo").Should().BeEquivalentTo(cAresDependencies); - } - - [TestMethod] - public async Task TestRustDetector_GitDependencyAsync() - { - var testLockString = @" -[[package]] -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.DetectorTestUtility - .WithFile("Cargo.lock", testLockString) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().ContainSingle(); - - var dependencyGraphs = componentRecorder.GetDependencyGraphsByLocation(); - dependencyGraphs.Should().ContainSingle(); - - var dependencyGraph = dependencyGraphs.Single().Value; - dependencyGraph.Contains("my_git_dep 0.1.0 - Cargo").Should().BeTrue(); - } - - [TestMethod] - public async Task TestRustDetector_MultipleRegistriesAsync() - { - 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/)"", -] - -[[package]] -name = ""common_name"" -version = ""0.2.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" - -[[package]] -name = ""common_name"" -version = ""0.2.0"" -source = ""registry+sparse+https://other.registry/index/"" -"; - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", testLockString) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - - // If registries have identity, this should be 2 - componentRecorder.GetDetectedComponents().Should().ContainSingle(); - - var dependencyGraphs = componentRecorder.GetDependencyGraphsByLocation(); - dependencyGraphs.Should().ContainSingle(); - - var dependencyGraph = dependencyGraphs.Single().Value; - - // 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()); - } - - [TestMethod] - public async Task TestRustV2Detector_StdWorkspaceDependencyAsync() - { - var testCargoLock = @" -[[package]] -name = ""addr2line"" -version = ""0.17.0"" -source = ""registry+https://github.com/rust-lang/crates.io-index"" -checksum = ""b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"" -dependencies = [ - ""rustc-std-workspace-alloc"", -] - -[[package]] -name = ""rustc-std-workspace-alloc"" -version = ""1.99.0"" -dependencies = [] -"; - - var (result, componentRecorder) = await this.DetectorTestUtility - .WithFile("Cargo.lock", testCargoLock) - .ExecuteDetectorAsync(); - - result.ResultCode.Should().Be(ProcessingResultCode.Success); - componentRecorder.GetDetectedComponents().Should().ContainSingle(); - - var graph = componentRecorder.GetDependencyGraphsByLocation().Values.First(); // There should only be 1 - - // Verify explicitly referenced roots - var rootComponents = new List - { - "addr2line 0.17.0 - Cargo", - }; - - rootComponents.ForEach(rootComponentId => graph.IsComponentExplicitlyReferenced(rootComponentId).Should().BeTrue()); - } -}