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
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:
@@ -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);
|
||||
}
|
||||
@@ -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 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user