sprints work.
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
using FluentAssertions;
|
||||
using NSubstitute;
|
||||
using StellaOps.BinaryIndex.Validation;
|
||||
using StellaOps.BinaryIndex.Validation.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.BinaryIndex.Validation.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for MismatchAnalyzer.
|
||||
/// </summary>
|
||||
public class MismatchAnalyzerTests
|
||||
{
|
||||
private readonly IMismatchCauseInferrer _causeInferrer;
|
||||
private readonly MismatchAnalyzer _sut;
|
||||
|
||||
public MismatchAnalyzerTests()
|
||||
{
|
||||
_causeInferrer = Substitute.For<IMismatchCauseInferrer>();
|
||||
_sut = new MismatchAnalyzer(_causeInferrer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithEmptyList_ReturnsEmptyBuckets()
|
||||
{
|
||||
// Arrange
|
||||
var mismatches = new List<MatchResult>();
|
||||
|
||||
// Act
|
||||
var analysis = await _sut.AnalyzeAsync(mismatches, maxExamplesPerBucket: 5);
|
||||
|
||||
// Assert
|
||||
analysis.Buckets.Should().BeEmpty();
|
||||
analysis.TotalMismatches.Should().Be(0);
|
||||
analysis.DominantCause.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_GroupsMismatchesByCause()
|
||||
{
|
||||
// Arrange
|
||||
var mismatches = new List<MatchResult>
|
||||
{
|
||||
CreateMismatch("func1@@GLIBC_2.17"),
|
||||
CreateMismatch("func2@@GLIBC_2.34"),
|
||||
CreateMismatch("small_func", size: 20)
|
||||
};
|
||||
|
||||
_causeInferrer.InferCauseAsync(Arg.Any<MatchResult>(), Arg.Any<CancellationToken>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
var mismatch = callInfo.Arg<MatchResult>();
|
||||
if (mismatch.SourceFunction.Name.Contains("@@"))
|
||||
return (MismatchCause.SymbolVersioning, 0.9);
|
||||
return (MismatchCause.Inlining, 0.6);
|
||||
});
|
||||
|
||||
// Act
|
||||
var analysis = await _sut.AnalyzeAsync(mismatches, maxExamplesPerBucket: 5);
|
||||
|
||||
// Assert
|
||||
analysis.Buckets.Should().HaveCount(2);
|
||||
analysis.TotalMismatches.Should().Be(3);
|
||||
analysis.Buckets[MismatchCause.SymbolVersioning].Count.Should().Be(2);
|
||||
analysis.Buckets[MismatchCause.Inlining].Count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DominantCause_IsHighestCount()
|
||||
{
|
||||
// Arrange
|
||||
var mismatches = Enumerable.Range(0, 10)
|
||||
.Select(i => CreateMismatch($"func_{i}"))
|
||||
.ToList();
|
||||
|
||||
_causeInferrer.InferCauseAsync(Arg.Any<MatchResult>(), Arg.Any<CancellationToken>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
var index = int.Parse(callInfo.Arg<MatchResult>().SourceFunction.Name.Split('_')[1]);
|
||||
return index < 7
|
||||
? (MismatchCause.OptimizationLevel, 0.8)
|
||||
: (MismatchCause.CompilerVersion, 0.7);
|
||||
});
|
||||
|
||||
// Act
|
||||
var analysis = await _sut.AnalyzeAsync(mismatches, maxExamplesPerBucket: 3);
|
||||
|
||||
// Assert
|
||||
analysis.DominantCause.Should().Be(MismatchCause.OptimizationLevel);
|
||||
analysis.Buckets[MismatchCause.OptimizationLevel].Count.Should().Be(7);
|
||||
analysis.Buckets[MismatchCause.CompilerVersion].Count.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_LimitsExamplesPerBucket()
|
||||
{
|
||||
// Arrange
|
||||
var mismatches = Enumerable.Range(0, 20)
|
||||
.Select(i => CreateMismatch($"func_{i}"))
|
||||
.ToList();
|
||||
|
||||
_causeInferrer.InferCauseAsync(Arg.Any<MatchResult>(), Arg.Any<CancellationToken>())
|
||||
.Returns((MismatchCause.Unknown, 0.5));
|
||||
|
||||
// Act
|
||||
var analysis = await _sut.AnalyzeAsync(mismatches, maxExamplesPerBucket: 5);
|
||||
|
||||
// Assert
|
||||
analysis.Buckets[MismatchCause.Unknown].Examples.Should().HaveCount(5);
|
||||
analysis.Buckets[MismatchCause.Unknown].Count.Should().Be(20);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_CalculatesPercentages()
|
||||
{
|
||||
// Arrange
|
||||
var mismatches = Enumerable.Range(0, 100)
|
||||
.Select(i => CreateMismatch($"func_{i}"))
|
||||
.ToList();
|
||||
|
||||
_causeInferrer.InferCauseAsync(Arg.Any<MatchResult>(), Arg.Any<CancellationToken>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
var index = int.Parse(callInfo.Arg<MatchResult>().SourceFunction.Name.Split('_')[1]);
|
||||
return index < 60
|
||||
? (MismatchCause.Inlining, 0.8)
|
||||
: (MismatchCause.LinkTimeOptimization, 0.7);
|
||||
});
|
||||
|
||||
// Act
|
||||
var analysis = await _sut.AnalyzeAsync(mismatches, maxExamplesPerBucket: 5);
|
||||
|
||||
// Assert
|
||||
analysis.Buckets[MismatchCause.Inlining].Percentage.Should().BeApproximately(60, 0.1);
|
||||
analysis.Buckets[MismatchCause.LinkTimeOptimization].Percentage.Should().BeApproximately(40, 0.1);
|
||||
}
|
||||
|
||||
private static MatchResult CreateMismatch(string functionName, ulong? size = null)
|
||||
{
|
||||
return new MatchResult
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RunId = Guid.NewGuid(),
|
||||
SecurityPairId = Guid.NewGuid(),
|
||||
SourceFunction = new FunctionIdentifier
|
||||
{
|
||||
Name = functionName,
|
||||
Address = 0x1000,
|
||||
Size = size,
|
||||
BuildId = "abc123",
|
||||
BinaryName = "test.so"
|
||||
},
|
||||
ExpectedTarget = new FunctionIdentifier
|
||||
{
|
||||
Name = functionName.Replace("@@GLIBC_2.17", "").Replace("@@GLIBC_2.34", ""),
|
||||
Address = 0x2000,
|
||||
BuildId = "def456",
|
||||
BinaryName = "test.so"
|
||||
},
|
||||
Outcome = MatchOutcome.FalseNegative
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for HeuristicMismatchCauseInferrer.
|
||||
/// </summary>
|
||||
public class HeuristicMismatchCauseInferrerTests
|
||||
{
|
||||
private readonly HeuristicMismatchCauseInferrer _sut = new();
|
||||
|
||||
[Theory]
|
||||
[InlineData("printf@@GLIBC_2.17", MismatchCause.SymbolVersioning)]
|
||||
[InlineData("malloc@@GLIBC_2.34", MismatchCause.SymbolVersioning)]
|
||||
public async Task InferCauseAsync_SymbolVersioning_DetectedCorrectly(string name, MismatchCause expected)
|
||||
{
|
||||
// Arrange
|
||||
var mismatch = CreateMismatch(name);
|
||||
|
||||
// Act
|
||||
var (cause, confidence) = await _sut.InferCauseAsync(mismatch);
|
||||
|
||||
// Assert
|
||||
cause.Should().Be(expected);
|
||||
confidence.Should().BeGreaterThan(0.8);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("small_func", 20, MismatchCause.Inlining)]
|
||||
[InlineData("tiny_func", 10, MismatchCause.Inlining)]
|
||||
public async Task InferCauseAsync_SmallFunction_InfersInlining(string name, ulong size, MismatchCause expected)
|
||||
{
|
||||
// Arrange
|
||||
var mismatch = CreateMismatch(name, size);
|
||||
|
||||
// Act
|
||||
var (cause, _) = await _sut.InferCauseAsync(mismatch);
|
||||
|
||||
// Assert
|
||||
cause.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("func.cold", MismatchCause.FunctionSplit)]
|
||||
[InlineData("func.isra.0", MismatchCause.FunctionSplit)]
|
||||
[InlineData("func.part.1", MismatchCause.FunctionSplit)]
|
||||
public async Task InferCauseAsync_SplitFunction_DetectedCorrectly(string name, MismatchCause expected)
|
||||
{
|
||||
// Arrange
|
||||
var mismatch = CreateMismatch(name, 500);
|
||||
|
||||
// Act
|
||||
var (cause, confidence) = await _sut.InferCauseAsync(mismatch);
|
||||
|
||||
// Assert
|
||||
cause.Should().Be(expected);
|
||||
confidence.Should().BeGreaterThan(0.7);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("__asan_load8", MismatchCause.SanitizerInstrumentation)]
|
||||
[InlineData("__tsan_write4", MismatchCause.SanitizerInstrumentation)]
|
||||
[InlineData("__ubsan_handle_divrem_overflow", MismatchCause.SanitizerInstrumentation)]
|
||||
public async Task InferCauseAsync_Sanitizer_DetectedCorrectly(string name, MismatchCause expected)
|
||||
{
|
||||
// Arrange
|
||||
var mismatch = CreateMismatch(name, 100);
|
||||
|
||||
// Act
|
||||
var (cause, confidence) = await _sut.InferCauseAsync(mismatch);
|
||||
|
||||
// Assert
|
||||
cause.Should().Be(expected);
|
||||
confidence.Should().BeGreaterThan(0.9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InferCauseAsync_UnknownPattern_ReturnsUnknown()
|
||||
{
|
||||
// Arrange
|
||||
var mismatch = CreateMismatch("normal_large_function", 1000);
|
||||
|
||||
// Act
|
||||
var (cause, confidence) = await _sut.InferCauseAsync(mismatch);
|
||||
|
||||
// Assert
|
||||
cause.Should().Be(MismatchCause.Unknown);
|
||||
confidence.Should().BeLessThan(0.5);
|
||||
}
|
||||
|
||||
private static MatchResult CreateMismatch(string name, ulong? size = null)
|
||||
{
|
||||
return new MatchResult
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RunId = Guid.NewGuid(),
|
||||
SecurityPairId = Guid.NewGuid(),
|
||||
SourceFunction = new FunctionIdentifier
|
||||
{
|
||||
Name = name,
|
||||
Address = 0x1000,
|
||||
Size = size,
|
||||
BuildId = "abc123",
|
||||
BinaryName = "test.so"
|
||||
},
|
||||
ExpectedTarget = new FunctionIdentifier
|
||||
{
|
||||
Name = name,
|
||||
Address = 0x2000,
|
||||
BuildId = "def456",
|
||||
BinaryName = "test.so"
|
||||
},
|
||||
Outcome = MatchOutcome.FalseNegative
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user