Add unit tests for RancherHubConnector and various exporters
- Implemented tests for RancherHubConnector to validate fetching documents, handling errors, and managing state. - Added tests for CsafExporter to ensure deterministic serialization of CSAF documents. - Created tests for CycloneDX exporters and reconciler to verify correct handling of VEX claims and output structure. - Developed OpenVEX exporter tests to confirm the generation of canonical OpenVEX documents and statement merging logic. - Introduced Rust file caching and license scanning functionality, including a cache key structure and hash computation. - Added sample Cargo.toml and LICENSE files for testing Rust license scanning functionality.
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Rust.Internal;
|
||||
|
||||
internal static class RustCargoLockParser
|
||||
{
|
||||
private static readonly ConcurrentDictionary<RustFileCacheKey, ImmutableArray<RustCargoPackage>> Cache = new();
|
||||
|
||||
public static IReadOnlyList<RustCargoPackage> Parse(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
@@ -9,17 +14,26 @@ internal static class RustCargoLockParser
|
||||
throw new ArgumentException("Lock path is required", nameof(path));
|
||||
}
|
||||
|
||||
var info = new FileInfo(path);
|
||||
if (!info.Exists)
|
||||
if (!RustFileCacheKey.TryCreate(path, out var key))
|
||||
{
|
||||
return Array.Empty<RustCargoPackage>();
|
||||
}
|
||||
|
||||
var packages = new List<RustCargoPackage>();
|
||||
var packages = Cache.GetOrAdd(
|
||||
key,
|
||||
static (_, state) => ParseInternal(state.Path, state.CancellationToken),
|
||||
(Path: path, CancellationToken: cancellationToken));
|
||||
|
||||
return packages.IsDefaultOrEmpty ? Array.Empty<RustCargoPackage>() : packages;
|
||||
}
|
||||
|
||||
private static ImmutableArray<RustCargoPackage> ParseInternal(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
var resultBuilder = ImmutableArray.CreateBuilder<RustCargoPackage>();
|
||||
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
RustCargoPackageBuilder? builder = null;
|
||||
RustCargoPackageBuilder? packageBuilder = null;
|
||||
string? currentArrayKey = null;
|
||||
var arrayValues = new List<string>();
|
||||
|
||||
@@ -41,14 +55,14 @@ internal static class RustCargoLockParser
|
||||
|
||||
if (IsPackageHeader(trimmed))
|
||||
{
|
||||
FlushCurrent(builder, packages);
|
||||
builder = new RustCargoPackageBuilder();
|
||||
FlushCurrent(packageBuilder, resultBuilder);
|
||||
packageBuilder = new RustCargoPackageBuilder();
|
||||
currentArrayKey = null;
|
||||
arrayValues.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (builder is null)
|
||||
if (packageBuilder is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -94,12 +108,12 @@ internal static class RustCargoLockParser
|
||||
}
|
||||
|
||||
if (valuePart[0] == '[')
|
||||
{
|
||||
currentArrayKey = key.ToString();
|
||||
arrayValues.Clear();
|
||||
|
||||
if (valuePart.Length > 1 && valuePart[^1] == ']')
|
||||
{
|
||||
currentArrayKey = key.ToString();
|
||||
arrayValues.Clear();
|
||||
|
||||
if (valuePart.Length > 1 && valuePart[^1] == ']')
|
||||
{
|
||||
var inline = valuePart[1..^1].Trim();
|
||||
if (inline.Length > 0)
|
||||
{
|
||||
@@ -113,7 +127,7 @@ internal static class RustCargoLockParser
|
||||
}
|
||||
}
|
||||
|
||||
builder.SetArray(currentArrayKey, arrayValues);
|
||||
packageBuilder.SetArray(currentArrayKey, arrayValues);
|
||||
currentArrayKey = null;
|
||||
arrayValues.Clear();
|
||||
}
|
||||
@@ -124,17 +138,17 @@ internal static class RustCargoLockParser
|
||||
var parsed = ExtractString(valuePart);
|
||||
if (parsed is not null)
|
||||
{
|
||||
builder.SetField(key, parsed);
|
||||
packageBuilder.SetField(key, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentArrayKey is not null && arrayValues.Count > 0)
|
||||
{
|
||||
builder?.SetArray(currentArrayKey, arrayValues);
|
||||
packageBuilder?.SetArray(currentArrayKey, arrayValues);
|
||||
}
|
||||
|
||||
FlushCurrent(builder, packages);
|
||||
return packages;
|
||||
FlushCurrent(packageBuilder, resultBuilder);
|
||||
return resultBuilder.ToImmutable();
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> TrimComments(ReadOnlySpan<char> line)
|
||||
@@ -204,14 +218,14 @@ internal static class RustCargoLockParser
|
||||
return trimmed.Length == 0 ? null : trimmed.ToString();
|
||||
}
|
||||
|
||||
private static void FlushCurrent(RustCargoPackageBuilder? builder, List<RustCargoPackage> packages)
|
||||
private static void FlushCurrent(RustCargoPackageBuilder? packageBuilder, ImmutableArray<RustCargoPackage>.Builder packages)
|
||||
{
|
||||
if (builder is null || !builder.HasData)
|
||||
if (packageBuilder is null || !packageBuilder.HasData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (builder.TryBuild(out var package))
|
||||
if (packageBuilder.TryBuild(out var package))
|
||||
{
|
||||
packages.Add(package);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user