From d0b5fdc7bf255be95026482296a016a36f162b29 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Fri, 8 Mar 2024 23:08:17 -0700 Subject: [PATCH] Add failing test; fix BufferedStreamSource bug where .Position is queried on non-seekable streams, causing a failure. --- src/Imageflow/Fluent/BufferedStreamSource.cs | 2 +- tests/Imageflow.Test/NonSeekableReadStream.cs | 62 +++++++++++++++++++ tests/Imageflow.Test/TestApi.cs | 6 +- 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/Imageflow.Test/NonSeekableReadStream.cs diff --git a/src/Imageflow/Fluent/BufferedStreamSource.cs b/src/Imageflow/Fluent/BufferedStreamSource.cs index cd2b69b..6fca985 100644 --- a/src/Imageflow/Fluent/BufferedStreamSource.cs +++ b/src/Imageflow/Fluent/BufferedStreamSource.cs @@ -8,7 +8,7 @@ public sealed class BufferedStreamSource : IAsyncMemorySource, IMemorySource { private BufferedStreamSource(Stream stream, bool disposeUnderlying, bool seekToStart) { - if (stream.Position != 0 && !stream.CanSeek && seekToStart) + if (!stream.CanSeek && seekToStart) { throw new ArgumentException("Stream must be seekable if seekToStart is true"); } diff --git a/tests/Imageflow.Test/NonSeekableReadStream.cs b/tests/Imageflow.Test/NonSeekableReadStream.cs new file mode 100644 index 0000000..c3fac57 --- /dev/null +++ b/tests/Imageflow.Test/NonSeekableReadStream.cs @@ -0,0 +1,62 @@ +namespace Imageflow.Test; + +using System; +using System.IO; + +public class NonSeekableReadStream : Stream +{ + private byte[] data; + private long position = 0; // Current position within the data + + public NonSeekableReadStream(byte[] dataSource) + { + data = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); + } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + + public override long Length => data.Length; + + public override long Position + { + get => throw new NotSupportedException("Seeking not supported."); + set => throw new NotSupportedException("Seeking not supported."); + } + + public override void Flush() + { + // No-op for read-only stream + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException("offset or count is negative."); + if (buffer.Length - offset < count) throw new ArgumentException("The sum of offset and count is greater than the buffer length."); + + int available = data.Length - (int)position; + if (available <= 0) return 0; // End of stream + + int toCopy = Math.Min(available, count); + Array.Copy(data, position, buffer, offset, toCopy); + position += toCopy; + return toCopy; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seeking not supported."); + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Setting length not supported."); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Writing not supported."); + } +} diff --git a/tests/Imageflow.Test/TestApi.cs b/tests/Imageflow.Test/TestApi.cs index cba1968..e3b5464 100644 --- a/tests/Imageflow.Test/TestApi.cs +++ b/tests/Imageflow.Test/TestApi.cs @@ -332,7 +332,7 @@ public async Task TestBuildCommandStringWithStreamsAndWatermarksLegacy() var stream1 = new BufferedStream(new MemoryStream(imageBytes)); Assert.Equal(137, stream1.ReadByte()); stream1.Seek(0, SeekOrigin.Begin); - var stream2 = new BufferedStream(new MemoryStream(imageBytes)); + var stream2 = new NonSeekableReadStream(imageBytes); var stream3 = new BufferedStream(new MemoryStream(imageBytes)); using (var b = new ImageJob()) { @@ -361,12 +361,12 @@ public async Task TestBuildCommandStringWithStreamsAndWatermarks() var stream1 = new BufferedStream(new MemoryStream(imageBytes)); Assert.Equal(137, stream1.ReadByte()); stream1.Seek(0, SeekOrigin.Begin); - var stream2 = new BufferedStream(new MemoryStream(imageBytes)); + var stream2 = new NonSeekableReadStream(imageBytes); var stream3 = new BufferedStream(new MemoryStream(imageBytes)); using var b = new ImageJob(); var watermarks = new List(); watermarks.Add(new InputWatermark(BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream1), new WatermarkOptions())); - watermarks.Add(new InputWatermark(BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream2), new WatermarkOptions().SetGravity(new ConstraintGravity(100, 100)))); + watermarks.Add(new InputWatermark(BufferedStreamSource.UseStreamRemainderAndDisposeWithSource(stream2), new WatermarkOptions().SetGravity(new ConstraintGravity(100, 100)))); var r = await b.BuildCommandString( BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream3),