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());
- }
-}