Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
// <copyright file="EbpfSignalMergerTests.cs" company="StellaOps">
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// </copyright>
|
||||
|
||||
namespace StellaOps.Signals.Ebpf.Tests;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using StellaOps.Scanner.Reachability.Runtime;
|
||||
using StellaOps.Scanner.Reachability.Slices;
|
||||
using StellaOps.Signals.Ebpf.Schema;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="EbpfSignalMerger"/>.
|
||||
/// </summary>
|
||||
public sealed class EbpfSignalMergerTests
|
||||
{
|
||||
private readonly EbpfSignalMerger _merger;
|
||||
private readonly RuntimeStaticMerger _baseMerger;
|
||||
|
||||
public EbpfSignalMergerTests()
|
||||
{
|
||||
_baseMerger = new RuntimeStaticMerger();
|
||||
_merger = new EbpfSignalMerger(
|
||||
_baseMerger,
|
||||
NullLogger<EbpfSignalMerger>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_WithNoSignals_ReturnsSameGraph()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
|
||||
var result = _merger.Merge(graph, null);
|
||||
|
||||
Assert.Same(graph, result.MergedGraph);
|
||||
Assert.Empty(result.Evidence);
|
||||
Assert.Equal(2, result.Statistics.StaticEdgeCount);
|
||||
Assert.Equal(0, result.Statistics.RuntimeEventCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_WithEmptySignals_ReturnsSameGraph()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var signals = new RuntimeSignalSummary
|
||||
{
|
||||
ContainerId = "container-123",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
StoppedAt = DateTimeOffset.UtcNow,
|
||||
TotalEvents = 0,
|
||||
CallPaths = [],
|
||||
ObservedSymbols = [],
|
||||
};
|
||||
|
||||
var result = _merger.Merge(graph, signals);
|
||||
|
||||
Assert.Same(graph, result.MergedGraph);
|
||||
Assert.Empty(result.Evidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_WithMatchingSignals_CreatesConfirmedEvidence()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var signals = new RuntimeSignalSummary
|
||||
{
|
||||
ContainerId = "container-123",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
StoppedAt = DateTimeOffset.UtcNow,
|
||||
TotalEvents = 100,
|
||||
CallPaths = new List<ObservedCallPath>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Symbols = ["main", "processRequest"],
|
||||
ObservationCount = 50,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
},
|
||||
},
|
||||
ObservedSymbols = ["main", "processRequest"],
|
||||
};
|
||||
|
||||
var result = _merger.Merge(graph, signals);
|
||||
|
||||
Assert.NotEmpty(result.Evidence);
|
||||
Assert.Contains(result.Evidence, e =>
|
||||
e.Type == RuntimeEvidenceType.RuntimeConfirmed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_WithRuntimeOnlyPath_CreatesRuntimeOnlyEvidence()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var signals = new RuntimeSignalSummary
|
||||
{
|
||||
ContainerId = "container-123",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
StoppedAt = DateTimeOffset.UtcNow,
|
||||
TotalEvents = 100,
|
||||
CallPaths = new List<ObservedCallPath>
|
||||
{
|
||||
new()
|
||||
{
|
||||
// Path not in static graph
|
||||
Symbols = ["dynamic_dispatch", "hidden_method"],
|
||||
ObservationCount = 20,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
},
|
||||
},
|
||||
ObservedSymbols = ["dynamic_dispatch", "hidden_method"],
|
||||
};
|
||||
|
||||
var result = _merger.Merge(graph, signals);
|
||||
|
||||
Assert.Contains(result.Evidence, e =>
|
||||
e.Type == RuntimeEvidenceType.RuntimeOnly);
|
||||
Assert.True(result.Statistics.RuntimeOnlyPathCount > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_WithDetectedRuntimes_CreatesRuntimeDetectedEvidence()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var signals = new RuntimeSignalSummary
|
||||
{
|
||||
ContainerId = "container-123",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
StoppedAt = DateTimeOffset.UtcNow,
|
||||
TotalEvents = 100,
|
||||
CallPaths = [],
|
||||
ObservedSymbols = [],
|
||||
DetectedRuntimes = [RuntimeType.Node, RuntimeType.Python],
|
||||
};
|
||||
|
||||
var result = _merger.Merge(graph, signals);
|
||||
|
||||
Assert.Contains(result.Evidence, e =>
|
||||
e.Type == RuntimeEvidenceType.RuntimeDetected &&
|
||||
e.RuntimeType == "Node");
|
||||
Assert.Contains(result.Evidence, e =>
|
||||
e.Type == RuntimeEvidenceType.RuntimeDetected &&
|
||||
e.RuntimeType == "Python");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatePath_WithValidPath_ReturnsConfirmed()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var path = new ObservedCallPath
|
||||
{
|
||||
Symbols = ["main", "processRequest"],
|
||||
ObservationCount = 10,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow,
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
var result = _merger.ValidatePath(graph, path);
|
||||
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Equal(PathType.Confirmed, result.PathType);
|
||||
Assert.Equal(1.0, result.MatchRatio);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatePath_WithUnknownPath_ReturnsRuntimeOnly()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var path = new ObservedCallPath
|
||||
{
|
||||
Symbols = ["unknown", "method"],
|
||||
ObservationCount = 10,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow,
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
var result = _merger.ValidatePath(graph, path);
|
||||
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Equal(PathType.RuntimeOnly, result.PathType);
|
||||
Assert.Equal(0.0, result.MatchRatio);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatePath_WithShortPath_ReturnsInvalid()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var path = new ObservedCallPath
|
||||
{
|
||||
Symbols = ["single"],
|
||||
ObservationCount = 10,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow,
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
var result = _merger.ValidatePath(graph, path);
|
||||
|
||||
Assert.False(result.IsValid);
|
||||
Assert.Equal(PathType.Invalid, result.PathType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Merge_StatisticsAreAccurate()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var signals = new RuntimeSignalSummary
|
||||
{
|
||||
ContainerId = "container-123",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
StoppedAt = DateTimeOffset.UtcNow,
|
||||
TotalEvents = 500,
|
||||
CallPaths = new List<ObservedCallPath>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Symbols = ["main", "processRequest"],
|
||||
ObservationCount = 100,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
},
|
||||
},
|
||||
ObservedSymbols = ["main", "processRequest"],
|
||||
DroppedEvents = 10,
|
||||
};
|
||||
|
||||
var result = _merger.Merge(graph, signals);
|
||||
|
||||
Assert.Equal(2, result.Statistics.StaticEdgeCount);
|
||||
Assert.Equal(500, result.Statistics.RuntimeEventCount);
|
||||
Assert.Equal(1, result.Statistics.CallPathCount);
|
||||
Assert.Equal(10, result.Statistics.DroppedEventCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeEvidence_ContainsContainerId()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var signals = new RuntimeSignalSummary
|
||||
{
|
||||
ContainerId = "my-container-id",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
StoppedAt = DateTimeOffset.UtcNow,
|
||||
TotalEvents = 100,
|
||||
CallPaths = new List<ObservedCallPath>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Symbols = ["main", "processRequest"],
|
||||
ObservationCount = 10,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
},
|
||||
},
|
||||
ObservedSymbols = [],
|
||||
};
|
||||
|
||||
var result = _merger.Merge(graph, signals);
|
||||
|
||||
Assert.All(result.Evidence, e =>
|
||||
Assert.Equal("my-container-id", e.ContainerId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EvidenceSource_IsEbpf()
|
||||
{
|
||||
var graph = CreateTestGraph();
|
||||
var signals = new RuntimeSignalSummary
|
||||
{
|
||||
ContainerId = "container-123",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
StoppedAt = DateTimeOffset.UtcNow,
|
||||
TotalEvents = 100,
|
||||
CallPaths = new List<ObservedCallPath>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Symbols = ["main", "processRequest"],
|
||||
ObservationCount = 10,
|
||||
FirstObservedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
LastObservedAt = DateTimeOffset.UtcNow,
|
||||
},
|
||||
},
|
||||
ObservedSymbols = [],
|
||||
};
|
||||
|
||||
var result = _merger.Merge(graph, signals);
|
||||
|
||||
Assert.All(result.Evidence, e =>
|
||||
Assert.Equal(EvidenceSource.Ebpf, e.Source));
|
||||
}
|
||||
|
||||
private static RichGraph CreateTestGraph()
|
||||
{
|
||||
return new RichGraph(
|
||||
Nodes: new List<RichGraphNode>
|
||||
{
|
||||
new("main", "main", null, null, "native", "entrypoint", null, null, null, null, null),
|
||||
new("processRequest", "processRequest", null, null, "native", "function", null, null, null, null, null),
|
||||
new("handleError", "handleError", null, null, "native", "function", null, null, null, null, null),
|
||||
},
|
||||
Edges: new List<RichGraphEdge>
|
||||
{
|
||||
new("main", "processRequest", "call", null, null, null, 1.0, null),
|
||||
new("processRequest", "handleError", "call", null, null, null, 0.8, null),
|
||||
},
|
||||
Roots: new List<RichGraphRoot>
|
||||
{
|
||||
new("main", "main", "entrypoint")
|
||||
},
|
||||
Analyzer: new RichGraphAnalyzer("test-analyzer", "1.0.0", null)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user