From 874f90d123e2d4bce556f3ad268c27aa8819c975 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Sat, 13 Jun 2026 00:25:30 -0400 Subject: [PATCH] test(engine): cover scaler zero-dimension source guard Verifies a source frame with height==0, width==0, or both does not throw (previously a divide-by-zero / NaN extents in ComputeFitRect) and instead yields a black, fully-opaque frame at the target resolution. --- .../ManagedNearestNeighborFrameScalerTests.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs index 589abd0..66e520a 100644 --- a/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs @@ -69,4 +69,41 @@ public class ManagedNearestNeighborFrameScalerTests var centerOffset = (1080 / 2 * 1920 + 1920 / 2) * 4; dst.Pixels.Span[centerOffset].Should().Be(0x10); } + + // ============================================================ + // Zero-dimension source guard + // ============================================================ + // + // A malformed/glitched NDI frame can report a zero dimension. Pre-fix this + // divided by zero in ComputeFitRect (Pillarbox/Letterbox) or drove a + // degenerate copy loop (Stretch). The guard must absorb it as a black frame + // at the target size rather than throw into the pipeline supervisor. + + [Theory] + [InlineData(0, 100)] + [InlineData(100, 0)] + [InlineData(0, 0)] + public void ZeroDimensionSource_DoesNotThrow_AndYieldsBlackTargetSizedFrame(int srcW, int srcH) + { + var scaler = new ManagedNearestNeighborFrameScaler(); + var src = Solid(Math.Max(srcW, 1), Math.Max(srcH, 1), 0x10, 0x20, 0x30, 0xFF) with + { + Width = srcW, + Height = srcH, + }; + + ProcessedFrame dst = default!; + var act = () => dst = scaler.Scale(src, 1280, 720, AspectMode.Pillarbox, 77); + act.Should().NotThrow(); + + dst.Width.Should().Be(1280); + dst.Height.Should().Be(720); + dst.Pixels.Length.Should().Be(1280 * 720 * 4); + dst.TimestampTicks.Should().Be(77); + // Black, fully opaque: B=G=R=0, A=0xFF. + dst.Pixels.Span[0].Should().Be(0x00); + dst.Pixels.Span[1].Should().Be(0x00); + dst.Pixels.Span[2].Should().Be(0x00); + dst.Pixels.Span[3].Should().Be(0xFF); + } }