using FluentAssertions; using System.Diagnostics; using Vav1Player.Container; namespace Vav1Player.Tests.Container; public class Av1BitstreamParserPerformanceTests { [Fact] public void ParseMp4Sample_WithLargeValidSample_ShouldCompleteWithinTimeLimit() { // Arrange - Create large but valid sample (5MB total) var obuCount = 100; var obuSize = 50_000; // 50KB per OBU var sampleData = CreateLargeSampleData(obuCount, obuSize); var stopwatch = Stopwatch.StartNew(); // Act var result = Av1BitstreamParser.ParseMp4Sample(sampleData); // Assert stopwatch.Stop(); result.Should().HaveCount(obuCount); result.All(obu => obu.Length == obuSize).Should().BeTrue(); // Performance assertion - should complete within 100ms for 5MB stopwatch.ElapsedMilliseconds.Should().BeLessThan(100, "because parsing 5MB of valid OBU data should be fast"); } [Fact] public void ParseMp4Sample_WithManySmallOBUs_ShouldScaleLinearly() { // Arrange - Test with different numbers of small OBUs var testCases = new[] { 100, 500, 1000 }; var timings = new List(); foreach (var obuCount in testCases) { var sampleData = CreateLargeSampleData(obuCount, 100); // 100 bytes per OBU var stopwatch = Stopwatch.StartNew(); // Act var result = Av1BitstreamParser.ParseMp4Sample(sampleData); stopwatch.Stop(); timings.Add(stopwatch.ElapsedMilliseconds); // Assert correctness result.Should().HaveCount(obuCount); } // Performance assertion - should scale roughly linearly // Time for 1000 OBUs should be less than 10x time for 100 OBUs var ratio = (double)timings[2] / Math.Max(timings[0], 1); ratio.Should().BeLessThan(10.0, "because parsing should scale roughly linearly with OBU count"); } [Fact] public void CombineOBUs_WithLargeDataSet_ShouldCompleteQuickly() { // Arrange - Create 1000 OBUs of 1KB each (headers without size field) var obuList = new List(); for (int i = 0; i < 1000; i++) { var obu = new byte[1024]; obu[0] = (byte)(i & 0xFC); // Ensure bit 1 (size field) is 0 // Fill with pattern for (int j = 1; j < obu.Length; j++) { obu[j] = (byte)((i + j) & 0xFF); } obuList.Add(obu); } var stopwatch = Stopwatch.StartNew(); // Act var result = Av1BitstreamParser.CombineOBUs(obuList); // Assert stopwatch.Stop(); // Should be larger than original due to added size fields (~1MB + size fields) result.Length.Should().BeGreaterThan(1000 * 1024); // Original 1MB + size overhead // Performance assertion - should complete within 50ms for ~1MB stopwatch.ElapsedMilliseconds.Should().BeLessThan(50, "because combining 1MB of OBU data should be fast"); } [Fact] public void ParseMp4Sample_WithMaxSizeLEB128_ShouldHandleEfficiently() { // Arrange - Create sample with maximum reasonable LEB128 values var samples = new List(); // Test various LEB128 sizes: 1-byte, 2-byte, 3-byte samples.Add(CreateSampleWithLEB128Size(127, 1)); // 1-byte LEB128 samples.Add(CreateSampleWithLEB128Size(16383, 2)); // 2-byte LEB128 samples.Add(CreateSampleWithLEB128Size(2097151, 3)); // 3-byte LEB128 foreach (var sampleData in samples) { var stopwatch = Stopwatch.StartNew(); // Act var result = Av1BitstreamParser.ParseMp4Sample(sampleData); stopwatch.Stop(); // Assert result.Should().HaveCount(1); // Performance assertion - even large LEB128 should parse quickly stopwatch.ElapsedMilliseconds.Should().BeLessThan(10, "because LEB128 parsing should be efficient regardless of size"); } } [Fact] public void ParseMp4Sample_WithInvalidData_ShouldFailFast() { // Arrange - Various types of invalid data that should fail quickly var invalidSamples = new[] { new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80 }, // Truncated LEB128 new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F }, // Oversized LEB128 new byte[] { 0x80, 0x00 }, // Invalid multi-byte zero new byte[0], // Empty data new byte[] { 0x10, 0x01, 0x02 } // Length=16 but only 2 bytes }; foreach (var invalidData in invalidSamples) { var stopwatch = Stopwatch.StartNew(); // Act var result = Av1BitstreamParser.ParseMp4Sample(invalidData); stopwatch.Stop(); // Assert result.Should().BeEmpty(); // Performance assertion - should fail fast stopwatch.ElapsedMilliseconds.Should().BeLessThan(5, "because invalid data should be rejected quickly"); } } [Fact] public void ParseMp4Sample_StressTest_ShouldHandleRepeatedParsing() { // Arrange - Medium-sized sample that will be parsed many times var sampleData = CreateLargeSampleData(50, 1000); // 50 OBUs of 1KB each const int iterations = 1000; var stopwatch = Stopwatch.StartNew(); // Act - Parse the same data many times for (int i = 0; i < iterations; i++) { var result = Av1BitstreamParser.ParseMp4Sample(sampleData); result.Should().HaveCount(50); // Sanity check } // Assert stopwatch.Stop(); var avgTimePerParse = stopwatch.ElapsedMilliseconds / (double)iterations; // Performance assertion - average parse time should be reasonable avgTimePerParse.Should().BeLessThan(1.0, "because repeated parsing of the same data should be consistently fast"); } [Theory] [InlineData(10, 100)] // Small: 10 OBUs × 100 bytes [InlineData(100, 1000)] // Medium: 100 OBUs × 1KB [InlineData(50, 10000)] // Large: 50 OBUs × 10KB public void ParseMp4Sample_VariousDataSizes_ShouldCompleteReasonably(int obuCount, int obuSize) { // Arrange var sampleData = CreateLargeSampleData(obuCount, obuSize); var totalSize = obuCount * obuSize; var stopwatch = Stopwatch.StartNew(); // Act var result = Av1BitstreamParser.ParseMp4Sample(sampleData); // Assert stopwatch.Stop(); result.Should().HaveCount(obuCount); // Performance target: should complete within reasonable time (less than 1 second for reasonable data sizes) stopwatch.ElapsedMilliseconds.Should().BeLessThan(1000, $"because parsing {totalSize} bytes should complete within 1 second (actual: {stopwatch.ElapsedMilliseconds}ms)"); } private static byte[] CreateLargeSampleData(int obuCount, int obuSize) { var sampleList = new List(); for (int i = 0; i < obuCount; i++) { // Add LEB128 length var lengthBytes = EncodeLEB128((uint)obuSize); sampleList.AddRange(lengthBytes); // Add OBU data with pattern for (int j = 0; j < obuSize; j++) { sampleList.Add((byte)((i + j) & 0xFF)); } } return sampleList.ToArray(); } private static byte[] CreateSampleWithLEB128Size(uint value, int expectedBytes) { var lengthBytes = EncodeLEB128(value); lengthBytes.Length.Should().Be(expectedBytes, $"LEB128 encoding of {value} should use {expectedBytes} bytes"); var obuData = new byte[value]; for (int i = 0; i < obuData.Length; i++) { obuData[i] = (byte)(i & 0xFF); } var result = new byte[lengthBytes.Length + obuData.Length]; Array.Copy(lengthBytes, 0, result, 0, lengthBytes.Length); Array.Copy(obuData, 0, result, lengthBytes.Length, obuData.Length); return result; } private static byte[] EncodeLEB128(uint value) { var bytes = new List(); while (value >= 0x80) { bytes.Add((byte)((value & 0x7F) | 0x80)); value >>= 7; } bytes.Add((byte)(value & 0x7F)); return bytes.ToArray(); } }