Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,118 @@
[
{
"analyzerId": "golang",
"componentKey": "purl::pkg:golang/example.com/app@v1.2.3",
"purl": "pkg:golang/example.com/app@v1.2.3",
"name": "example.com/app",
"version": "v1.2.3",
"type": "golang",
"usedByEntrypoint": false,
"metadata": {
"binaryPath": "app",
"build.GOARCH": "amd64",
"build.GOOS": "linux",
"build.vcs": "git",
"build.vcs.modified": "false",
"build.vcs.revision": "1234567890abcdef1234567890abcdef12345678",
"build.vcs.time": "2025-09-14T12:34:56Z",
"go.version": "go1.22.5",
"modulePath": "example.com/app",
"modulePath.main": "example.com/app",
"moduleSum": "h1:mainchecksum",
"moduleVersion": "v1.2.3"
},
"evidence": [
{
"kind": "metadata",
"source": "go.buildinfo.setting",
"locator": "GOARCH",
"value": "amd64"
},
{
"kind": "metadata",
"source": "go.buildinfo.setting",
"locator": "GOOS",
"value": "linux"
},
{
"kind": "metadata",
"source": "go.buildinfo.setting",
"locator": "vcs.modified",
"value": "false"
},
{
"kind": "metadata",
"source": "go.buildinfo.setting",
"locator": "vcs.revision",
"value": "1234567890abcdef1234567890abcdef12345678"
},
{
"kind": "metadata",
"source": "go.buildinfo.setting",
"locator": "vcs.time",
"value": "2025-09-14T12:34:56Z"
},
{
"kind": "metadata",
"source": "go.buildinfo.setting",
"locator": "vcs",
"value": "git"
},
{
"kind": "metadata",
"source": "go.buildinfo",
"locator": "module:example.com/app",
"value": "v1.2.3",
"sha256": "h1:mainchecksum"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs.modified",
"value": "false"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs.revision",
"value": "1234567890abcdef1234567890abcdef12345678"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs.time",
"value": "2025-09-14T12:34:56Z"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs",
"value": "git"
}
]
},
{
"analyzerId": "golang",
"componentKey": "purl::pkg:golang/example.com/lib@v1.0.0",
"purl": "pkg:golang/example.com/lib@v1.0.0",
"name": "example.com/lib",
"version": "v1.0.0",
"type": "golang",
"usedByEntrypoint": false,
"metadata": {
"binaryPath": "app",
"modulePath": "example.com/lib",
"moduleSum": "h1:depchecksum",
"moduleVersion": "v1.0.0"
},
"evidence": [
{
"kind": "metadata",
"source": "go.buildinfo",
"locator": "module:example.com/lib",
"value": "v1.0.0",
"sha256": "h1:depchecksum"
}
]
}
]

View File

@@ -0,0 +1,80 @@
[
{
"analyzerId": "golang",
"componentKey": "purl::pkg:golang/example.com/app@v0.0.0",
"purl": "pkg:golang/example.com/app@v0.0.0",
"name": "example.com/app",
"version": "v0.0.0",
"type": "golang",
"usedByEntrypoint": false,
"metadata": {
"binaryPath": "app",
"build.vcs": "git",
"build.vcs.modified": "true",
"build.vcs.revision": "abcdef0123456789abcdef0123456789abcdef01",
"build.vcs.time": "2025-01-02T03:04:05Z",
"go.version": "go1.20.3",
"modulePath": "example.com/app",
"modulePath.main": "example.com/app",
"moduleSum": "h1:dwarfchecksum",
"moduleVersion": "v0.0.0"
},
"evidence": [
{
"kind": "metadata",
"source": "go.buildinfo",
"locator": "module:example.com/app",
"value": "v0.0.0",
"sha256": "h1:dwarfchecksum"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs.modified",
"value": "true"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs.revision",
"value": "abcdef0123456789abcdef0123456789abcdef01"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs.time",
"value": "2025-01-02T03:04:05Z"
},
{
"kind": "metadata",
"source": "go.dwarf",
"locator": "vcs",
"value": "git"
}
]
},
{
"analyzerId": "golang",
"componentKey": "purl::pkg:golang/example.com/lib@v0.1.0",
"purl": "pkg:golang/example.com/lib@v0.1.0",
"name": "example.com/lib",
"version": "v0.1.0",
"type": "golang",
"usedByEntrypoint": false,
"metadata": {
"binaryPath": "app",
"modulePath": "example.com/lib",
"moduleSum": "h1:libchecksum",
"moduleVersion": "v0.1.0"
},
"evidence": [
{
"kind": "metadata",
"source": "go.buildinfo",
"locator": "module:example.com/lib",
"value": "v0.1.0",
"sha256": "h1:libchecksum"
}
]
}
]

View File

@@ -0,0 +1,5 @@
MZfakebinaryheader
Go build ID: "random-go-build-id"
....gopclntab....
runtime.buildVersion=go1.22.8
padding0000000000000000000000000000000000000000000000000000000000000000

View File

@@ -0,0 +1,30 @@
[
{
"analyzerId": "golang",
"componentKey": "golang::bin::sha256:7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99",
"name": "app",
"type": "bin",
"usedByEntrypoint": false,
"metadata": {
"binary.sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99",
"binaryPath": "app",
"go.version.hint": "go1.22.8",
"languageHint": "golang",
"provenance": "binary"
},
"evidence": [
{
"kind": "file",
"source": "binary",
"locator": "app",
"sha256": "7125d65230b913faa744a33acd884899c81a1dbc6d88cbf251a74b19621cde99"
},
{
"kind": "metadata",
"source": "go.heuristic",
"locator": "classification",
"value": "build-id"
}
]
}
]

View File

@@ -0,0 +1,134 @@
using System;
using System.Diagnostics.Metrics;
using System.IO;
using System.Linq;
using StellaOps.Scanner.Analyzers.Lang.Go;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Tests;
public sealed class GoLanguageAnalyzerTests
{
[Fact]
public async Task BuildInfoFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "go", "basic");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new GoLanguageAnalyzer(),
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task DwarfOnlyFixtureFallsBackToMetadataAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "go", "dwarf-only");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new GoLanguageAnalyzer(),
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task StrippedBinaryFallsBackToHeuristicBinHashAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "go", "stripped");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new GoLanguageAnalyzer(),
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task ParallelRunsRemainDeterministicAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "go", "basic");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new GoLanguageAnalyzer(),
};
var tasks = Enumerable
.Range(0, Environment.ProcessorCount)
.Select(_ => LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken));
await Task.WhenAll(tasks);
}
[Fact]
public async Task HeuristicMetricCounterIncrementsAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "go", "stripped");
var analyzers = new ILanguageAnalyzer[]
{
new GoLanguageAnalyzer(),
};
var total = 0L;
using var listener = new MeterListener
{
InstrumentPublished = (instrument, meterListener) =>
{
if (instrument.Meter.Name == "StellaOps.Scanner.Analyzers.Lang.Go"
&& instrument.Name == "scanner_analyzer_golang_heuristic_total")
{
meterListener.EnableMeasurementEvents(instrument);
}
}
};
listener.SetMeasurementEventCallback<long>((_, measurement, _, _) =>
{
Interlocked.Add(ref total, measurement);
});
listener.Start();
await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken: cancellationToken).ConfigureAwait(false);
listener.Dispose();
Assert.Equal(1, Interlocked.Read(ref total));
}
}

View File

@@ -0,0 +1,46 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.v3" Version="3.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Scanner.Analyzers.Lang.Tests\StellaOps.Scanner.Analyzers.Lang.Tests.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Go/StellaOps.Scanner.Analyzers.Lang.Go.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,35 @@
[
{
"analyzerId": "java",
"componentKey": "purl::pkg:maven/com/example/demo@1.0.0",
"purl": "pkg:maven/com/example/demo@1.0.0",
"name": "demo",
"version": "1.0.0",
"type": "maven",
"usedByEntrypoint": true,
"metadata": {
"artifactId": "demo",
"displayName": "Demo Library",
"groupId": "com.example",
"jarPath": "libs/demo.jar",
"manifestTitle": "Demo",
"manifestVendor": "Example Corp",
"manifestVersion": "1.0.0",
"packaging": "jar"
},
"evidence": [
{
"kind": "file",
"source": "MANIFEST.MF",
"locator": "libs/demo.jar!META-INF/MANIFEST.MF",
"value": "title=Demo;version=1.0.0;vendor=Example Corp"
},
{
"kind": "file",
"source": "pom.properties",
"locator": "libs/demo.jar!META-INF/maven/com.example/demo/pom.properties",
"sha256": "c20f36aa1b9d89d28cf9ed131519ffd6287a4dac0c7cb926130496f3f8157bf1"
}
]
}
]

View File

@@ -0,0 +1,172 @@
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ClassPath;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests;
public sealed class JavaClassPathBuilderTests
{
[Fact]
public void Build_ClassPathForSimpleJar()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
JavaFixtureBuilder.CreateSampleJar(root, "libs/simple.jar");
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, CancellationToken.None);
var analysis = JavaClassPathBuilder.Build(workspace, CancellationToken.None);
var segment = Assert.Single(analysis.Segments);
Assert.Equal("libs/simple.jar", segment.Identifier.Replace('\\', '/'));
Assert.Contains("com.example.Demo", segment.Classes);
var package = Assert.Single(segment.Packages);
Assert.Equal("com.example", package.Key);
Assert.Equal(1, package.Value.ClassCount);
Assert.Empty(analysis.DuplicateClasses);
Assert.Empty(analysis.SplitPackages);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Build_CapturesServiceDefinitions()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var services = new Dictionary<string, string[]>
{
["java.sql.Driver"] = new[] { "com.example.DriverImpl" },
};
CreateJarWithClasses(root, "libs/spi.jar", new[] { "com.example.DriverImpl" }, services);
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, CancellationToken.None);
var analysis = JavaClassPathBuilder.Build(workspace, CancellationToken.None);
var segment = Assert.Single(analysis.Segments);
var providers = Assert.Single(segment.ServiceDefinitions);
Assert.Equal("java.sql.Driver", providers.Key);
Assert.Contains("com.example.DriverImpl", providers.Value);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Build_FatJarIncludesNestedLibraries()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
JavaFixtureBuilder.CreateSpringBootFatJar(root, "apps/app-fat.jar");
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, CancellationToken.None);
var analysis = JavaClassPathBuilder.Build(workspace, CancellationToken.None);
Assert.Equal(2, analysis.Segments.Length);
var classesSegment = analysis.Segments[0];
Assert.Equal("apps/app-fat.jar!BOOT-INF/classes/", classesSegment.Identifier.Replace('\\', '/'));
Assert.Contains("com.example.App", classesSegment.Classes);
var librarySegment = analysis.Segments[1];
Assert.Equal("apps/app-fat.jar!BOOT-INF/lib/library.jar", librarySegment.Identifier.Replace('\\', '/'));
Assert.Contains("com.example.Lib", librarySegment.Classes);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Build_ReportsDuplicateClassesAndSplitPackages()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
CreateJarWithClasses(root, "libs/a.jar", "com.example.Demo");
CreateJarWithClasses(root, "libs/b.jar", "com.example.Demo", "com.example.Other");
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, CancellationToken.None);
var analysis = JavaClassPathBuilder.Build(workspace, CancellationToken.None);
Assert.Equal(2, analysis.Segments.Length);
var duplicate = Assert.Single(analysis.DuplicateClasses);
Assert.Equal("com.example.Demo", duplicate.ClassName);
Assert.Equal(2, duplicate.SegmentIdentifiers.Length);
var split = Assert.Single(analysis.SplitPackages);
Assert.Equal("com.example", split.PackageName);
Assert.Equal(2, split.SegmentIdentifiers.Length);
}
finally
{
TestPaths.SafeDelete(root);
}
}
private static void CreateJarWithClasses(string rootDirectory, string relativePath, params string[] classNames)
=> CreateJarWithClasses(rootDirectory, relativePath, classNames.AsEnumerable(), serviceDefinitions: null);
private static void CreateJarWithClasses(
string rootDirectory,
string relativePath,
IEnumerable<string> classNames,
IDictionary<string, string[]>? serviceDefinitions)
{
ArgumentNullException.ThrowIfNull(rootDirectory);
ArgumentException.ThrowIfNullOrEmpty(relativePath);
var jarPath = Path.Combine(rootDirectory, relativePath.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using var fileStream = new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, leaveOpen: false);
var timestamp = new DateTimeOffset(2024, 01, 01, 0, 0, 0, TimeSpan.Zero);
foreach (var className in classNames)
{
var entryPath = className.Replace('.', '/') + ".class";
var entry = archive.CreateEntry(entryPath, CompressionLevel.NoCompression);
entry.LastWriteTime = timestamp;
using var writer = new BinaryWriter(entry.Open(), Encoding.UTF8, leaveOpen: false);
writer.Write(new byte[] { 0xCA, 0xFE, 0xBA, 0xBE });
}
if (serviceDefinitions is not null)
{
foreach (var pair in serviceDefinitions)
{
var entryPath = "META-INF/services/" + pair.Key;
var entry = archive.CreateEntry(entryPath, CompressionLevel.NoCompression);
entry.LastWriteTime = timestamp;
using var writer = new StreamWriter(entry.Open(), Encoding.UTF8, leaveOpen: false);
foreach (var provider in pair.Value)
{
writer.WriteLine(provider);
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
using StellaOps.Scanner.Analyzers.Lang.Java;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests;
public sealed class JavaLanguageAnalyzerTests
{
[Fact]
public async Task ExtractsMavenArtifactFromJarAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = JavaFixtureBuilder.CreateSampleJar(root);
var usageHints = new LanguageUsageHints(new[] { jarPath });
var analyzers = new ILanguageAnalyzer[] { new JavaLanguageAnalyzer() };
var goldenPath = TestPaths.ResolveFixture("java", "basic", "expected.json");
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath: root,
goldenPath: goldenPath,
analyzers: analyzers,
cancellationToken: cancellationToken,
usageHints: usageHints);
}
finally
{
TestPaths.SafeDelete(root);
}
}
}

View File

@@ -0,0 +1,102 @@
using System.IO.Compression;
using System.Threading;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ClassPath;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Reflection;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests;
public sealed class JavaReflectionAnalyzerTests
{
[Fact]
public void Analyze_ClassForNameLiteral_ProducesEdge()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "libs", "reflect.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = new ZipArchive(new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ZipArchiveMode.Create, leaveOpen: false))
{
var entry = archive.CreateEntry("com/example/Reflective.class");
var bytes = JavaClassFileFactory.CreateClassForNameInvoker("com/example/Reflective", "com.example.Plugin");
using var stream = entry.Open();
stream.Write(bytes);
}
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaReflectionAnalyzer.Analyze(classPath, cancellationToken);
var edge = Assert.Single(analysis.Edges);
Assert.Equal("com.example.Reflective", edge.SourceClass);
Assert.Equal("com.example.Plugin", edge.TargetType);
Assert.Equal(JavaReflectionReason.ClassForName, edge.Reason);
Assert.Equal(JavaReflectionConfidence.High, edge.Confidence);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Analyze_TcclUsage_ProducesWarning()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var jarPath = Path.Combine(root, "libs", "tccl.jar");
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using (var archive = new ZipArchive(new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ZipArchiveMode.Create, leaveOpen: false))
{
var entry = archive.CreateEntry("com/example/Tccl.class");
var bytes = JavaClassFileFactory.CreateTcclChecker("com/example/Tccl");
using var stream = entry.Open();
stream.Write(bytes);
}
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaReflectionAnalyzer.Analyze(classPath, cancellationToken);
Assert.Empty(analysis.Edges);
var warning = Assert.Single(analysis.Warnings);
Assert.Equal("tccl", warning.WarningCode);
Assert.Equal("com.example.Tccl", warning.SourceClass);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Analyze_SpringBootFatJar_ScansEmbeddedAndBootSegments()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
JavaFixtureBuilder.CreateSpringBootFatJar(root, "apps/app-fat.jar");
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaReflectionAnalyzer.Analyze(classPath, cancellationToken);
// Expect at least one edge originating from BOOT-INF classes
Assert.Contains(analysis.Edges, edge => edge.SourceClass == "com.example.App" && edge.Reason == JavaReflectionReason.ClassForName);
Assert.Contains(analysis.Edges, edge => edge.SourceClass == "com.example.Lib" && edge.Reason == JavaReflectionReason.ClassForName);
}
finally
{
TestPaths.SafeDelete(root);
}
}
}

View File

@@ -0,0 +1,147 @@
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ClassPath;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.ServiceProviders;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
using Xunit;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests;
public sealed class JavaServiceProviderScannerTests
{
[Fact]
public void Scan_SelectsFirstProviderByClasspathOrder()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var servicesA = new Dictionary<string, string[]>
{
["java.sql.Driver"] = new[] { "com.example.ADriver" },
};
var servicesB = new Dictionary<string, string[]>
{
["java.sql.Driver"] = new[] { "com.example.BDriver" },
};
CreateJarWithClasses(root, "libs/a.jar", new[] { "com.example.ADriver" }, servicesA);
CreateJarWithClasses(root, "libs/b.jar", new[] { "com.example.BDriver" }, servicesB);
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaServiceProviderScanner.Scan(classPath, JavaSpiCatalog.Default, cancellationToken);
var service = Assert.Single(analysis.Services, record => record.ServiceId == "java.sql.Driver");
Assert.Equal("jdk", service.Category);
var selected = Assert.Single(service.Candidates.Where(candidate => candidate.IsSelected));
Assert.Equal("com.example.ADriver", selected.ProviderClass);
Assert.Empty(service.Warnings);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Scan_FlagsDuplicateProviders()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
var services = new Dictionary<string, string[]>
{
["java.sql.Driver"] = new[] { "com.example.DuplicateDriver" },
};
CreateJarWithClasses(root, "libs/a.jar", new[] { "com.example.DuplicateDriver" }, services);
CreateJarWithClasses(root, "libs/b.jar", new[] { "com.example.Other" }, services);
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaServiceProviderScanner.Scan(classPath, JavaSpiCatalog.Default, cancellationToken);
var service = Assert.Single(analysis.Services, record => record.ServiceId == "java.sql.Driver");
Assert.NotEmpty(service.Warnings);
Assert.Contains(service.Warnings, warning => warning.Contains("duplicate-provider", StringComparison.OrdinalIgnoreCase));
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Scan_RespectsBootFatJarOrdering()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
JavaFixtureBuilder.CreateSpringBootFatJar(root, "apps/app-fat.jar");
var cancellationToken = TestContext.Current.CancellationToken;
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, cancellationToken);
var classPath = JavaClassPathBuilder.Build(workspace, cancellationToken);
var analysis = JavaServiceProviderScanner.Scan(classPath, JavaSpiCatalog.Default, cancellationToken);
var service = Assert.Single(analysis.Services, record => record.ServiceId == "java.sql.Driver");
var selected = Assert.Single(service.Candidates.Where(candidate => candidate.IsSelected));
Assert.Equal("com.example.AppDriver", selected.ProviderClass);
Assert.Contains(service.Candidates.Select(candidate => candidate.ProviderClass), provider => provider == "com.example.LibDriver");
}
finally
{
TestPaths.SafeDelete(root);
}
}
private static void CreateJarWithClasses(
string rootDirectory,
string relativePath,
IEnumerable<string> classNames,
IDictionary<string, string[]> serviceDefinitions)
{
ArgumentNullException.ThrowIfNull(rootDirectory);
ArgumentException.ThrowIfNullOrEmpty(relativePath);
var jarPath = Path.Combine(rootDirectory, relativePath.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(Path.GetDirectoryName(jarPath)!);
using var fileStream = new FileStream(jarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, leaveOpen: false);
var timestamp = new DateTimeOffset(2024, 01, 01, 0, 0, 0, TimeSpan.Zero);
foreach (var className in classNames)
{
var entryPath = className.Replace('.', '/') + ".class";
var entry = archive.CreateEntry(entryPath, CompressionLevel.NoCompression);
entry.LastWriteTime = timestamp;
using var writer = new BinaryWriter(entry.Open(), Encoding.UTF8, leaveOpen: false);
writer.Write(new byte[] { 0xCA, 0xFE, 0xBA, 0xBE });
}
foreach (var pair in serviceDefinitions)
{
var entryPath = "META-INF/services/" + pair.Key;
var entry = archive.CreateEntry(entryPath, CompressionLevel.NoCompression);
entry.LastWriteTime = timestamp;
using var writer = new StreamWriter(entry.Open(), Encoding.UTF8, leaveOpen: false);
foreach (var provider in pair.Value)
{
writer.WriteLine(provider);
}
}
}
}

View File

@@ -0,0 +1,93 @@
using System.Linq;
using System.Threading;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests;
public sealed class JavaWorkspaceNormalizerTests
{
[Fact]
public void Normalize_ClassifiesPackagingAndLayers()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
JavaFixtureBuilder.CreateSampleJar(root, "libs/simple.jar");
JavaFixtureBuilder.CreateSpringBootFatJar(root, "libs/app-fat.jar");
JavaFixtureBuilder.CreateWarArchive(root, "apps/sample.war");
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, CancellationToken.None);
var archivesByPath = workspace.Archives.ToDictionary(
archive => archive.RelativePath.Replace('\\', '/'),
archive => archive,
StringComparer.Ordinal);
var simpleJar = Assert.Contains("libs/simple.jar", archivesByPath);
Assert.Equal(JavaPackagingKind.Jar, simpleJar.Packaging);
Assert.Empty(simpleJar.LayeredDirectories);
var fatJar = Assert.Contains("libs/app-fat.jar", archivesByPath);
Assert.Equal(JavaPackagingKind.SpringBootFatJar, fatJar.Packaging);
Assert.Contains("BOOT-INF", fatJar.LayeredDirectories);
var war = Assert.Contains("apps/sample.war", archivesByPath);
Assert.Equal(JavaPackagingKind.War, war.Packaging);
Assert.Contains("WEB-INF", war.LayeredDirectories);
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Normalize_SelectsMultiReleaseOverlay()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
JavaFixtureBuilder.CreateMultiReleaseJar(root, "libs/mr.jar");
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, CancellationToken.None);
var archive = Assert.Single(workspace.Archives);
Assert.True(archive.IsMultiRelease);
Assert.False(archive.HasModuleInfo);
Assert.True(archive.TryGetEntry("com/example/App.class", out var entry));
Assert.Equal(11, entry.Version);
Assert.Equal("META-INF/versions/11/com/example/App.class", entry.OriginalPath.Replace('\\', '/'));
}
finally
{
TestPaths.SafeDelete(root);
}
}
[Fact]
public void Normalize_DetectsRuntimeImageMetadata()
{
var root = TestPaths.CreateTemporaryDirectory();
try
{
JavaFixtureBuilder.CreateRuntimeImage(root, "runtime/jre");
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = JavaWorkspaceNormalizer.Normalize(context, CancellationToken.None);
var runtime = Assert.Single(workspace.RuntimeImages);
Assert.Equal("17.0.8", runtime.JavaVersion);
Assert.Equal("Eclipse Adoptium", runtime.Vendor);
Assert.Equal("runtime/jre", runtime.RelativePath.Replace('\\', '/'));
}
finally
{
TestPaths.SafeDelete(root);
}
}
}

View File

@@ -0,0 +1,46 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.v3" Version="3.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Scanner.Analyzers.Lang.Tests\StellaOps.Scanner.Analyzers.Lang.Tests.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Java/StellaOps.Scanner.Analyzers.Lang.Java.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,134 @@
[
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/left-pad@1.3.0",
"purl": "pkg:npm/left-pad@1.3.0",
"name": "left-pad",
"version": "1.3.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"integrity": "sha512-LEFTPAD",
"path": "packages/app/node_modules/left-pad",
"resolved": "https://registry.example/left-pad-1.3.0.tgz"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "packages/app/node_modules/left-pad/package.json"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/lib@2.0.1",
"purl": "pkg:npm/lib@2.0.1",
"name": "lib",
"version": "2.0.1",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"integrity": "sha512-LIB",
"path": "packages/lib",
"resolved": "https://registry.example/lib-2.0.1.tgz",
"workspaceLink": "packages/app/node_modules/lib",
"workspaceMember": "true",
"workspaceRoot": "packages/lib"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "packages/app/node_modules/lib/package.json"
},
{
"kind": "file",
"source": "package.json",
"locator": "packages/lib/package.json"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/root-workspace@1.0.0",
"purl": "pkg:npm/root-workspace@1.0.0",
"name": "root-workspace",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"path": ".",
"private": "true"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/shared@3.1.4",
"purl": "pkg:npm/shared@3.1.4",
"name": "shared",
"version": "3.1.4",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"integrity": "sha512-SHARED",
"path": "packages/shared",
"resolved": "https://registry.example/shared-3.1.4.tgz",
"workspaceLink": "packages/app/node_modules/shared",
"workspaceMember": "true",
"workspaceRoot": "packages/shared",
"workspaceTargets": "packages/lib"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "packages/app/node_modules/shared/package.json"
},
{
"kind": "file",
"source": "package.json",
"locator": "packages/shared/package.json"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/workspace-app@1.0.0",
"purl": "pkg:npm/workspace-app@1.0.0",
"name": "workspace-app",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"installScripts": "true",
"path": "packages/app",
"policyHint.installLifecycle": "postinstall",
"script.postinstall": "node scripts/setup.js",
"workspaceMember": "true",
"workspaceRoot": "packages/app",
"workspaceTargets": "packages/lib;packages/shared"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "packages/app/package.json"
},
{
"kind": "metadata",
"source": "package.json:scripts",
"locator": "packages/app/package.json#scripts.postinstall",
"value": "node scripts/setup.js",
"sha256": "f9ae4e4c9313857d1acc31947cee9984232cbefe93c8a56c718804744992728a"
}
]
}
]

View File

@@ -0,0 +1,49 @@
{
"name": "root-workspace",
"version": "1.0.0",
"lockfileVersion": 3,
"packages": {
"": {
"name": "root-workspace",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
]
},
"packages/app": {
"name": "workspace-app",
"version": "1.0.0"
},
"packages/lib": {
"name": "lib",
"version": "2.0.1",
"resolved": "https://registry.example/lib-2.0.1.tgz",
"integrity": "sha512-LIB"
},
"packages/shared": {
"name": "shared",
"version": "3.1.4",
"resolved": "https://registry.example/shared-3.1.4.tgz",
"integrity": "sha512-SHARED"
},
"packages/app/node_modules/lib": {
"name": "lib",
"version": "2.0.1",
"resolved": "https://registry.example/lib-2.0.1.tgz",
"integrity": "sha512-LIB"
},
"packages/app/node_modules/shared": {
"name": "shared",
"version": "3.1.4",
"resolved": "https://registry.example/shared-3.1.4.tgz",
"integrity": "sha512-SHARED"
},
"packages/app/node_modules/left-pad": {
"name": "left-pad",
"version": "1.3.0",
"resolved": "https://registry.example/left-pad-1.3.0.tgz",
"integrity": "sha512-LEFTPAD"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"name": "root-workspace",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/app",
"packages/lib",
"packages/shared"
]
}

View File

@@ -0,0 +1,11 @@
{
"name": "workspace-app",
"version": "1.0.0",
"dependencies": {
"lib": "workspace:../lib",
"shared": "workspace:../shared"
},
"scripts": {
"postinstall": "node scripts/setup.js"
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "lib",
"version": "2.0.1",
"dependencies": {
"left-pad": "1.3.0"
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "shared",
"version": "3.1.4",
"dependencies": {
"lib": "workspace:../lib"
}
}

View File

@@ -0,0 +1,27 @@
using StellaOps.Scanner.Analyzers.Lang.Node;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Tests;
public sealed class NodeLanguageAnalyzerTests
{
[Fact]
public async Task WorkspaceFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "node", "workspaces");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new NodeLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
}

View File

@@ -0,0 +1,46 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.v3" Version="3.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Scanner.Analyzers.Lang.Tests\StellaOps.Scanner.Analyzers.Lang.Tests.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/StellaOps.Scanner.Analyzers.Lang.Node.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,110 @@
[
{
"analyzerId": "python",
"componentKey": "purl::pkg:pypi/layered@2.0",
"purl": "pkg:pypi/layered@2.0",
"name": "layered",
"version": "2.0",
"type": "pypi",
"usedByEntrypoint": true,
"metadata": {
"author": "Layered Maintainer",
"authorEmail": "maintainer@example.com",
"classifier[0]": "Programming Language :: Python :: 3",
"classifiers": "Programming Language :: Python :: 3",
"distInfoPath": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info",
"editable": "true",
"entryPoints.console_scripts": "layered-cli=layered.cli:main",
"entryPoints.layered.hooks": "register=layered.plugins:register",
"installer": "pip",
"license": "Apache-2.0",
"license.classifier[0]": "License :: OSI Approved :: Apache Software License",
"license.file[0]": "layer2/usr/lib/python3.11/site-packages/LICENSE",
"licenseExpression": "Apache-2.0",
"name": "layered",
"normalizedName": "layered",
"projectUrl": "Documentation, https://example.com/layered/docs",
"provenance": "dist-info",
"record.hashMismatches": "0",
"record.hashedEntries": "8",
"record.ioErrors": "0",
"record.missingFiles": "0",
"record.totalEntries": "9",
"requiresDist": "requests",
"requiresPython": "\u003E=3.9",
"sourceCommit": "abc123",
"sourceSubdirectory": "src/layered",
"sourceUrl": "https://git.example.com/layered",
"sourceVcs": "git",
"summary": "Base layer metadata",
"version": "2.0",
"wheel.generator": "pip 24.0",
"wheel.rootIsPurelib": "true",
"wheel.tags": "py3-none-any",
"wheel.version": "1.0"
},
"evidence": [
{
"kind": "file",
"source": "INSTALLER",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/INSTALLER"
},
{
"kind": "file",
"source": "INSTALLER",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/INSTALLER"
},
{
"kind": "file",
"source": "METADATA",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/METADATA"
},
{
"kind": "file",
"source": "METADATA",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/METADATA"
},
{
"kind": "file",
"source": "RECORD",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/RECORD"
},
{
"kind": "file",
"source": "RECORD",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/RECORD"
},
{
"kind": "file",
"source": "WHEEL",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/WHEEL"
},
{
"kind": "file",
"source": "WHEEL",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/WHEEL"
},
{
"kind": "file",
"source": "entry_points.txt",
"locator": "layer1/usr/lib/python3.11/site-packages/layered-2.0.dist-info/entry_points.txt"
},
{
"kind": "file",
"source": "entry_points.txt",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/entry_points.txt"
},
{
"kind": "file",
"source": "license",
"locator": "layer2/usr/lib/python3.11/site-packages/LICENSE"
},
{
"kind": "metadata",
"source": "direct_url.json",
"locator": "layer2/usr/lib/python3.11/site-packages/layered-2.0.dist-info/direct_url.json",
"value": "https://git.example.com/layered"
}
]
}
]

View File

@@ -0,0 +1,8 @@
Metadata-Version: 2.1
Name: layered
Version: 2.0
Summary: Base layer metadata
License: Apache-2.0
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Requires-Dist: requests

View File

@@ -0,0 +1,9 @@
layered/__init__.py,sha256=3Q3bv/BWCZLW2p2dVYw8QfAfBYV2YBtuYtT9TIJCmFM=,139
layered/core.py,sha256=izBXI4cRE/cgf7hgc/gHfTp1OyIIJ23NgUJXaHWVFpU=,80
layered/cli.py,sha256=xQrTznF7ch6C9qyQALJpsqRTIh9DVCRG4IXoQ1eLLnY=,126
../../../bin/layered-cli,sha256=6IGTGCEapolFoAUnXGNrWIfPSXU8W7bsZ07DYF/wmNc=,91
layered-2.0.dist-info/METADATA,sha256=jNEi7xsj4V+SSzOJJToxMoZmZ7gxyto7zuKxCjxUFjk=,193
layered-2.0.dist-info/WHEEL,sha256=m8MHT7vQnqC5W8H/y4uJdEpx9ijH0jJGpuoaxRbcsQg=,79
layered-2.0.dist-info/entry_points.txt,sha256=hm8bgJUYe2zoYNATyAsQzQKQTdQtFe4ctbf5kSlxFj0=,47
layered-2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ+UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg=,4
layered-2.0.dist-info/RECORD,,

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: pip 24.0
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,5 @@
"""Layered package demonstrating merged metadata across layers."""
from .core import get_version # noqa: F401
__all__ = ["get_version"]

View File

@@ -0,0 +1,7 @@
from __future__ import annotations
from .core import get_version
def main() -> None:
print(f"layered {get_version()}")

View File

@@ -0,0 +1,5 @@
from __future__ import annotations
def get_version() -> str:
return "2.0"

View File

@@ -0,0 +1,10 @@
Metadata-Version: 2.1
Name: layered
Version: 2.0
Summary: Overlay metadata adding direct URL information
License-Expression: Apache-2.0
License-File: LICENSE
Author: Layered Maintainer
Author-email: maintainer@example.com
Project-URL: Documentation, https://example.com/layered/docs
Classifier: License :: OSI Approved :: Apache Software License

View File

@@ -0,0 +1,9 @@
layered/plugins/__init__.py,sha256=hMd8TidtznWDaiA4biHZ04ZoVXcAc7z/p77bIdAsPyE=,53
layered/plugins/plugin.py,sha256=PBVqd9coVIzSBTQ2qdL5qxoK0fnsRZZ1DkhqnaVySPA=,87
LICENSE,sha256=cXKP+wQk9Jyqh8mUi7nURl9jOOjojDqrabZ119S2EzM=,27
layered-2.0.dist-info/METADATA,sha256=V09W93ILksWKLP8My6UatmScZ+5MCLiQ/5ieWzb585M=,346
layered-2.0.dist-info/WHEEL,sha256=m8MHT7vQnqC5W8H/y4uJdEpx9ijH0jJGpuoaxRbcsQg=,79
layered-2.0.dist-info/entry_points.txt,sha256=JYpkYczwozo6Ek7diDPgPj8ReYv5wTpaW0pFjL82bGU=,50
layered-2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ+UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg=,4
layered-2.0.dist-info/direct_url.json,sha256=8NtnZQZq2S5tcEn+P5fH6/EpABJ9+Ha5aIq8Sn2szig=,189
layered-2.0.dist-info/RECORD,,

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: pip 24.0
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,11 @@
{
"url": "https://git.example.com/layered",
"dir_info": {
"editable": true,
"subdirectory": "src/layered"
},
"vcs_info": {
"vcs": "git",
"commit_id": "abc123"
}
}

View File

@@ -0,0 +1,5 @@
from __future__ import annotations
def register() -> str:
return "layer2-plugin"

View File

@@ -0,0 +1,87 @@
[
{
"analyzerId": "python",
"componentKey": "purl::pkg:pypi/cache-pkg@1.2.3",
"purl": "pkg:pypi/cache-pkg@1.2.3",
"name": "Cache-Pkg",
"version": "1.2.3",
"type": "pypi",
"usedByEntrypoint": true,
"metadata": {
"classifier[0]": "Intended Audience :: Developers",
"classifier[1]": "License :: OSI Approved :: BSD License",
"classifier[2]": "Programming Language :: Python :: 3",
"classifiers": "Intended Audience :: Developers;License :: OSI Approved :: BSD License;Programming Language :: Python :: 3",
"distInfoPath": "lib/python3.11/site-packages/cache_pkg-1.2.3.dist-info",
"entryPoints.console_scripts": "cache-tool=cache_pkg:main",
"installer": "pip",
"license": "BSD-3-Clause",
"license.classifier[0]": "License :: OSI Approved :: BSD License",
"license.file[0]": "LICENSE",
"name": "Cache-Pkg",
"normalizedName": "cache-pkg",
"projectUrl": "Source, https://example.com/cache-pkg",
"provenance": "dist-info",
"record.hashMismatches": "1",
"record.hashedEntries": "9",
"record.ioErrors": "0",
"record.missingFiles": "2",
"record.totalEntries": "12",
"record.unsupportedAlgorithms": "md5",
"requiresDist": "click",
"requiresPython": "\u003E=3.8",
"summary": "Cache test package for hashed RECORD coverage",
"version": "1.2.3",
"wheel.generator": "pip 24.0",
"wheel.rootIsPurelib": "true",
"wheel.tags": "py3-none-any",
"wheel.version": "1.0"
},
"evidence": [
{
"kind": "derived",
"source": "RECORD",
"locator": "../etc/passwd",
"value": "outside-root"
},
{
"kind": "derived",
"source": "RECORD",
"locator": "lib/python3.11/site-packages/cache_pkg/LICENSE",
"value": "sha256 mismatch expected=pdUY6NGoyWCpcK8ThpBPUIVXbLvU9PzP8lsXEVEnFd0= actual=pdUY6NGoyWCpcK8ThpBPUIVXbLvU9PzP8lsXEVEnFdk=",
"sha256": "pdUY6NGoyWCpcK8ThpBPUIVXbLvU9PzP8lsXEVEnFdk="
},
{
"kind": "derived",
"source": "RECORD",
"locator": "lib/python3.11/site-packages/cache_pkg/missing/data.json",
"value": "missing"
},
{
"kind": "file",
"source": "INSTALLER",
"locator": "lib/python3.11/site-packages/cache_pkg-1.2.3.dist-info/INSTALLER"
},
{
"kind": "file",
"source": "METADATA",
"locator": "lib/python3.11/site-packages/cache_pkg-1.2.3.dist-info/METADATA"
},
{
"kind": "file",
"source": "RECORD",
"locator": "lib/python3.11/site-packages/cache_pkg-1.2.3.dist-info/RECORD"
},
{
"kind": "file",
"source": "WHEEL",
"locator": "lib/python3.11/site-packages/cache_pkg-1.2.3.dist-info/WHEEL"
},
{
"kind": "file",
"source": "entry_points.txt",
"locator": "lib/python3.11/site-packages/cache_pkg-1.2.3.dist-info/entry_points.txt"
}
]
}
]

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env python3
from cache_pkg import main
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,12 @@
Metadata-Version: 2.1
Name: Cache-Pkg
Version: 1.2.3
Summary: Cache test package for hashed RECORD coverage
License: BSD-3-Clause
License-File: LICENSE
Classifier: Programming Language :: Python :: 3
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Requires-Python: >=3.8
Requires-Dist: click
Project-URL: Source, https://example.com/cache-pkg

View File

@@ -0,0 +1,12 @@
cache_pkg/__init__.py,sha256=iw2XGXcGU2Si1KAQ7o82tPSKxwEFc6UNZfITpGPh7mM=,189
cache_pkg/data/config.json,sha256=Oa/wlgi1qJHC93RF5vwoOFffyviGtm5ccL7lrI0gkeY=,49
cache_pkg/LICENSE,sha256=pdUY6NGoyWCpcK8ThpBPUIVXbLvU9PzP8lsXEVEnFd0=,73
cache_pkg/md5only.txt,md5=Zm9v,4
cache_pkg-1.2.3.data/scripts/cache-tool,sha256=2rsv/gnYOtlJZCy75Wz0rCADxYPnQAkyKvNbuoquZQ4=,89
cache_pkg-1.2.3.dist-info/METADATA,sha256=DXPSItxOR1kPkVzjyq8F50jf8FOR9brSs/TGcZmcEHo=,390
cache_pkg-1.2.3.dist-info/WHEEL,sha256=m8MHT7vQnqC5W8H/y4uJdEpx9ijH0jJGpuoaxRbcsQg=,79
cache_pkg-1.2.3.dist-info/entry_points.txt,sha256=S1tGoBGlzWL6jeECCTw1pZP09HM8voEm/qQ7DtOHPyc=,44
cache_pkg-1.2.3.dist-info/INSTALLER,sha256=zuuue4knoyJ+UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg=,4
cache_pkg-1.2.3.dist-info/RECORD,,
cache_pkg/missing/data.json,sha256=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,12
../../../../etc/passwd,,

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: pip 24.0
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,4 @@
BSD 3-Clause License
Copyright (c) 2025, StellaOps
All rights reserved.

View File

@@ -0,0 +1,7 @@
"""Cache fixture package for determinism tests."""
from .data import config # noqa: F401
def main() -> None:
"""Entry point used by console script."""
print("cache-pkg running")

View File

@@ -0,0 +1,85 @@
[
{
"analyzerId": "python",
"componentKey": "purl::pkg:pypi/simple@1.0.0",
"purl": "pkg:pypi/simple@1.0.0",
"name": "simple",
"version": "1.0.0",
"type": "pypi",
"usedByEntrypoint": true,
"metadata": {
"author": "Example Dev",
"authorEmail": "dev@example.com",
"classifier[0]": "License :: OSI Approved :: Apache Software License",
"classifier[1]": "Programming Language :: Python :: 3",
"classifiers": "License :: OSI Approved :: Apache Software License;Programming Language :: Python :: 3",
"distInfoPath": "lib/python3.11/site-packages/simple-1.0.0.dist-info",
"editable": "true",
"entryPoints.console_scripts": "simple-tool=simple.core:main",
"homePage": "https://example.com/simple",
"installer": "pip",
"license": "Apache-2.0",
"license.classifier[0]": "License :: OSI Approved :: Apache Software License",
"name": "simple",
"normalizedName": "simple",
"projectUrl": "Source, https://example.com/simple/src",
"provenance": "dist-info",
"record.hashMismatches": "0",
"record.hashedEntries": "8",
"record.ioErrors": "0",
"record.missingFiles": "1",
"record.totalEntries": "10",
"requiresDist": "requests (\u003E=2.0)",
"requiresPython": "\u003E=3.9",
"sourceCommit": "abc123def",
"sourceSubdirectory": "src/simple",
"sourceUrl": "https://example.com/simple-1.0.0.tar.gz",
"sourceVcs": "git",
"summary": "Simple fixture package",
"version": "1.0.0",
"wheel.generator": "pip 24.0",
"wheel.rootIsPurelib": "true",
"wheel.tags": "py3-none-any",
"wheel.version": "1.0"
},
"evidence": [
{
"kind": "derived",
"source": "RECORD",
"locator": "bin/simple-tool",
"value": "missing"
},
{
"kind": "file",
"source": "INSTALLER",
"locator": "lib/python3.11/site-packages/simple-1.0.0.dist-info/INSTALLER"
},
{
"kind": "file",
"source": "METADATA",
"locator": "lib/python3.11/site-packages/simple-1.0.0.dist-info/METADATA"
},
{
"kind": "file",
"source": "RECORD",
"locator": "lib/python3.11/site-packages/simple-1.0.0.dist-info/RECORD"
},
{
"kind": "file",
"source": "WHEEL",
"locator": "lib/python3.11/site-packages/simple-1.0.0.dist-info/WHEEL"
},
{
"kind": "file",
"source": "entry_points.txt",
"locator": "lib/python3.11/site-packages/simple-1.0.0.dist-info/entry_points.txt"
},
{
"kind": "metadata",
"source": "direct_url.json",
"locator": "lib/python3.11/site-packages/simple-1.0.0.dist-info/direct_url.json",
"value": "https://example.com/simple-1.0.0.tar.gz"
}
]
}
]

View File

@@ -0,0 +1,13 @@
Metadata-Version: 2.1
Name: simple
Version: 1.0.0
Summary: Simple fixture package
Home-page: https://example.com/simple
Author: Example Dev
Author-email: dev@example.com
License: Apache-2.0
Project-URL: Source, https://example.com/simple/src
Requires-Python: >=3.9
Requires-Dist: requests (>=2.0)
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License

View File

@@ -0,0 +1,10 @@
simple/__init__.py,sha256=03NWG/tm5eky+tnGlynp/vcyjtR944EtQMKtwrutl/U=,79
simple/__main__.py,sha256=7pHsIZX9uNTyp1e1AkmZ1vXZxw/dYB1TbR1rYDJca6c=,62
simple/core.py,sha256=8HaF+vPTo2roSP7kivqePnjG+d/WqotH269Qey/BM+s=,67
../../../bin/simple-tool,sha256=E7eVnffg2E4646m1Ml/5ixyROcpc24GJvy03sEkg6DA=,91
simple-1.0.0.dist-info/METADATA,sha256=Da/AG+nYa85WfbUSNmmRjpTeEEM8Kinf6Z197xb8X2o=,408
simple-1.0.0.dist-info/WHEEL,sha256=m8MHT7vQnqC5W8H/y4uJdEpx9ijH0jJGpuoaxRbcsQg=,79
simple-1.0.0.dist-info/entry_points.txt,sha256=A2WCkblioa0YbdUDurLzv+sIbx7TRSJ9zfBLYMYwpBQ=,49
simple-1.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ+UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg=,4
simple-1.0.0.dist-info/direct_url.json,sha256=EXd4Xj5iohEIqiF7mlR7sCLGhqXiU1/LuOPjijstKCU=,199
simple-1.0.0.dist-info/RECORD,,

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: pip 24.0
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,11 @@
{
"url": "https://example.com/simple-1.0.0.tar.gz",
"dir_info": {
"editable": true,
"subdirectory": "src/simple"
},
"vcs_info": {
"vcs": "git",
"commit_id": "abc123def"
}
}

View File

@@ -0,0 +1,4 @@
__all__ = ["main"]
__version__ = "1.0.0"
from .core import main # noqa: F401

View File

@@ -0,0 +1,4 @@
from .core import main
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,4 @@
import sys
def main() -> None:
print("simple core", sys.argv)

View File

@@ -0,0 +1,83 @@
using StellaOps.Scanner.Analyzers.Lang.Python;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Python.Tests;
public sealed class PythonLanguageAnalyzerTests
{
[Fact]
public async Task SimpleVenvFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "python", "simple-venv");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "bin", "simple-tool")
});
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints);
}
[Fact]
public async Task PipCacheFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "python", "pip-cache");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "lib", "python3.11", "site-packages", "cache_pkg-1.2.3.data", "scripts", "cache-tool")
});
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints);
}
[Fact]
public async Task LayeredEditableFixtureMergesAcrossLayersAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "python", "layered-editable");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "layer1", "usr", "bin", "layered-cli")
});
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints);
}
}

View File

@@ -0,0 +1,46 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.v3" Version="3.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Scanner.Analyzers.Lang.Tests\StellaOps.Scanner.Analyzers.Lang.Tests.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Python/StellaOps.Scanner.Analyzers.Lang.Python.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,88 @@
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Core;
public sealed class LanguageAnalyzerResultTests
{
[Fact]
public async Task MergesDuplicateComponentsDeterministicallyAsync()
{
var analyzer = new DuplicateComponentAnalyzer();
var engine = new LanguageAnalyzerEngine(new[] { analyzer });
var root = TestPaths.CreateTemporaryDirectory();
try
{
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var result = await engine.AnalyzeAsync(context, CancellationToken.None);
var component = Assert.Single(result.Components);
Assert.Equal("purl::pkg:example/acme@2.0.0", component.ComponentKey);
Assert.Equal("pkg:example/acme@2.0.0", component.Purl);
Assert.True(component.UsedByEntrypoint);
Assert.Equal(2, component.Evidence.Count);
Assert.Equal(3, component.Metadata.Count);
// Metadata retains stable ordering (sorted by key)
var keys = component.Metadata.Keys.ToArray();
Assert.Equal(new[] { "artifactId", "groupId", "path" }, keys);
// Evidence de-duplicates via comparison key
Assert.Equal(2, component.Evidence.Count);
}
finally
{
TestPaths.SafeDelete(root);
}
}
private sealed class DuplicateComponentAnalyzer : ILanguageAnalyzer
{
public string Id => "duplicate";
public string DisplayName => "Duplicate Analyzer";
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
{
await Task.Yield();
var metadataA = new[]
{
new KeyValuePair<string, string?>("groupId", "example"),
new KeyValuePair<string, string?>("artifactId", "acme")
};
var metadataB = new[]
{
new KeyValuePair<string, string?>("artifactId", "acme"),
new KeyValuePair<string, string?>("path", ".")
};
var evidence = new[]
{
new LanguageComponentEvidence(LanguageEvidenceKind.File, "manifest", "META-INF/MANIFEST.MF", null, null),
new LanguageComponentEvidence(LanguageEvidenceKind.Metadata, "pom", "pom.xml", "groupId=example", null)
};
writer.AddFromPurl(
analyzerId: Id,
purl: "pkg:example/acme@2.0.0",
name: "acme",
version: "2.0.0",
type: "example",
metadata: metadataA,
evidence: evidence,
usedByEntrypoint: true);
// duplicate insert with different metadata ordering
writer.AddFromPurl(
analyzerId: Id,
purl: "pkg:example/acme@2.0.0",
name: "acme",
version: "2.0.0",
type: "example",
metadata: metadataB,
evidence: evidence,
usedByEntrypoint: false);
}
}
}

View File

@@ -0,0 +1,70 @@
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Core;
public sealed class LanguageComponentMapperTests
{
[Fact]
public void ToComponentRecordsProjectsDeterministicComponents()
{
// Arrange
var analyzerId = "node";
var records = new[]
{
LanguageComponentRecord.FromPurl(
analyzerId: analyzerId,
purl: "pkg:npm/example@1.0.0",
name: "example",
version: "1.0.0",
type: "npm",
metadata: new Dictionary<string, string?>()
{
["path"] = "packages/app",
["license"] = "MIT"
},
evidence: new[]
{
new LanguageComponentEvidence(LanguageEvidenceKind.File, "package.json", "packages/app/package.json", null, "abc123")
},
usedByEntrypoint: true),
LanguageComponentRecord.FromExplicitKey(
analyzerId: analyzerId,
componentKey: "bin::sha256:deadbeef",
purl: null,
name: "app-binary",
version: null,
type: "binary",
metadata: new Dictionary<string, string?>()
{
["description"] = "Utility binary"
},
evidence: new[]
{
new LanguageComponentEvidence(LanguageEvidenceKind.Derived, "entrypoint", "/usr/local/bin/app", "ENTRYPOINT", null)
})
};
// Act
var layerDigest = LanguageComponentMapper.ComputeLayerDigest(analyzerId);
var results = LanguageComponentMapper.ToComponentRecords(analyzerId, records, layerDigest);
// Assert
Assert.Equal(2, results.Length);
Assert.All(results, component => Assert.Equal(layerDigest, component.LayerDigest));
var first = results[0];
Assert.Equal("bin::sha256:deadbeef", first.Identity.Key);
Assert.Equal("Utility binary", first.Metadata!.Properties!["stellaops.lang.meta.description"]);
Assert.Equal("derived", first.Evidence.Single().Kind);
var second = results[1];
Assert.Equal("pkg:npm/example@1.0.0", second.Identity.Key); // prefix removed
Assert.True(second.Usage.UsedByEntrypoint);
Assert.Contains("MIT", second.Metadata!.Licenses!);
Assert.Equal("packages/app", second.Metadata.Properties!["stellaops.lang.meta.path"]);
Assert.Equal("abc123", second.Metadata.Properties!["stellaops.lang.evidence.0.sha256"]);
Assert.Equal("file", second.Evidence.Single().Kind);
Assert.Equal("packages/app/package.json", second.Evidence.Single().Value);
Assert.Equal("package.json", second.Evidence.Single().Source);
}
}

View File

@@ -0,0 +1,102 @@
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Determinism;
public sealed class LanguageAnalyzerHarnessTests
{
[Fact]
public async Task HarnessProducesDeterministicOutputAsync()
{
var fixturePath = TestPaths.ResolveFixture("determinism", "basic", "input");
var goldenPath = TestPaths.ResolveFixture("determinism", "basic", "expected.json");
var cancellationToken = TestContext.Current.CancellationToken;
var analyzers = new ILanguageAnalyzer[]
{
new FakeLanguageAnalyzer(
"fake-java",
LanguageComponentRecord.FromPurl(
analyzerId: "fake-java",
purl: "pkg:maven/org.example/example-lib@1.2.3",
name: "example-lib",
version: "1.2.3",
type: "maven",
metadata: new Dictionary<string, string?>
{
["groupId"] = "org.example",
["artifactId"] = "example-lib",
},
evidence: new []
{
new LanguageComponentEvidence(LanguageEvidenceKind.File, "pom.properties", "META-INF/maven/org.example/example-lib/pom.properties", null, "abc123"),
}),
LanguageComponentRecord.FromExplicitKey(
analyzerId: "fake-java",
componentKey: "bin::sha256:deadbeef",
purl: null,
name: "example-cli",
version: null,
type: "bin",
metadata: new Dictionary<string, string?>
{
["sha256"] = "deadbeef",
},
evidence: new []
{
new LanguageComponentEvidence(LanguageEvidenceKind.File, "binary", "usr/local/bin/example", null, "deadbeef"),
})),
new FakeLanguageAnalyzer(
"fake-node",
LanguageComponentRecord.FromPurl(
analyzerId: "fake-node",
purl: "pkg:npm/example-package@4.5.6",
name: "example-package",
version: "4.5.6",
type: "npm",
metadata: new Dictionary<string, string?>
{
["workspace"] = "packages/example",
},
evidence: new []
{
new LanguageComponentEvidence(LanguageEvidenceKind.File, "package.json", "packages/example/package.json", null, null),
},
usedByEntrypoint: true)),
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(fixturePath, goldenPath, analyzers, cancellationToken);
var first = await LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken);
var second = await LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken);
Assert.Equal(first, second);
}
private sealed class FakeLanguageAnalyzer : ILanguageAnalyzer
{
private readonly IReadOnlyList<LanguageComponentRecord> _components;
public FakeLanguageAnalyzer(string id, params LanguageComponentRecord[] components)
{
Id = id;
DisplayName = id;
_components = components ?? Array.Empty<LanguageComponentRecord>();
}
public string Id { get; }
public string DisplayName { get; }
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
{
await Task.Delay(5, cancellationToken).ConfigureAwait(false); // ensure asynchrony is handled
// Intentionally add in reverse order to prove determinism.
foreach (var component in _components.Reverse())
{
writer.Add(component);
}
}
}
}

View File

@@ -0,0 +1,179 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scanner.Analyzers.Lang.DotNet;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Tests.DotNet;
public sealed class DotNetLanguageAnalyzerTests
{
[Fact]
public async Task SimpleFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "simple");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task SignedFixtureCapturesAssemblyMetadataAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "signed");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
var inspector = new StubAuthenticodeInspector();
var services = new SingleServiceProvider(inspector);
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints: null,
services: services);
}
[Fact]
public async Task SelfContainedFixtureHandlesNativeAssetsAndUsageAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "selfcontained");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "lib", "net10.0", "StellaOps.Toolkit.dll"),
Path.Combine(fixturePath, "runtimes", "linux-x64", "native", "libstellaopsnative.so")
});
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints);
}
[Fact]
public async Task AnalyzerIsThreadSafeUnderConcurrencyAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "selfcontained");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
var workers = Math.Max(Environment.ProcessorCount, 4);
var tasks = Enumerable.Range(0, workers)
.Select(_ => LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken));
var results = await Task.WhenAll(tasks);
var first = results[0];
foreach (var result in results)
{
Assert.Equal(first, result);
}
}
[Fact]
public async Task MultiFixtureMergesRuntimeMetadataAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "multi");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.True(root.ValueKind == JsonValueKind.Array, "Result root should be an array.");
Assert.Equal(2, root.GetArrayLength());
var loggingComponent = root.EnumerateArray()
.First(element => element.GetProperty("name").GetString() == "StellaOps.Logging");
var metadata = loggingComponent.GetProperty("metadata");
Assert.Equal("StellaOps.Logging", loggingComponent.GetProperty("name").GetString());
Assert.Equal("2.5.1", loggingComponent.GetProperty("version").GetString());
Assert.Equal("pkg:nuget/stellaops.logging@2.5.1", loggingComponent.GetProperty("purl").GetString());
var ridValues = metadata.EnumerateObject()
.Where(property => property.Name.Contains(".rid", StringComparison.Ordinal))
.Select(property => property.Value.GetString())
.Where(value => !string.IsNullOrEmpty(value))
.Select(value => value!)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
Assert.Contains("linux-x64", ridValues);
Assert.Contains("osx-arm64", ridValues);
Assert.Contains("win-arm64", ridValues);
}
private sealed class StubAuthenticodeInspector : IDotNetAuthenticodeInspector
{
public DotNetAuthenticodeMetadata? TryInspect(string assemblyPath, CancellationToken cancellationToken)
=> new DotNetAuthenticodeMetadata(
Subject: "CN=StellaOps Test Signing",
Issuer: "CN=StellaOps Root",
NotBefore: new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
NotAfter: new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero),
Thumbprint: "AA11BB22CC33DD44EE55FF66GG77HH88II99JJ00",
SerialNumber: "0123456789ABCDEF");
}
private sealed class SingleServiceProvider : IServiceProvider
{
private readonly object _service;
public SingleServiceProvider(object service)
{
_service = service;
}
public object? GetService(Type serviceType)
=> serviceType == typeof(IDotNetAuthenticodeInspector) ? _service : null;
}
}

View File

@@ -0,0 +1,60 @@
[
{
"analyzerId": "fake-java",
"componentKey": "bin::sha256:deadbeef",
"name": "example-cli",
"type": "bin",
"usedByEntrypoint": false,
"metadata": {
"sha256": "deadbeef"
},
"evidence": [
{
"kind": "file",
"source": "binary",
"locator": "usr/local/bin/example",
"sha256": "deadbeef"
}
]
},
{
"analyzerId": "fake-java",
"componentKey": "purl::pkg:maven/org.example/example-lib@1.2.3",
"purl": "pkg:maven/org.example/example-lib@1.2.3",
"name": "example-lib",
"version": "1.2.3",
"type": "maven",
"usedByEntrypoint": false,
"metadata": {
"artifactId": "example-lib",
"groupId": "org.example"
},
"evidence": [
{
"kind": "file",
"source": "pom.properties",
"locator": "META-INF/maven/org.example/example-lib/pom.properties",
"sha256": "abc123"
}
]
},
{
"analyzerId": "fake-node",
"componentKey": "purl::pkg:npm/example-package@4.5.6",
"purl": "pkg:npm/example-package@4.5.6",
"name": "example-package",
"version": "4.5.6",
"type": "npm",
"usedByEntrypoint": true,
"metadata": {
"workspace": "packages/example"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": "packages/example/package.json"
}
]
}
]

View File

@@ -0,0 +1,84 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0/osx-arm64"
},
"targets": {
".NETCoreApp,Version=v10.0": {
"AppA/2.0.0": {
"dependencies": {
"StellaOps.Toolkit": "1.2.3",
"StellaOps.Logging": "2.5.1"
}
},
"StellaOps.Toolkit/1.2.3": {
"dependencies": {
"StellaOps.Logging": "2.5.1"
},
"runtime": {
"lib/net10.0/StellaOps.Toolkit.dll": {
"assemblyVersion": "1.2.3.0",
"fileVersion": "1.2.3.0"
}
}
},
"StellaOps.Logging/2.5.1": {
"runtime": {
"lib/net10.0/StellaOps.Logging.dll": {
"assemblyVersion": "2.5.1.0",
"fileVersion": "2.5.1.12345"
}
}
}
},
".NETCoreApp,Version=v10.0/linux-x64": {
"StellaOps.Toolkit/1.2.3": {
"runtimeTargets": {
"runtimes/linux-x64/native/libstellaops.toolkit.so": {
"rid": "linux-x64",
"assetType": "native"
}
}
},
"StellaOps.Logging/2.5.1": {
"runtime": {
"runtimes/linux-x64/lib/net10.0/StellaOps.Logging.dll": {}
}
}
},
".NETCoreApp,Version=v10.0/osx-arm64": {
"StellaOps.Toolkit/1.2.3": {
"runtimeTargets": {
"runtimes/osx-arm64/native/libstellaops.toolkit.dylib": {
"rid": "osx-arm64",
"assetType": "native"
}
}
},
"StellaOps.Logging/2.5.1": {
"runtime": {
"runtimes/osx-arm64/lib/net10.0/StellaOps.Logging.dll": {}
}
}
}
},
"libraries": {
"AppA/2.0.0": {
"type": "project",
"serviceable": false
},
"StellaOps.Toolkit/1.2.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_TOOLKIT_SHA==",
"path": "stellaops.toolkit/1.2.3",
"hashPath": "stellaops.toolkit.1.2.3.nupkg.sha512"
},
"StellaOps.Logging/2.5.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_LOGGING_SHA==",
"path": "stellaops.logging/2.5.1",
"hashPath": "stellaops.logging.2.5.1.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,39 @@
{
"runtimeOptions": {
"tfm": "net10.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "10.0.1"
},
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "10.0.1"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "10.0.0"
},
{
"name": "StellaOps.Hosting",
"version": "2.0.0"
}
],
"runtimeGraph": {
"runtimes": {
"osx-arm64": {
"fallbacks": [
"osx",
"unix"
]
},
"linux-x64": {
"fallbacks": [
"linux",
"unix"
]
}
}
}
}
}

View File

@@ -0,0 +1,76 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0/win-arm64"
},
"targets": {
".NETCoreApp,Version=v10.0": {
"AppB/3.1.0": {
"dependencies": {
"StellaOps.Toolkit": "1.2.3",
"StellaOps.Logging": "2.5.1"
}
},
"StellaOps.Toolkit/1.2.3": {
"runtime": {
"lib/net10.0/StellaOps.Toolkit.dll": {
"assemblyVersion": "1.2.3.0",
"fileVersion": "1.2.3.0"
}
}
},
"StellaOps.Logging/2.5.1": {
"runtime": {
"lib/net10.0/StellaOps.Logging.dll": {
"assemblyVersion": "2.5.1.0",
"fileVersion": "2.5.1.12345"
}
}
}
},
".NETCoreApp,Version=v10.0/win-arm64": {
"StellaOps.Toolkit/1.2.3": {
"runtimeTargets": {
"runtimes/win-arm64/native/stellaops.toolkit.dll": {
"rid": "win-arm64",
"assetType": "native"
}
}
},
"StellaOps.Logging/2.5.1": {
"runtimeTargets": {
"runtimes/win-arm64/native/stellaops.logging.dll": {
"rid": "win-arm64",
"assetType": "native"
}
}
}
},
".NETCoreApp,Version=v10.0/linux-arm64": {
"StellaOps.Logging/2.5.1": {
"runtime": {
"runtimes/linux-arm64/lib/net10.0/StellaOps.Logging.dll": {}
}
}
}
},
"libraries": {
"AppB/3.1.0": {
"type": "project",
"serviceable": false
},
"StellaOps.Toolkit/1.2.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_TOOLKIT_SHA==",
"path": "stellaops.toolkit/1.2.3",
"hashPath": "stellaops.toolkit.1.2.3.nupkg.sha512"
},
"StellaOps.Logging/2.5.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_LOGGING_SHA==",
"path": "stellaops.logging/2.5.1",
"hashPath": "stellaops.logging.2.5.1.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,38 @@
{
"runtimeOptions": {
"tfm": "net10.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
{
"name": "Microsoft.WindowsDesktop.App",
"version": "10.0.0"
}
],
"additionalProbingPaths": [
"C:/Users/runner/.nuget/packages"
],
"runtimeGraph": {
"runtimes": {
"win-arm64": {
"fallbacks": [
"win",
"any"
]
},
"linux-arm64": {
"fallbacks": [
"linux",
"unix"
]
}
}
}
}
}

View File

@@ -0,0 +1,120 @@
[
{
"analyzerId": "dotnet",
"componentKey": "purl::pkg:nuget/stellaops.logging@2.5.1",
"purl": "pkg:nuget/stellaops.logging@2.5.1",
"name": "StellaOps.Logging",
"version": "2.5.1",
"type": "nuget",
"usedByEntrypoint": false,
"metadata": {
"assembly[0].assetPath": "lib/net10.0/StellaOps.Logging.dll",
"assembly[0].fileVersion": "2.5.1.12345",
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[0].version": "2.5.1.0",
"assembly[1].assetPath": "runtimes/linux-arm64/lib/net10.0/StellaOps.Logging.dll",
"assembly[1].rid[0]": "linux-arm64",
"assembly[1].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[2].assetPath": "runtimes/linux-x64/lib/net10.0/StellaOps.Logging.dll",
"assembly[2].rid[0]": "linux-x64",
"assembly[2].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[3].assetPath": "runtimes/osx-arm64/lib/net10.0/StellaOps.Logging.dll",
"assembly[3].rid[0]": "osx-arm64",
"assembly[3].tfm[0]": ".NETCoreApp,Version=v10.0",
"deps.path[0]": "AppA.deps.json",
"deps.path[1]": "AppB.deps.json",
"deps.rid[0]": "linux-arm64",
"deps.rid[1]": "linux-x64",
"deps.rid[2]": "osx-arm64",
"deps.rid[3]": "win-arm64",
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
"license.expression[0]": "Apache-2.0",
"native[0].assetPath": "runtimes/win-arm64/native/stellaops.logging.dll",
"native[0].rid[0]": "win-arm64",
"native[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"package.hashPath[0]": "stellaops.logging.2.5.1.nupkg.sha512",
"package.id": "StellaOps.Logging",
"package.id.normalized": "stellaops.logging",
"package.path[0]": "stellaops.logging/2.5.1",
"package.serviceable": "true",
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
"package.version": "2.5.1",
"provenance": "manifest"
},
"evidence": [
{
"kind": "file",
"source": "deps.json",
"locator": "AppA.deps.json",
"value": "StellaOps.Logging/2.5.1"
},
{
"kind": "file",
"source": "deps.json",
"locator": "AppB.deps.json",
"value": "StellaOps.Logging/2.5.1"
}
]
},
{
"analyzerId": "dotnet",
"componentKey": "purl::pkg:nuget/stellaops.toolkit@1.2.3",
"purl": "pkg:nuget/stellaops.toolkit@1.2.3",
"name": "StellaOps.Toolkit",
"version": "1.2.3",
"type": "nuget",
"usedByEntrypoint": false,
"metadata": {
"assembly[0].assetPath": "lib/net10.0/StellaOps.Toolkit.dll",
"assembly[0].fileVersion": "1.2.3.0",
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[0].version": "1.2.3.0",
"deps.dependency[0]": "stellaops.logging",
"deps.path[0]": "AppA.deps.json",
"deps.path[1]": "AppB.deps.json",
"deps.rid[0]": "linux-x64",
"deps.rid[1]": "osx-arm64",
"deps.rid[2]": "win-arm64",
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
"license.file.sha256[0]": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c",
"license.file[0]": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
"native[0].assetPath": "runtimes/linux-x64/native/libstellaops.toolkit.so",
"native[0].rid[0]": "linux-x64",
"native[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"native[1].assetPath": "runtimes/osx-arm64/native/libstellaops.toolkit.dylib",
"native[1].rid[0]": "osx-arm64",
"native[1].tfm[0]": ".NETCoreApp,Version=v10.0",
"native[2].assetPath": "runtimes/win-arm64/native/stellaops.toolkit.dll",
"native[2].rid[0]": "win-arm64",
"native[2].tfm[0]": ".NETCoreApp,Version=v10.0",
"package.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512",
"package.id": "StellaOps.Toolkit",
"package.id.normalized": "stellaops.toolkit",
"package.path[0]": "stellaops.toolkit/1.2.3",
"package.serviceable": "true",
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
"package.version": "1.2.3",
"provenance": "manifest"
},
"evidence": [
{
"kind": "file",
"source": "deps.json",
"locator": "AppA.deps.json",
"value": "StellaOps.Toolkit/1.2.3"
},
{
"kind": "file",
"source": "deps.json",
"locator": "AppB.deps.json",
"value": "StellaOps.Toolkit/1.2.3"
},
{
"kind": "file",
"source": "license",
"locator": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
"sha256": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c"
}
]
}
]

View File

@@ -0,0 +1,15 @@
StellaOps Logging
Copyright (c) 2025 StellaOps.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>StellaOps.Logging</id>
<version>2.5.1</version>
<authors>StellaOps</authors>
<description>Logging sample package for analyzer fixtures.</description>
<license type="expression">Apache-2.0</license>
<licenseUrl>https://stella-ops.example/licenses/logging</licenseUrl>
<projectUrl>https://stella-ops.example/projects/logging</projectUrl>
</metadata>
</package>

View File

@@ -0,0 +1,7 @@
StellaOps Toolkit License
=========================
This sample license is provided for test fixtures only.
Permission is granted to use, copy, modify, and distribute this fixture
for the purpose of automated testing.

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>StellaOps.Toolkit</id>
<version>1.2.3</version>
<authors>StellaOps</authors>
<description>Toolkit sample package for analyzer fixtures.</description>
<license type="file">LICENSE.txt</license>
<licenseUrl>https://stella-ops.example/licenses/toolkit</licenseUrl>
</metadata>
</package>

View File

@@ -0,0 +1,85 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0",
"signature": null
},
"targets": {
".NETCoreApp,Version=v10.0/linux-x64": {
"MyApp/1.0.0": {
"dependencies": {
"StellaOps.Toolkit": "1.2.3",
"StellaOps.Runtime.SelfContained": "2.1.0"
},
"runtime": {
"MyApp.dll": {}
}
},
"StellaOps.Toolkit/1.2.3": {
"runtime": {
"lib/net10.0/StellaOps.Toolkit.dll": {
"assemblyVersion": "1.2.3.0",
"fileVersion": "1.2.3.0"
}
}
},
"StellaOps.Runtime.SelfContained/2.1.0": {
"runtimeTargets": {
"runtimes/linux-x64/native/libstellaopsnative.so": {
"rid": "linux-x64",
"assetType": "native"
}
}
}
},
".NETCoreApp,Version=v10.0/win-x64": {
"MyApp/1.0.0": {
"dependencies": {
"StellaOps.Toolkit": "1.2.3",
"StellaOps.Runtime.SelfContained": "2.1.0"
},
"runtime": {
"MyApp.dll": {}
}
},
"StellaOps.Toolkit/1.2.3": {
"runtime": {
"lib/net10.0/StellaOps.Toolkit.dll": {
"assemblyVersion": "1.2.3.0",
"fileVersion": "1.2.3.0"
}
}
},
"StellaOps.Runtime.SelfContained/2.1.0": {
"runtimeTargets": {
"runtimes/win-x64/native/stellaopsnative.dll": {
"rid": "win-x64",
"assetType": "native"
}
}
}
}
},
"libraries": {
"MyApp/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": "",
"path": null,
"hashPath": null
},
"StellaOps.Toolkit/1.2.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_TOOLKIT_SHA==",
"path": "stellaops.toolkit/1.2.3",
"hashPath": "stellaops.toolkit.1.2.3.nupkg.sha512"
},
"StellaOps.Runtime.SelfContained/2.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_RUNTIME_SHA==",
"path": "stellaops.runtime.selfcontained/2.1.0",
"hashPath": "stellaops.runtime.selfcontained.2.1.0.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,15 @@
{
"runtimeOptions": {
"tfm": "net10.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
"includedFrameworks": [
{
"name": "Microsoft.NETCore.DotNetAppHost",
"version": "10.0.0"
}
]
}
}

View File

@@ -0,0 +1,94 @@
[
{
"analyzerId": "dotnet",
"componentKey": "purl::pkg:nuget/stellaops.runtime.selfcontained@2.1.0",
"purl": "pkg:nuget/stellaops.runtime.selfcontained@2.1.0",
"name": "StellaOps.Runtime.SelfContained",
"version": "2.1.0",
"type": "nuget",
"usedByEntrypoint": true,
"metadata": {
"deps.path[0]": "MyApp.deps.json",
"deps.rid[0]": "linux-x64",
"deps.rid[1]": "win-x64",
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
"license.expression[0]": "Apache-2.0",
"native[0].assetPath": "runtimes/linux-x64/native/libstellaopsnative.so",
"native[0].path": "runtimes/linux-x64/native/libstellaopsnative.so",
"native[0].rid[0]": "linux-x64",
"native[0].sha256": "6cf3d2a487d6a42fc7c3e2edbc452224e99a3656287a534f1164ee6ec9daadf0",
"native[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"native[1].assetPath": "runtimes/win-x64/native/stellaopsnative.dll",
"native[1].rid[0]": "win-x64",
"native[1].tfm[0]": ".NETCoreApp,Version=v10.0",
"package.hashPath[0]": "stellaops.runtime.selfcontained.2.1.0.nupkg.sha512",
"package.id": "StellaOps.Runtime.SelfContained",
"package.id.normalized": "stellaops.runtime.selfcontained",
"package.path[0]": "stellaops.runtime.selfcontained/2.1.0",
"package.serviceable": "true",
"package.sha512[0]": "sha512-FAKE_RUNTIME_SHA==",
"package.version": "2.1.0",
"provenance": "manifest"
},
"evidence": [
{
"kind": "file",
"source": "deps.json",
"locator": "MyApp.deps.json",
"value": "StellaOps.Runtime.SelfContained/2.1.0"
},
{
"kind": "file",
"source": "native",
"locator": "runtimes/linux-x64/native/libstellaopsnative.so",
"value": "runtimes/linux-x64/native/libstellaopsnative.so",
"sha256": "6cf3d2a487d6a42fc7c3e2edbc452224e99a3656287a534f1164ee6ec9daadf0"
}
]
},
{
"analyzerId": "dotnet",
"componentKey": "purl::pkg:nuget/stellaops.toolkit@1.2.3",
"purl": "pkg:nuget/stellaops.toolkit@1.2.3",
"name": "StellaOps.Toolkit",
"version": "1.2.3",
"type": "nuget",
"usedByEntrypoint": false,
"metadata": {
"assembly[0].assetPath": "lib/net10.0/StellaOps.Toolkit.dll",
"assembly[0].fileVersion": "1.2.3.0",
"assembly[0].rid[0]": "linux-x64",
"assembly[0].rid[1]": "win-x64",
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[0].version": "1.2.3.0",
"deps.path[0]": "MyApp.deps.json",
"deps.rid[0]": "linux-x64",
"deps.rid[1]": "win-x64",
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
"license.file.sha256[0]": "f94d89a576c63e8ba6ee01760c52fa7861ba609491d7c6e6c01ead5ca66b6048",
"license.file[0]": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
"package.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512",
"package.id": "StellaOps.Toolkit",
"package.id.normalized": "stellaops.toolkit",
"package.path[0]": "stellaops.toolkit/1.2.3",
"package.serviceable": "true",
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
"package.version": "1.2.3",
"provenance": "manifest"
},
"evidence": [
{
"kind": "file",
"source": "deps.json",
"locator": "MyApp.deps.json",
"value": "StellaOps.Toolkit/1.2.3"
},
{
"kind": "file",
"source": "license",
"locator": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
"sha256": "f94d89a576c63e8ba6ee01760c52fa7861ba609491d7c6e6c01ead5ca66b6048"
}
]
}
]

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>StellaOps.Runtime.SelfContained</id>
<version>2.1.0</version>
<authors>StellaOps</authors>
<description>Runtime bundle used for self-contained analyzer fixtures.</description>
<license type="expression">Apache-2.0</license>
<licenseUrl>https://stella-ops.example/licenses/runtime</licenseUrl>
</metadata>
</package>

View File

@@ -0,0 +1,6 @@
StellaOps Toolkit License
=========================
Reusable toolkit licensing terms for analyzer fixtures.
This document is intentionally short for deterministic hashing tests.

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>StellaOps.Toolkit</id>
<version>1.2.3</version>
<authors>StellaOps</authors>
<description>Toolkit package for self-contained analyzer fixtures.</description>
<license type="file">LICENSE.txt</license>
<licenseUrl>https://stella-ops.example/licenses/toolkit</licenseUrl>
</metadata>
</package>

View File

@@ -0,0 +1,42 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0/linux-x64"
},
"targets": {
".NETCoreApp,Version=v10.0": {
"Signed.App/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Logging": "9.0.0"
}
},
"Microsoft.Extensions.Logging/9.0.0": {
"runtime": {
"lib/net9.0/Microsoft.Extensions.Logging.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.24.52809"
}
}
}
},
".NETCoreApp,Version=v10.0/linux-x64": {
"Microsoft.Extensions.Logging/9.0.0": {
"runtime": {
"runtimes/linux-x64/lib/net9.0/Microsoft.Extensions.Logging.dll": {}
}
}
}
},
"libraries": {
"Signed.App/1.0.0": {
"type": "project",
"serviceable": false
},
"Microsoft.Extensions.Logging/9.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_LOGGING_SHA==",
"path": "microsoft.extensions.logging/9.0.0",
"hashPath": "microsoft.extensions.logging.9.0.0.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"runtimeOptions": {
"tfm": "net10.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
}
}
}

View File

@@ -0,0 +1,40 @@
[
{
"analyzerId": "dotnet",
"componentKey": "purl::pkg:nuget/microsoft.extensions.logging@9.0.0",
"purl": "pkg:nuget/microsoft.extensions.logging@9.0.0",
"name": "Microsoft.Extensions.Logging",
"version": "9.0.0",
"type": "nuget",
"usedByEntrypoint": false,
"metadata": {
"assembly[0].assetPath": "lib/net9.0/Microsoft.Extensions.Logging.dll",
"assembly[0].fileVersion": "9.0.24.52809",
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[0].version": "9.0.0.0",
"assembly[1].assetPath": "runtimes/linux-x64/lib/net9.0/Microsoft.Extensions.Logging.dll",
"assembly[1].rid[0]": "linux-x64",
"assembly[1].tfm[0]": ".NETCoreApp,Version=v10.0",
"deps.path[0]": "Signed.App.deps.json",
"deps.rid[0]": "linux-x64",
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
"license.expression[0]": "MIT",
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
"package.id": "Microsoft.Extensions.Logging",
"package.id.normalized": "microsoft.extensions.logging",
"package.path[0]": "microsoft.extensions.logging/9.0.0",
"package.serviceable": "true",
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
"package.version": "9.0.0",
"provenance": "manifest"
},
"evidence": [
{
"kind": "file",
"source": "deps.json",
"locator": "Signed.App.deps.json",
"value": "Microsoft.Extensions.Logging/9.0.0"
}
]
}
]

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Microsoft.Extensions.Logging</id>
<version>9.0.0</version>
<authors>Microsoft</authors>
<description>Signed logging package fixture.</description>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
</metadata>
</package>

View File

@@ -0,0 +1,73 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0/linux-x64"
},
"targets": {
".NETCoreApp,Version=v10.0": {
"Sample.App/1.0.0": {
"dependencies": {
"StellaOps.Toolkit": "1.2.3",
"Microsoft.Extensions.Logging": "9.0.0"
}
},
"StellaOps.Toolkit/1.2.3": {
"dependencies": {
"Microsoft.Extensions.Logging": "9.0.0"
},
"runtime": {
"lib/net10.0/StellaOps.Toolkit.dll": {
"assemblyVersion": "1.2.3.0",
"fileVersion": "1.2.3.0"
}
}
},
"Microsoft.Extensions.Logging/9.0.0": {
"runtime": {
"lib/net9.0/Microsoft.Extensions.Logging.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.24.52809"
}
}
}
},
".NETCoreApp,Version=v10.0/linux-x64": {
"StellaOps.Toolkit/1.2.3": {
"runtime": {
"runtimes/linux-x64/native/libstellaops.toolkit.so": {}
}
},
"Microsoft.Extensions.Logging/9.0.0": {
"runtime": {
"runtimes/linux-x64/lib/net9.0/Microsoft.Extensions.Logging.dll": {}
}
}
},
".NETCoreApp,Version=v10.0/win-x86": {
"Microsoft.Extensions.Logging/9.0.0": {
"runtime": {
"runtimes/win-x86/lib/net9.0/Microsoft.Extensions.Logging.dll": {}
}
}
}
},
"libraries": {
"Sample.App/1.0.0": {
"type": "project",
"serviceable": false
},
"StellaOps.Toolkit/1.2.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_TOOLKIT_SHA==",
"path": "stellaops.toolkit/1.2.3",
"hashPath": "stellaops.toolkit.1.2.3.nupkg.sha512"
},
"Microsoft.Extensions.Logging/9.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FAKE_LOGGING_SHA==",
"path": "microsoft.extensions.logging/9.0.0",
"hashPath": "microsoft.extensions.logging.9.0.0.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,35 @@
{
"runtimeOptions": {
"tfm": "net10.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "10.0.0"
}
],
"runtimeGraph": {
"runtimes": {
"linux-x64": {
"fallbacks": [
"linux",
"unix"
]
},
"win-x86": {
"fallbacks": [
"win",
"any"
]
}
}
}
}
}

View File

@@ -0,0 +1,87 @@
[
{
"analyzerId": "dotnet",
"componentKey": "purl::pkg:nuget/microsoft.extensions.logging@9.0.0",
"purl": "pkg:nuget/microsoft.extensions.logging@9.0.0",
"name": "Microsoft.Extensions.Logging",
"version": "9.0.0",
"type": "nuget",
"usedByEntrypoint": false,
"metadata": {
"assembly[0].assetPath": "lib/net9.0/Microsoft.Extensions.Logging.dll",
"assembly[0].fileVersion": "9.0.24.52809",
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[0].version": "9.0.0.0",
"assembly[1].assetPath": "runtimes/linux-x64/lib/net9.0/Microsoft.Extensions.Logging.dll",
"assembly[1].rid[0]": "linux-x64",
"assembly[1].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[2].assetPath": "runtimes/win-x86/lib/net9.0/Microsoft.Extensions.Logging.dll",
"assembly[2].rid[0]": "win-x86",
"assembly[2].tfm[0]": ".NETCoreApp,Version=v10.0",
"deps.path[0]": "Sample.App.deps.json",
"deps.rid[0]": "linux-x64",
"deps.rid[1]": "win-x86",
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
"license.expression[0]": "MIT",
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
"package.id": "Microsoft.Extensions.Logging",
"package.id.normalized": "microsoft.extensions.logging",
"package.path[0]": "microsoft.extensions.logging/9.0.0",
"package.serviceable": "true",
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
"package.version": "9.0.0",
"provenance": "manifest"
},
"evidence": [
{
"kind": "file",
"source": "deps.json",
"locator": "Sample.App.deps.json",
"value": "Microsoft.Extensions.Logging/9.0.0"
}
]
},
{
"analyzerId": "dotnet",
"componentKey": "purl::pkg:nuget/stellaops.toolkit@1.2.3",
"purl": "pkg:nuget/stellaops.toolkit@1.2.3",
"name": "StellaOps.Toolkit",
"version": "1.2.3",
"type": "nuget",
"usedByEntrypoint": false,
"metadata": {
"assembly[0].assetPath": "lib/net10.0/StellaOps.Toolkit.dll",
"assembly[0].fileVersion": "1.2.3.0",
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
"assembly[0].version": "1.2.3.0",
"deps.dependency[0]": "microsoft.extensions.logging",
"deps.path[0]": "Sample.App.deps.json",
"deps.rid[0]": "linux-x64",
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
"license.file.sha256[0]": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c",
"license.file[0]": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
"package.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512",
"package.id": "StellaOps.Toolkit",
"package.id.normalized": "stellaops.toolkit",
"package.path[0]": "stellaops.toolkit/1.2.3",
"package.serviceable": "true",
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
"package.version": "1.2.3",
"provenance": "manifest"
},
"evidence": [
{
"kind": "file",
"source": "deps.json",
"locator": "Sample.App.deps.json",
"value": "StellaOps.Toolkit/1.2.3"
},
{
"kind": "file",
"source": "license",
"locator": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
"sha256": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c"
}
]
}
]

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Microsoft.Extensions.Logging</id>
<version>9.0.0</version>
<authors>Microsoft</authors>
<description>Logging abstractions for StellaOps test fixture.</description>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
</metadata>
</package>

View File

@@ -0,0 +1,7 @@
StellaOps Toolkit License
=========================
This sample license is provided for test fixtures only.
Permission is granted to use, copy, modify, and distribute this fixture
for the purpose of automated testing.

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>StellaOps.Toolkit</id>
<version>1.2.3</version>
<authors>StellaOps</authors>
<description>Toolkit sample package for analyzer fixtures.</description>
<license type="file">LICENSE.txt</license>
<licenseUrl>https://stella-ops.example/licenses/toolkit</licenseUrl>
</metadata>
</package>

View File

@@ -0,0 +1,12 @@
[[package]]
name = "my_app"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abc123"

View File

@@ -0,0 +1,62 @@
[
{
"analyzerId": "rust",
"componentKey": "purl::pkg:cargo/my_app@0.1.0",
"purl": "pkg:cargo/my_app@0.1.0",
"name": "my_app",
"version": "0.1.0",
"type": "cargo",
"usedByEntrypoint": false,
"metadata": {
"cargo.lock.path": "Cargo.lock",
"fingerprint.profile": "debug",
"fingerprint.targetKind": "bin",
"source": "registry\u002Bhttps://github.com/rust-lang/crates.io-index"
},
"evidence": [
{
"kind": "file",
"source": "cargo.fingerprint",
"locator": "target/debug/.fingerprint/my_app-1234567890abcdef/bin-my_app-1234567890abcdef.json",
"value": "bin"
},
{
"kind": "file",
"source": "cargo.lock",
"locator": "Cargo.lock",
"value": "my_app 0.1.0"
}
]
},
{
"analyzerId": "rust",
"componentKey": "purl::pkg:cargo/serde@1.0.188",
"purl": "pkg:cargo/serde@1.0.188",
"name": "serde",
"version": "1.0.188",
"type": "cargo",
"usedByEntrypoint": false,
"metadata": {
"cargo.lock.path": "Cargo.lock",
"checksum": "abc123",
"fingerprint.profile": "release",
"fingerprint.targetKind": "lib",
"source": "registry\u002Bhttps://github.com/rust-lang/crates.io-index"
},
"evidence": [
{
"kind": "file",
"source": "cargo.fingerprint",
"locator": "target/debug/.fingerprint/serde-abcdef1234567890/libserde-abcdef1234567890.json",
"value": "lib"
},
{
"kind": "file",
"source": "cargo.lock",
"locator": "Cargo.lock",
"value": "serde 1.0.188",
"sha256": "abc123"
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"pkgid": "my_app 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"profile": "debug",
"target_kind": ["bin"]
}

Some files were not shown because too many files have changed in this diff Show More