141 lines
5.5 KiB
C#
141 lines
5.5 KiB
C#
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>());
|
|
}
|
|
|