up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-24 07:52:25 +02:00
parent 5970f0d9bd
commit 150b3730ef
215 changed files with 8119 additions and 740 deletions

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scanner.Cache.Abstractions;
namespace StellaOps.Scanner.Core.Tests.Fakes;
internal sealed class FakeFileContentAddressableStore : IFileContentAddressableStore
{
private readonly ConcurrentDictionary<string, byte[]> store = new();
public ValueTask<FileCasEntry?> TryGetAsync(string sha256, CancellationToken cancellationToken = default)
{
if (store.TryGetValue(sha256, out var bytes))
{
return ValueTask.FromResult<FileCasEntry?>(new FileCasEntry(sha256, bytes.LongLength, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, sha256 + ".zip"));
}
return ValueTask.FromResult<FileCasEntry?>(null);
}
public Task<FileCasEntry> PutAsync(FileCasPutRequest request, CancellationToken cancellationToken = default)
{
using var ms = new MemoryStream();
request.Content.CopyTo(ms);
store[request.Sha256] = ms.ToArray();
return Task.FromResult(new FileCasEntry(request.Sha256, ms.Length, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, request.Sha256 + ".zip"));
}
public Task<bool> RemoveAsync(string sha256, CancellationToken cancellationToken = default)
{
return Task.FromResult(store.TryRemove(sha256, out _));
}
public Task<int> EvictExpiredAsync(CancellationToken cancellationToken = default) => Task.FromResult(0);
public Task<int> ExportAsync(string destinationDirectory, CancellationToken cancellationToken = default) => Task.FromResult(0);
public Task<int> ImportAsync(string sourceDirectory, CancellationToken cancellationToken = default) => Task.FromResult(0);
public Task<int> CompactAsync(CancellationToken cancellationToken = default) => Task.FromResult(0);
}

View File

@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using StellaOps.Scanner.Reachability;
using Xunit;
namespace StellaOps.Scanner.Core.Tests;
public class ReachabilityGraphBuilderUnionTests
{
[Fact]
public async Task ConvertsBuilderToUnionGraphAndWritesNdjson()
{
var builder = new ReachabilityGraphBuilder()
.AddNode("sym:dotnet:A")
.AddNode("sym:dotnet:B")
.AddEdge("sym:dotnet:A", "sym:dotnet:B", "call");
var graph = builder.ToUnionGraph("dotnet");
var writer = new ReachabilityUnionWriter();
using var temp = new TempDir();
var result = await writer.WriteAsync(graph, temp.Path, "analysis-graph-1");
Assert.Equal(2, result.Nodes.RecordCount);
Assert.Equal(1, result.Edges.RecordCount);
Assert.True(System.IO.File.Exists(result.MetaPath));
}
private sealed class TempDir : System.IDisposable
{
public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "reach-union-" + System.Guid.NewGuid().ToString("N"));
public TempDir() => System.IO.Directory.CreateDirectory(Path);
public void Dispose()
{
try { System.IO.Directory.Delete(Path, recursive: true); } catch { /* ignore */ }
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using StellaOps.Scanner.Core.Tests.Fakes;
using StellaOps.Scanner.Reachability;
using Xunit;
namespace StellaOps.Scanner.Core.Tests;
public class ReachabilityUnionPublisherTests
{
[Fact]
public async Task PublishesZipToCas()
{
var graph = new ReachabilityUnionGraph(
Nodes: new[] { new ReachabilityUnionNode("sym:dotnet:A", "dotnet", "method") },
Edges: new ReachabilityUnionEdge[0]);
var cas = new FakeFileContentAddressableStore();
using var temp = new TempDir();
var publisher = new ReachabilityUnionPublisher(new ReachabilityUnionWriter());
var result = await publisher.PublishAsync(graph, cas, temp.Path, "analysis-pub-1");
Assert.False(string.IsNullOrWhiteSpace(result.Sha256));
Assert.Equal(1, result.Records);
var entry = await cas.TryGetAsync(result.Sha256);
Assert.NotNull(entry);
Assert.True(entry!.Value.SizeBytes > 0);
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using StellaOps.Scanner.Reachability;
using Xunit;
namespace StellaOps.Scanner.Core.Tests;
public class ReachabilityUnionWriterTests
{
[Fact]
public async Task WritesDeterministicFilesAndHashes()
{
var writer = new ReachabilityUnionWriter();
using var temp = new TempDir();
var graph = new ReachabilityUnionGraph(
Nodes: new[]
{
new ReachabilityUnionNode("sym:dotnet:B", "dotnet", "method", display: "B"),
new ReachabilityUnionNode("sym:dotnet:A", "dotnet", "method", display: "A",
Source: new ReachabilitySource("static", "il", "file.cs:10"),
Attributes: new Dictionary<string, string> { { "visibility", "public" } }),
},
Edges: new[]
{
new ReachabilityUnionEdge("sym:dotnet:B", "sym:dotnet:A", "call", confidence: "high"),
new ReachabilityUnionEdge("sym:dotnet:A", "sym:dotnet:B", "call", confidence: "high"),
},
RuntimeFacts: new[]
{
new ReachabilityRuntimeFact(
"sym:dotnet:A",
new ReachabilityRuntimeSamples(2, DateTimeOffset.Parse("2025-11-20T12:00:00Z"), DateTimeOffset.Parse("2025-11-20T12:00:02Z")),
new ReachabilityRuntimeEnv(1234, "sha256:deadbeef", "Program.Main", new [] {"sealed", "offline"}))
});
var result = await writer.WriteAsync(graph, temp.Path, "analysis-1");
// Files exist
Assert.True(File.Exists(result.Nodes.Path));
Assert.True(File.Exists(result.Edges.Path));
Assert.NotNull(result.Facts);
Assert.True(File.Exists(result.MetaPath));
// Nodes sorted by symbol_id
var nodeLines = await File.ReadAllLinesAsync(result.Nodes.Path);
Assert.Equal(2, nodeLines.Length);
Assert.Contains("sym:dotnet:A", nodeLines[0]);
Assert.Contains("sym:dotnet:B", nodeLines[1]);
// Hashes recorded in meta match content
var meta = await JsonDocument.ParseAsync(File.OpenRead(result.MetaPath));
var files = meta.RootElement.GetProperty("files").EnumerateArray().ToList();
Assert.Contains(files, f => f.GetProperty("path").GetString() == result.Nodes.Path && f.GetProperty("sha256").GetString() == result.Nodes.Sha256);
Assert.Contains(files, f => f.GetProperty("path").GetString() == result.Edges.Path && f.GetProperty("sha256").GetString() == result.Edges.Sha256);
// Determinism: re-run with shuffled inputs yields identical hashes
var shuffled = new ReachabilityUnionGraph(
Nodes: graph.Nodes.Reverse().ToArray(),
Edges: graph.Edges.Reverse().ToArray(),
RuntimeFacts: graph.RuntimeFacts);
var second = await writer.WriteAsync(shuffled, temp.Path, "analysis-1");
Assert.Equal(result.Nodes.Sha256, second.Nodes.Sha256);
Assert.Equal(result.Edges.Sha256, second.Edges.Sha256);
Assert.Equal(result.Facts!.Sha256, second.Facts!.Sha256);
}
private sealed class TempDir : IDisposable
{
public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "reach-union-" + Guid.NewGuid().ToString("N"));
public TempDir() => Directory.CreateDirectory(Path);
public void Dispose()
{
try { Directory.Delete(Path, recursive: true); } catch { /* best effort */ }
}
}
}

View File

@@ -7,10 +7,12 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Reachability/StellaOps.Scanner.Reachability.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Cache/StellaOps.Scanner.Cache.csproj" />
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Fixtures\*.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
</Project>

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scanner.Cache.Abstractions;
namespace StellaOps.Scanner.Reachability.Tests;
internal sealed class FakeFileContentAddressableStore : IFileContentAddressableStore
{
private readonly ConcurrentDictionary<string, byte[]> store = new();
public ValueTask<FileCasEntry?> TryGetAsync(string sha256, CancellationToken cancellationToken = default)
{
if (store.TryGetValue(sha256, out var bytes))
{
return ValueTask.FromResult<FileCasEntry?>(new FileCasEntry(sha256, bytes.LongLength, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, sha256 + ".zip"));
}
return ValueTask.FromResult<FileCasEntry?>(null);
}
public Task<FileCasEntry> PutAsync(FileCasPutRequest request, CancellationToken cancellationToken = default)
{
using var ms = new MemoryStream();
request.Content.CopyTo(ms);
store[request.Sha256] = ms.ToArray();
return Task.FromResult(new FileCasEntry(request.Sha256, ms.Length, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, request.Sha256 + ".zip"));
}
public Task<bool> RemoveAsync(string sha256, CancellationToken cancellationToken = default)
=> Task.FromResult(store.TryRemove(sha256, out _));
public Task<int> EvictExpiredAsync(CancellationToken cancellationToken = default) => Task.FromResult(0);
public Task<int> ExportAsync(string destinationDirectory, CancellationToken cancellationToken = default) => Task.FromResult(0);
public Task<int> ImportAsync(string sourceDirectory, CancellationToken cancellationToken = default) => Task.FromResult(0);
public Task<int> CompactAsync(CancellationToken cancellationToken = default) => Task.FromResult(0);
}

View File

@@ -0,0 +1,27 @@
using System.Threading.Tasks;
using StellaOps.Scanner.Reachability;
using Xunit;
namespace StellaOps.Scanner.Reachability.Tests;
public class ReachabilityUnionPublisherTests
{
[Fact]
public async Task PublishesZipToCas()
{
var graph = new ReachabilityUnionGraph(
Nodes: new[] { new ReachabilityUnionNode("sym:dotnet:A", "dotnet", "method") },
Edges: new ReachabilityUnionEdge[0]);
using var temp = new TempDir();
var cas = new FakeFileContentAddressableStore();
var publisher = new ReachabilityUnionPublisher(new ReachabilityUnionWriter());
var result = await publisher.PublishAsync(graph, cas, temp.Path, "analysis-pub-1");
Assert.False(string.IsNullOrWhiteSpace(result.Sha256));
var entry = await cas.TryGetAsync(result.Sha256);
Assert.NotNull(entry);
Assert.True(entry!.SizeBytes > 0);
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using StellaOps.Scanner.Reachability;
using Xunit;
namespace StellaOps.Scanner.Reachability.Tests;
public class ReachabilityUnionWriterTests
{
[Fact]
public async Task WritesDeterministicNdjson()
{
var writer = new ReachabilityUnionWriter();
using var temp = new TempDir();
var graph = new ReachabilityUnionGraph(
Nodes: new[]
{
new ReachabilityUnionNode("sym:dotnet:B", "dotnet", "method"),
new ReachabilityUnionNode("sym:dotnet:A", "dotnet", "method")
},
Edges: new[]
{
new ReachabilityUnionEdge("sym:dotnet:A", "sym:dotnet:B", "call")
});
var result = await writer.WriteAsync(graph, temp.Path, "analysis-x");
var meta = await JsonDocument.ParseAsync(File.OpenRead(result.MetaPath));
var files = meta.RootElement.GetProperty("files").EnumerateArray().ToList();
Assert.Equal(2, files.Count); // nodes + edges
// Deterministic order
var nodeLines = await File.ReadAllLinesAsync(Path.Combine(temp.Path, "reachability_graphs/analysis-x/nodes.ndjson"));
Assert.Contains(nodeLines, l => l.Contains("sym:dotnet:A"));
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</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.Reachability\StellaOps.Scanner.Reachability.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.Scanner.Cache\StellaOps.Scanner.Cache.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,27 @@
using System;
using System.IO;
namespace StellaOps.Scanner.Reachability.Tests;
internal sealed class TempDir : IDisposable
{
public string Path { get; }
public TempDir()
{
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "reach-tests-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(Path);
}
public void Dispose()
{
try
{
Directory.Delete(Path, recursive: true);
}
catch
{
// best-effort cleanup only
}
}
}