diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0faaf3c93..b235908ac 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -23,7 +23,6 @@
-
@@ -40,6 +39,7 @@
+
diff --git a/docs/running-verification-tests.md b/docs/running-verification-tests.md
new file mode 100644
index 000000000..4898a9e77
--- /dev/null
+++ b/docs/running-verification-tests.md
@@ -0,0 +1,42 @@
+# Running Verification Tests On Your Local Machine
+Verification tests are used to confirm that no detectors are lost when changes are made to the project. The tests are run on every PR build. They work by comparing the detection results from the main branch to detection results from the new changes on your PR. You can follow the steps below to run them locally.
+
+## Step 1 : Run Detection on the main branch
+
+- Checkout the main branch in your local repo
+- Create a folder to store detection results (e.g C:\old-output-folder)
+- Use command line to run detection on the main branch with the code below:
+
+```dotnet run scan --Verbosity Verbose --SourceDirectory {path to your local repo} --Output {path to the output folder you created}```
+
+For Example:
+
+```dotnet run scan --Verbosity Verbose --SourceDirectory C:\componentdetection --Output C:\old-output-folder```
+
+
+## Step 2 : Run Detection on your new branch
+
+- Checkout the branch with the new changes you are trying to merge
+- Create a folder to store detection results. This folder should be seperate from the one you used in Step 1 (e.g C:\new-output-folder)
+- Use command line to run detection on the main branch with the code below:
+
+```dotnet run scan --Verbosity Verbose --SourceDirectory {path to your local repo} --Output {path to the output folder you created}```
+
+For Example:
+
+```dotnet run scan --Verbosity Verbose --SourceDirectory C:\componentdetection --Output C:\new-output-folder```
+
+## Step 3 : Update variables in the test
+
+- Open the Microsoft.ComponentDetection.VerificationTests project in VS Studio
+- Navigate to `GatherResources()` in `ComponentDetectionIntegrationTests.cs`
+- Update the following variables:
+ - `oldGithubArtifactsDir` : This should be the output folder you created in Step 1
+ - `newGithubArtifactsDir` : This should be the output folder you created in Step 2
+ - `allowedTimeDriftRatioString`: This should be ".75"
+
+
+## Step 4: Run The tests
+You can run the tests in two ways:
+- Run or Debug the tests in test explorer.
+- Use the command line to navigate to the Microsoft.ComponentDetection.VerificationTests folder, and run `dotnet test`.
\ No newline at end of file
diff --git a/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj b/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj
index 26c167bc5..6b0313e14 100644
--- a/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj
+++ b/src/Microsoft.ComponentDetection.Detectors/Microsoft.ComponentDetection.Detectors.csproj
@@ -1,9 +1,8 @@
-
+
-
@@ -18,6 +17,7 @@
+
@@ -34,4 +34,4 @@
-
\ No newline at end of file
+
diff --git a/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryLock.cs b/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryLock.cs
index cb8bfd9da..87dce51c5 100644
--- a/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryLock.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryLock.cs
@@ -1,11 +1,17 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
namespace Microsoft.ComponentDetection.Detectors.Poetry.Contracts
{
// Represents Poetry.Lock file structure.
+ [DataContract]
public class PoetryLock
{
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Deserialization contract. Casing cannot be overwritten.")]
- public PoetryPackage[] package { get; set; }
+ [DataMember(Name = "Package")]
+ public List Package { get; set; }
+
+ [DataMember(Name = "metadata")]
+ public Dictionary Metadata { get; set; }
}
}
diff --git a/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryPackage.cs b/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryPackage.cs
index 984e367f4..2cb38e07b 100644
--- a/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryPackage.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/poetry/Contracts/PoetryPackage.cs
@@ -1,19 +1,27 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
namespace Microsoft.ComponentDetection.Detectors.Poetry.Contracts
{
+ [DataContract]
public class PoetryPackage
{
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Deserialization contract. Casing cannot be overwritten.")]
- public string category { get; set; }
+ [DataMember(Name = "category")]
+ public string Category { get; set; }
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Deserialization contract. Casing cannot be overwritten.")]
- public string name { get; set; }
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Deserialization contract. Casing cannot be overwritten.")]
- public string version { get; set; }
+ [DataMember(Name = "version")]
+ public string Version { get; set; }
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Deserialization contract. Casing cannot be overwritten.")]
- public PoetrySource source { get; set; }
+ [DataMember(Name = "source")]
+ public PoetrySource Source { get; set; }
+
+ [DataMember(Name = "dependencies")]
+ public Dictionary Dependencies { get; set; }
+
+ [DataMember(Name = "extras")]
+ public Dictionary Extras { get; set; }
}
}
diff --git a/src/Microsoft.ComponentDetection.Detectors/poetry/PoetryComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/poetry/PoetryComponentDetector.cs
index ef743bce0..ecd3543e7 100644
--- a/src/Microsoft.ComponentDetection.Detectors/poetry/PoetryComponentDetector.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/poetry/PoetryComponentDetector.cs
@@ -3,11 +3,12 @@
using System.Composition;
using System.Linq;
using System.Threading.Tasks;
+using System.IO;
+using Tomlyn;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Detectors.Poetry.Contracts;
-using Nett;
namespace Microsoft.ComponentDetection.Detectors.Poetry
{
@@ -30,23 +31,27 @@ protected override Task OnFileFound(ProcessRequest processRequest, IDictionary();
- poetryLock.package.ToList().ForEach(package =>
+ var reader = new StreamReader(poetryLockFile.Stream);
+ var options = new TomlModelOptions
{
- var isDevelopmentDependency = package.category != "main";
+ IgnoreMissingProperties = true,
+ };
+ var poetryLock = Toml.ToModel(reader.ReadToEnd(), options: options);
+ poetryLock.Package.ToList().ForEach(package =>
+ {
+ var isDevelopmentDependency = package.Category != "main";
- if (package.source != null && package.source.type == "git")
+ if (package.Source != null && package.Source.type == "git")
{
- var component = new DetectedComponent(new GitComponent(new Uri(package.source.url), package.source.resolved_reference));
+ var component = new DetectedComponent(new GitComponent(new Uri(package.Source.url), package.Source.resolved_reference));
singleFileComponentRecorder.RegisterUsage(component, isDevelopmentDependency: isDevelopmentDependency);
}
else
{
- var component = new DetectedComponent(new PipComponent(package.name, package.version));
+ var component = new DetectedComponent(new PipComponent(package.Name, package.Version));
singleFileComponentRecorder.RegisterUsage(component, isDevelopmentDependency: isDevelopmentDependency);
}
});
-
return Task.CompletedTask;
}
}
diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoLock.cs b/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoLock.cs
index c0f088943..9b714d029 100644
--- a/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoLock.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/rust/Contracts/CargoLock.cs
@@ -1,15 +1,19 @@
-using System.Diagnostics.CodeAnalysis;
-using Nett;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.ComponentDetection.Detectors.Rust.Contracts
{
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+ using Tomlyn.Model;
+
// Represents Cargo.Lock file structure.
+ [DataContract]
public class CargoLock
{
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Deserialization contract. Casing cannot be overwritten.")]
- public CargoPackage[] package { get; set; }
+ [DataMember(Name = "package")]
+ public List Package { get; set; }
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Deserialization contract. Casing cannot be overwritten.")]
- public TomlTable metadata { get; set; }
+ [DataMember(Name = "metadata")]
+ public Dictionary Metadata { get; set; }
}
}
diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs
index 53d212e74..7b1bd4cb5 100644
--- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateDetector.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Composition;
using System.IO;
@@ -8,7 +8,7 @@
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Detectors.Rust.Contracts;
-using Nett;
+using Tomlyn;
namespace Microsoft.ComponentDetection.Detectors.Rust
{
@@ -32,10 +32,15 @@ protected override Task OnFileFound(ProcessRequest processRequest, IDictionary();
+ var reader = new StreamReader(cargoLockFile.Stream);
+ var options = new TomlModelOptions
+ {
+ IgnoreMissingProperties = true,
+ };
+ var cargoLock = Toml.ToModel(reader.ReadToEnd(), options: options);
// This makes sure we're only trying to parse Cargo.lock v1 formats
- if (cargoLock.metadata == null)
+ if (cargoLock.Metadata == null)
{
this.Logger.LogInfo($"Cargo.lock file at {cargoLockFile.Location} contains no metadata section so we're parsing it as the v2 format. The v1 detector will not process it.");
return Task.CompletedTask;
@@ -79,7 +84,7 @@ protected override Task OnFileFound(ProcessRequest processRequest, IDictionary(dependency);
- }
- else if (dependencies.Get(dependency).ContainsKey("version") && dependencies.Get(dependency).Get("version") != "0.0.0")
- {
- // We have a valid version that doesn't indicate 'internal' like 0.0.0 does.
- versionSpecifier = dependencies.Get(dependency).Get("version");
- }
- else if (dependencies.Get(dependency).ContainsKey("path"))
- {
- // If this is a workspace dependency specification that specifies a component by path reference, skip adding it directly here.
- // Example: kubos-app = { path = "../../apis/app-api/rust" }
- continue;
- }
- else
- {
- return null;
+ if (value is string valueAsString)
+ {
+ versionSpecifier = valueAsString;
+ }
+ else if ((value is TomlTable versionTable) && versionTable.TryGetValue("version", out var versionValue) && versionValue is string versionValueAsSring && (versionValueAsSring != "0.0.0"))
+ {
+ // We have a valid version that doesn't indicate 'internal' like 0.0.0 does.
+ versionSpecifier = versionValueAsSring;
+ }
+ else if ((value is TomlTable pathTable) && pathTable.TryGetValue("path", out var pathValue))
+ {
+ // If this is a workspace dependency specification that specifies a component by path reference, skip adding it directly here.
+ // Example: kubos-app = { path = "../../apis/app-api/rust" }
+ continue;
+ }
+ else
+ {
+ return null;
+ }
}
// If the dependency is renamed, use the actual name of the package:
// https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml
string dependencyName;
- if (dependencies[dependency].TomlType == TomlObjectType.Table &&
- dependencies.Get(dependency).ContainsKey("package"))
+ if (dependencies.TryGetValue(dependency, out var dependencyValue) && dependencyValue is TomlTable tomlTable && tomlTable.TryGetValue("package", out var packageValue) && packageValue is string packageValueAsString)
{
- dependencyName = dependencies.Get(dependency).Get("package");
+ dependencyName = packageValueAsString;
}
else
{
@@ -83,7 +87,7 @@ public static DependencySpecification GenerateDependencySpecifications(TomlTable
public static IEnumerable ConvertCargoLockV2PackagesToV1(CargoLock cargoLock)
{
var packageMap = new Dictionary>();
- cargoLock.package.ToList().ForEach(package =>
+ cargoLock.Package.ToList().ForEach(package =>
{
if (!packageMap.TryGetValue(package.name, out var packageList))
{
@@ -95,7 +99,7 @@ public static IEnumerable ConvertCargoLockV2PackagesToV1(CargoLock
}
});
- return cargoLock.package.Select(package =>
+ return cargoLock.Package.Select(package =>
{
if (package.dependencies == null)
{
@@ -174,37 +178,36 @@ public static CargoDependencyData ExtractRootDependencyAndWorkspaceSpecification
// We break at the end of this loop
foreach (var cargoTomlFile in cargoTomlComponentStream)
{
- var cargoToml = StreamTomlSerializer.Deserialize(cargoTomlFile.Stream, TomlSettings.Create());
+ var reader = new StreamReader(cargoTomlFile.Stream);
+ var cargoToml = Toml.ToModel(reader.ReadToEnd());
singleFileComponentRecorder.AddAdditionalRelatedFile(cargoTomlFile.Location);
// Extract the workspaces present, if any
- if (cargoToml.ContainsKey(WorkspaceKey))
+ if (cargoToml.TryGetValue(WorkspaceKey, out var value) && value is TomlTable workspaces)
{
- var workspaces = cargoToml.Get(WorkspaceKey);
-
- var workspaceMembers = workspaces.ContainsKey(WorkspaceMemberKey) ? workspaces[WorkspaceMemberKey] : null;
- var workspaceExclusions = workspaces.ContainsKey(WorkspaceExcludeKey) ? workspaces[WorkspaceExcludeKey] : null;
-
- if (workspaceMembers != null)
+ if (workspaces.TryGetValue(WorkspaceMemberKey, out var workspaceMembers))
{
- if (workspaceMembers.TomlType != TomlObjectType.Array)
+ if (workspaceMembers is TomlArray workspaceMembersArray)
{
- throw new InvalidRustTomlFileException($"In accompanying Cargo.toml file expected {WorkspaceMemberKey} within {WorkspaceKey} to be of type Array, but found {workspaceMembers.TomlType}");
+ cargoDependencyData.CargoWorkspaces.UnionWith(workspaceMembersArray.Select(i => i.ToString()));
+ }
+ else
+ {
+ throw new InvalidRustTomlFileException($"In accompanying Cargo.toml file expected {WorkspaceMemberKey} within {WorkspaceKey} to be of type Array, but found {workspaceMembers?.GetType()}");
}
-
- // TomlObject arrays do not natively implement a HashSet get, so add from a list
- cargoDependencyData.CargoWorkspaces.UnionWith(workspaceMembers.Get>());
}
- if (workspaceExclusions != null)
+ if (workspaces.TryGetValue(WorkspaceExcludeKey, out var workspaceExclusions))
{
- if (workspaceExclusions.TomlType != TomlObjectType.Array)
+ if (workspaceExclusions is TomlArray workspaceExclusionsArray)
{
- throw new InvalidRustTomlFileException($"In accompanying Cargo.toml file expected {WorkspaceExcludeKey} within {WorkspaceKey} to be of type Array, but found {workspaceExclusions.TomlType}");
+ cargoDependencyData.CargoWorkspaceExclusions.UnionWith(workspaceExclusionsArray.Select(i => i.ToString()));
+ }
+ else
+ {
+ throw new InvalidRustTomlFileException($"In accompanying Cargo.toml file expected {WorkspaceExcludeKey} within {WorkspaceKey} to be of type Array, but found {workspaceExclusions?.GetType()}");
}
-
- cargoDependencyData.CargoWorkspaceExclusions.UnionWith(workspaceExclusions.Get>());
}
}
@@ -252,7 +255,8 @@ public static void ExtractDependencySpecifications(IEnumerable
// This method is only used in non root toml extraction, so the whole list should be iterated
foreach (var cargoTomlFile in cargoTomlComponentStreams)
{
- var cargoToml = StreamTomlSerializer.Deserialize(cargoTomlFile.Stream, TomlSettings.Create());
+ var reader = new StreamReader(cargoTomlFile.Stream);
+ var cargoToml = Toml.ToModel(reader.ReadToEnd());
singleFileComponentRecorder.AddAdditionalRelatedFile(cargoTomlFile.Location);
@@ -457,16 +461,17 @@ private static IEnumerable GetDependencies(TomlTable cargoToml, IEnum
{
if (cargoToml.ContainsKey(tomlDependencyKey))
{
- dependencies.Add(cargoToml.Get(tomlDependencyKey));
+ var newDependencyKey = cargoToml[tomlDependencyKey] as TomlTable;
+ dependencies.Add(newDependencyKey);
}
}
if (cargoToml.ContainsKey(targetKey))
{
- var configs = cargoToml.Get(targetKey);
+ var configs = cargoToml[targetKey] as TomlTable;
foreach (var config in configs)
{
- var properties = configs.Get(config.Key);
+ var properties = configs[config.Key] as TomlTable;
foreach (var propertyKey in properties.Keys)
{
var isRelevantKey = tomlDependencyKeys.Any(dependencyKey =>
@@ -474,7 +479,8 @@ private static IEnumerable GetDependencies(TomlTable cargoToml, IEnum
if (isRelevantKey)
{
- dependencies.Add(properties.Get(propertyKey));
+ var newDependencyKey = properties[propertyKey] as TomlTable;
+ dependencies.Add(newDependencyKey);
}
}
}
diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateV2Detector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateV2Detector.cs
index bcaae2295..dd529ea44 100644
--- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateV2Detector.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/rust/RustCrateV2Detector.cs
@@ -8,7 +8,7 @@
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Detectors.Rust.Contracts;
-using Nett;
+using Tomlyn;
namespace Microsoft.ComponentDetection.Detectors.Rust
{
@@ -29,13 +29,17 @@ protected override Task OnFileFound(ProcessRequest processRequest, IDictionary();
+ var reader = new StreamReader(cargoLockFile.Stream);
+ var cargoLock = Toml.ToModel(reader.ReadToEnd(), options: options);
// This makes sure we're only trying to parse Cargo.lock v2 formats
- if (cargoLock.metadata != null)
+ if (cargoLock.Metadata != null)
{
this.Logger.LogInfo($"Cargo.lock file at {cargoLockFile.Location} contains a metadata section so we're parsing it as the v1 format. The v2 detector will no process it.");
return Task.CompletedTask;
diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs
index 9c978c8cf..6358291c1 100644
--- a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs
+++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetProjectModelProjectCentricComponentDetectorTests.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;