Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests and fix failures #3

Merged
merged 5 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/DotUtils.StreamUtils/ChunkedBufferStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ public override void Write(byte[] buffer, int offset, int count)

public override void WriteByte(byte value)
{
_buffer[_position++] = value;

if (_position == _buffer.Length)
{
Flush();
}

_buffer[_position++] = value;
}

public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
Expand Down Expand Up @@ -100,7 +100,7 @@ public override void Write(ReadOnlySpan<byte> buffer)
do
{
int currentCount = Math.Min(buffer.Length, _buffer.Length - _position);
buffer.CopyTo(_buffer.AsSpan(_position, currentCount));
buffer.Slice(0, currentCount).CopyTo(_buffer.AsSpan(_position, currentCount));
_position += currentCount;
buffer = buffer.Slice(currentCount);

Expand All @@ -119,7 +119,7 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
do
{
int currentCount = Math.Min(buffer.Length, _buffer.Length - _position);
buffer.CopyTo(_buffer.AsMemory(_position, currentCount));
buffer.Slice(0, currentCount).CopyTo(_buffer.AsMemory(_position, currentCount));
_position += currentCount;
buffer = buffer.Slice(currentCount);

Expand Down
4 changes: 4 additions & 0 deletions src/DotUtils.StreamUtils/ConcatenatedReadStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
int bytesRead = await _streams.Peek().ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
{
#if NET
await _streams.Dequeue().DisposeAsync().ConfigureAwait(false);
#else
_streams.Dequeue().Dispose();
#endif
continue;
}

Expand Down
83 changes: 83 additions & 0 deletions test/DotUtils.StreamUtils.Tests/ChunkedBufferStreamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using static DotUtils.StreamUtils.Tests.StreamTestExtensions;

namespace DotUtils.StreamUtils.Tests
{
public class ChunkedBufferStreamTests
{
[Theory]
[MemberData(nameof(StreamTestExtensions.EnumerateReadFunctionTypes), MemberType = typeof(StreamTestExtensions))]
public void Write_CusesChunking(StreamFunctionType streamFunctionType)
{
int chunkSize = 3;
byte[] bytes = new byte[100];
using MemoryStream ms = new(bytes);
using Stream stream = new ChunkedBufferStream(ms, chunkSize);

WriteBytes writeBytes = stream.GetWriteFunc(streamFunctionType);

writeBytes(new byte[]{1,2});
bytes.Should().AllBeEquivalentTo(0);

writeBytes(new byte[] { 3, 4 });
bytes.Take(3).Should().BeEquivalentTo(new byte[] { 1, 2, 3 });
bytes.Skip(3).Should().AllBeEquivalentTo(0);

writeBytes(new byte[] { 5, 6 });
bytes.Take(6).Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6 });
bytes.Skip(6).Should().AllBeEquivalentTo(0);

writeBytes(new byte[] { 7, 8 });
bytes.Take(6).Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6 });
bytes.Skip(6).Should().AllBeEquivalentTo(0);

writeBytes(new byte[] { 9, 10, 11, 12, 13, 14, 15, 16 });
bytes.Take(15).Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 });
bytes.Skip(15).Should().AllBeEquivalentTo(0);

stream.Flush();
bytes.Take(16).Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 });
bytes.Skip(16).Should().AllBeEquivalentTo(0);
}

[Fact]
public void WriteByte_CusesChunking()
{
int chunkSize = 3;
var bytes = new byte[100];
using MemoryStream ms = new(bytes);
using Stream stream = new ChunkedBufferStream(ms, chunkSize);

stream.WriteByte(1);
bytes.Should().AllBeEquivalentTo(0);
stream.WriteByte(2);
bytes.Should().AllBeEquivalentTo(0);

stream.WriteByte(3);
bytes.Take(3).Should().BeEquivalentTo(new byte[] { 1, 2, 3 });
bytes.Skip(3).Should().AllBeEquivalentTo(0);

stream.WriteByte(4);
stream.WriteByte(5);
bytes.Take(3).Should().BeEquivalentTo(new byte[] { 1, 2, 3 });
bytes.Skip(3).Should().AllBeEquivalentTo(0);

stream.WriteByte(6);
bytes.Take(6).Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6 });
bytes.Skip(6).Should().AllBeEquivalentTo(0);

stream.WriteByte(7);
bytes.Take(6).Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6 });
bytes.Skip(6).Should().AllBeEquivalentTo(0);

stream.Flush();
bytes.Take(7).Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6, 7 });
bytes.Skip(7).Should().AllBeEquivalentTo(0);
}
}
}
109 changes: 109 additions & 0 deletions test/DotUtils.StreamUtils.Tests/ConcatenatedReadStreamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using static DotUtils.StreamUtils.Tests.StreamTestExtensions;

namespace DotUtils.StreamUtils.Tests
{
public class ConcatenatedReadStreamTests
{
[Fact]
public void ReadByte_ReadsStreamSequentialy()
{
using MemoryStream ms1 = new(new byte[]{1, 2, 3});
using MemoryStream ms2 = new(new byte[] { 4 });
using MemoryStream ms3 = new(new byte[] { 5, 6 });

Stream stream = new ConcatenatedReadStream(ms1, ms2, ms3);

stream.ReadByte().Should().Be(1);
stream.ReadByte().Should().Be(2);
stream.ReadByte().Should().Be(3);
stream.ReadByte().Should().Be(4);
stream.ReadByte().Should().Be(5);
stream.ReadByte().Should().Be(6);

// cannot read anymore
stream.ReadByte().Should().Be(-1);
}

[Theory]
[MemberData(nameof(StreamTestExtensions.EnumerateReadFunctionTypes), MemberType = typeof(StreamTestExtensions))]
public void Read_ReadsStreamSequentialy(StreamFunctionType streamFunctionType)
{
using MemoryStream ms1 = new(new byte[] { 1, 2, 3 });
using MemoryStream ms2 = new(new byte[] { 4 });
using MemoryStream ms3 = new(new byte[] { 5, 6 });
using MemoryStream ms4 = new(new byte[] { 7, 8, 9 });

Stream stream = new ConcatenatedReadStream(ms1, ms2, ms3, ms4);

ReadBytes readBytes = stream.GetReadFunc(streamFunctionType);

var readBuffer = new byte[2];

readBytes(readBuffer).Should().Be(2);
readBuffer.Should().BeEquivalentTo(new byte[] { 1, 2 });

readBytes(readBuffer).Should().Be(2);
readBuffer.Should().BeEquivalentTo(new byte[] { 3, 4 });

readBytes(readBuffer).Should().Be(2);
readBuffer.Should().BeEquivalentTo(new byte[] { 5, 6 });

readBytes(readBuffer).Should().Be(2);
readBuffer.Should().BeEquivalentTo(new byte[] { 7, 8 });

// zero out for assertion clarity.
Array.Clear(readBuffer);

readBytes(readBuffer).Should().Be(1);
readBuffer.Should().BeEquivalentTo(new byte[] { 9, 0 });

// zero out for assertion clarity.
Array.Clear(readBuffer);

// cannot read anymore
readBytes(readBuffer).Should().Be(0);
readBuffer.Should().BeEquivalentTo(new byte[] { 0, 0 });
}

[Theory]
[MemberData(nameof(StreamTestExtensions.EnumerateReadFunctionTypes), MemberType = typeof(StreamTestExtensions))]
public void Read_ReadsStreamSequentialy_UsingMultipleSubstreams(StreamFunctionType streamFunctionType)
{
using MemoryStream ms1 = new(new byte[] { 1, 2, 3 });
using MemoryStream ms2 = new(new byte[] { 4 });
using MemoryStream ms3 = new(new byte[] { 5, 6 });
using MemoryStream ms4 = new(new byte[] { 7, 8, 9, 10, 11 });

Stream stream = new ConcatenatedReadStream(ms1, ms2, ms3, ms4);

ReadBytes readBytes = stream.GetReadFunc(streamFunctionType);

var readBuffer = new byte[5];

readBytes(readBuffer).Should().Be(5);
readBuffer.Should().BeEquivalentTo(new byte[] { 1, 2, 3, 4, 5 });

readBytes(readBuffer).Should().Be(5);
readBuffer.Should().BeEquivalentTo(new byte[] { 6, 7, 8, 9, 10 });

// zero out for assertion clarity.
Array.Clear(readBuffer);

readBytes(readBuffer).Should().Be(1);
readBuffer.Should().BeEquivalentTo(new byte[] { 11, 0, 0, 0, 0 });

// zero out for assertion clarity.
Array.Clear(readBuffer);

// cannot read anymore
readBytes(readBuffer).Should().Be(0);
readBuffer.Should().BeEquivalentTo(new byte[] { 0, 0, 0, 0, 0 });
}
}
}
39 changes: 38 additions & 1 deletion test/DotUtils.StreamUtils.Tests/StreamExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace DotUtils.StreamUtils.Tests;
using System.IO.Compression;
using FluentAssertions;

namespace DotUtils.StreamUtils.Tests;

public class StreamExtensionsTests
{
Expand All @@ -10,4 +13,38 @@ public void ReadAtLeast_ThrowsOnEndOfStream()

Assert.Throws<InvalidDataException>(() => stream.ReadAtLeast(buffer, 0, 10, throwOnEndOfStream: true));
}

[Fact]
public void ReadToEnd_OnSeekableStream()
{
using MemoryStream ms1 = new(new byte[] { 1, 2, 3, 4, 5 });

ms1.ReadByte();
ms1.ReadByte();

ms1.ReadToEnd().Should().BeEquivalentTo(new[] { 3, 4, 5 });

// cannot read anymore
ms1.ReadByte().Should().Be(-1);
}

[Fact]
public void ReadToEnd_OnNonseekableStream()
{
using MemoryStream ms1 = new();
GZipStream zipStream = new GZipStream(ms1, CompressionMode.Compress);
zipStream.Write(new byte[] { 1, 2, 3, 4, 5 });
zipStream.Flush();

ms1.Position = 0;
using GZipStream unzipStream = new GZipStream(ms1, CompressionMode.Decompress);

unzipStream.ReadByte();
unzipStream.ReadByte();

unzipStream.ReadToEnd().Should().BeEquivalentTo(new[] { 3, 4, 5 });

// cannot read anymore
unzipStream.ReadByte().Should().Be(-1);
}
}
51 changes: 51 additions & 0 deletions test/DotUtils.StreamUtils.Tests/StreamTestExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotUtils.StreamUtils.Tests
{
public delegate int ReadBytes(byte[] bytes);

public delegate void WriteBytes(byte[] bytes);


public static class StreamTestExtensions
{
public enum StreamFunctionType
{
Array,
Span,
AsyncArray,
AsyncMemory,
}

public static ReadBytes GetReadFunc(this Stream stream, StreamFunctionType streamFunctionType)
=> streamFunctionType switch
{
StreamFunctionType.Array => (bytes) => stream.Read(bytes, 0, bytes.Length),
StreamFunctionType.Span => (bytes) => stream.Read(bytes),
// We do sync over async here - since we are interested in just exercising the method code in tests.
StreamFunctionType.AsyncArray => (bytes) =>
stream.ReadAsync(bytes, 0, bytes.Length, CancellationToken.None).Result,
StreamFunctionType.AsyncMemory => (bytes) => stream.ReadAsync(bytes, CancellationToken.None).Result,
_ => throw new ArgumentOutOfRangeException(nameof(streamFunctionType), streamFunctionType, null)
};

public static WriteBytes GetWriteFunc(this Stream stream, StreamFunctionType streamFunctionType)
=> streamFunctionType switch
{
StreamFunctionType.Array => (bytes) => stream.Write(bytes, 0, bytes.Length),
StreamFunctionType.Span => (bytes) => stream.Write(bytes),
// We do sync over async here - since we are interested in just exercising the method code in tests.
StreamFunctionType.AsyncArray => (bytes) =>
stream.WriteAsync(bytes, 0, bytes.Length, CancellationToken.None).Wait(),
StreamFunctionType.AsyncMemory => (bytes) => stream.WriteAsync(bytes, CancellationToken.None).AsTask().Wait(),
_ => throw new ArgumentOutOfRangeException(nameof(streamFunctionType), streamFunctionType, null)
};

public static IEnumerable<object[]> EnumerateReadFunctionTypes()
=> Enum.GetValues<StreamFunctionType>().Select(v => new object[] { v });
}
}
Loading
Loading