using System; using System.IO; using System.Text; using SabreTools.Matching; using Xunit; #pragma warning disable IDE0017 // Object initialization can be simplified #pragma warning disable IDE0230 // Use UTF-8 string literal namespace SabreTools.IO.Extensions.Test { public class StreamExtensionsTests { #region AlignToBoundary [Fact] public void AlignToBoundary_Null_False() { Stream? stream = null; byte alignment = 4; bool actual = stream.AlignToBoundary(alignment); Assert.False(actual); } [Fact] public void AlignToBoundary_Empty_False() { Stream? stream = new MemoryStream([]); byte alignment = 4; bool actual = stream.AlignToBoundary(alignment); Assert.False(actual); } [Fact] public void AlignToBoundary_EOF_False() { Stream? stream = new MemoryStream([0x01, 0x02]); byte alignment = 4; stream.Position = 1; bool actual = stream.AlignToBoundary(alignment); Assert.False(actual); } [Fact] public void AlignToBoundary_TooShort_False() { Stream? stream = new MemoryStream([0x01, 0x02]); byte alignment = 4; stream.Position = 1; bool actual = stream.AlignToBoundary(alignment); Assert.False(actual); } [Fact] public void AlignToBoundary_CanAlign_True() { Stream? stream = new MemoryStream([0x01, 0x02, 0x03, 0x04, 0x05]); byte alignment = 4; stream.Position = 1; bool actual = stream.AlignToBoundary(alignment); Assert.True(actual); } #endregion #region BlockCopy [Fact] public void BlockCopy_NullInput_False() { Stream? input = null; Stream? output = new MemoryStream(); int blockSize = 8192; bool actual = input.BlockCopy(output, blockSize); Assert.False(actual); } [Fact] public void BlockCopy_NullOutput_False() { Stream? input = new MemoryStream(); Stream? output = null; int blockSize = 8192; bool actual = input.BlockCopy(output, blockSize); Assert.False(actual); } [Fact] public void BlockCopy_UnreadableInput_False() { Stream? input = new MemoryStream(); input.Close(); Stream? output = new MemoryStream(); int blockSize = 8192; bool actual = input.BlockCopy(output, blockSize); Assert.False(actual); } [Fact] public void BlockCopy_UnwritableOutput_False() { Stream? input = new MemoryStream(); Stream? output = new MemoryStream([], writable: false); int blockSize = 8192; bool actual = input.BlockCopy(output, blockSize); Assert.False(actual); } [Fact] public void BlockCopy_InvalidBlockSize_False() { Stream? input = new MemoryStream(); Stream? output = new MemoryStream(); int blockSize = -1; bool actual = input.BlockCopy(output, blockSize); Assert.False(actual); } [Fact] public void BlockCopy_ValidInputs_True() { byte[] inputBytes = [0x00, 0x01, 0x02, 0x03]; Stream? input = new MemoryStream(inputBytes); MemoryStream? output = new MemoryStream(); int blockSize = 8192; bool actual = input.BlockCopy(output, blockSize); Assert.True(actual); byte[] actualBytes = output.ToArray(); Assert.True(actualBytes.SequenceEqual(inputBytes)); } #endregion #region InterleaveWith [Fact] public void Interleave_EvenNotExists_False() { string even = "NOT A REAL PATH"; string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string output = Guid.NewGuid().ToString(); bool actual = even.InterleaveWith(odd, output, 1); Assert.False(actual); } [Fact] public void Interleave_OddNotExists_False() { string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string odd = "NOT A REAL PATH"; string output = Guid.NewGuid().ToString(); bool actual = even.InterleaveWith(odd, output, 1); Assert.False(actual); } [Fact] public void Interleave_InvalidValue_False() { string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string output = Guid.NewGuid().ToString(); bool actual = even.InterleaveWith(odd, output, -1); Assert.False(actual); } [Theory] [InlineData(1, "TThhiiss ddooeessnn''tt mmaattcchh aannyytthhiinngg")] [InlineData(2, "ThThisis d doeoesnsn't't m matatchch a anynyththiningg")] [InlineData(4, "ThisThis doe doesn'tsn't mat match ach anythnythinging")] [InlineData(8, "This doeThis doesn't matsn't match anythch anythinging")] public void Interleave_SameLength_True(int blockSize, string expected) { string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string output = Guid.NewGuid().ToString(); bool actual = even.InterleaveWith(odd, output, blockSize); Assert.True(actual); string text = File.ReadAllText(output); Assert.Equal(expected, text); File.Delete(output); } [Fact] public void Interleave_DifferentLength_True() { string even = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string odd = Path.Combine(Environment.CurrentDirectory, "TestData", "file-to-compress.bin"); string output = Guid.NewGuid().ToString(); bool actual = even.InterleaveWith(odd, output, 1); Assert.True(actual); string text = File.ReadAllText(output); Assert.Equal("TThhiiss diose sjnu'stt maa tfcihl ea ntyhtahti nhgas a known set of hashes to make sure that everything with hashing is still working as anticipated.", text); File.Delete(output); } #endregion #region ReadFrom [Theory] [InlineData(true)] [InlineData(false)] public void ReadFrom_Null_Null(bool retainPosition) { Stream? stream = null; byte[]? actual = stream.ReadFrom(0, 1, retainPosition); Assert.Null(actual); } [Theory] [InlineData(true)] [InlineData(false)] public void ReadFrom_NonSeekable_Null(bool retainPosition) { Stream? stream = new NonSeekableStream(); byte[]? actual = stream.ReadFrom(0, 1, retainPosition); Assert.Null(actual); } [Theory] [InlineData(true)] [InlineData(false)] public void ReadFrom_Empty_Null(bool retainPosition) { Stream? stream = new MemoryStream([]); byte[]? actual = stream.ReadFrom(0, 1, retainPosition); Assert.Null(actual); } [Theory] [InlineData(-1, true)] [InlineData(2048, true)] [InlineData(-1, false)] [InlineData(2048, false)] public void ReadFrom_InvalidOffset_Null(long offset, bool retainPosition) { Stream? stream = new MemoryStream(new byte[1024]); byte[]? actual = stream.ReadFrom(offset, 1, retainPosition); Assert.Null(actual); } [Theory] [InlineData(-1, true)] [InlineData(2048, true)] [InlineData(-1, false)] [InlineData(2048, false)] public void ReadFrom_InvalidLength_Null(int length, bool retainPosition) { Stream? stream = new MemoryStream(new byte[1024]); byte[]? actual = stream.ReadFrom(0, length, retainPosition); Assert.Null(actual); } [Theory] [InlineData(true)] [InlineData(false)] public void ReadFrom_Valid_Filled(bool retainPosition) { Stream? stream = new MemoryStream(new byte[1024]); byte[]? actual = stream.ReadFrom(0, 512, retainPosition); Assert.NotNull(actual); Assert.Equal(512, actual.Length); if (retainPosition) Assert.Equal(0, stream.Position); else Assert.Equal(512, stream.Position); } #endregion #region ReadStringsFrom [Fact] public void ReadStringsFrom_Null_Null() { Stream? stream = null; var actual = stream.ReadStringsFrom(0, 1, 3); Assert.Null(actual); } [Fact] public void ReadStringsFrom_NonSeekable_Null() { Stream? stream = new NonSeekableStream(); var actual = stream.ReadStringsFrom(0, 1, 3); Assert.Null(actual); } [Fact] public void ReadStringsFrom_Empty_Null() { Stream? stream = new MemoryStream([]); var actual = stream.ReadStringsFrom(0, 1, 3); Assert.Null(actual); } [Theory] [InlineData(-1)] [InlineData(0)] [InlineData(2048)] public void ReadStringsFrom_InvalidLimit_Empty(int charLimit) { Stream? stream = new MemoryStream(new byte[1024]); var actual = stream.ReadStringsFrom(0, 1024, charLimit); Assert.NotNull(actual); Assert.Empty(actual); } [Fact] public void ReadStringsFrom_NoValidStrings_Empty() { Stream? stream = new MemoryStream(new byte[1024]); var actual = stream.ReadStringsFrom(0, 1024, 4); Assert.NotNull(actual); Assert.Empty(actual); } [Fact] public void ReadStringsFrom_AsciiStrings_Filled() { byte[]? bytes = [ .. Encoding.ASCII.GetBytes("TEST"), .. new byte[] { 0x00 }, .. Encoding.ASCII.GetBytes("TWO"), .. new byte[] { 0x00 }, .. Encoding.ASCII.GetBytes("DATA"), .. new byte[] { 0x00 }, ]; Stream? stream = new MemoryStream(bytes); var actual = stream.ReadStringsFrom(0, bytes.Length, 4); Assert.NotNull(actual); Assert.Equal(2, actual.Count); } [Fact] public void ReadStringsFrom_Latin1Strings_Filled() { byte[]? bytes = [ .. Encoding.Latin1.GetBytes("TEST"), .. new byte[] { 0x00 }, .. Encoding.Latin1.GetBytes("TWO"), .. new byte[] { 0x00 }, .. Encoding.Latin1.GetBytes("DATA"), .. new byte[] { 0x00 }, ]; Stream? stream = new MemoryStream(bytes); var actual = stream.ReadStringsFrom(0, bytes.Length, 4); Assert.NotNull(actual); Assert.Equal(2, actual.Count); } [Fact] public void ReadStringsFrom_UTF16_Filled() { byte[]? bytes = [ .. Encoding.Unicode.GetBytes("TEST"), .. new byte[] { 0x00 }, .. Encoding.Unicode.GetBytes("TWO"), .. new byte[] { 0x00 }, .. Encoding.Unicode.GetBytes("DATA"), .. new byte[] { 0x00 }, ]; Stream? stream = new MemoryStream(bytes); var actual = stream.ReadStringsFrom(0, bytes.Length, 4); Assert.NotNull(actual); Assert.Equal(2, actual.Count); } [Fact] public void ReadStringsFrom_Mixed_Filled() { byte[]? bytes = [ .. Encoding.ASCII.GetBytes("TEST1"), .. new byte[] { 0x00 }, .. Encoding.ASCII.GetBytes("TWO1"), .. new byte[] { 0x00 }, .. Encoding.ASCII.GetBytes("DATA1"), .. new byte[] { 0x00 }, .. Encoding.Latin1.GetBytes("TEST2"), .. new byte[] { 0x00 }, .. Encoding.Latin1.GetBytes("TWO2"), .. new byte[] { 0x00 }, .. Encoding.Latin1.GetBytes("DATA2"), .. new byte[] { 0x00 }, .. Encoding.Unicode.GetBytes("TEST3"), .. new byte[] { 0x00 }, .. Encoding.Unicode.GetBytes("TWO3"), .. new byte[] { 0x00 }, .. Encoding.Unicode.GetBytes("DATA3"), .. new byte[] { 0x00 }, ]; Stream? stream = new MemoryStream(bytes); var actual = stream.ReadStringsFrom(0, bytes.Length, 5); Assert.NotNull(actual); Assert.Equal(6, actual.Count); } #endregion #region SeekIfPossible [Fact] public void SeekIfPossible_NonSeekable_CurrentPosition() { var stream = new NonSeekableStream(); long actual = stream.SeekIfPossible(0); Assert.Equal(8, actual); } [Fact] public void SeekIfPossible_NonPositionable_InvalidPosition() { var stream = new NonPositionableStream(); long actual = stream.SeekIfPossible(0); Assert.Equal(-1, actual); } [Fact] public void SeekIfPossible_HiddenNonSeekable_InvalidPosition() { var stream = new HiddenNonSeekableStream(); long actual = stream.SeekIfPossible(0); Assert.Equal(-1, actual); } [Fact] public void SeekIfPossible_NonNegative_ValidPosition() { var stream = new MemoryStream(new byte[16], 0, 16, false, true); long actual = stream.SeekIfPossible(5); Assert.Equal(5, actual); } [Fact] public void SeekIfPossible_Negative_ValidPosition() { var stream = new MemoryStream(new byte[16], 0, 16, false, true); long actual = stream.SeekIfPossible(-3); Assert.Equal(13, actual); } [Theory] [InlineData(SeekOrigin.Begin)] [InlineData(SeekOrigin.Current)] [InlineData(SeekOrigin.End)] public void SeekIfPossible_NonSeekable_OriginTest(SeekOrigin origin) { var stream = new NonSeekableStream(); long actual = stream.SeekIfPossible(0, origin); Assert.Equal(8, actual); } [Theory] [InlineData(SeekOrigin.Begin)] [InlineData(SeekOrigin.Current)] [InlineData(SeekOrigin.End)] public void SeekIfPossible_NonPositionable_OriginTest(SeekOrigin origin) { var stream = new NonPositionableStream(); long actual = stream.SeekIfPossible(0, origin); Assert.Equal(-1, actual); } [Theory] [InlineData(SeekOrigin.Begin)] [InlineData(SeekOrigin.Current)] [InlineData(SeekOrigin.End)] public void SeekIfPossible_HiddenNonSeekable_OriginTest(SeekOrigin origin) { var stream = new HiddenNonSeekableStream(); long actual = stream.SeekIfPossible(0, origin); Assert.Equal(-1, actual); } [Theory] [InlineData(SeekOrigin.Begin, 5, 5)] [InlineData(SeekOrigin.Current, 5, 7)] [InlineData(SeekOrigin.End, -5, 11)] public void SeekIfPossible_Seekable_OriginTest(SeekOrigin origin, long offset, long expected) { var stream = new MemoryStream(new byte[16], 0, 16, false, true); stream.Position = 2; long actual = stream.SeekIfPossible(offset, origin); Assert.Equal(expected, actual); } #endregion #region SegmentValid [Fact] public void SegmentValid_Null_False() { Stream? stream = null; bool actual = stream.SegmentValid(0, 1); Assert.False(actual); } [Theory] [InlineData(-1)] [InlineData(2048)] public void SegmentValid_InvalidOffset_False(long offset) { Stream? stream = new MemoryStream(new byte[1024]); bool actual = stream.SegmentValid(offset, 1); Assert.False(actual); } [Theory] [InlineData(-1)] [InlineData(2048)] public void SegmentValid_InvalidLength_False(int length) { Stream? stream = new MemoryStream(new byte[1024]); bool actual = stream.SegmentValid(0, length); Assert.False(actual); } [Fact] public void SegmentValid_ValidSegment_True() { Stream? stream = new MemoryStream(new byte[1024]); bool actual = stream.SegmentValid(0, 1024); Assert.True(actual); } #endregion #region SplitToChunks [Fact] public void SplitToChunks_EmptyFileName_False() { string input = string.Empty; string outputDir = string.Empty; int size = 1; bool actual = input.SplitToChunks(outputDir, size); Assert.False(actual); } [Fact] public void SplitToChunks_InvalidFile_False() { string input = "INVALID"; string outputDir = string.Empty; int size = 1; bool actual = input.SplitToChunks(outputDir, size); Assert.False(actual); } [Fact] public void SplitToChunks_InvalidSize_False() { string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string outputDir = string.Empty; int size = 0; bool actual = input.SplitToChunks(outputDir, size); Assert.False(actual); } [Fact] public void SplitToChunks_Valid_True() { string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string outputDir = Guid.NewGuid().ToString(); int size = 16; bool actual = input.SplitToChunks(outputDir, size); Assert.True(actual); Assert.Equal(2, Directory.GetFiles(outputDir).Length); string baseFilename = Path.GetFileName(input); string text = File.ReadAllText(Path.Combine(outputDir, $"{baseFilename}.0")); Assert.Equal("This doesn't mat", text); text = File.ReadAllText(Path.Combine(outputDir, $"{baseFilename}.1")); Assert.Equal("ch anything", text); File.Delete($"{baseFilename}.0"); File.Delete($"{baseFilename}.1"); } #endregion #region SplitToEvenOdd [Fact] public void SplitToEvenOdd_EmptyFileName_False() { string input = string.Empty; string even = string.Empty; string odd = string.Empty; bool actual = input.SplitToEvenOdd(even, odd, 1); Assert.False(actual); } [Fact] public void SplitToEvenOdd_InvalidFile_False() { string input = "INVALID"; string even = string.Empty; string odd = string.Empty; bool actual = input.SplitToEvenOdd(even, odd, 1); Assert.False(actual); } [Fact] public void SplitToEvenOdd_InvalidValue_False() { string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string baseFileName = Guid.NewGuid().ToString(); string even = $"{baseFileName}.even"; string odd = $"{baseFileName}.odd"; bool actual = input.SplitToEvenOdd(even, odd, -1); Assert.False(actual); } [Theory] [InlineData(1, "Ti os' ac ntig", "hsdentmthayhn")] [InlineData(2, "Th dsn mchnyin", "isoe'tat athg")] [InlineData(4, "Thissn'tch aing", " doe matnyth")] [InlineData(8, "This doech anyth", "sn't mating")] public void SplitToEvenOdd_ValidFile_True(int blockSize, string expectedEven, string expectedOdd) { string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string baseFileName = Guid.NewGuid().ToString(); string even = $"{baseFileName}.even"; string odd = $"{baseFileName}.odd"; bool actual = input.SplitToEvenOdd(even, odd, blockSize); Assert.True(actual); string text = File.ReadAllText(even); Assert.Equal(expectedEven, text); text = File.ReadAllText(odd); Assert.Equal(expectedOdd, text); File.Delete(even); File.Delete(odd); } [Fact] public void SplitToEvenOdd_ValidFile_SameEvenOdd_False() { string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string baseFileName = Guid.NewGuid().ToString(); string evenOdd = $"{baseFileName}.all"; bool actual = input.SplitToEvenOdd(evenOdd, evenOdd, 4); Assert.False(actual); File.Delete(evenOdd); } #endregion #region Swap [Fact] public void Swap_EmptyFileName_False() { string input = string.Empty; string output = string.Empty; bool actual = input.Swap(output, SwapOperation.Byteswap); Assert.False(actual); } [Fact] public void Swap_InvalidFile_False() { string input = "INVALID"; string output = string.Empty; bool actual = input.Swap(output, SwapOperation.Byteswap); Assert.False(actual); } [Fact] public void Swap_InvalidType_False() { string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string output = Guid.NewGuid().ToString(); bool actual = input.Swap(output, (SwapOperation)int.MaxValue); Assert.False(actual); } [Fact] public void Swap_Valid_True() { string input = Path.Combine(Environment.CurrentDirectory, "TestData", "ascii.txt"); string output = Guid.NewGuid().ToString(); // Bitswap bool actual = input.Swap(output, SwapOperation.Bitswap); Assert.True(actual); byte[] actualBytes = File.ReadAllBytes(output); Assert.True(new byte[] { 0x2A, 0x16, 0x96, 0xCE, 0x04, 0x26, 0xF6, 0xA6, 0xCE, 0x76, 0xE4, 0x2E, 0x04, 0xB6, 0x86, 0x2E, 0xC6, 0x16, 0x04, 0x86, 0x76, 0x9E, 0x2E, 0x16, 0x96, 0x76, 0xE6 }.EqualsExactly(actualBytes)); // Byteswap actual = input.Swap(output, SwapOperation.Byteswap); Assert.True(actual); actualBytes = File.ReadAllBytes(output); Assert.True(new byte[] { 0x68, 0x54, 0x73, 0x69, 0x64, 0x20, 0x65, 0x6F, 0x6E, 0x73, 0x74, 0x27, 0x6D, 0x20, 0x74, 0x61, 0x68, 0x63, 0x61, 0x20, 0x79, 0x6E, 0x68, 0x74, 0x6E, 0x69, 0x67 }.EqualsExactly(actualBytes)); // Wordswap actual = input.Swap(output, SwapOperation.Wordswap); Assert.True(actual); actualBytes = File.ReadAllBytes(output); Assert.True(new byte[] { 0x69, 0x73, 0x54, 0x68, 0x6F, 0x65, 0x20, 0x64, 0x27, 0x74, 0x73, 0x6E, 0x61, 0x74, 0x20, 0x6D, 0x20, 0x61, 0x63, 0x68, 0x74, 0x68, 0x6E, 0x79, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes)); // WordByteswap actual = input.Swap(output, SwapOperation.WordByteswap); Assert.True(actual); actualBytes = File.ReadAllBytes(output); Assert.True(new byte[] { 0x73, 0x69, 0x68, 0x54, 0x65, 0x6F, 0x64, 0x20, 0x74, 0x27, 0x6E, 0x73, 0x74, 0x61, 0x6D, 0x20, 0x61, 0x20, 0x68, 0x63, 0x68, 0x74, 0x79, 0x6E, 0x69, 0x6E, 0x67 }.EqualsExactly(actualBytes)); File.Delete(output); } #endregion /// /// Represents a hidden non-seekable stream /// private class HiddenNonSeekableStream : Stream { public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => true; public override long Length => 16; public override long Position { get => 8; set => throw new NotSupportedException(); } public override void Flush() { throw new NotImplementedException(); } public override int Read(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } } /// /// Represents a non-seekable stream /// private class NonSeekableStream : Stream { public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => true; public override long Length => 16; public override long Position { get => 8; set => throw new NotSupportedException(); } public override void Flush() { throw new NotImplementedException(); } public override int Read(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } } /// /// Represents a non-seekable, non-positionable stream /// private class NonPositionableStream : Stream { public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => true; public override long Length => 16; public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override void Flush() { throw new NotImplementedException(); } public override int Read(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } } } }