up
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StellaOps.Scanner.Reachability.Ordering;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Tests;
|
||||
|
||||
public sealed class DeterministicGraphOrdererTests
|
||||
{
|
||||
[Fact]
|
||||
public void Canonicalize_IsDeterministic_AcrossInputOrdering()
|
||||
{
|
||||
var orderer = new DeterministicGraphOrderer();
|
||||
|
||||
var graph1 = new RichGraph(
|
||||
Nodes: new[] { Node("B"), Node("A"), Node("C") },
|
||||
Edges: new[] { Edge("A", "C"), Edge("A", "B"), Edge("B", "C") },
|
||||
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
|
||||
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
var graph2 = new RichGraph(
|
||||
Nodes: new[] { Node("C"), Node("A"), Node("B") },
|
||||
Edges: new[] { Edge("B", "C"), Edge("A", "B"), Edge("A", "C") },
|
||||
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
|
||||
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
var canonical1 = orderer.Canonicalize(graph1, GraphOrderingStrategy.TopologicalLexicographic);
|
||||
var canonical2 = orderer.Canonicalize(graph2, GraphOrderingStrategy.TopologicalLexicographic);
|
||||
|
||||
Assert.Equal(canonical1.ContentHash, canonical2.ContentHash);
|
||||
Assert.Equal(canonical1.Nodes.Select(n => n.Id), canonical2.Nodes.Select(n => n.Id));
|
||||
Assert.Equal(canonical1.Edges.Select(e => (e.SourceIndex, e.TargetIndex, e.EdgeType)),
|
||||
canonical2.Edges.Select(e => (e.SourceIndex, e.TargetIndex, e.EdgeType)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TopologicalLexicographic_UsesLexicographicTiebreakers()
|
||||
{
|
||||
var orderer = new DeterministicGraphOrderer();
|
||||
var graph = new RichGraph(
|
||||
Nodes: new[] { Node("C"), Node("B"), Node("A") },
|
||||
Edges: new[] { Edge("A", "C"), Edge("B", "C") },
|
||||
Roots: Array.Empty<RichGraphRoot>(),
|
||||
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.TopologicalLexicographic);
|
||||
Assert.Equal(new[] { "A", "B", "C" }, order);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TopologicalLexicographic_HandlesCyclesByAppendingRemainder()
|
||||
{
|
||||
var orderer = new DeterministicGraphOrderer();
|
||||
var graph = new RichGraph(
|
||||
Nodes: new[] { Node("B"), Node("A"), Node("C") },
|
||||
Edges: new[] { Edge("A", "B"), Edge("B", "A") },
|
||||
Roots: Array.Empty<RichGraphRoot>(),
|
||||
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.TopologicalLexicographic);
|
||||
Assert.Equal(new[] { "C", "A", "B" }, order);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BreadthFirstLexicographic_TraversesFromAnchors()
|
||||
{
|
||||
var orderer = new DeterministicGraphOrderer();
|
||||
var graph = new RichGraph(
|
||||
Nodes: new[] { Node("D"), Node("C"), Node("B"), Node("A") },
|
||||
Edges: new[] { Edge("A", "C"), Edge("A", "B"), Edge("B", "D") },
|
||||
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
|
||||
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.BreadthFirstLexicographic);
|
||||
Assert.Equal(new[] { "A", "B", "C", "D" }, order);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DepthFirstLexicographic_TraversesFromAnchors()
|
||||
{
|
||||
var orderer = new DeterministicGraphOrderer();
|
||||
var graph = new RichGraph(
|
||||
Nodes: new[] { Node("D"), Node("C"), Node("B"), Node("A") },
|
||||
Edges: new[] { Edge("A", "C"), Edge("A", "B"), Edge("B", "D") },
|
||||
Roots: new[] { new RichGraphRoot("A", "runtime", null) },
|
||||
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
var order = orderer.OrderNodes(graph, GraphOrderingStrategy.DepthFirstLexicographic);
|
||||
Assert.Equal(new[] { "A", "B", "D", "C" }, order);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OrderEdges_SortsByNodeOrderThenKind()
|
||||
{
|
||||
var orderer = new DeterministicGraphOrderer();
|
||||
var graph = new RichGraph(
|
||||
Nodes: new[] { Node("C"), Node("B"), Node("A") },
|
||||
Edges: new[]
|
||||
{
|
||||
Edge("B", "C", kind: "import"),
|
||||
Edge("A", "B", kind: "call"),
|
||||
Edge("A", "C", kind: "call")
|
||||
},
|
||||
Roots: Array.Empty<RichGraphRoot>(),
|
||||
Analyzer: new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
var order = new[] { "A", "B", "C" };
|
||||
var edges = orderer.OrderEdges(graph, order).ToList();
|
||||
|
||||
Assert.Equal(("A", "B", "call"), (edges[0].From, edges[0].To, edges[0].Kind));
|
||||
Assert.Equal(("A", "C", "call"), (edges[1].From, edges[1].To, edges[1].Kind));
|
||||
Assert.Equal(("B", "C", "import"), (edges[2].From, edges[2].To, edges[2].Kind));
|
||||
}
|
||||
|
||||
private static RichGraphNode Node(string id, IReadOnlyDictionary<string, string>? attributes = null)
|
||||
=> new(
|
||||
Id: id,
|
||||
SymbolId: id,
|
||||
CodeId: null,
|
||||
Purl: null,
|
||||
Lang: "dotnet",
|
||||
Kind: "method",
|
||||
Display: id,
|
||||
BuildId: null,
|
||||
Evidence: Array.Empty<string>(),
|
||||
Attributes: attributes,
|
||||
SymbolDigest: null);
|
||||
|
||||
private static RichGraphEdge Edge(string from, string to, string kind = "call")
|
||||
=> new(
|
||||
From: from,
|
||||
To: to,
|
||||
Kind: kind,
|
||||
Purl: null,
|
||||
SymbolDigest: null,
|
||||
Evidence: Array.Empty<string>(),
|
||||
Confidence: 1.0,
|
||||
Candidates: Array.Empty<string>());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Tests;
|
||||
|
||||
public sealed class SinkRegistryTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("dotnet", "System.Diagnostics.Process.Start", SinkCategory.CmdExec)]
|
||||
[InlineData("dotnet", "SYSTEM.DIAGNOSTICS.PROCESS.START", SinkCategory.CmdExec)]
|
||||
[InlineData("java", "java.io.ObjectInputStream.readObject", SinkCategory.UnsafeDeser)]
|
||||
[InlineData("node", "child_process.exec", SinkCategory.CmdExec)]
|
||||
[InlineData("python", "pickle.loads", SinkCategory.UnsafeDeser)]
|
||||
public void MatchSink_ReturnsExpectedCategory(string language, string symbol, SinkCategory expectedCategory)
|
||||
{
|
||||
var sink = SinkRegistry.MatchSink(language, symbol);
|
||||
Assert.NotNull(sink);
|
||||
Assert.Equal(expectedCategory, sink!.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchSink_ReturnsNull_WhenUnknownLanguage()
|
||||
{
|
||||
Assert.Null(SinkRegistry.MatchSink("unknown", "whatever"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user