diff --git a/.gitea/workflows/test-dotnet.yaml b/.gitea/workflows/test-dotnet.yaml index 3dfb5d8..63e9466 100644 --- a/.gitea/workflows/test-dotnet.yaml +++ b/.gitea/workflows/test-dotnet.yaml @@ -15,13 +15,19 @@ jobs: test: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + strategy: + matrix: + dotnet-version: [ '8.x', '9.x' ] + env: + DOTNET_CLI_TELEMETRY_OPTOUT: 'true' - - name: Setup .NET - uses: https://github.com/actions/setup-dotnet@v3 + steps: + - uses: actions/checkout@v5 + + - name: Setup .NET ${{ matrix.dotnet-version }} + uses: https://github.com/actions/setup-dotnet@v4 with: - dotnet-version: 9.x + dotnet-version: ${{ matrix.dotnet-version }} - name: Restore dependencies run: dotnet restore @@ -30,12 +36,12 @@ jobs: run: dotnet build --no-restore - name: Test - run: dotnet test --no-build --verbosity normal --logger trx --results-directory "TestResults-9.x" + run: dotnet test --no-build --verbosity normal --logger trx --results-directory "TestResults-${{ matrix.dotnet-version }}" - name: Upload dotnet test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: dotnet-results-9.x - path: TestResults-9.x + name: dotnet-results-${{ matrix.dotnet-version }} + path: TestResults-${{ matrix.dotnet-version }} if: ${{ always() }} retention-days: 30 diff --git a/.vscode/settings.json b/.vscode/settings.json index 09cb4c4..b3259f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "dotnet.defaultSolution": "JustDotNet.Core.sln", - "dotnetAcquisitionExtension.enableTelemetry": false + "dotnetAcquisitionExtension.enableTelemetry": false, + "dotnet.testWindow.useTestingPlatformProtocol": true } diff --git a/Core.Tests/Base32Conversions/Decode.cs b/Core.Tests/Base32Conversions/Decode.cs index 21ce83a..f474c04 100644 --- a/Core.Tests/Base32Conversions/Decode.cs +++ b/Core.Tests/Base32Conversions/Decode.cs @@ -19,7 +19,7 @@ public class Decode var resultString = Base32.Encode(testBytes); var resultBytes = Base32.Decode(resultString); - resultBytes.Should().BeEquivalentTo(testBytes); + resultBytes.ShouldBeEquivalentTo(testBytes); } } @@ -34,7 +34,7 @@ public class Decode public void WhenCalledWithValidString_ShouldReturnValidByteArray(string str, byte[] expected) { var actualBytesArray = Base32.Decode(str); - actualBytesArray.Should().Equal(expected); + actualBytesArray.ShouldBe(expected); } [Theory] @@ -44,7 +44,7 @@ public class Decode public void WhenCalledWithValidStringThatEndsWithPaddingSign_ShouldReturnValidByteArray(string testString, byte[] expected) { var actualBytesArray = Base32.Decode(testString); - actualBytesArray.Should().Equal(expected); + actualBytesArray.ShouldBe(expected); } [Theory] @@ -54,7 +54,7 @@ public class Decode public void WhenCalledWithValidStringWithoutPaddingSign_ShouldReturnValidByteArray(string testString, byte[] expected) { var actualBytesArray = Base32.Decode(testString); - actualBytesArray.Should().Equal(expected); + actualBytesArray.ShouldBe(expected); } [Theory] @@ -66,7 +66,7 @@ public class Decode public void WhenCalledWithNotValidString_ShouldThrowFormatException(string testString) { Action action = () => Base32.Decode(testString); - action.Should().Throw(); + action.ShouldThrow(); } [Theory] @@ -74,6 +74,6 @@ public class Decode [InlineData("")] public void WhenCalledWithNullString_ShouldReturnEmptyArray(string? testString) { - Base32.Decode(testString).Should().BeEmpty(); + Base32.Decode(testString).ShouldBeEmpty(); } } diff --git a/Core.Tests/Base32Conversions/Encode.cs b/Core.Tests/Base32Conversions/Encode.cs index e3e1751..230afc0 100644 --- a/Core.Tests/Base32Conversions/Encode.cs +++ b/Core.Tests/Base32Conversions/Encode.cs @@ -31,7 +31,7 @@ public class Encode var resultBytes = Base32.Decode(testString); var resultString = Base32.Encode(resultBytes); - resultString.Should().Be(testString); + resultString.ShouldBe(testString); } [Theory] @@ -47,7 +47,7 @@ public class Encode public void WhenCalledWithNotEmptyByteArray_ShouldReturnValidString(string expected, byte[] testArray) { var str = Base32.Encode(testArray); - str.Should().Be(expected); + str.ShouldBe(expected); } [Theory] @@ -56,7 +56,7 @@ public class Encode public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[]? testArray) { var actualBase32 = Base32.Encode(testArray); - actualBase32.Should().Be(string.Empty); + actualBase32.ShouldBe(string.Empty); } [Theory] @@ -68,7 +68,7 @@ public class Encode var charsWritten = Base32.Encode(testArray, output); - charsWritten.Should().Be(0); - output.Should().Equal(['1', '2', '3', '4']); + charsWritten.ShouldBe(0); + output.ShouldBe(['1', '2', '3', '4']); } } diff --git a/Core.Tests/Base64UrlConversions/Decode.cs b/Core.Tests/Base64UrlConversions/Decode.cs index 6baf0ea..4a26894 100644 --- a/Core.Tests/Base64UrlConversions/Decode.cs +++ b/Core.Tests/Base64UrlConversions/Decode.cs @@ -19,7 +19,7 @@ public class Decode var resultString = Base64Url.Encode(testBytes); var resultBytes = Base64Url.Decode(resultString); - resultBytes.Should().BeEquivalentTo(testBytes); + resultBytes.ShouldBeEquivalentTo(testBytes); } } @@ -39,7 +39,7 @@ public class Decode var resultString = Base64Url.Encode(testLong); var resultLong = Base64Url.DecodeLong(resultString); - resultLong.Should().Be(testLong); + resultLong.ShouldBe(testLong); } } @@ -57,7 +57,7 @@ public class Decode public void WhenCalled_ShouldReturnValidLong(string testString, long expected) { var result = Base64Url.DecodeLong(testString); - result.Should().Be(expected); + result.ShouldBe(expected); } [Theory] @@ -70,7 +70,7 @@ public class Decode { var result = Base64Url.DecodeGuid(testString); var expected = Guid.Parse(expectedStr); - result.Should().Be(expected); + result.ShouldBe(expected); } [Theory] @@ -85,7 +85,7 @@ public class Decode public void WhenCalled_ShouldReturnValidBytes(string testString, byte[] expected) { var result = Base64Url.Decode(testString); - result.Should().BeEquivalentTo(expected); + result.ShouldBeEquivalentTo(expected); } [Theory] @@ -97,7 +97,7 @@ public class Decode public void WhenCalledWithInvalidString_ShouldThrowFormatException(string testString) { Action action = () => Base64Url.Decode(testString); - action.Should().Throw(); + action.ShouldThrow(); } [Theory] @@ -109,7 +109,7 @@ public class Decode public void WhenCalledWithInvalidGuidString_ShouldThrowFormatException(string testString) { Action action = () => Base64Url.DecodeGuid(testString); - action.Should().Throw(); + action.ShouldThrow(); } [Theory] @@ -122,7 +122,7 @@ public class Decode public void WhenCalledWithInvalidLongString_ShouldThrowFormatException(string testString) { Action action = () => Base64Url.DecodeLong(testString); - action.Should().Throw(); + action.ShouldThrow(); } [Theory] @@ -130,6 +130,6 @@ public class Decode [InlineData("")] public void WhenCalledWithNullString_ShouldReturnEmptyArray(string? testString) { - Base64Url.Decode(testString).Should().BeEmpty(); + Base64Url.Decode(testString).ShouldBeEmpty(); } } diff --git a/Core.Tests/Base64UrlConversions/Encode.cs b/Core.Tests/Base64UrlConversions/Encode.cs index 9b9fa25..a598b5c 100644 --- a/Core.Tests/Base64UrlConversions/Encode.cs +++ b/Core.Tests/Base64UrlConversions/Encode.cs @@ -12,7 +12,7 @@ public class Encode { var testGuid = Guid.Parse(testGuidString); var result = Base64Url.Encode(testGuid); - result.Should().Be(expected); + result.ShouldBe(expected); } [Theory] @@ -29,7 +29,7 @@ public class Encode public void WhenCalledWithLong_ShouldReturnValidString(string expected, long testLong) { var result = Base64Url.Encode(testLong); - result.Should().Be(expected); + result.ShouldBe(expected); } [Theory] @@ -44,7 +44,7 @@ public class Encode public void WhenCalled_ShouldReturnValidString(string expected, byte[] testBytes) { var result = Base64Url.Encode(testBytes); - result.Should().Be(expected); + result.ShouldBe(expected); } [Theory] @@ -53,7 +53,7 @@ public class Encode public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[]? testArray) { var actualBase32 = Base64Url.Encode(testArray); - actualBase32.Should().Be(string.Empty); + actualBase32.ShouldBe(string.Empty); } [Theory] @@ -65,7 +65,7 @@ public class Encode var charsWritten = Base64Url.Encode(testArray, output); - charsWritten.Should().Be(0); - output.Should().Equal(['1', '2', '3', '4']); + charsWritten.ShouldBe(0); + output.ShouldBe((char[])['1', '2', '3', '4']); } } diff --git a/Core.Tests/Core.Tests.csproj b/Core.Tests/Core.Tests.csproj index dc2a347..bb1aab0 100644 --- a/Core.Tests/Core.Tests.csproj +++ b/Core.Tests/Core.Tests.csproj @@ -1,10 +1,11 @@ - net9.0 + net8.0;net9.0 enable enable - + Exe + Just.Core.Tests Just.Core.Tests @@ -14,13 +15,8 @@ - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Core.Tests/GlobalUsings.cs b/Core.Tests/GlobalUsings.cs index 91743bb..b37457e 100644 --- a/Core.Tests/GlobalUsings.cs +++ b/Core.Tests/GlobalUsings.cs @@ -1,2 +1,2 @@ global using Xunit; -global using FluentAssertions; +global using Shouldly; diff --git a/Core.Tests/GuidV8Tests/NewGuid.cs b/Core.Tests/GuidV8Tests/NewGuid.cs index 2fbfa58..f18055c 100644 --- a/Core.Tests/GuidV8Tests/NewGuid.cs +++ b/Core.Tests/GuidV8Tests/NewGuid.cs @@ -14,8 +14,19 @@ public class NewGuid { var timestamp = referenceTime.AddSeconds(rng.Next()); var result = GuidV8.NewGuid(timestamp, entropy); - result.Version.Should().Be(8); - (result.Variant & 0b1100).Should().Be(0b1000); + +#if NET9_0_OR_GREATER + result.Version.ShouldBe(8); + (result.Variant & 0b1100).ShouldBe(0b1000); +#else + var bytes = result.ToByteArray(); + // Check version (bits 4-7 of the 7th byte) + var version = (bytes[7] >> 4) & 0x0F; + version.ShouldBe(8); // UUID version 8 + // Check variant (bits 6-7 of the 8th byte) + var variant = bytes[8] >> 6; + variant.ShouldBe(0b10); // Standard UUID variant +#endif } } @@ -86,11 +97,11 @@ public class NewGuid var sut = expected.Values.ToArray(); rng.Shuffle(sut); - sut.Order().Should().Equal(expected.Select(x => x.Value)); - sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value)); + sut.Order().ShouldBe(expected.Select(x => x.Value)); + sut.OrderBy(x => x.ToString()).ShouldBe(expected.Select(x => x.Value)); - sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value)); - sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value)); + sut.OrderDescending().ShouldBe(expected.Reverse().Select(x => x.Value)); + sut.OrderByDescending(x => x.ToString()).ShouldBe(expected.Reverse().Select(x => x.Value)); } [Theory] @@ -160,11 +171,11 @@ public class NewGuid var sut = expected.Values.ToArray(); rng.Shuffle(sut); - sut.Order().Should().Equal(expected.Select(x => x.Value)); - sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value)); + sut.Order().ShouldBe(expected.Select(x => x.Value)); + sut.OrderBy(x => x.ToString()).ShouldBe(expected.Select(x => x.Value)); - sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value)); - sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value)); + sut.OrderDescending().ShouldBe(expected.Reverse().Select(x => x.Value)); + sut.OrderByDescending(x => x.ToString()).ShouldBe(expected.Reverse().Select(x => x.Value)); } [Theory] @@ -234,10 +245,10 @@ public class NewGuid var sut = expected.Values.ToArray(); rng.Shuffle(sut); - sut.Order().Should().Equal(expected.Select(x => x.Value)); - sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value)); + sut.Order().ShouldBe(expected.Select(x => x.Value)); + sut.OrderBy(x => x.ToString()).ShouldBe(expected.Select(x => x.Value)); - sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value)); - sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value)); + sut.OrderDescending().ShouldBe(expected.Reverse().Select(x => x.Value)); + sut.OrderByDescending(x => x.ToString()).ShouldBe(expected.Reverse().Select(x => x.Value)); } } diff --git a/Core.Tests/SeqIdTests/NextId.cs b/Core.Tests/SeqIdTests/NextId.cs index 9892abe..8d55af0 100644 --- a/Core.Tests/SeqIdTests/NextId.cs +++ b/Core.Tests/SeqIdTests/NextId.cs @@ -1,3 +1,4 @@ + namespace Just.Core.Tests.SeqIdTests; public class NextId @@ -25,9 +26,9 @@ public class NextId long sequencePart = (id >> SeqShift) & SeqMask; long randomPart = id & RandMask; - timestampPart.Should().Be(500); - sequencePart.Should().Be(0); - randomPart.Should().BeInRange(0, RandMask); + timestampPart.ShouldBe(500); + sequencePart.ShouldBe(0); + randomPart.ShouldBeInRange(0, RandMask); } [Fact] @@ -44,7 +45,7 @@ public class NextId long sequence1 = (id1 >> SeqShift) & SeqMask; long sequence2 = (id2 >> SeqShift) & SeqMask; - sequence2.Should().Be(sequence1 + 1); + sequence2.ShouldBe(sequence1 + 1); } [Fact] @@ -61,7 +62,7 @@ public class NextId // Assert long sequence = (id >> SeqShift) & SeqMask; - sequence.Should().Be(0); + sequence.ShouldBe(0); } [Fact] @@ -74,7 +75,7 @@ public class NextId // Act & Assert _seqId.Next(time1); // First call sets last timestamp Action act = () => _seqId.Next(time2); - act.Should().Throw() + act.ShouldThrow() .WithMessage("Refused to create new SeqId. Last timestamp is in the future."); } @@ -90,7 +91,7 @@ public class NextId _seqId.Next(time); // Exhauste sequence } Action act = () => _seqId.Next(time); - act.Should().Throw() + act.ShouldThrow() .WithMessage("Refused to create new SeqId. Sequence exhausted."); } @@ -105,7 +106,7 @@ public class NextId // Assert long randomPart = id & RandMask; - randomPart.Should().BeInRange(0, RandMask); + randomPart.ShouldBeInRange(0, RandMask); } [Fact] @@ -119,7 +120,7 @@ public class NextId // Assert long randomPart = id & RandMask; - randomPart.Should().BeInRange(0, RandMask); + randomPart.ShouldBeInRange(0, RandMask); } [Fact] @@ -127,14 +128,14 @@ public class NextId { // Arrange var now = DateTime.UtcNow; - var defaultEpoch = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc); - long expectedTimestamp = (long)(now - defaultEpoch).TotalMilliseconds; + var defaultEpoch = SeqId.DefaultEpoch; + long expectedTimestamp = ((long)(now - defaultEpoch).TotalMilliseconds) & TimestampMask; // Mask handles overflow // Act long id = SeqId.NextId(); // Assert long timestampPart = (id >> TimestampShift) & TimestampMask; - timestampPart.Should().BeCloseTo(expectedTimestamp & TimestampMask, 1); // Mask handles overflow + timestampPart.ShouldBeInRange(expectedTimestamp, expectedTimestamp + 1); } } \ No newline at end of file diff --git a/Core.Tests/ShouldlyExtensions.cs b/Core.Tests/ShouldlyExtensions.cs new file mode 100644 index 0000000..9b217c5 --- /dev/null +++ b/Core.Tests/ShouldlyExtensions.cs @@ -0,0 +1,11 @@ +namespace Just.Core.Tests; + +public static class ShouldlyExtensions +{ + public static TException WithMessage(this TException exception, string expectedMessage, string? customMessage = null) + where TException : Exception + { + exception.Message.ShouldBe(expectedMessage, customMessage: customMessage); + return exception; + } +} diff --git a/Core.Tests/SystemIOStreamExtensionsTests/Populate.cs b/Core.Tests/SystemIOStreamExtensionsTests/Populate.cs index 875a643..17e38b7 100644 --- a/Core.Tests/SystemIOStreamExtensionsTests/Populate.cs +++ b/Core.Tests/SystemIOStreamExtensionsTests/Populate.cs @@ -23,7 +23,7 @@ public class Populate stream.Populate(buffer, offset, length); - buffer.Skip(offset).Take(length).Should().Equal(streamContent.Take(length)); + buffer.Skip(offset).Take(length).ShouldBe(streamContent.Take(length)); } [Theory] @@ -38,7 +38,7 @@ public class Populate stream.Populate(buffer); - buffer.Should().Equal(streamContent.Take(bufferSize)); + buffer.ShouldBe(streamContent.Take(bufferSize)); } [Theory] @@ -53,6 +53,6 @@ public class Populate Action action = () => stream.Populate(buffer); - action.Should().Throw(); + action.ShouldThrow(); } } diff --git a/Core.Tests/SystemIOStreamExtensionsTests/PopulateAsync.cs b/Core.Tests/SystemIOStreamExtensionsTests/PopulateAsync.cs index 70aa318..3bf68c4 100644 --- a/Core.Tests/SystemIOStreamExtensionsTests/PopulateAsync.cs +++ b/Core.Tests/SystemIOStreamExtensionsTests/PopulateAsync.cs @@ -15,7 +15,7 @@ public class PopulateAsync Func action = async () => await stream.PopulateAsync(buffer, cts.Token); cts.Cancel(); - await action.Should().ThrowAsync(); + await action.ShouldThrowAsync(); } [Theory] @@ -31,13 +31,14 @@ public class PopulateAsync [InlineData(5, 5)] public async Task WhenCalled_ShouldPopulateSpecifiedRange(int offset, int length) { + using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50)); byte[] streamContent = [0x01, 0x02, 0x03, 0x04, 0x05,]; using var stream = new MemoryStream(streamContent); var buffer = new byte[10]; - await stream.PopulateAsync(buffer, offset, length); + await stream.PopulateAsync(buffer, offset, length, cts.Token); - buffer.Skip(offset).Take(length).Should().Equal(streamContent.Take(length)); + buffer.Skip(offset).Take(length).ShouldBe(streamContent.Take(length)); } [Theory] @@ -47,12 +48,13 @@ public class PopulateAsync [InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, 5)] public async Task WhenStreamContainsSameOrGreaterAmmountOfItems_ShouldPopulateBuffer(byte[] streamContent, int bufferSize) { + using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50)); using var stream = new MemoryStream(streamContent); var buffer = new byte[bufferSize]; - await stream.PopulateAsync(buffer); + await stream.PopulateAsync(buffer, cts.Token); - buffer.Should().Equal(streamContent.Take(bufferSize)); + buffer.ShouldBe(streamContent.Take(bufferSize)); } [Theory] @@ -67,6 +69,6 @@ public class PopulateAsync Func action = async () => await stream.PopulateAsync(buffer); - await action.Should().ThrowAsync(); + await action.ShouldThrowAsync(); } }