Skip to content

Commit

Permalink
Merge pull request #3 from dotutils/dev/add-unit-tests
Browse files Browse the repository at this point in the history
Add unit tests and fix failures
  • Loading branch information
JanKrivanek authored Sep 27, 2024
2 parents 6ad2e10 + 47707f0 commit 6e53f44
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 5 deletions.
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

0 comments on commit 6e53f44

Please sign in to comment.