work work hard work

This commit is contained in:
StellaOps Bot
2025-12-18 00:47:24 +02:00
parent dee252940b
commit b4235c134c
189 changed files with 9627 additions and 3258 deletions

View File

@@ -0,0 +1,45 @@
using StellaOps.Scanner.CallGraph;
using StellaOps.Scanner.CallGraph.Node;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class BenchmarkIntegrationTests
{
[Theory]
[InlineData("unsafe-eval", true)]
[InlineData("guarded-eval", false)]
public async Task NodeTraceExtractor_AlignsWithBenchmarkReachability(string caseName, bool expectSinkReachable)
{
var repoRoot = FindRepoRoot();
var caseDir = Path.Combine(repoRoot, "bench", "reachability-benchmark", "cases", "js", caseName);
var extractor = new NodeCallGraphExtractor();
var snapshot = await extractor.ExtractAsync(new CallGraphExtractionRequest(
ScanId: $"bench-{caseName}",
Language: "node",
TargetPath: caseDir));
var analyzer = new ReachabilityAnalyzer();
var result = analyzer.Analyze(snapshot);
Assert.Equal(expectSinkReachable, result.ReachableSinkIds.Length > 0);
}
private static string FindRepoRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
if (Directory.Exists(Path.Combine(directory.FullName, "bench", "reachability-benchmark")))
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new InvalidOperationException("Unable to locate repository root for benchmark integration tests.");
}
}

View File

@@ -0,0 +1,42 @@
using StellaOps.Scanner.CallGraph.Caching;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class CircuitBreakerStateTests
{
[Fact]
public void RecordFailure_TripsOpen_AfterThreshold()
{
var config = new CircuitBreakerConfig
{
FailureThreshold = 2,
TimeoutSeconds = 60,
HalfOpenTimeout = 10
};
var cb = new CircuitBreakerState(config);
Assert.Equal(CircuitState.Closed, cb.State);
cb.RecordFailure();
Assert.Equal(CircuitState.Closed, cb.State);
cb.RecordFailure();
Assert.Equal(CircuitState.Open, cb.State);
Assert.True(cb.IsOpen);
}
[Fact]
public void RecordSuccess_ResetsToClosed()
{
var config = new CircuitBreakerConfig { FailureThreshold = 1, TimeoutSeconds = 60, HalfOpenTimeout = 10 };
var cb = new CircuitBreakerState(config);
cb.RecordFailure();
Assert.True(cb.IsOpen);
cb.RecordSuccess();
Assert.Equal(CircuitState.Closed, cb.State);
Assert.False(cb.IsOpen);
}
}

View File

@@ -0,0 +1,166 @@
using StellaOps.Scanner.CallGraph;
using StellaOps.Scanner.CallGraph.DotNet;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class DotNetCallGraphExtractorTests
{
[Fact]
public async Task ExtractAsync_SimpleProject_ProducesEntrypointAndSink()
{
await using var temp = await TempDirectory.CreateAsync();
var csprojPath = Path.Combine(temp.Path, "App.csproj");
await File.WriteAllTextAsync(csprojPath, """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
""");
await File.WriteAllTextAsync(Path.Combine(temp.Path, "Program.cs"), """
using System;
public sealed class HttpGetAttribute : Attribute { }
namespace System.Diagnostics
{
public static class Process
{
public static void Start(string cmd) { }
}
}
public sealed class FooController
{
[HttpGet]
public void Get()
{
Helper();
}
private void Helper()
{
System.Diagnostics.Process.Start("cmd.exe");
}
}
""");
var fixedTime = DateTimeOffset.Parse("2025-12-17T00:00:00Z");
var extractor = new DotNetCallGraphExtractor(new FixedTimeProvider(fixedTime));
var snapshot = await extractor.ExtractAsync(new CallGraphExtractionRequest(
ScanId: "scan-001",
Language: "dotnet",
TargetPath: csprojPath));
Assert.Equal("scan-001", snapshot.ScanId);
Assert.Equal("dotnet", snapshot.Language);
Assert.False(string.IsNullOrWhiteSpace(snapshot.GraphDigest));
Assert.NotEmpty(snapshot.Nodes);
Assert.NotEmpty(snapshot.Edges);
Assert.Contains(snapshot.Nodes, n => n.IsEntrypoint && n.EntrypointType == EntrypointType.HttpHandler);
Assert.Contains(snapshot.Nodes, n => n.IsSink);
Assert.NotEmpty(snapshot.SinkIds);
Assert.NotEmpty(snapshot.EntrypointIds);
}
[Fact]
public async Task ExtractAsync_IsDeterministic_ForSameInputs()
{
await using var temp = await TempDirectory.CreateAsync();
var csprojPath = Path.Combine(temp.Path, "App.csproj");
await File.WriteAllTextAsync(csprojPath, """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
""");
await File.WriteAllTextAsync(Path.Combine(temp.Path, "Program.cs"), """
public static class Program
{
public static void Main()
{
A();
}
private static void A()
{
B();
}
private static void B()
{
}
}
""");
var extractor = new DotNetCallGraphExtractor();
var request = new CallGraphExtractionRequest("scan-001", "dotnet", csprojPath);
var first = await extractor.ExtractAsync(request);
var second = await extractor.ExtractAsync(request);
Assert.Equal(first.GraphDigest, second.GraphDigest);
Assert.Equal(first.Nodes.Select(n => n.NodeId), second.Nodes.Select(n => n.NodeId));
Assert.Equal(first.Edges.Select(e => (e.SourceId, e.TargetId, e.CallKind)), second.Edges.Select(e => (e.SourceId, e.TargetId, e.CallKind)));
}
private sealed class FixedTimeProvider : TimeProvider
{
private readonly DateTimeOffset _instant;
public FixedTimeProvider(DateTimeOffset instant)
{
_instant = instant;
}
public override DateTimeOffset GetUtcNow() => _instant;
}
private sealed class TempDirectory : IAsyncDisposable
{
public string Path { get; }
private TempDirectory(string path)
{
Path = path;
}
public static Task<TempDirectory> CreateAsync()
{
var root = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"stella_callgraph_{Guid.NewGuid():N}");
Directory.CreateDirectory(root);
return Task.FromResult(new TempDirectory(root));
}
public ValueTask DisposeAsync()
{
try
{
if (Directory.Exists(Path))
{
Directory.Delete(Path, recursive: true);
}
}
catch
{
// best effort cleanup
}
return ValueTask.CompletedTask;
}
}
}

View File

@@ -0,0 +1,67 @@
using System.Collections.Immutable;
using StellaOps.Scanner.CallGraph;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class ReachabilityAnalyzerTests
{
[Fact]
public void Analyze_WhenSinkReachable_ReturnsShortestPath()
{
var entry = CallGraphNodeIds.Compute("dotnet:test:entry");
var mid = CallGraphNodeIds.Compute("dotnet:test:mid");
var sink = CallGraphNodeIds.Compute("dotnet:test:sink");
var snapshot = new CallGraphSnapshot(
ScanId: "scan-1",
GraphDigest: "sha256:placeholder",
Language: "dotnet",
ExtractedAt: DateTimeOffset.UtcNow,
Nodes:
[
new CallGraphNode(entry, "Entry", "file.cs", 1, "app", Visibility.Public, true, EntrypointType.HttpHandler, false, null),
new CallGraphNode(mid, "Mid", "file.cs", 2, "app", Visibility.Public, false, null, false, null),
new CallGraphNode(sink, "Sink", "file.cs", 3, "System", Visibility.Public, false, null, true, StellaOps.Scanner.Reachability.SinkCategory.CmdExec),
],
Edges:
[
new CallGraphEdge(entry, mid, CallKind.Direct),
new CallGraphEdge(mid, sink, CallKind.Direct),
],
EntrypointIds: [entry],
SinkIds: [sink]);
var analyzer = new ReachabilityAnalyzer();
var result = analyzer.Analyze(snapshot);
Assert.Contains(sink, result.ReachableSinkIds);
Assert.Single(result.Paths);
Assert.Equal(entry, result.Paths[0].EntrypointId);
Assert.Equal(sink, result.Paths[0].SinkId);
Assert.Equal(ImmutableArray.Create(entry, mid, sink), result.Paths[0].NodeIds);
}
[Fact]
public void Analyze_WhenNoEntrypoints_ReturnsEmpty()
{
var snapshot = new CallGraphSnapshot(
ScanId: "scan-1",
GraphDigest: "sha256:placeholder",
Language: "dotnet",
ExtractedAt: DateTimeOffset.UtcNow,
Nodes: ImmutableArray<CallGraphNode>.Empty,
Edges: ImmutableArray<CallGraphEdge>.Empty,
EntrypointIds: ImmutableArray<string>.Empty,
SinkIds: ImmutableArray<string>.Empty);
var analyzer = new ReachabilityAnalyzer();
var result = analyzer.Analyze(snapshot);
Assert.Empty(result.ReachableNodeIds);
Assert.Empty(result.ReachableSinkIds);
Assert.Empty(result.Paths);
Assert.False(string.IsNullOrWhiteSpace(result.ResultDigest));
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Scanner.CallGraph\\StellaOps.Scanner.CallGraph.csproj" />
<ProjectReference Include="..\\..\\..\\__Libraries\\StellaOps.Messaging.Testing\\StellaOps.Messaging.Testing.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,85 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Messaging.Testing.Fixtures;
using StellaOps.Scanner.CallGraph;
using StellaOps.Scanner.CallGraph.Caching;
using Xunit;
namespace StellaOps.Scanner.CallGraph.Tests;
[Collection(nameof(ValkeyFixtureCollection))]
public class ValkeyCallGraphCacheServiceTests : IAsyncLifetime
{
private readonly ValkeyFixture _fixture;
private ValkeyCallGraphCacheService _cache = null!;
public ValkeyCallGraphCacheServiceTests(ValkeyFixture fixture)
{
_fixture = fixture;
}
public Task InitializeAsync()
{
var options = Options.Create(new CallGraphCacheConfig
{
Enabled = true,
ConnectionString = _fixture.ConnectionString,
KeyPrefix = "test:callgraph:",
TtlSeconds = 60,
EnableGzip = true,
CircuitBreaker = new CircuitBreakerConfig { FailureThreshold = 3, TimeoutSeconds = 30, HalfOpenTimeout = 10 }
});
_cache = new ValkeyCallGraphCacheService(options, NullLogger<ValkeyCallGraphCacheService>.Instance);
return Task.CompletedTask;
}
public async Task DisposeAsync()
{
await _cache.DisposeAsync();
}
[Fact]
public async Task SetThenGet_CallGraph_RoundTrips()
{
var nodeId = CallGraphNodeIds.Compute("dotnet:test:entry");
var snapshot = new CallGraphSnapshot(
ScanId: "scan-cache-1",
GraphDigest: "sha256:cg",
Language: "dotnet",
ExtractedAt: DateTimeOffset.UtcNow,
Nodes: [new CallGraphNode(nodeId, "Entry", "file.cs", 1, "app", Visibility.Public, true, EntrypointType.HttpHandler, false, null)],
Edges: [],
EntrypointIds: [nodeId],
SinkIds: []);
await _cache.SetCallGraphAsync(snapshot);
var loaded = await _cache.TryGetCallGraphAsync("scan-cache-1", "dotnet");
Assert.NotNull(loaded);
Assert.Equal(snapshot.ScanId, loaded!.ScanId);
Assert.Equal(snapshot.Language, loaded.Language);
Assert.Equal(snapshot.GraphDigest, loaded.GraphDigest);
}
[Fact]
public async Task SetThenGet_ReachabilityResult_RoundTrips()
{
var result = new ReachabilityAnalysisResult(
ScanId: "scan-cache-2",
GraphDigest: "sha256:cg",
Language: "dotnet",
ComputedAt: DateTimeOffset.UtcNow,
ReachableNodeIds: [],
ReachableSinkIds: [],
Paths: [],
ResultDigest: "sha256:r");
await _cache.SetReachabilityResultAsync(result);
var loaded = await _cache.TryGetReachabilityResultAsync("scan-cache-2", "dotnet");
Assert.NotNull(loaded);
Assert.Equal(result.ResultDigest, loaded!.ResultDigest);
}
}