Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/unit-testing.yml
Original file line number Diff line number Diff line change
@@ -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@v2

- 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.UnitTests\TechnitiumLibrary.UnitTests.csproj /t:Test /p:Configuration=${{ env.BUILD_CONFIGURATION }}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions TechnitiumLibrary.UnitTests/MSTestSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
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 <http://www.gnu.org/licenses/>.

*/

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<IOException>(() => 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);
}
}
}
Loading