277 lines
8.9 KiB
C#
277 lines
8.9 KiB
C#
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
|
|
};
|
|
}
|
|
}
|