This commit is contained in:
StellaOps Bot
2025-12-09 00:20:52 +02:00
parent 3d01bf9edc
commit bc0762e97d
261 changed files with 14033 additions and 4427 deletions

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
@@ -7,72 +7,72 @@ 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()
};
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,
@@ -104,6 +104,68 @@ public sealed class DotNetLanguageAnalyzerTests
}
}
[Fact]
public async Task ConfigEnablesIlMetadataEdgesAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var sourceFixture = TestPaths.ResolveFixture("lang", "dotnet", "simple");
var tempRoot = TestPaths.CreateTemporaryDirectory();
try
{
CopyDirectory(sourceFixture, tempRoot);
File.WriteAllText(
Path.Combine(tempRoot, "dotnet-il.config.json"),
"""
{
"emitDependencyEdges": true,
"includeEntrypoints": true,
"runtimeEvidencePath": "runtime-evidence.ndjson",
"runtimeEvidenceConfidence": "medium"
}
""");
File.WriteAllLines(
Path.Combine(tempRoot, "runtime-evidence.ndjson"),
new[]
{
"{\"package\":\"stellaops.toolkit\",\"target\":\"native-lib\",\"reason\":\"runtime-load\",\"confidence\":\"medium\",\"source\":\"trace\"}"
});
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
tempRoot,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var packages = document.RootElement.EnumerateArray().ToArray();
var toolkit = packages.First(element => element.GetProperty("name").GetString() == "StellaOps.Toolkit");
var logging = packages.First(element => element.GetProperty("name").GetString() == "Microsoft.Extensions.Logging");
var toolkitMetadata = toolkit.GetProperty("metadata");
Assert.Equal("microsoft.extensions.logging", toolkitMetadata.GetProperty("edge[0].target").GetString());
Assert.Equal("declared-dependency", toolkitMetadata.GetProperty("edge[0].reason").GetString());
Assert.Equal("native-lib", toolkitMetadata.GetProperty("edge.runtime[0].target").GetString());
Assert.Equal("runtime-load", toolkitMetadata.GetProperty("edge.runtime[0].reason").GetString());
var entrypointId = toolkitMetadata.GetProperty("entrypoint[0].id").GetString();
Assert.NotNull(entrypointId);
Assert.StartsWith("Sample.App:", entrypointId, StringComparison.Ordinal);
Assert.True(toolkitMetadata.TryGetProperty("entrypoint[0].tfm[0]", out _));
Assert.True(logging.GetProperty("metadata").TryGetProperty("entrypoint[0].rid[0]", out _));
}
finally
{
TestPaths.SafeDelete(tempRoot);
}
}
[Fact]
public async Task MultiFixtureMergesRuntimeMetadataAsync()
{
@@ -151,29 +213,49 @@ public sealed class DotNetLanguageAnalyzerTests
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;
}
}
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;
}
private static void CopyDirectory(string sourceDir, string destinationDir)
{
if (!Directory.Exists(destinationDir))
{
Directory.CreateDirectory(destinationDir);
}
foreach (var file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.TopDirectoryOnly))
{
var targetPath = Path.Combine(destinationDir, Path.GetFileName(file));
File.Copy(file, targetPath, overwrite: true);
}
foreach (var directory in Directory.EnumerateDirectories(sourceDir, "*", SearchOption.TopDirectoryOnly))
{
var targetDirectory = Path.Combine(destinationDir, Path.GetFileName(directory));
CopyDirectory(directory, targetDirectory);
}
}
}