Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism

- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
This commit is contained in:
StellaOps Bot
2025-12-26 15:17:15 +02:00
parent 7792749bb4
commit 907783f625
354 changed files with 79727 additions and 1346 deletions

View File

@@ -0,0 +1,308 @@
using StellaOps.Signals.Services;
using Xunit;
namespace StellaOps.Signals.Tests;
/// <summary>
/// Tests for SlimSymbolCache.
/// Sprint: SPRINT_20251226_010_SIGNALS_runtime_stack
/// Task: STACK-17
/// </summary>
public sealed class SlimSymbolCacheTests : IDisposable
{
private readonly SlimSymbolCache _cache;
private readonly string _tempPath;
public SlimSymbolCacheTests()
{
_tempPath = Path.Combine(Path.GetTempPath(), $"symbol-cache-test-{Guid.NewGuid()}");
_cache = new SlimSymbolCache(new SlimSymbolCacheOptions
{
MaxEntries = 100,
EntryTtl = TimeSpan.FromHours(1),
PersistencePath = _tempPath,
PersistOnAdd = true,
EnableAutoCleanup = false,
});
}
public void Dispose()
{
_cache.Dispose();
if (Directory.Exists(_tempPath))
{
Directory.Delete(_tempPath, recursive: true);
}
}
[Fact]
public void Add_ShouldAddSymbolsToCache()
{
// Arrange
var buildId = "abcd1234";
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
new() { StartAddress = 0x1100, Size = 50, Name = "parse" },
};
// Act
_cache.Add(buildId, "libtest.so", symbols);
// Assert
Assert.True(_cache.Contains(buildId));
}
[Fact]
public void TryResolve_ShouldResolveKnownAddress()
{
// Arrange
var buildId = "abcd1234";
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
};
_cache.Add(buildId, "libtest.so", symbols);
// Act
var found = _cache.TryResolve(buildId, 0x1050, out var symbol);
// Assert
Assert.True(found);
Assert.NotNull(symbol);
Assert.Equal("main", symbol.FunctionName);
Assert.Equal(0x50UL, symbol.Offset);
Assert.Equal(buildId, symbol.BuildId);
}
[Fact]
public void TryResolve_ShouldReturnFalseForUnknownBuildId()
{
// Act
var found = _cache.TryResolve("unknown", 0x1000, out var symbol);
// Assert
Assert.False(found);
Assert.Null(symbol);
}
[Fact]
public void TryResolve_ShouldReturnFalseForAddressOutsideSymbols()
{
// Arrange
var buildId = "abcd1234";
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
};
_cache.Add(buildId, "libtest.so", symbols);
// Act
var found = _cache.TryResolve(buildId, 0x2000, out var symbol);
// Assert
Assert.False(found);
Assert.Null(symbol);
}
[Fact]
public void TryResolve_ShouldUseBinarySearchForLargeSymbolTable()
{
// Arrange
var buildId = "large-table";
var symbols = Enumerable.Range(0, 1000)
.Select(i => new SymbolEntry
{
StartAddress = (ulong)(i * 100),
Size = 50,
Name = $"func_{i}",
})
.ToList();
_cache.Add(buildId, "liblarge.so", symbols);
// Act - resolve address in the middle
var found = _cache.TryResolve(buildId, 50025, out var symbol);
// Assert
Assert.True(found);
Assert.NotNull(symbol);
Assert.Equal("func_500", symbol.FunctionName);
Assert.Equal(25UL, symbol.Offset);
}
[Fact]
public void GetStatistics_ShouldReturnCorrectStats()
{
// Arrange
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
new() { StartAddress = 0x1100, Size = 50, Name = "parse" },
};
_cache.Add("build1", "lib1.so", symbols);
_cache.Add("build2", "lib2.so", symbols);
_cache.TryResolve("build1", 0x1050, out _); // hit
_cache.TryResolve("unknown", 0x1000, out _); // miss
// Act
var stats = _cache.GetStatistics();
// Assert
Assert.Equal(2, stats.EntryCount);
Assert.Equal(4, stats.TotalSymbols);
Assert.Equal(1, stats.HitCount);
Assert.Equal(1, stats.MissCount);
Assert.Equal(0.5, stats.HitRate);
}
[Fact]
public void Clear_ShouldRemoveAllEntries()
{
// Arrange
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
};
_cache.Add("build1", "lib1.so", symbols);
_cache.Add("build2", "lib2.so", symbols);
// Act
_cache.Clear();
// Assert
Assert.False(_cache.Contains("build1"));
Assert.False(_cache.Contains("build2"));
var stats = _cache.GetStatistics();
Assert.Equal(0, stats.EntryCount);
}
[Fact]
public void Remove_ShouldRemoveSpecificEntry()
{
// Arrange
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
};
_cache.Add("build1", "lib1.so", symbols);
_cache.Add("build2", "lib2.so", symbols);
// Act
var removed = _cache.Remove("build1");
// Assert
Assert.True(removed);
Assert.False(_cache.Contains("build1"));
Assert.True(_cache.Contains("build2"));
}
[Fact]
public void Persistence_ShouldWriteToDisk()
{
// Arrange
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
};
// Act
_cache.Add("persist-test", "libtest.so", symbols);
// Assert
var files = Directory.GetFiles(_tempPath, "*.symbols");
Assert.NotEmpty(files);
}
[Fact]
public void TrustedSymbols_ShouldBeFlaggedCorrectly()
{
// Arrange
var symbols = new List<SymbolEntry>
{
new() { StartAddress = 0x1000, Size = 100, Name = "main" },
};
_cache.Add("trusted-build", "libtrusted.so", symbols, isTrusted: true);
// Act
_cache.TryResolve("trusted-build", 0x1050, out var symbol);
// Assert
Assert.NotNull(symbol);
Assert.True(symbol.IsTrusted);
}
}
/// <summary>
/// Tests for CanonicalSymbol.
/// </summary>
public sealed class CanonicalSymbolTests
{
[Fact]
public void ToCanonicalString_ShouldFormatCorrectly()
{
// Arrange
var symbol = new CanonicalSymbol
{
Address = 0x1234,
BuildId = "abcdef1234567890",
FunctionName = "process_request",
Offset = 0x50,
};
// Act
var canonical = symbol.ToCanonicalString();
// Assert
Assert.Equal("abcdef1234567890:process_request+0x50", canonical);
}
[Fact]
public void Parse_ShouldParseCanonicalString()
{
// Arrange
var canonical = "abcdef12:main+0x100";
// Act
var symbol = CanonicalSymbol.Parse(canonical);
// Assert
Assert.NotNull(symbol);
Assert.Equal("abcdef12", symbol.BuildId);
Assert.Equal("main", symbol.FunctionName);
Assert.Equal(0x100UL, symbol.Offset);
}
[Fact]
public void Parse_ShouldReturnNullForInvalidFormat()
{
Assert.Null(CanonicalSymbol.Parse(""));
Assert.Null(CanonicalSymbol.Parse("no-colon"));
Assert.Null(CanonicalSymbol.Parse("build:no-plus"));
Assert.Null(CanonicalSymbol.Parse("build:func+invalid"));
}
[Fact]
public void RoundTrip_ShouldPreserveData()
{
// Arrange
var original = new CanonicalSymbol
{
Address = 0,
BuildId = "deadbeef",
FunctionName = "test_func",
Offset = 0x42,
};
// Act
var canonical = original.ToCanonicalString();
var parsed = CanonicalSymbol.Parse(canonical);
// Assert
Assert.NotNull(parsed);
Assert.Equal(original.BuildId, parsed.BuildId);
Assert.Equal(original.FunctionName, parsed.FunctionName);
Assert.Equal(original.Offset, parsed.Offset);
}
}