From e7ca89c57bef7f8ba91d4424d64a2b66d964a413 Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Wed, 17 Dec 2025 13:41:38 +0200 Subject: [PATCH 1/8] Added unit tests Signed-off-by: Zafer Balkan --- .github/workflows/unit-testing.yml | 41 +++ README.md | 4 + TechnitiumLibrary.UnitTests/MSTestSettings.cs | 3 + .../BinaryReaderExtensionsTests.cs | 146 ++++++++ .../BinaryWriterExtensionsTests.cs | 174 ++++++++++ .../TechnitiumLibrary.IO/JointTests.cs | 186 +++++++++++ .../TechnitiumLibrary.IO/OffsetStreamTests.cs | 245 ++++++++++++++ .../TechnitiumLibrary.IO/PackageItemTests.cs | 315 ++++++++++++++++++ .../TechnitiumLibrary.IO/PackageTests.cs | 241 ++++++++++++++ .../TechnitiumLibrary.IO/PipeTests.cs | 175 ++++++++++ .../StreamExtensionsTests.cs | 188 +++++++++++ .../WriteBufferedStreamTests.cs | 279 ++++++++++++++++ .../TechnitiumLibrary.UnitTests.csproj | 15 + TechnitiumLibrary.sln | 6 + 14 files changed, 2018 insertions(+) create mode 100644 .github/workflows/unit-testing.yml create mode 100644 TechnitiumLibrary.UnitTests/MSTestSettings.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs create mode 100644 TechnitiumLibrary.UnitTests/TechnitiumLibrary.UnitTests.csproj diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml new file mode 100644 index 00000000..11ef5256 --- /dev/null +++ b/.github/workflows/unit-testing.yml @@ -0,0 +1,41 @@ +name: Unit testing (Windows / MSBuild) + +on: + workflow_dispatch: + push: + branches: ["master"] + pull_request: + branches: ["master"] + schedule: + - cron: "0 0 * * 0" # weekly, Sunday 00:00 UTC + +permissions: + contents: read + +jobs: + test: + runs-on: windows-latest + + env: + SOLUTION_NAME: TechnitiumLibrary.sln + BUILD_CONFIGURATION: Debug + + steps: + - uses: actions/checkout@v4 + + - name: Install .NET 9 SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v1 + + - name: Restore + run: msbuild ${{ env.SOLUTION_NAME }} /t:Restore + + - name: Build + run: msbuild ${{ env.SOLUTION_NAME }} /m /p:Configuration=${{ env.BUILD_CONFIGURATION }} + + - name: Test (msbuild) + run: msbuild TechnitiumLibrary.Tests\TechnitiumLibrary.Tests.csproj /t:Test /p:Configuration=${{ env.BUILD_CONFIGURATION }} \ No newline at end of file diff --git a/README.md b/README.md index b329873a..ad146a6c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # TechnitiumLibrary A library for .net based applications. + +## Quality Assurance + +[![Unit testing (Windows / MSBuild)](https://github.com/TechnitiumSoftware/TechnitiumLibrary/actions/workflows/unit-testing.yml/badge.svg)](https://github.com/TechnitiumSoftware/TechnitiumLibrary/actions/workflows/unit-testing.yml) \ No newline at end of file diff --git a/TechnitiumLibrary.UnitTests/MSTestSettings.cs b/TechnitiumLibrary.UnitTests/MSTestSettings.cs new file mode 100644 index 00000000..e466aa12 --- /dev/null +++ b/TechnitiumLibrary.UnitTests/MSTestSettings.cs @@ -0,0 +1,3 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs new file mode 100644 index 00000000..33356011 --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs @@ -0,0 +1,146 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Linq; +using System.Text; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class BinaryReaderExtensionsTests + { + private static BinaryReader ReaderOf(params byte[] bytes) + { + return new BinaryReader(new MemoryStream(bytes)); + } + + // ----------------------------------------------- + // ReadLength() + // ----------------------------------------------- + + [TestMethod] + public void ReadLength_ShouldReadSingleByteLengths() + { + // GIVEN + BinaryReader reader = ReaderOf(0x05); + + // WHEN + int length = reader.ReadLength(); + + // THEN + Assert.AreEqual(5, length); + Assert.AreEqual(1, reader.BaseStream.Position); + } + + [TestMethod] + public void ReadLength_ShouldReadMultiByteBigEndianLengths() + { + // GIVEN + // 0x82 => 2-byte length follows → value = 0x01 0x2C → 300 decimal + BinaryReader reader = ReaderOf(0x82, 0x01, 0x2C); + + // WHEN + int length = reader.ReadLength(); + + // THEN + Assert.AreEqual(300, length); + Assert.AreEqual(3, reader.BaseStream.Position); + } + + [TestMethod] + public void ReadLength_ShouldThrow_WhenLengthPrefixTooLarge() + { + // GIVEN + // lower 7 bits = 0x05, meaning "next 5 bytes", exceeding allowed 4 + BinaryReader reader = ReaderOf(0x85); + + // WHEN-THEN + Assert.ThrowsExactly(() => reader.ReadLength()); + } + + // ----------------------------------------------- + // ReadBuffer() + // ----------------------------------------------- + + [TestMethod] + public void ReadBuffer_ShouldReturnBytes_WhenLengthPrefixed() + { + // GIVEN + // length=3, then bytes 0xAA, 0xBB, 0xCC + BinaryReader reader = ReaderOf(0x03, 0xAA, 0xBB, 0xCC); + + // WHEN + byte[] data = reader.ReadBuffer(); + + // THEN + Assert.HasCount(3, data); + CollectionAssert.AreEqual(new byte[] { 0xAA, 0xBB, 0xCC }, data); + } + + // ----------------------------------------------- + // ReadShortString() + // ----------------------------------------------- + + [TestMethod] + public void ReadShortString_ShouldDecodeUtf8StringCorrectly() + { + // GIVEN + string text = "Hello"; + byte[] encoded = Encoding.UTF8.GetBytes(text); + + byte[] bytes = new byte[] { (byte)encoded.Length }.Concat(encoded).ToArray(); + BinaryReader reader = ReaderOf(bytes); + + // WHEN + string result = reader.ReadShortString(); + + // THEN + Assert.AreEqual(text, result); + } + + [TestMethod] + public void ReadShortString_ShouldUseSpecifiedEncoding() + { + // GIVEN + string text = "Å"; + Encoding encoding = Encoding.UTF32; + byte[] encoded = encoding.GetBytes(text); + + byte[] bytes = new byte[] { (byte)encoded.Length }.Concat(encoded).ToArray(); + BinaryReader reader = ReaderOf(bytes); + + // WHEN + string result = reader.ReadShortString(encoding); + + // THEN + Assert.AreEqual(text, result); + } + + // ----------------------------------------------- + // ReadDateTime() + // ----------------------------------------------- + + [TestMethod] + public void ReadDateTime_ShouldConvertEpochMilliseconds() + { + // GIVEN + DateTime expected = new DateTime(2024, 01, 01, 12, 00, 00, DateTimeKind.Utc); + long millis = (long)(expected - DateTime.UnixEpoch).TotalMilliseconds; + + byte[] encoded = BitConverter.GetBytes(millis); + + // Normalize to little-endian, which BinaryReader expects + if (!BitConverter.IsLittleEndian) + Array.Reverse(encoded); + + BinaryReader reader = ReaderOf(encoded); + + // WHEN + DateTime result = reader.ReadDateTime(); + + // THEN + Assert.AreEqual(expected, result); + } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs new file mode 100644 index 00000000..c48dd2f3 --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs @@ -0,0 +1,174 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Linq; +using System.Text; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class BinaryWriterExtensionsTests + { + private static (BinaryWriter writer, MemoryStream stream) CreateWriter() + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + return (bw, ms); + } + + private static byte[] WrittenBytes(MemoryStream ms) => + ms.ToArray(); + + // --------------------------------------- + // WriteLength() tests + // --------------------------------------- + + [TestMethod] + public void WriteLength_ShouldEncodeSingleByte_WhenLessThan128() + { + // GIVEN + (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + + // WHEN + bw.WriteLength(42); + + // THEN + CollectionAssert.AreEqual(new byte[] { 42 }, WrittenBytes(ms)); + } + + [TestMethod] + public void WriteLength_ShouldEncodeMultiByte_BigEndianForm() + { + // GIVEN + (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + + // WHEN + // length = 0x0000012C (300 decimal) + bw.WriteLength(300); + + // THEN + // Prefix = 0x82 (2 bytes follow) + // Then big-endian 01 2C + CollectionAssert.AreEqual( + new byte[] { 0x82, 0x01, 0x2C }, + WrittenBytes(ms) + ); + } + + // --------------------------------------- + // WriteBuffer() + // --------------------------------------- + + [TestMethod] + public void WriteBuffer_ShouldPrefixLength_AndWriteBytes() + { + // GIVEN + (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + byte[] data = new byte[] { 0xAA, 0xBB, 0xCC }; + + // WHEN + bw.WriteBuffer(data); + + // THEN + CollectionAssert.AreEqual( + new byte[] { 0x03, 0xAA, 0xBB, 0xCC }, + WrittenBytes(ms) + ); + } + + [TestMethod] + public void WriteBuffer_WithOffset_ShouldWriteExpectedSegment() + { + // GIVEN + (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + byte[] data = new byte[] { 1, 2, 3, 4, 5 }; + + // WHEN + bw.WriteBuffer(data, offset: 1, count: 3); + + // THEN + CollectionAssert.AreEqual( + new byte[] { 0x03, 2, 3, 4 }, + WrittenBytes(ms) + ); + } + + // --------------------------------------- + // WriteShortString() + // --------------------------------------- + + [TestMethod] + public void WriteShortString_ShouldWriteUtf8EncodedWithLength() + { + // GIVEN + (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + string text = "Hello"; + byte[] utf8 = Encoding.UTF8.GetBytes(text); + + // WHEN + bw.WriteShortString(text); + + // THEN + byte[] expected = new byte[] { (byte)utf8.Length } + .Concat(utf8) + .ToArray(); + + CollectionAssert.AreEqual(expected, WrittenBytes(ms)); + } + + [TestMethod] + public void WriteShortString_ShouldUseSpecifiedEncoding() + { + // GIVEN + (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + string text = "Å"; + Encoding enc = Encoding.UTF32; + byte[] bytes = enc.GetBytes(text); + + // WHEN + bw.WriteShortString(text, enc); + + // THEN + byte[] expected = new byte[] { (byte)bytes.Length } + .Concat(bytes) + .ToArray(); + + CollectionAssert.AreEqual(expected, WrittenBytes(ms)); + } + + [TestMethod] + public void WriteShortString_ShouldThrow_WhenStringTooLong() + { + // GIVEN + (BinaryWriter bw, MemoryStream _) = CreateWriter(); + string input = new string('x', 256); // UTF-8 => 256 bytes + + // WHEN–THEN + Assert.ThrowsExactly(() => + bw.WriteShortString(input) + ); + } + + // --------------------------------------- + // Write(DateTime) + // --------------------------------------- + + [TestMethod] + public void WriteDate_ShouldEncodeMillisecondsFromUnixEpoch() + { + // GIVEN + DateTime expected = new DateTime(2024, 1, 2, 12, 00, 00, DateTimeKind.Utc); + long millis = (long)(expected - DateTime.UnixEpoch).TotalMilliseconds; + + byte[] bytes = BitConverter.GetBytes(millis); + (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + + // WHEN + bw.Write(expected); + + // THEN + CollectionAssert.AreEqual(bytes, WrittenBytes(ms)); + } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs new file mode 100644 index 00000000..3faa4dbd --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs @@ -0,0 +1,186 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Threading.Tasks; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class JointTests + { + private static async Task WaitForCopyCompletion() + { + // The copy tasks run asynchronously and Joint.Dispose() executes + // when either side reaches EOF. Wait slightly longer than default buffering time. + await Task.Delay(80); + } + + // --------------------------------------- + // Constructor and property access + // --------------------------------------- + + [TestMethod] + public void Constructor_ShouldStoreStreams() + { + // GIVEN + MemoryStream s1 = new MemoryStream(); + MemoryStream s2 = new MemoryStream(); + + // WHEN + Joint joint = new Joint(s1, s2); + + // THEN + Assert.AreSame(s1, joint.Stream1); + Assert.AreSame(s2, joint.Stream2); + } + + // --------------------------------------- + // Data transfer behavior + // --------------------------------------- + + [TestMethod] + public async Task Start_ShouldCopyData_FromStream1ToStream2() + { + // GIVEN + byte[] sourceData = new byte[] { 1, 2, 3, 4 }; + using MemoryStream s1 = new MemoryStream(sourceData); + using MemoryStream s2 = new MemoryStream(); + using Joint joint = new Joint(s1, s2); + + // WHEN + joint.Start(); + await WaitForCopyCompletion(); + + // THEN + byte[] result = s2.ToArray(); + CollectionAssert.AreEqual(sourceData, result); + } + + [TestMethod] + public async Task Start_ShouldCopyData_FromStream2ToStream1() + { + // GIVEN + byte[] sourceData = new byte[] { 7, 8, 9 }; + using MemoryStream s1 = new MemoryStream(); + using MemoryStream s2 = new MemoryStream(sourceData); + using Joint joint = new Joint(s1, s2); + + // WHEN + joint.Start(); + await WaitForCopyCompletion(); + + // THEN + byte[] result = s2.ToArray(); + CollectionAssert.AreEqual(sourceData, result); + } + + // --------------------------------------- + // Empty stream scenarios + // --------------------------------------- + + [TestMethod] + public async Task Start_ShouldSupportEmptyStreams() + { + // GIVEN + using MemoryStream s1 = new MemoryStream(); + using MemoryStream s2 = new MemoryStream(); + using Joint joint = new Joint(s1, s2); + + // WHEN + joint.Start(); + await WaitForCopyCompletion(); + + // THEN + byte[] buff1 = s1.ToArray(); + byte[] buff2 = s2.ToArray(); + + CollectionAssert.AreEqual(Array.Empty(), buff1); + CollectionAssert.AreEqual(Array.Empty(), buff2); + } + + // --------------------------------------- + // Disposal semantics + // --------------------------------------- + + [TestMethod] + public async Task Dispose_ShouldCloseStreams() + { + // GIVEN + MemoryStream s1 = new MemoryStream(new byte[] { 10 }); + MemoryStream s2 = new MemoryStream(new byte[] { 20 }); + Joint joint = new Joint(s1, s2); + + // WHEN + joint.Dispose(); + await WaitForCopyCompletion(); + + // THEN + Assert.ThrowsExactly(() => { long _ = s1.Length; }); + Assert.ThrowsExactly(() => { long _ = s2.Length; }); + } + + [TestMethod] + public void Dispose_ShouldBeIdempotent() + { + // GIVEN + MemoryStream s1 = new MemoryStream(); + MemoryStream s2 = new MemoryStream(); + Joint joint = new Joint(s1, s2); + + // WHEN + joint.Dispose(); + joint.Dispose(); + joint.Dispose(); // Should not throw + + // THEN + Assert.IsTrue(true); // No exception was thrown + } + + // --------------------------------------- + // Disposal callback behavior + // --------------------------------------- + + [TestMethod] + public void Dispose_ShouldRaiseDisposingEvent() + { + // GIVEN + using MemoryStream s1 = new MemoryStream(); + using MemoryStream s2 = new MemoryStream(); + Joint joint = new Joint(s1, s2); + + bool raised = false; + joint.Disposing += (_, __) => raised = true; + + // WHEN + joint.Dispose(); + + // THEN + Assert.IsTrue(raised); + } + + // --------------------------------------- + // Concurrency semantics + // --------------------------------------- + + [TestMethod] + public async Task Start_ShouldDisposeOnce_WhenBothDirectionsComplete() + { + // GIVEN + using MemoryStream s1 = new MemoryStream(new byte[] { 1 }); + using MemoryStream s2 = new MemoryStream(new byte[] { 2 }); + + using Joint joint = new Joint(s1, s2); + + int disposedCount = 0; + joint.Disposing += (_, __) => disposedCount++; + + // WHEN + joint.Start(); + await WaitForCopyCompletion(); + + // THEN + Assert.AreEqual(1, disposedCount, "Disposing must fire only once"); + } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs new file mode 100644 index 00000000..fdf9880c --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs @@ -0,0 +1,245 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Threading.Tasks; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class OffsetStreamTests + { + private static MemoryStream CreateStream(byte[] data) => new MemoryStream(data, writable: true); + + // ------------------------------------------------------ + // CONSTRUCTION & BASIC METADATA + // ------------------------------------------------------ + + [TestMethod] + public void Constructor_ShouldExposeCorrectBasicProperties() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4, 5 }); + + // WHEN + OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 3); + + // THEN + Assert.AreEqual(3, offsetStream.Length); + Assert.AreEqual(0, offsetStream.Position); + Assert.IsTrue(offsetStream.CanRead); + Assert.IsTrue(offsetStream.CanSeek); + } + + [TestMethod] + public void Constructor_ShouldRespectReadOnlyFlag() + { + // GIVEN + MemoryStream source = CreateStream(new byte[10]); + + // WHEN + OffsetStream offsetStream = new OffsetStream(source, readOnly: true); + + // THEN + Assert.IsFalse(offsetStream.CanWrite); + } + + // ------------------------------------------------------ + // READ OPERATIONS + // ------------------------------------------------------ + + [TestMethod] + public void Read_ShouldReturnSegmentWithinBounds() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 10, 20, 30, 40, 50 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 3); + + byte[] buffer = new byte[10]; + + // WHEN + int readCount = offsetStream.Read(buffer, 0, 10); + + // THEN + Assert.AreEqual(3, readCount); + CollectionAssert.AreEqual(new byte[] { 20, 30, 40 }, buffer[..3]); + } + + [TestMethod] + public void Read_ShouldReturnZero_WhenPastLength() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 2, length: 1); + + byte[] buffer = new byte[5]; + offsetStream.Position = 1; + + // WHEN + int count = offsetStream.Read(buffer, 0, 5); + + // THEN + Assert.AreEqual(0, count); + } + + [TestMethod] + public void ReadAsync_ShouldReturnCorrectData() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 9, 8, 7, 6 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); + byte[] buffer = new byte[10]; + + // WHEN + int count = offsetStream.ReadAsync(buffer, 0, 10, TestContext.CancellationToken).Result; + + // THEN + Assert.AreEqual(2, count); + CollectionAssert.AreEqual(new byte[] { 8, 7 }, buffer[..2]); + } + + // ------------------------------------------------------ + // WRITE OPERATIONS + // ------------------------------------------------------ + + [TestMethod] + public void Write_ShouldPlaceDataAtOffset() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); + + // WHEN + offsetStream.Write("23"u8.ToArray(), 0, 2); + + // THEN + CollectionAssert.AreEqual(new byte[] { 1, 50, 51, 4 }, source.ToArray()); + } + + [TestMethod] + public void Write_ShouldExtendLength() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1, 2, 3 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 2); + + // WHEN + offsetStream.Position = 2; + offsetStream.Write("\t"u8.ToArray(), 0, 1); + + // THEN + Assert.AreEqual(3, offsetStream.Length); + } + + [TestMethod] + public void Write_ShouldThrow_WhenReadOnly() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1, 2, 3 }); + OffsetStream offsetStream = new OffsetStream(source, readOnly: true); + + // WHEN–THEN + Assert.ThrowsExactly(() => + offsetStream.Write(new byte[] { 0 }, 0, 1)); + } + + // ------------------------------------------------------ + // SEEK OPERATIONS + // ------------------------------------------------------ + + [TestMethod] + public void Seek_ShouldMoveWithinValidRange() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 4); + + // WHEN + long newPos = offsetStream.Seek(2, SeekOrigin.Begin); + + // THEN + Assert.AreEqual(2, newPos); + Assert.AreEqual(2, offsetStream.Position); + } + + [TestMethod] + public void Seek_ShouldThrow_WhenSeekingPastEnd() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1, 2, 3 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 3); + + // WHEN–THEN + Assert.ThrowsExactly(() => + offsetStream.Seek(4, SeekOrigin.Begin)); + } + + // ------------------------------------------------------ + // DISPOSAL OWNERSHIP + // ------------------------------------------------------ + + [TestMethod] + public void Dispose_ShouldCloseBaseStream_WhenOwnsStream() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1 }); + OffsetStream offsetStream = new OffsetStream(source, ownsStream: true); + + // WHEN + offsetStream.Dispose(); + + // THEN + Assert.ThrowsExactly(() => source.ReadByte()); + } + + [TestMethod] + public void Dispose_ShouldNotCloseBaseStream_WhenNotOwned() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 1 }); + OffsetStream offsetStream = new OffsetStream(source, ownsStream: false); + + // WHEN + offsetStream.Dispose(); + + // THEN + Assert.AreEqual(1, source.ReadByte()); + } + + // ------------------------------------------------------ + // WRITETO & WRITETOASYNC + // ------------------------------------------------------ + + [TestMethod] + public void WriteTo_ShouldCopyOnlyOffsetRange() + { + // GIVEN + MemoryStream source = CreateStream(new byte[] { 10, 20, 30, 40 }); + OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); + MemoryStream target = new MemoryStream(); + + // WHEN + offsetStream.WriteTo(target); + + // THEN + CollectionAssert.AreEqual(new byte[] { 20, 30 }, target.ToArray()); + } + + [TestMethod] + public async Task WriteToAsync_ShouldCopyOnlyOffsetRange() + { + // GIVEN + MemoryStream source = CreateStream("2 + new MemoryStream(bytes, writable: true); + + private static PackageItem CreateMinimalWritable() + { + MemoryStream ms = StreamOf(1, 2, 3); + return new PackageItem("file.bin", ms); + } + + // --------------------------------------------------------- + // CONSTRUCTION + // --------------------------------------------------------- + + [TestMethod] + public void Constructor_ShouldCreateItemFromStream() + { + using MemoryStream ms = StreamOf(10, 20, 30); + using PackageItem item = new PackageItem("abc.txt", ms); + + Assert.AreEqual("abc.txt", item.Name); + Assert.IsFalse(item.IsAttributeSet(PackageItemAttributes.ExecuteFile)); + Assert.AreEqual(ms, item.DataStream); + } + + [TestMethod] + public void Constructor_FromFilePath_ShouldCaptureAttributesAndOwnStream() + { + // Create an isolated private subfolder under temp, + // because direct writes to global temp root are unsafe. + string secureTempRoot = Path.Combine( + Path.GetTempPath(), + "pkgtest_" + Guid.NewGuid().ToString("N")); + + Directory.CreateDirectory(secureTempRoot); + + string path = Path.Combine( + secureTempRoot, + Path.GetRandomFileName()); + + // Create securely using exclusive, non-shareable access + using (FileStream file = new FileStream( + path, + FileMode.CreateNew, + FileAccess.ReadWrite, + FileShare.None)) + { + file.Write(new byte[] { 9, 8, 7 }); + } + + File.SetLastWriteTimeUtc( + path, + new DateTime(2022, 5, 1, 12, 0, 0, DateTimeKind.Utc)); + + try + { + using PackageItem item = new PackageItem(path, PackageItemAttributes.ExecuteFile); + + Assert.AreEqual(Path.GetFileName(path), item.Name); + Assert.IsTrue(item.IsAttributeSet(PackageItemAttributes.ExecuteFile)); + Assert.IsGreaterThanOrEqualTo(3, item.DataStream.Length); + } + finally + { + // Secure cleanup: remove file then folder + if (File.Exists(path)) + File.Delete(path); + + if (Directory.Exists(secureTempRoot)) + Directory.Delete(secureTempRoot, recursive: true); + } + } + + + // --------------------------------------------------------- + // WRITE FORMAT + RE-PARSE + // --------------------------------------------------------- + + private static PackageItem Roundtrip(PackageItem source) + { + MemoryStream buffer = new MemoryStream(); // do NOT dispose here + source.WriteTo(buffer); + + buffer.Position = 0; + return PackageItem.Parse(buffer); + } + + [TestMethod] + public void WriteThenParse_ShouldReturnEquivalentName() + { + using PackageItem item = CreateMinimalWritable(); + using PackageItem parsed = Roundtrip(item); + + Assert.AreEqual(item.Name, parsed.Name); + } + + [TestMethod] + public void WriteThenParse_ShouldPreserveTimestamp() + { + DateTime dt = new DateTime(2022, 10, 30, 11, 0, 0, DateTimeKind.Utc); + using PackageItem item = new PackageItem("f", dt, StreamOf(1, 2, 3)); + using PackageItem parsed = Roundtrip(item); + + Assert.AreEqual(dt, parsed.LastModifiedUTC); + } + + [TestMethod] + public void WriteThenParse_ShouldPreserveAttributes() + { + using PackageItem item = new PackageItem("a", DateTime.UtcNow, StreamOf(1), + attributes: PackageItemAttributes.FixedExtractLocation); + + using PackageItem parsed = Roundtrip(item); + + Assert.IsTrue(parsed.IsAttributeSet(PackageItemAttributes.FixedExtractLocation)); + } + + [TestMethod] + public void WriteThenParse_ShouldPreserveData() + { + using PackageItem item = CreateMinimalWritable(); + using PackageItem parsed = Roundtrip(item); + + using BinaryReader reader = new BinaryReader(parsed.DataStream); + + byte[] bytes = reader.ReadBytes(3); + + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, bytes); + } + + // --------------------------------------------------------- + // CUSTOM EXTRACT LOCATION + // --------------------------------------------------------- + + [TestMethod] + public void WriteThenParse_WithCustomLocation_ShouldRoundtrip() + { + // Create a private temp subfolder so location is not globally predictable + string secureTempRoot = Path.Combine( + Path.GetTempPath(), + "pkgtest_" + Guid.NewGuid().ToString("N")); + + Directory.CreateDirectory(secureTempRoot); + + try + { + using PackageItem item = new PackageItem( + "x.txt", + DateTime.UtcNow, + StreamOf(1, 2), + attributes: PackageItemAttributes.FixedExtractLocation, + extractTo: ExtractLocation.Custom, + extractToCustomLocation: secureTempRoot); + + using PackageItem parsed = Roundtrip(item); + + Assert.AreEqual(secureTempRoot, parsed.ExtractToCustomLocation); + } + finally + { + if (Directory.Exists(secureTempRoot)) + Directory.Delete(secureTempRoot, recursive: true); + } + } + + // --------------------------------------------------------- + // GET EXTRACTION PATH LOGIC + // --------------------------------------------------------- + + [TestMethod] + public void GetExtractionFilePath_ShouldRespectFixedAttribute() + { + using PackageItem item = new PackageItem("abc.dll", DateTime.UtcNow, + StreamOf(1), + attributes: PackageItemAttributes.FixedExtractLocation, + extractTo: ExtractLocation.System); + + string result = item.GetExtractionFilePath(ExtractLocation.Temp, null); + + // path must be under System, not requested Temp + string expectedRoot = Package.GetExtractLocation(ExtractLocation.System, null); + Assert.StartsWith(expectedRoot, result); + } + + [TestMethod] + public void GetExtractionFilePath_ShouldUseSuppliedLocation_WhenNotFixed() + { + using PackageItem item = new PackageItem("abc.dll", StreamOf(7)); + + string path = item.GetExtractionFilePath(ExtractLocation.Temp); + + Assert.StartsWith(Path.GetTempPath(), path); + } + + // --------------------------------------------------------- + // EXTRACTION TRANSACTION + // --------------------------------------------------------- + + [TestMethod] + public void Extract_ShouldBackupExisting_WhenOverwriteEnabled() + { + string target = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + byte[] originalBytes = "c"u8.ToArray(); + + // Securely create target + using (FileStream fs = new FileStream( + target, + FileMode.CreateNew, + FileAccess.ReadWrite, + FileShare.None)) + { + fs.Write(originalBytes, 0, originalBytes.Length); + } + + string? backupPath = null; + + try + { + using PackageItem item = CreateMinimalWritable(); + PackageItemTransactionLog log = item.Extract(target, overwrite: true); + + Assert.IsNotNull(log); + Assert.IsTrue(File.Exists(log.FilePath)); + + // Track for cleanup + backupPath = log.OriginalFilePath; + + Assert.IsTrue(File.Exists(backupPath), "Backup should exist"); + + CollectionAssert.AreEqual( + originalBytes, + File.ReadAllBytes(backupPath)); + + CollectionAssert.AreEqual( + new byte[] { 1, 2, 3 }, + File.ReadAllBytes(target)); + } + finally + { + if (File.Exists(target)) + File.Delete(target); + + // Now valid (conditional reachability eliminated) + if (!string.IsNullOrWhiteSpace(backupPath) && File.Exists(backupPath)) + File.Delete(backupPath); + } + } + + [TestMethod] + public void Extract_ShouldNotOverwrite_WhenFlagDisabled() + { + string target = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + byte[] originalBytes = "X"u8.ToArray(); + + // Create file securely + using (FileStream fs = new FileStream( + target, + FileMode.CreateNew, + FileAccess.ReadWrite, + FileShare.None)) + { + fs.Write(originalBytes, 0, originalBytes.Length); + } + + try + { + using PackageItem item = CreateMinimalWritable(); + PackageItemTransactionLog log = item.Extract(target, overwrite: false); + + Assert.IsNull(log, "Extract must return null when overwrite=false"); + CollectionAssert.AreEqual(originalBytes, File.ReadAllBytes(target)); + } + finally + { + // cleanup + if (File.Exists(target)) + File.Delete(target); + } + } + + // --------------------------------------------------------- + // PARSE ERROR SCENARIOS + // --------------------------------------------------------- + + [TestMethod] + public void Parse_ShouldThrow_WhenVersionIsUnsupported() + { + using MemoryStream buffer = StreamOf("\t"u8.ToArray() /* invalid version */); + + Assert.ThrowsExactly(() => + { + PackageItem _ = PackageItem.Parse(buffer); + }); + } + + [TestMethod] + public void Parse_ShouldReturnNull_WhenEOFMarker() + { + using MemoryStream buffer = StreamOf(0); + + PackageItem item = PackageItem.Parse(buffer); + + Assert.IsNull(item); + } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs new file mode 100644 index 00000000..34f67567 --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs @@ -0,0 +1,241 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; +using System.Linq; +using System.Text; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class PackageTests + { + private static MemoryStream CreateWritableStream() => new MemoryStream(); + + private static byte[] BuildEmptyPackageFile() + { + // Header: + // TP format id + // 01 version + // 00 EOF (no items) + return "TP"u8.ToArray() + .Append((byte)1) + .Append((byte)0) + .ToArray(); + } + + /// + /// Creates a serialized single PackageItem with name "A" and empty content. + /// + private static byte[] CreateMinimalItem() + { + using MemoryStream ms = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(ms); + + // Write NAME field (short) + writer.Write((byte)1); // length + writer.Write("A"u8.ToArray()); // ASCII name + + // Extract location = 0 + writer.Write((byte)0); + + // Flags = 0 + writer.Write((byte)0); + + // File size = 0 (Int64) + writer.Write((long)0); + + // Because file size = 0, Write no content + return ms.ToArray(); + } + + private static void WriteItem(Stream stream) + { + using MemoryStream data = new MemoryStream(); // empty payload + using PackageItem item = new PackageItem("A", data); + + item.WriteTo(stream); + } + + + + // ------------------------------------------------------------- + // CONSTRUCTION + // ------------------------------------------------------------- + + [TestMethod] + public void Constructor_ShouldWriteHeader_WhenCreating() + { + using MemoryStream backing = CreateWritableStream(); + + using (Package pkg = new Package(backing, PackageMode.Create)) + { + pkg.Close(); + } + + byte[] data = backing.ToArray(); + + Assert.IsGreaterThanOrEqualTo(3, data.Length); + Assert.AreEqual("TP", Encoding.ASCII.GetString(data[..2])); + Assert.AreEqual(1, data[2]); // version marker + } + + [TestMethod] + public void Constructor_ShouldReadExisting_WhenOpening() + { + byte[] bytes = BuildEmptyPackageFile(); + using MemoryStream backing = new MemoryStream(bytes); + + using Package pkg = new Package(backing, PackageMode.Open); + + Assert.IsEmpty(pkg.Items); + } + + [TestMethod] + public void Constructor_ShouldThrow_WhenInvalidHeader() + { + using MemoryStream backing = new MemoryStream("XY"u8.ToArray()); + + Assert.ThrowsExactly(() => + new Package(backing, PackageMode.Open)); + } + + // ------------------------------------------------------------- + // MODE RESTRICTION + // ------------------------------------------------------------- + + [TestMethod] + public void AddItem_ShouldThrow_WhenNotInCreateMode() + { + using MemoryStream backing = new MemoryStream(BuildEmptyPackageFile()); + using Package pkg = new Package(backing, PackageMode.Open); + + Assert.ThrowsExactly(() => + { + // simulate write by raw call — not allowed in Open mode + pkg.AddItem(null); + }); + } + + [TestMethod] + public void Items_ShouldThrow_WhenNotInOpenMode() + { + using MemoryStream backing = CreateWritableStream(); + using Package pkg = new Package(backing, PackageMode.Create); + + Assert.ThrowsExactly(() => + { + System.Collections.ObjectModel.ReadOnlyCollection _ = pkg.Items; + }); + } + + // ------------------------------------------------------------- + // WRITE AND READ BACK + // ------------------------------------------------------------- + + [TestMethod] + public void WriteAndRead_ShouldReturnSameItems() + { + using MemoryStream backing = CreateWritableStream(); + + // Write + using (Package pkg = new Package(backing, PackageMode.Create)) + { + WriteItem(backing); + pkg.Close(); + } + + // Reopen + backing.Position = 0; + using Package pkg2 = new Package(backing, PackageMode.Open); + + Assert.HasCount(1, pkg2.Items); + } + + [TestMethod] + public void Close_ShouldWriteEOF_Once() + { + using MemoryStream backing = CreateWritableStream(); + using Package pkg = new Package(backing, PackageMode.Create); + WriteItem(backing); + pkg.Close(); + long len1 = backing.Length; + pkg.Close(); + long len2 = backing.Length; + Assert.AreEqual(len1, len2); + } + + // ------------------------------------------------------------- + // STREAM OWNERSHIP + // ------------------------------------------------------------- + + [TestMethod] + public void Dispose_ShouldCloseOwnedStream() + { + // secure temp file creation + string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + // create file exclusively before passing to Package + using (FileStream fs = new FileStream( + tempFile, + FileMode.CreateNew, // guarantees file does not exist + FileAccess.ReadWrite, + FileShare.None)) // no external access allowed + { + fs.WriteByte(99); // write something so the file exists + } + + try + { + using (Package pkg = new Package(tempFile, PackageMode.Create)) + pkg.Close(); // Close → flush EOF marker → close underlying stream + + using FileStream fs = new FileStream(tempFile, FileMode.Open, FileAccess.Read); + Assert.IsGreaterThanOrEqualTo(3, fs.Length); + } + finally + { + if (File.Exists(tempFile)) + File.Delete(tempFile); + } + } + + [TestMethod] + public void Dispose_ShouldNotCloseExternalStream() + { + using MemoryStream backing = CreateWritableStream(); + using (Package pkg = new Package(backing, PackageMode.Create, ownsStream: false)) + pkg.Close(); + + // external stream still usable + backing.WriteByte(255); + backing.Position = 0; + } + + // ------------------------------------------------------------- + // INVALID FORMATS + // ------------------------------------------------------------- + + [TestMethod] + public void ShouldThrow_WhenMissingVersion() + { + using MemoryStream backing = new MemoryStream("TP"u8.ToArray()); + + Assert.ThrowsExactly(() => + new Package(backing, PackageMode.Open)); + } + + [TestMethod] + public void ShouldThrow_WhenUnsupportedVersion() + { + byte[] bytes = "TP"u8.ToArray() + .Concat("*"u8.ToArray()) // bogus version + .Concat(new byte[] { 0 }) + .ToArray(); + + using MemoryStream backing = new MemoryStream(bytes); + + Assert.ThrowsExactly(() => + new Package(backing, PackageMode.Open)); + } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs new file mode 100644 index 00000000..29be7dca --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs @@ -0,0 +1,175 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class PipeTests + { + private static Pipe CreatePipe() => new Pipe(); + + // ------------------------------------------------------------ + // CONSTRUCTION + // ------------------------------------------------------------ + + [TestMethod] + public void Constructor_ShouldExposeTwoConnectedStreams() + { + Pipe p = CreatePipe(); + + Assert.IsNotNull(p.Stream1); + Assert.IsNotNull(p.Stream2); + + Assert.IsTrue(p.Stream1.CanRead); + Assert.IsTrue(p.Stream1.CanWrite); + + Assert.IsTrue(p.Stream2.CanRead); + Assert.IsTrue(p.Stream2.CanWrite); + } + + // ------------------------------------------------------------ + // BASIC DATA TRANSFER + // ------------------------------------------------------------ + + [TestMethod] + public void WriteOnStream1_ShouldBeReadableFromStream2() + { + Pipe pipe = CreatePipe(); + byte[] data = new byte[] { 1, 2, 3 }; + + pipe.Stream1.Write(data, 0, data.Length); + + byte[] buffer = new byte[10]; + int read = pipe.Stream2.Read(buffer, 0, 10); + + Assert.AreEqual(3, read); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, buffer[..3]); + } + + [TestMethod] + public void Read_ShouldReturnZero_WhenOtherSideDisposed() + { + Pipe pipe = CreatePipe(); + + pipe.Stream1.Dispose(); + + byte[] buffer = new byte[5]; + int read = pipe.Stream2.Read(buffer, 0, 5); + + Assert.AreEqual(0, read); + } + + // ------------------------------------------------------------ + // SEEK PROHIBITIONS + // ------------------------------------------------------------ + + [TestMethod] + public void Position_ShouldThrowOnGet() + { + Pipe pipe = CreatePipe(); + Assert.ThrowsExactly(() => _ = pipe.Stream1.Position); + } + + [TestMethod] + public void Position_ShouldThrowOnSet() + { + Pipe pipe = CreatePipe(); + Assert.ThrowsExactly(() => pipe.Stream1.Position = 10); + } + + [TestMethod] + public void Seek_ShouldThrow() + { + Pipe pipe = CreatePipe(); + Assert.ThrowsExactly(() => pipe.Stream1.Seek(10, SeekOrigin.Begin)); + } + + [TestMethod] + public void Length_ShouldThrow() + { + Pipe pipe = CreatePipe(); + Assert.ThrowsExactly(() => _ = pipe.Stream1.Length); + } + + // ------------------------------------------------------------ + // BUFFER BOUNDARY BEHAVIOR + // ------------------------------------------------------------ + + [TestMethod] + public void Write_ShouldBlockWhenBufferFull_ThenResumeAfterRead() + { + Pipe pipe = CreatePipe(); + Stream stream1 = pipe.Stream1; + Stream stream2 = pipe.Stream2; + + stream1.WriteTimeout = 2000; + stream2.ReadTimeout = 2000; + + byte[] large = new byte[64 * 1024]; // exactly buffer size + + // Fill buffer completely + stream1.Write(large, 0, large.Length); + + // Now write again, but on another thread + using Task t = Task.Run(() => + { + // Should block until read + stream1.Write(new byte[] { 7 }, 0, 1); + }, TestContext.CancellationToken); + + // Give writer thread chance to block + Thread.Sleep(100); + + // Now read entire buffer + byte[] readBuffer = new byte[large.Length]; + int readTotal = stream2.Read(readBuffer, 0, large.Length); + + Assert.AreEqual(large.Length, readTotal); + + // Now writer should have completed + t.Wait(TestContext.CancellationToken); + } + + [TestMethod] + public void Write_ShouldFailWhenTimeoutExceeded() + { + Pipe pipe = CreatePipe(); + pipe.Stream1.WriteTimeout = 300; + + // fill buffer without draining + pipe.Stream1.Write(new byte[64 * 1024], 0, 64 * 1024); + + Assert.ThrowsExactly(() => pipe.Stream1.Write(new byte[] { 1 }, 0, 1)); + } + + [TestMethod] + public void Read_ShouldFailWhenTimeoutExceeded() + { + Pipe pipe = CreatePipe(); + pipe.Stream2.ReadTimeout = 200; + + byte[] buffer = new byte[1]; + + Assert.ThrowsExactly(() => pipe.Stream2.Read(buffer, 0, 1)); + } + + // ------------------------------------------------------------ + // DISPOSAL CASCADE + // ------------------------------------------------------------ + + [TestMethod] + public void Dispose_ShouldStopOtherSideFromDeliveringData() + { + Pipe pipe = CreatePipe(); + pipe.Stream1.Dispose(); + + Assert.ThrowsExactly(() => pipe.Stream1.Write(new byte[] { 1 }, 0, 1)); + } + + public TestContext TestContext { get; set; } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs new file mode 100644 index 00000000..7ea867a6 --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs @@ -0,0 +1,188 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Threading.Tasks; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class StreamExtensionsTests + { + private static MemoryStream StreamOf(params byte[] data) => + new MemoryStream(data, writable: true); + + // -------------------------------------------------------------------- + // ReadByteValue & WriteByteAsync + // -------------------------------------------------------------------- + + [TestMethod] + public void ReadByteValue_ShouldReturnFirstByte() + { + using MemoryStream s = StreamOf("c"u8.ToArray()); + Assert.AreEqual(99, s.ReadByteValue()); + } + + [TestMethod] + public void ReadByteValue_ShouldThrow_WhenEmpty() + { + using MemoryStream s = StreamOf(); + Assert.ThrowsExactly(() => s.ReadByteValue()); + } + + [TestMethod] + public async Task WriteByteAsync_ShouldWriteByte() + { + await using MemoryStream s = new MemoryStream(); // expandable stream + + await s.WriteByteAsync(42, TestContext.CancellationToken); + + s.Position = 0; + + byte value = await s.ReadByteValueAsync(TestContext.CancellationToken); + + Assert.AreEqual(42, value); + } + + // -------------------------------------------------------------------- + // ReadExactly + // -------------------------------------------------------------------- + + [TestMethod] + public void ReadExactly_ShouldReturnRequestedBytes() + { + using MemoryStream s = StreamOf(1, 2, 3, 4); + byte[] data = s.ReadExactly(3); + + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, data); + } + + [TestMethod] + public void ReadExactly_ShouldThrow_WhenInsufficientData() + { + using MemoryStream s = StreamOf(1, 2); + Assert.ThrowsExactly(() => s.ReadExactly(3)); + } + + [TestMethod] + public async Task ReadExactlyAsync_ShouldReturnRequestedBytes() + { + await using MemoryStream s = StreamOf(10, 20, 30); + byte[] result = await s.ReadExactlyAsync(2, TestContext.CancellationToken); + + CollectionAssert.AreEqual(new byte[] { 10, 20 }, result); + } + + [TestMethod] + public async Task ReadExactlyAsync_ShouldThrow_WhenStreamEnds() + { + await using MemoryStream s = StreamOf(5); + await Assert.ThrowsExactlyAsync(() => s.ReadExactlyAsync(2, TestContext.CancellationToken)); + } + + // -------------------------------------------------------------------- + // Short string read/write + // -------------------------------------------------------------------- + + [TestMethod] + public void WriteShortString_ThenReadShortString_ShouldRoundtrip() + { + using MemoryStream s = new MemoryStream(); // expandable stream + + s.WriteShortString("Hello"); + + s.Position = 0; + string str = s.ReadShortString(); + + Assert.AreEqual("Hello", str); + } + + [TestMethod] + public void WriteShortString_ShouldThrow_WhenLengthExceeds255() + { + string oversized = new string('A', 300); + + using MemoryStream s = StreamOf(); + Assert.ThrowsExactly(() => s.WriteShortString(oversized)); + } + + [TestMethod] + public void ReadShortString_ShouldThrow_WhenLengthGreaterThanAvailableData() + { + using MemoryStream s = StreamOf(2, 65); // length=2, only 1 byte remains + Assert.ThrowsExactly(() => s.ReadShortString()); + } + + [TestMethod] + public async Task WriteShortStringAsync_ShouldRoundtripWithUTF8() + { + await using MemoryStream s = new MemoryStream(); // expandable + + await s.WriteShortStringAsync("test✓", TestContext.CancellationToken); + + s.Position = 0; + string parsed = await s.ReadShortStringAsync(TestContext.CancellationToken); + + Assert.AreEqual("test✓", parsed); + } + + // -------------------------------------------------------------------- + // CopyTo & CopyToAsync + // -------------------------------------------------------------------- + + [TestMethod] + public void CopyTo_ShouldCopyExactBytes() + { + using MemoryStream src = StreamOf(1, 2, 3, 4); + using MemoryStream dst = new MemoryStream(); // must be expandable here + + src.CopyTo(dst, bufferSize: 3, length: 3); + + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, dst.ToArray()); + } + + [TestMethod] + public void CopyTo_ShouldFailWhenEOSIsReachedPrematurely() + { + using MemoryStream src = StreamOf(1, 2); + using MemoryStream dst = new MemoryStream(); // must allow writing + + Assert.ThrowsExactly(() => + src.CopyTo(dst, bufferSize: 4, length: 3)); + } + + [TestMethod] + public async Task CopyToAsync_ShouldCopyExactBytes() + { + await using MemoryStream src = StreamOf("cba"u8.ToArray()); + await using MemoryStream dst = new MemoryStream(); // expandable destination + + await src.CopyToAsync(dst, bufferSize: 10, length: 3, TestContext.CancellationToken); + + CollectionAssert.AreEqual("cba"u8.ToArray(), dst.ToArray()); + } + + [TestMethod] + public async Task CopyToAsync_ShouldFailWhenEOSReachedPrematurely() + { + await using MemoryStream src = StreamOf("\t"u8.ToArray()); + await using MemoryStream dst = new MemoryStream(); // expandable + + await Assert.ThrowsExactlyAsync(async () => + await src.CopyToAsync(dst, bufferSize: 8, length: 2, TestContext.CancellationToken)); + } + + [TestMethod] + public void CopyTo_ShouldReturnImmediately_WhenLengthIsZero() + { + using MemoryStream src = StreamOf(1, 2, 3); + using MemoryStream dst = StreamOf(); + + src.CopyTo(dst, bufferSize: 5, length: 0); + + Assert.IsEmpty(dst.ToArray()); + } + + public TestContext TestContext { get; set; } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs new file mode 100644 index 00000000..4da8db0a --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs @@ -0,0 +1,279 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TechnitiumLibrary.IO; + +namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO +{ + [TestClass] + public sealed class WriteBufferedStreamTests + { + private sealed class NonWritableStream : MemoryStream + { + public override bool CanWrite => false; + } + + private static MemoryStream CreateBaseStream(byte[]? initial = null) => + initial is null ? new MemoryStream() : new MemoryStream(initial); + + // ------------------------------------------------------ + // CONSTRUCTION / CAPABILITIES + // ------------------------------------------------------ + + [TestMethod] + public void Constructor_ShouldThrow_WhenBaseStreamNotWritable() + { + // GIVEN + using NonWritableStream baseStream = new NonWritableStream(); + + // WHEN-THEN + Assert.ThrowsExactly( + () => new WriteBufferedStream(baseStream)); + } + + [TestMethod] + public void Constructor_ShouldExposeCapabilitiesFromBaseStream() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + + // WHEN + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // THEN + Assert.IsTrue(buffered.CanWrite); + Assert.AreEqual(baseStream.CanRead, buffered.CanRead); + Assert.AreEqual(baseStream.CanTimeout, buffered.CanTimeout); + Assert.IsFalse(buffered.CanSeek); + } + + // ------------------------------------------------------ + // BASIC WRITE & FLUSH (SYNC) + // ------------------------------------------------------ + + [TestMethod] + public void Write_ShouldBufferUntilFlushed() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream, bufferSize: 8); + + byte[] data = Encoding.ASCII.GetBytes("ABCD"); // 4 bytes + + // WHEN + buffered.Write(data, 0, data.Length); + + // THEN – nothing written yet to base + CollectionAssert.AreEqual(Array.Empty(), baseStream.ToArray()); + Assert.AreEqual(0L, baseStream.Length); + + // WHEN + buffered.Flush(); + + // THEN – data should now exist in base stream + CollectionAssert.AreEqual(data, baseStream.ToArray()); + } + + [TestMethod] + public void Write_ShouldFlushBufferWhenFull_AndKeepRemainderBuffered() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream, bufferSize: 4); + + // 6 bytes, buffer 4 -> first 4 flushed, last 2 remain buffered after Flush + byte[] data = Encoding.ASCII.GetBytes("ABCDEF"); + + // WHEN + buffered.Write(data, 0, data.Length); + + // buffer is full internally twice, so Flush() is invoked from Write + // After Write completes, we call Flush() to ensure remainder is written. + buffered.Flush(); + + // THEN + CollectionAssert.AreEqual(data, baseStream.ToArray()); + } + + // ------------------------------------------------------ + // BASIC WRITE & FLUSH (ASYNC) + // ------------------------------------------------------ + + [TestMethod] + public async Task WriteAsync_ShouldBufferAndFlushAsync() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream, bufferSize: 8); + + byte[] data = Encoding.UTF8.GetBytes("123456"); + + // WHEN + await buffered.WriteAsync(data, 0, data.Length, CancellationToken.None); + + // Still buffered + CollectionAssert.AreEqual(Array.Empty(), baseStream.ToArray()); + + await buffered.FlushAsync(CancellationToken.None); + + // THEN + CollectionAssert.AreEqual(data, baseStream.ToArray()); + } + + [TestMethod] + public async Task WriteAsync_MemoryOverload_ShouldRespectBuffering() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream, bufferSize: 4); + + byte[] data = Encoding.ASCII.GetBytes("WXYZ12"); // 6 bytes + + // WHEN + await buffered.WriteAsync(data.AsMemory(), CancellationToken.None); + await buffered.FlushAsync(CancellationToken.None); + + // THEN + CollectionAssert.AreEqual(data, baseStream.ToArray()); + } + + // ------------------------------------------------------ + // READ DELEGATION + // ------------------------------------------------------ + + [TestMethod] + public void Read_ShouldDelegateToBaseStream() + { + // GIVEN + byte[] initial = Encoding.ASCII.GetBytes("HELLO"); + using MemoryStream baseStream = CreateBaseStream(initial); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // WHEN + byte[] buffer = new byte[5]; + baseStream.Position = 0; // ensure we read from start + int read = buffered.Read(buffer, 0, buffer.Length); + + // THEN + Assert.AreEqual(5, read); + CollectionAssert.AreEqual(initial, buffer); + } + + // ------------------------------------------------------ + // SEEK / LENGTH / POSITION BEHAVIOR + // ------------------------------------------------------ + + [TestMethod] + public void Position_Get_ShouldMatchBaseStreamPosition() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(new byte[10]); + baseStream.Position = 4; + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // WHEN + long position = buffered.Position; + + // THEN + Assert.AreEqual(4L, position); + } + + [TestMethod] + public void Position_Set_ShouldThrow_NotSupported() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // WHEN-THEN + Assert.ThrowsExactly(() => + buffered.Position = 1); + } + + [TestMethod] + public void Seek_ShouldThrow_NotSupported() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // WHEN-THEN + Assert.ThrowsExactly(() => + buffered.Seek(0, SeekOrigin.Begin)); + } + + [TestMethod] + public void SetLength_ShouldThrow_NotSupported() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // WHEN-THEN + Assert.ThrowsExactly(() => + buffered.SetLength(10)); + } + + // ------------------------------------------------------ + // DISPOSAL & OWNERSHIP + // ------------------------------------------------------ + + [TestMethod] + public void Dispose_ShouldDisposeUnderlyingStream() + { + // GIVEN + MemoryStream baseStream = CreateBaseStream(); + WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // WHEN + buffered.Dispose(); + + // THEN – base stream also disposed + Assert.ThrowsExactly(() => + baseStream.WriteByte(1)); + } + + [TestMethod] + public void Write_ShouldThrow_WhenDisposed() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + buffered.Dispose(); + + // WHEN-THEN + Assert.ThrowsExactly(() => + buffered.Write(new byte[] { 1 }, 0, 1)); + } + + [TestMethod] + public async Task WriteAsync_ShouldThrow_WhenDisposed() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + buffered.Dispose(); + + // WHEN-THEN + await Assert.ThrowsExactlyAsync(() => + buffered.WriteAsync(new byte[] { 1 }, 0, 1, CancellationToken.None)); + } + + [TestMethod] + public async Task FlushAsync_ShouldNotFlush_WhenNothingBuffered() + { + // GIVEN + using MemoryStream baseStream = CreateBaseStream(); + using WriteBufferedStream buffered = new WriteBufferedStream(baseStream); + + // WHEN + await buffered.FlushAsync(CancellationToken.None); + + // THEN – nothing written + CollectionAssert.AreEqual(Array.Empty(), baseStream.ToArray()); + } + } +} diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.UnitTests.csproj b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.UnitTests.csproj new file mode 100644 index 00000000..9aab8e95 --- /dev/null +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.UnitTests.csproj @@ -0,0 +1,15 @@ + + + + net9.0 + latest + disable + enable + true + + + + + + + diff --git a/TechnitiumLibrary.sln b/TechnitiumLibrary.sln index 9cbcda3e..bfc3298a 100644 --- a/TechnitiumLibrary.sln +++ b/TechnitiumLibrary.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnitiumLibrary", "Techni EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnitiumLibrary.Security.OTP", "TechnitiumLibrary.Security.OTP\TechnitiumLibrary.Security.OTP.csproj", "{72AF4EB6-EB81-4655-9998-8BF24B304614}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnitiumLibrary.UnitTests", "TechnitiumLibrary.UnitTests\TechnitiumLibrary.UnitTests.csproj", "{D0CD41D8-E5F0-4EEF-81E3-587A2A877C49}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -75,6 +77,10 @@ Global {72AF4EB6-EB81-4655-9998-8BF24B304614}.Debug|Any CPU.Build.0 = Debug|Any CPU {72AF4EB6-EB81-4655-9998-8BF24B304614}.Release|Any CPU.ActiveCfg = Release|Any CPU {72AF4EB6-EB81-4655-9998-8BF24B304614}.Release|Any CPU.Build.0 = Release|Any CPU + {D0CD41D8-E5F0-4EEF-81E3-587A2A877C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0CD41D8-E5F0-4EEF-81E3-587A2A877C49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0CD41D8-E5F0-4EEF-81E3-587A2A877C49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0CD41D8-E5F0-4EEF-81E3-587A2A877C49}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 02f825dc020a7ed95ac84dbb9afc55a23ee8dabb Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Thu, 29 Jan 2026 10:19:39 +0200 Subject: [PATCH 2/8] Fixed workflow --- .github/workflows/unit-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index 11ef5256..74c7a992 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -38,4 +38,4 @@ jobs: run: msbuild ${{ env.SOLUTION_NAME }} /m /p:Configuration=${{ env.BUILD_CONFIGURATION }} - name: Test (msbuild) - run: msbuild TechnitiumLibrary.Tests\TechnitiumLibrary.Tests.csproj /t:Test /p:Configuration=${{ env.BUILD_CONFIGURATION }} \ No newline at end of file + run: msbuild TechnitiumLibrary.UnitTests\TechnitiumLibrary.UnitTests.csproj /t:Test /p:Configuration=${{ env.BUILD_CONFIGURATION }} \ No newline at end of file From 3c6a6f600d84364e1ac88576741a10c805766829 Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Thu, 29 Jan 2026 10:41:29 +0200 Subject: [PATCH 3/8] Updated workflow file --- .github/workflows/unit-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index 74c7a992..5fbbae8e 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -29,7 +29,7 @@ jobs: dotnet-version: 9.0.x - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v2 - name: Restore run: msbuild ${{ env.SOLUTION_NAME }} /t:Restore From a0a256cfde3c560f5202c5031edb097cb09aa75c Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Thu, 29 Jan 2026 18:26:15 +0200 Subject: [PATCH 4/8] Formatting --- .../TechnitiumLibrary.IO/OffsetStreamTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs index fdf9880c..98d8dce2 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs @@ -11,6 +11,9 @@ public sealed class OffsetStreamTests { private static MemoryStream CreateStream(byte[] data) => new MemoryStream(data, writable: true); + public TestContext TestContext { get; set; } = null!; + + // ------------------------------------------------------ // CONSTRUCTION & BASIC METADATA // ------------------------------------------------------ @@ -239,7 +242,5 @@ public async Task WriteToAsync_ShouldCopyOnlyOffsetRange() // THEN CollectionAssert.AreEqual(" Date: Thu, 29 Jan 2026 18:40:39 +0200 Subject: [PATCH 5/8] Removed unnecessary helpers --- .../BinaryWriterExtensionsTests.cs | 102 ++++++++++++------ 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs index c48dd2f3..d460f626 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs @@ -10,16 +10,6 @@ namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO [TestClass] public sealed class BinaryWriterExtensionsTests { - private static (BinaryWriter writer, MemoryStream stream) CreateWriter() - { - MemoryStream ms = new MemoryStream(); - BinaryWriter bw = new BinaryWriter(ms); - return (bw, ms); - } - - private static byte[] WrittenBytes(MemoryStream ms) => - ms.ToArray(); - // --------------------------------------- // WriteLength() tests // --------------------------------------- @@ -28,20 +18,23 @@ private static byte[] WrittenBytes(MemoryStream ms) => public void WriteLength_ShouldEncodeSingleByte_WhenLessThan128() { // GIVEN - (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); // WHEN bw.WriteLength(42); // THEN - CollectionAssert.AreEqual(new byte[] { 42 }, WrittenBytes(ms)); + + CollectionAssert.AreEqual(new byte[] { 42 }, ms.ToArray()); } [TestMethod] public void WriteLength_ShouldEncodeMultiByte_BigEndianForm() { // GIVEN - (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); // WHEN // length = 0x0000012C (300 decimal) @@ -50,10 +43,17 @@ public void WriteLength_ShouldEncodeMultiByte_BigEndianForm() // THEN // Prefix = 0x82 (2 bytes follow) // Then big-endian 01 2C - CollectionAssert.AreEqual( - new byte[] { 0x82, 0x01, 0x2C }, - WrittenBytes(ms) - ); + try + { + CollectionAssert.AreEqual( + new byte[] { 0x82, 0x01, 0x2C }, + ms.ToArray() + ); + } + finally + { + ms.Dispose(); + } } // --------------------------------------- @@ -64,34 +64,50 @@ public void WriteLength_ShouldEncodeMultiByte_BigEndianForm() public void WriteBuffer_ShouldPrefixLength_AndWriteBytes() { // GIVEN - (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); byte[] data = new byte[] { 0xAA, 0xBB, 0xCC }; // WHEN bw.WriteBuffer(data); // THEN - CollectionAssert.AreEqual( - new byte[] { 0x03, 0xAA, 0xBB, 0xCC }, - WrittenBytes(ms) - ); + try + { + CollectionAssert.AreEqual( + new byte[] { 0x03, 0xAA, 0xBB, 0xCC }, + ms.ToArray() + ); + } + finally + { + ms.Dispose(); + } } [TestMethod] public void WriteBuffer_WithOffset_ShouldWriteExpectedSegment() { // GIVEN - (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); byte[] data = new byte[] { 1, 2, 3, 4, 5 }; // WHEN bw.WriteBuffer(data, offset: 1, count: 3); // THEN - CollectionAssert.AreEqual( - new byte[] { 0x03, 2, 3, 4 }, - WrittenBytes(ms) - ); + try + { + CollectionAssert.AreEqual( + new byte[] { 0x03, 2, 3, 4 }, + ms.ToArray() + ); + } + finally + { + ms.Dispose(); + } } // --------------------------------------- @@ -102,7 +118,8 @@ public void WriteBuffer_WithOffset_ShouldWriteExpectedSegment() public void WriteShortString_ShouldWriteUtf8EncodedWithLength() { // GIVEN - (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); string text = "Hello"; byte[] utf8 = Encoding.UTF8.GetBytes(text); @@ -114,14 +131,22 @@ public void WriteShortString_ShouldWriteUtf8EncodedWithLength() .Concat(utf8) .ToArray(); - CollectionAssert.AreEqual(expected, WrittenBytes(ms)); + try + { + CollectionAssert.AreEqual(expected, ms.ToArray()); + } + finally + { + ms.Dispose(); + } } [TestMethod] public void WriteShortString_ShouldUseSpecifiedEncoding() { // GIVEN - (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); string text = "Å"; Encoding enc = Encoding.UTF32; byte[] bytes = enc.GetBytes(text); @@ -134,14 +159,22 @@ public void WriteShortString_ShouldUseSpecifiedEncoding() .Concat(bytes) .ToArray(); - CollectionAssert.AreEqual(expected, WrittenBytes(ms)); + try + { + CollectionAssert.AreEqual(expected, ms.ToArray()); + } + finally + { + ms.Dispose(); + } } [TestMethod] public void WriteShortString_ShouldThrow_WhenStringTooLong() { // GIVEN - (BinaryWriter bw, MemoryStream _) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); string input = new string('x', 256); // UTF-8 => 256 bytes // WHEN–THEN @@ -162,13 +195,14 @@ public void WriteDate_ShouldEncodeMillisecondsFromUnixEpoch() long millis = (long)(expected - DateTime.UnixEpoch).TotalMilliseconds; byte[] bytes = BitConverter.GetBytes(millis); - (BinaryWriter bw, MemoryStream ms) = CreateWriter(); + using MemoryStream ms = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(ms); // WHEN bw.Write(expected); // THEN - CollectionAssert.AreEqual(bytes, WrittenBytes(ms)); + CollectionAssert.AreEqual(bytes, ms.ToArray()); } } } From ee59f56f551cb44edb8fe360f3af839aa5be88ea Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Thu, 29 Jan 2026 18:47:42 +0200 Subject: [PATCH 6/8] Removed unnecessary helper --- .../TechnitiumLibrary.IO/OffsetStreamTests.cs | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs index 98d8dce2..6138ffdc 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs @@ -9,8 +9,6 @@ namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO [TestClass] public sealed class OffsetStreamTests { - private static MemoryStream CreateStream(byte[] data) => new MemoryStream(data, writable: true); - public TestContext TestContext { get; set; } = null!; @@ -22,10 +20,10 @@ public sealed class OffsetStreamTests public void Constructor_ShouldExposeCorrectBasicProperties() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4, 5 }); + using MemoryStream source = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }, writable: true); // WHEN - OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 3); + using OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 3); // THEN Assert.AreEqual(3, offsetStream.Length); @@ -38,10 +36,10 @@ public void Constructor_ShouldExposeCorrectBasicProperties() public void Constructor_ShouldRespectReadOnlyFlag() { // GIVEN - MemoryStream source = CreateStream(new byte[10]); + using MemoryStream source = new MemoryStream(new byte[10], writable: true); // WHEN - OffsetStream offsetStream = new OffsetStream(source, readOnly: true); + using OffsetStream offsetStream = new OffsetStream(source, readOnly: true); // THEN Assert.IsFalse(offsetStream.CanWrite); @@ -55,8 +53,8 @@ public void Constructor_ShouldRespectReadOnlyFlag() public void Read_ShouldReturnSegmentWithinBounds() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 10, 20, 30, 40, 50 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 3); + using MemoryStream source = new MemoryStream(new byte[] { 10, 20, 30, 40, 50 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 3); byte[] buffer = new byte[10]; @@ -72,8 +70,8 @@ public void Read_ShouldReturnSegmentWithinBounds() public void Read_ShouldReturnZero_WhenPastLength() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 2, length: 1); + using MemoryStream source = new MemoryStream(new byte[] { 1, 2, 3, 4 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 2, length: 1); byte[] buffer = new byte[5]; offsetStream.Position = 1; @@ -89,8 +87,8 @@ public void Read_ShouldReturnZero_WhenPastLength() public void ReadAsync_ShouldReturnCorrectData() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 9, 8, 7, 6 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); + using MemoryStream source = new MemoryStream(new byte[] { 9, 8, 7, 6 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); byte[] buffer = new byte[10]; // WHEN @@ -109,8 +107,8 @@ public void ReadAsync_ShouldReturnCorrectData() public void Write_ShouldPlaceDataAtOffset() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); + using MemoryStream source = new MemoryStream(new byte[] { 1, 2, 3, 4 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); // WHEN offsetStream.Write("23"u8.ToArray(), 0, 2); @@ -123,8 +121,8 @@ public void Write_ShouldPlaceDataAtOffset() public void Write_ShouldExtendLength() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1, 2, 3 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 2); + using MemoryStream source = new MemoryStream(new byte[] { 1, 2, 3 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 2); // WHEN offsetStream.Position = 2; @@ -138,8 +136,8 @@ public void Write_ShouldExtendLength() public void Write_ShouldThrow_WhenReadOnly() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1, 2, 3 }); - OffsetStream offsetStream = new OffsetStream(source, readOnly: true); + using MemoryStream source = new MemoryStream(new byte[] { 1, 2, 3 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, readOnly: true); // WHEN–THEN Assert.ThrowsExactly(() => @@ -154,8 +152,8 @@ public void Write_ShouldThrow_WhenReadOnly() public void Seek_ShouldMoveWithinValidRange() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1, 2, 3, 4 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 4); + using MemoryStream source = new MemoryStream(new byte[] { 1, 2, 3, 4 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 4); // WHEN long newPos = offsetStream.Seek(2, SeekOrigin.Begin); @@ -169,8 +167,8 @@ public void Seek_ShouldMoveWithinValidRange() public void Seek_ShouldThrow_WhenSeekingPastEnd() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1, 2, 3 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 3); + using MemoryStream source = new MemoryStream(new byte[] { 1, 2, 3 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 0, length: 3); // WHEN–THEN Assert.ThrowsExactly(() => @@ -185,8 +183,8 @@ public void Seek_ShouldThrow_WhenSeekingPastEnd() public void Dispose_ShouldCloseBaseStream_WhenOwnsStream() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1 }); - OffsetStream offsetStream = new OffsetStream(source, ownsStream: true); + using MemoryStream source = new MemoryStream(new byte[] { 1 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, ownsStream: true); // WHEN offsetStream.Dispose(); @@ -199,8 +197,8 @@ public void Dispose_ShouldCloseBaseStream_WhenOwnsStream() public void Dispose_ShouldNotCloseBaseStream_WhenNotOwned() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 1 }); - OffsetStream offsetStream = new OffsetStream(source, ownsStream: false); + using MemoryStream source = new MemoryStream(new byte[] { 1 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, ownsStream: false); // WHEN offsetStream.Dispose(); @@ -217,9 +215,9 @@ public void Dispose_ShouldNotCloseBaseStream_WhenNotOwned() public void WriteTo_ShouldCopyOnlyOffsetRange() { // GIVEN - MemoryStream source = CreateStream(new byte[] { 10, 20, 30, 40 }); - OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); - MemoryStream target = new MemoryStream(); + using MemoryStream source = new MemoryStream(new byte[] { 10, 20, 30, 40 }, writable: true); + using OffsetStream offsetStream = new OffsetStream(source, offset: 1, length: 2); + using MemoryStream target = new MemoryStream(); // WHEN offsetStream.WriteTo(target); @@ -232,9 +230,9 @@ public void WriteTo_ShouldCopyOnlyOffsetRange() public async Task WriteToAsync_ShouldCopyOnlyOffsetRange() { // GIVEN - MemoryStream source = CreateStream("2 Date: Thu, 29 Jan 2026 18:55:41 +0200 Subject: [PATCH 7/8] Removed unnecessary helpers --- .../TechnitiumLibrary.IO/JointTests.cs | 4 +- .../TechnitiumLibrary.IO/PackageItemTests.cs | 2 +- .../TechnitiumLibrary.IO/PackageTests.cs | 53 +++---------------- 3 files changed, 11 insertions(+), 48 deletions(-) diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs index 3faa4dbd..8a7aee4a 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs @@ -116,8 +116,8 @@ public async Task Dispose_ShouldCloseStreams() await WaitForCopyCompletion(); // THEN - Assert.ThrowsExactly(() => { long _ = s1.Length; }); - Assert.ThrowsExactly(() => { long _ = s2.Length; }); + Assert.ThrowsExactly(() => { _ = s1.Length; }); + Assert.ThrowsExactly(() => { _ = s2.Length; }); } [TestMethod] diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs index 644cb9da..19d71028 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs @@ -298,7 +298,7 @@ public void Parse_ShouldThrow_WhenVersionIsUnsupported() Assert.ThrowsExactly(() => { - PackageItem _ = PackageItem.Parse(buffer); + _ = PackageItem.Parse(buffer); }); } diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs index 34f67567..d1753bcf 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs @@ -9,8 +9,6 @@ namespace TechnitiumLibrary.UnitTests.TechnitiumLibrary.IO [TestClass] public sealed class PackageTests { - private static MemoryStream CreateWritableStream() => new MemoryStream(); - private static byte[] BuildEmptyPackageFile() { // Header: @@ -23,41 +21,6 @@ private static byte[] BuildEmptyPackageFile() .ToArray(); } - /// - /// Creates a serialized single PackageItem with name "A" and empty content. - /// - private static byte[] CreateMinimalItem() - { - using MemoryStream ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); - - // Write NAME field (short) - writer.Write((byte)1); // length - writer.Write("A"u8.ToArray()); // ASCII name - - // Extract location = 0 - writer.Write((byte)0); - - // Flags = 0 - writer.Write((byte)0); - - // File size = 0 (Int64) - writer.Write((long)0); - - // Because file size = 0, Write no content - return ms.ToArray(); - } - - private static void WriteItem(Stream stream) - { - using MemoryStream data = new MemoryStream(); // empty payload - using PackageItem item = new PackageItem("A", data); - - item.WriteTo(stream); - } - - - // ------------------------------------------------------------- // CONSTRUCTION // ------------------------------------------------------------- @@ -65,7 +28,7 @@ private static void WriteItem(Stream stream) [TestMethod] public void Constructor_ShouldWriteHeader_WhenCreating() { - using MemoryStream backing = CreateWritableStream(); + using MemoryStream backing = new MemoryStream(); using (Package pkg = new Package(backing, PackageMode.Create)) { @@ -119,12 +82,12 @@ public void AddItem_ShouldThrow_WhenNotInCreateMode() [TestMethod] public void Items_ShouldThrow_WhenNotInOpenMode() { - using MemoryStream backing = CreateWritableStream(); + using MemoryStream backing = new MemoryStream(); using Package pkg = new Package(backing, PackageMode.Create); Assert.ThrowsExactly(() => { - System.Collections.ObjectModel.ReadOnlyCollection _ = pkg.Items; + _ = pkg.Items; }); } @@ -135,12 +98,12 @@ public void Items_ShouldThrow_WhenNotInOpenMode() [TestMethod] public void WriteAndRead_ShouldReturnSameItems() { - using MemoryStream backing = CreateWritableStream(); + using MemoryStream backing = new MemoryStream(); // Write using (Package pkg = new Package(backing, PackageMode.Create)) { - WriteItem(backing); + pkg.AddItem(new PackageItem("A", Stream.Null)); pkg.Close(); } @@ -154,9 +117,9 @@ public void WriteAndRead_ShouldReturnSameItems() [TestMethod] public void Close_ShouldWriteEOF_Once() { - using MemoryStream backing = CreateWritableStream(); + using MemoryStream backing = new MemoryStream(); using Package pkg = new Package(backing, PackageMode.Create); - WriteItem(backing); + pkg.AddItem(new PackageItem("A", Stream.Null)); pkg.Close(); long len1 = backing.Length; pkg.Close(); @@ -202,7 +165,7 @@ public void Dispose_ShouldCloseOwnedStream() [TestMethod] public void Dispose_ShouldNotCloseExternalStream() { - using MemoryStream backing = CreateWritableStream(); + using MemoryStream backing = new MemoryStream(); using (Package pkg = new Package(backing, PackageMode.Create, ownsStream: false)) pkg.Close(); From 5ce7ec16fca85943313dce66ebac5c41838bcddd Mon Sep 17 00:00:00 2001 From: Zafer Balkan Date: Tue, 3 Feb 2026 19:24:11 +0200 Subject: [PATCH 8/8] Added copyright --- .../BinaryReaderExtensionsTests.cs | 22 ++++++++++++++++++- .../BinaryWriterExtensionsTests.cs | 22 ++++++++++++++++++- .../TechnitiumLibrary.IO/JointTests.cs | 22 ++++++++++++++++++- .../TechnitiumLibrary.IO/OffsetStreamTests.cs | 22 ++++++++++++++++++- .../TechnitiumLibrary.IO/PackageItemTests.cs | 22 ++++++++++++++++++- .../TechnitiumLibrary.IO/PackageTests.cs | 22 ++++++++++++++++++- .../TechnitiumLibrary.IO/PipeTests.cs | 22 ++++++++++++++++++- .../StreamExtensionsTests.cs | 22 ++++++++++++++++++- .../WriteBufferedStreamTests.cs | 22 ++++++++++++++++++- 9 files changed, 189 insertions(+), 9 deletions(-) diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs index 33356011..e74b15a0 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryReaderExtensionsTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Linq; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs index d460f626..d26c8ebb 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/BinaryWriterExtensionsTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Linq; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs index 8a7aee4a..3abfb3b0 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/JointTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Threading.Tasks; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs index 6138ffdc..4064485e 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/OffsetStreamTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Threading.Tasks; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs index 19d71028..d6218cd6 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageItemTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using TechnitiumLibrary.IO; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs index d1753bcf..5d27dafc 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PackageTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using System.Linq; using System.Text; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs index 29be7dca..1ace137b 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/PipeTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Threading; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs index 7ea867a6..abe3eea5 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/StreamExtensionsTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Threading.Tasks; diff --git a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs index 4da8db0a..cfd1b3d7 100644 --- a/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs +++ b/TechnitiumLibrary.UnitTests/TechnitiumLibrary.IO/WriteBufferedStreamTests.cs @@ -1,4 +1,24 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* +Technitium Library +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2026 Zafer Balkan (zafer@zaferbalkan.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Text;