From 25062bbf8b674437f8b884b79fd74d535da8cf69 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sat, 17 Feb 2024 16:23:04 +0000 Subject: [PATCH 01/12] fix(ci): fix coverage reports --- .github/workflows/dotnet.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 348a2cc..bf0b871 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -33,8 +33,10 @@ jobs: - name: Restore dependencies run: dotnet restore - - name: Install dotCover - run: dotnet tool install --global JetBrains.dotCover.GlobalTool + - name: Install coverage tools + run: | + dotnet tool install --global JetBrains.dotCover.GlobalTool + dotnet tool install --global dotnet-reportgenerator-globaltool - name: Build run: dotnet build --no-restore --configuration Release @@ -50,13 +52,17 @@ jobs: - name: Test .NET 8 run: dotnet test --no-build --verbosity normal --configuration Release --framework net8.0 - + - name: Collect coverage - run: dotnet dotcover test --dcReportType=XML + run: dotnet dotcover test --dcReportType=DetailedXML + + - name: Convert coverage + run: reportgenerator -reports:./dotCover.Output.xml -targetdir:. -reporttypes:Cobertura - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.0.0 with: + disable_search: true + file: Cobertura.xml token: ${{ secrets.CODECOV_TOKEN }} - files: dotCover.Output.xml slug: oliverbooth/X10D From bd1b12da71410e37d7262b2600bfc5b3960023a3 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sat, 17 Feb 2024 17:54:06 +0000 Subject: [PATCH 02/12] docs: update 3.x.x migration docs --- docfx_project/articles/migration-from-3.x.x.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docfx_project/articles/migration-from-3.x.x.md b/docfx_project/articles/migration-from-3.x.x.md index 1dbe179..da88073 100644 --- a/docfx_project/articles/migration-from-3.x.x.md +++ b/docfx_project/articles/migration-from-3.x.x.md @@ -3,6 +3,9 @@ X10D 4.0.0 is a major release that introduces breaking changes. This document will help you migrate your code from 3.x.x to 4.0.0. +When a breaking change is mentioned, the compatibility mirrors that of the Microsoft documentation for .NET, which you +can find [here](https://learn.microsoft.com/en-us/dotnet/core/compatibility/categories). + ## Removed APIs ### X10D.DSharpPlus library @@ -12,8 +15,18 @@ wrapper library. However, I have since moved to using a different library, and a scope of X10D or in my best interest to maintain it. The library will remain available on NuGet until DSharpPlus release 5.0.0 as stable, and X10D.DSharpPlus will NOT be part of X10D 4.0.0. I'm sorry for any inconvenience this may cause. +### X10D.Unity library + +The `X10D.Unity` library has been removed. This library was used to provide extension methods for the Unity API. Due to +game development politics, I no longer feel it in my best interest to continue development of the Unity package. The +library will remain on NuGet for the foreseeable future but will no longer be maintained. The `upm` branch of the Git +repository will remain available indefinitely also. + + ### `Endianness` enum +**Source incompatible change** + The `Endianness` enum was used to specify the endianness of data when reading or writing to a stream. This was causing some clutter, and makes it harder to develop X10D, so it was removed. In its stead, any method which accepted an `Endianness` parameter now has two overloads: one for big-endian, and one for little-endian. For example, the following @@ -41,12 +54,16 @@ Span buffer = stackalloc byte[4]; ### `IEnumerable.ConcatOne(T)` extension method +**Source incompatible change** + The `IEnumerable.ConcatOne` extension method was used to concatenate a single item to an enumerable. At the time, I was unaware of the `Enumerable.Append` method, which does the same thing. As such, `ConcatOne` has been removed. There is no migration path for this, as the built in `Append` method from LINQ is a drop-in replacement. ## Exception Changes +**Source incompatible change** + If you were previously catching TypeInitializationException when calling `Stream.GetHash<>` or `Stream.TryWriteHash<>`, you will now need to catch a ArgumentException instead. The justification for this change is that ArgumentException is more general, and more easily understood by developers. \ No newline at end of file From 195e25e0e3d7058e03b2ffab481fe671b88acbce Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 03:40:34 +0100 Subject: [PATCH 03/12] feat: add range-based foreach --- CHANGELOG.md | 1 + X10D.Tests/src/Core/RangeTests.cs | 66 +++++++++++++++++++++++++++++++ X10D/src/Core/RangeEnumerator.cs | 53 +++++++++++++++++++++++++ X10D/src/Core/RangeExtensions.cs | 48 ++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 X10D.Tests/src/Core/RangeTests.cs create mode 100644 X10D/src/Core/RangeEnumerator.cs create mode 100644 X10D/src/Core/RangeExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 00ce823..6ebdd98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - X10D: Added `TextWriter.WriteLineNoAlloc(uint[, ReadOnlySpan[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(long[, ReadOnlySpan[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(ulong[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `Range.GetEnumerator` (and `RangeEnumerator`), implementing Python-esque `for` loops in C#. - X10D: Added `string.ConcatIf`. - X10D: Added `string.MDBold`, `string.MDCode`, `string.MDCodeBlock([string])`, `string.MDHeading(int)`, `string.MDItalic`, `string.MDLink`, `string.MDStrikeOut`, and `string.MDUnderline` for Markdown formatting. diff --git a/X10D.Tests/src/Core/RangeTests.cs b/X10D.Tests/src/Core/RangeTests.cs new file mode 100644 index 0000000..7acdbce --- /dev/null +++ b/X10D.Tests/src/Core/RangeTests.cs @@ -0,0 +1,66 @@ +using NUnit.Framework; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestFixture] +internal class RangeTests +{ + [Test] + public void Range_GetEnumerator_ShouldReturnRangeEnumerator() + { + Assert.Multiple(() => + { + Assert.That(5..10, Is.TypeOf()); + Assert.That((5..10).GetEnumerator(), Is.TypeOf()); + }); + } + + [Test] + public void Loop_OverRange0To10_ShouldCountFrom0To10Inclusive() + { + int value = 0; + + foreach (int i in 0..10) + { + Assert.That(i, Is.EqualTo(value)); + value++; + } + } + + [Test] + public void Loop_OverRangeNegative5To5_ShouldCountFromNegative5To5Inclusive() + { + int value = -5; + + foreach (int i in ^5..5) + { + Assert.That(i, Is.EqualTo(value)); + value++; + } + } + + [Test] + public void Loop_OverRange5ToNegative5_ShouldCountFrom5ToNegative5Inclusive() + { + int value = 5; + + foreach (int i in 5..^5) + { + Assert.That(i, Is.EqualTo(value)); + value--; + } + } + + [Test] + public void Loop_OverRange10To0_ShouldCountFrom10To0Inclusive() + { + int value = 10; + + foreach (int i in 10..0) + { + Assert.That(i, Is.EqualTo(value)); + value--; + } + } +} diff --git a/X10D/src/Core/RangeEnumerator.cs b/X10D/src/Core/RangeEnumerator.cs new file mode 100644 index 0000000..e2dd59b --- /dev/null +++ b/X10D/src/Core/RangeEnumerator.cs @@ -0,0 +1,53 @@ +namespace X10D.Core; + +/// +/// Enumerates the indices of a . +/// +public struct RangeEnumerator +{ + private readonly bool _decrement; + private readonly int _endValue; + + /// + /// Initializes a new instance of the structure. + /// + /// The range over which to enumerate. + public RangeEnumerator(Range range) + { + Index start = range.Start; + Index end = range.End; + + int startValue = start.IsFromEnd ? -start.Value : start.Value; + _endValue = end.IsFromEnd ? -end.Value : end.Value; + + _decrement = _endValue < startValue; + Current = _decrement ? startValue + 1 : startValue - 1; + } + + /// + /// Gets the element in the collection at the current position of the enumerator. + /// + /// The element in the collection at the current position of the enumerator. + public int Current { get; private set; } + + /// + /// Advances the enumerator to the next element of the collection. + /// + /// + /// if the enumerator was successfully advanced to the next element; if + /// the enumerator has passed the end of the collection. + /// + public bool MoveNext() + { + int value = Current; + + if (_decrement) + { + Current--; + return value > _endValue; + } + + Current++; + return value < _endValue; + } +} diff --git a/X10D/src/Core/RangeExtensions.cs b/X10D/src/Core/RangeExtensions.cs new file mode 100644 index 0000000..a6164bd --- /dev/null +++ b/X10D/src/Core/RangeExtensions.cs @@ -0,0 +1,48 @@ +namespace X10D.Core; + +/// +/// Extension methods for . +/// +public static class RangeExtensions +{ + /// + /// Allows the ability to use a for loop to iterate over the indices of a . The indices of the + /// range are the inclusive lower and upper bounds of the enumeration. + /// + /// The range whose indices over which will be enumerated. + /// A that will enumerate over the indices of . + /// + /// This method aims to implement Python-esque for loops in C# by taking advantage of the language syntax used to define + /// a value. Negative bounds may be specified using the C# ^ operator, or by providing an + /// whose property is . + /// + /// + /// The following example counts from 0 to 10 inclusive: + /// + /// foreach (var i in 0..10) + /// { + /// Console.WriteLine(i); + /// } + /// + /// + /// To use negative bounds, use the ^ operator. The following example counts from -5 to 5 inclusive: + /// + /// foreach (var i in ^5..5) + /// { + /// Console.WriteLine(i); + /// } + /// + /// + /// Decrementing enumeration is supported. The following example counts from 5 to -5 inclusive: + /// + /// foreach (var i in 5..^5) + /// { + /// Console.WriteLine(i); + /// } + /// + /// + public static RangeEnumerator GetEnumerator(this Range range) + { + return new RangeEnumerator(range); + } +} From f389731703819ca1d8cdb8aca95f27aa0e08f3ca Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 03:42:21 +0100 Subject: [PATCH 04/12] style: remove trailing whitespace on line 10 --- X10D/src/Core/RangeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/X10D/src/Core/RangeExtensions.cs b/X10D/src/Core/RangeExtensions.cs index a6164bd..6c91922 100644 --- a/X10D/src/Core/RangeExtensions.cs +++ b/X10D/src/Core/RangeExtensions.cs @@ -7,7 +7,7 @@ public static class RangeExtensions { /// /// Allows the ability to use a for loop to iterate over the indices of a . The indices of the - /// range are the inclusive lower and upper bounds of the enumeration. + /// range are the inclusive lower and upper bounds of the enumeration. /// /// The range whose indices over which will be enumerated. /// A that will enumerate over the indices of . From 4a80c93cc214648279504c8788d39244562f8fbc Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 03:45:51 +0100 Subject: [PATCH 05/12] chore!: drop net7.0 target (#90) --- X10D.Hosting/X10D.Hosting.csproj | 2 +- X10D.Tests/X10D.Tests.csproj | 2 +- X10D/X10D.csproj | 2 +- tools/Benchmarks/Benchmarks.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/X10D.Hosting/X10D.Hosting.csproj b/X10D.Hosting/X10D.Hosting.csproj index b54a518..cc93b63 100644 --- a/X10D.Hosting/X10D.Hosting.csproj +++ b/X10D.Hosting/X10D.Hosting.csproj @@ -1,7 +1,7 @@ - net8.0;net7.0;net6.0 + net8.0;net6.0 diff --git a/X10D.Tests/X10D.Tests.csproj b/X10D.Tests/X10D.Tests.csproj index eb3895c..9ac986d 100644 --- a/X10D.Tests/X10D.Tests.csproj +++ b/X10D.Tests/X10D.Tests.csproj @@ -1,7 +1,7 @@ - net8.0;net7.0;net6.0 + net8.0;net6.0 false true xml,cobertura diff --git a/X10D/X10D.csproj b/X10D/X10D.csproj index 8f36b52..592e773 100644 --- a/X10D/X10D.csproj +++ b/X10D/X10D.csproj @@ -1,7 +1,7 @@ - net8.0;net7.0;net6.0 + net8.0;net6.0 diff --git a/tools/Benchmarks/Benchmarks.csproj b/tools/Benchmarks/Benchmarks.csproj index 0791ad9..197c37b 100644 --- a/tools/Benchmarks/Benchmarks.csproj +++ b/tools/Benchmarks/Benchmarks.csproj @@ -1,7 +1,7 @@ - net8.0;net7.0;net6.0 + net8.0;net6.0 Release Exe true From 69157b95174a7c6f59e68cb0b8d87b4f09fcd582 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 03:46:46 +0100 Subject: [PATCH 06/12] ci: drop net7.0 target for tests --- .github/workflows/dotnet.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index d42292f..4b60600 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -24,7 +24,6 @@ jobs: with: dotnet-version: | 6.0.x - 7.0.x 8.0.x - name: Add NuGet source @@ -39,9 +38,6 @@ jobs: - name: Test .NET 6 run: dotnet test --no-build --verbosity normal --configuration Release --framework net6.0 --collect:"XPlat Code Coverage" --results-directory test-results/net6.0 - - name: Test .NET 7 - run: dotnet test --no-build --verbosity normal --configuration Release --framework net7.0 --collect:"XPlat Code Coverage" --results-directory test-results/net7.0 - - name: Test .NET 8 run: dotnet test --no-build --verbosity normal --configuration Release --framework net8.0 --collect:"XPlat Code Coverage" --results-directory test-results/net8.0 From 77dd14432182ffbc31ce08620cde3e07d69f542e Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 03:54:22 +0100 Subject: [PATCH 07/12] ci: ignore compile-generated file from coverage --- codecov.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..f9f76f1 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "X10D/src/ExceptionMessages.Designer.cs" From d2924d7ac0055891ab73bbb0a46dccf373fb5051 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 04:00:25 +0100 Subject: [PATCH 08/12] refactor!: remove extensions for Progress --- CHANGELOG.md | 3 +- X10D.Tests/src/Reactive/ProgressTests.cs | 67 ------------------ X10D/src/Reactive/ObservableDisposer.cs | 33 --------- X10D/src/Reactive/ProgressExtensions.cs | 89 ------------------------ X10D/src/Reactive/ProgressObservable.cs | 36 ---------- 5 files changed, 2 insertions(+), 226 deletions(-) delete mode 100644 X10D.Tests/src/Reactive/ProgressTests.cs delete mode 100644 X10D/src/Reactive/ObservableDisposer.cs delete mode 100644 X10D/src/Reactive/ProgressExtensions.cs delete mode 100644 X10D/src/Reactive/ProgressObservable.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ebdd98..c3d26dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 BigEndian/LittleEndian methods. - X10D: `Stream.GetHash<>` and `Stream.TryWriteHash<>` now throw ArgumentException in lieu of TypeInitializationException. -- X10D: `char.IsEmoji` no longer allocates for .NET 7. +- X10D: `char.IsEmoji` no longer allocates for .NET 7+. - X10D: `string.Repeat` is now more efficient. ### Removed @@ -49,6 +49,7 @@ TypeInitializationException. - X10D: Removed `Endianness` enum. - X10D: Removed `Span.Replace(T, T)` for .NET 8 target. - X10D: Removed .NET Standard 2.1 target. +- X10D: Removed extensions for `Progress`. These are already provided by [`Observable.FromEventPattern`](https://learn.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh229424(v=vs.103)). - X10D.Hosting: Removed .NET Standard 2.1 target. - X10D.DSharpPlus: Complete sunset of library. This library will not be updated to support DSharpPlus v5.0.0 (#83). - X10D.Unity: Complete sunset of library. This library will not be updated effective immediately (#86). diff --git a/X10D.Tests/src/Reactive/ProgressTests.cs b/X10D.Tests/src/Reactive/ProgressTests.cs deleted file mode 100644 index 5b8f180..0000000 --- a/X10D.Tests/src/Reactive/ProgressTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -using NUnit.Framework; -using X10D.Reactive; - -namespace X10D.Tests.Reactive; - -[TestFixture] -internal class ProgressTests -{ - [Test] - public void OnProgressChanged_ShouldCallCompletionDelegate_GivenCompletionValue() - { - var subscriberWasCalled = false; - var completionWasCalled = false; - - var progress = new Progress(); - progress.OnProgressChanged(1.0f).Subscribe(_ => subscriberWasCalled = true, () => completionWasCalled = true); - - ((IProgress)progress).Report(0.5f); - ((IProgress)progress).Report(1.0f); - - Thread.Sleep(1000); - Assert.That(subscriberWasCalled); - Assert.That(completionWasCalled); - } - - [Test] - public void OnProgressChanged_ShouldCallSubscribers_OnProgressChanged() - { - var subscriberWasCalled = false; - - var progress = new Progress(); - progress.OnProgressChanged().Subscribe(_ => subscriberWasCalled = true); - - ((IProgress)progress).Report(0.5f); - - Thread.Sleep(1000); - Assert.That(subscriberWasCalled); - } - - [Test] - public void OnProgressChanged_ShouldCallSubscribers_OnProgressChanged_GivenCompletionValue() - { - var subscriberWasCalled = false; - - var progress = new Progress(); - progress.OnProgressChanged(1.0f).Subscribe(_ => subscriberWasCalled = true); - - ((IProgress)progress).Report(0.5f); - - Thread.Sleep(1000); - Assert.That(subscriberWasCalled); - } - - [Test] - public void OnProgressChanged_ShouldThrowArgumentNullException_GivenNullProgress() - { - Progress progress = null!; - Assert.Throws(() => progress.OnProgressChanged()); - } - - [Test] - public void OnProgressChanged_ShouldThrowArgumentNullException_GivenNullProgressAndCompletionValue() - { - Progress progress = null!; - Assert.Throws(() => progress.OnProgressChanged(1.0f)); - } -} diff --git a/X10D/src/Reactive/ObservableDisposer.cs b/X10D/src/Reactive/ObservableDisposer.cs deleted file mode 100644 index b935112..0000000 --- a/X10D/src/Reactive/ObservableDisposer.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace X10D.Reactive; - -/// -/// Represents a disposable that removes an observer from a collection of observers. -/// -internal readonly struct ObservableDisposer : IDisposable -{ - private readonly HashSet> _observers; - private readonly IObserver _observer; - private readonly Action? _additionalAction; - - /// - /// Initializes a new instance of the struct. - /// - /// A collection of observers from which to remove the specified observer. - /// The observer to remove from the collection. - /// The additional action to run on dispose. - public ObservableDisposer(HashSet> observers, IObserver observer, Action? additionalAction) - { - _observers = observers ?? throw new ArgumentNullException(nameof(observers)); - _observer = observer ?? throw new ArgumentNullException(nameof(observer)); - _additionalAction = additionalAction; - } - - /// - /// Removes the observer from the collection of observers. - /// - public void Dispose() - { - _observers.Remove(_observer); - _additionalAction?.Invoke(); - } -} diff --git a/X10D/src/Reactive/ProgressExtensions.cs b/X10D/src/Reactive/ProgressExtensions.cs deleted file mode 100644 index 93f696e..0000000 --- a/X10D/src/Reactive/ProgressExtensions.cs +++ /dev/null @@ -1,89 +0,0 @@ -namespace X10D.Reactive; - -/// -/// Provides extension methods for . -/// -public static class ProgressExtensions -{ - /// - /// Wraps the event of the current in an - /// object. - /// - /// The progress whose event to wrap. - /// The type of progress update value. - /// - /// An object that wraps the event of the current - /// . - /// - /// is . - public static IObservable OnProgressChanged(this Progress progress) - { - if (progress is null) - { - throw new ArgumentNullException(nameof(progress)); - } - - var progressObservable = new ProgressObservable(); - - void ProgressChangedHandler(object? sender, T args) - { - IObserver[] observers = progressObservable.Observers; - - for (var index = 0; index < observers.Length; index++) - { - observers[index].OnNext(args); - } - } - - progress.ProgressChanged += ProgressChangedHandler; - progressObservable.OnDispose = () => progress.ProgressChanged -= ProgressChangedHandler; - - return progressObservable; - } - - /// - /// Wraps the event of the current in an - /// object, and completes the observable when the progress reaches the specified value. - /// - /// The progress whose event to wrap. - /// The value that indicates completion. - /// The type of progress update value. - /// - /// An object that wraps the event of the current - /// . - /// - /// is . - public static IObservable OnProgressChanged(this Progress progress, T completeValue) - { - if (progress is null) - { - throw new ArgumentNullException(nameof(progress)); - } - - var progressObservable = new ProgressObservable(); - var comparer = EqualityComparer.Default; - - void ProgressChangedHandler(object? sender, T args) - { - IObserver[] observers = progressObservable.Observers; - - for (var index = 0; index < observers.Length; index++) - { - observers[index].OnNext(args); - } - - if (comparer.Equals(args, completeValue)) - { - for (var index = 0; index < observers.Length; index++) - { - observers[index].OnCompleted(); - } - } - } - - progress.ProgressChanged += ProgressChangedHandler; - progressObservable.OnDispose = () => progress.ProgressChanged -= ProgressChangedHandler; - - return progressObservable; - } -} diff --git a/X10D/src/Reactive/ProgressObservable.cs b/X10D/src/Reactive/ProgressObservable.cs deleted file mode 100644 index 32a0870..0000000 --- a/X10D/src/Reactive/ProgressObservable.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace X10D.Reactive; - -/// -/// Represents a concrete implementation of that tracks progress of a . -/// -internal sealed class ProgressObservable : IObservable -{ - private readonly HashSet> _observers = new(); - - /// - /// Gets the observers. - /// - /// The observers. - public IObserver[] Observers - { - get => _observers.ToArray(); - } - - internal Action? OnDispose { get; set; } - - /// - /// Subscribes the specified observer to the progress tracker. - /// - /// The observer. - /// An object which can be disposed to unsubscribe from progress tracking. - public IDisposable Subscribe(IObserver observer) - { - if (observer is null) - { - throw new ArgumentNullException(nameof(observer)); - } - - _observers.Add(observer); - return new ObservableDisposer(_observers, observer, OnDispose); - } -} From c338f4263edf93fcfb38604a88d1d439f7b7aff7 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 11:50:48 +0100 Subject: [PATCH 09/12] chore: favour use of c# 12 --- CONTRIBUTING.md | 4 ++-- Directory.Build.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5aae0b4..a6b5007 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ or submit a pull request. ### Pull request guidelines -This project uses C# 11.0 language features where feasible, and adheres to StyleCop rules with some minor adjustments. +This project uses C# 12.0 language features where feasible, and adheres to StyleCop rules with some minor adjustments. There is an `.editorconfig` included in this repository. For quick and painless pull requests, ensure that the analyzer does not throw warnings. @@ -17,7 +17,7 @@ convetional commits specification. Below are a few pointers to which you may refer, but keep in mind this is not an exhaustive list: -- Use C# 11.0 features where possible +- Use C# 12.0 features where possible - Try to ensure code is CLS-compliant. Where this is not possible, decorate methods with `CLSCompliantAttribute` and pass `false` - Follow all .NET guidelines and coding conventions. See https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions diff --git a/Directory.Build.props b/Directory.Build.props index 9f2994d..283ddde 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 11.0 + 12.0 true true true From e5e27c0afdc7afe3f54117a96dff9034991f4c83 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 11:52:31 +0100 Subject: [PATCH 10/12] [ci skip] test: remove NOSONAR markers --- X10D/src/Core/SpanExtensions.cs | 2 -- X10D/src/Text/RuneExtensions.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/X10D/src/Core/SpanExtensions.cs b/X10D/src/Core/SpanExtensions.cs index c1f2a1e..ab79a16 100644 --- a/X10D/src/Core/SpanExtensions.cs +++ b/X10D/src/Core/SpanExtensions.cs @@ -92,14 +92,12 @@ public static class SpanExtensions } // dotcover disable - //NOSONAR default: #if NET7_0_OR_GREATER throw new UnreachableException(string.Format(ExceptionMessages.EnumSizeIsUnexpected, Unsafe.SizeOf())); #else throw new ArgumentException(string.Format(ExceptionMessages.EnumSizeIsUnexpected, Unsafe.SizeOf())); #endif - //NOSONAR // dotcover enable } } diff --git a/X10D/src/Text/RuneExtensions.cs b/X10D/src/Text/RuneExtensions.cs index 47349b1..c1f3ef9 100644 --- a/X10D/src/Text/RuneExtensions.cs +++ b/X10D/src/Text/RuneExtensions.cs @@ -98,7 +98,6 @@ public static class RuneExtensions } // dotcover disable - //NOSONAR default: string exceptionFormat = ExceptionMessages.UnexpectedRuneUtf8SequenceLength; string message = string.Format(CultureInfo.CurrentCulture, exceptionFormat, length); @@ -107,7 +106,6 @@ public static class RuneExtensions #else throw new InvalidOperationException(message); #endif - //NOSONAR // dotcover enable } } From 68197ef5c7e6bd6bcbe8496bbf4f1a662a2e7d55 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 11:57:27 +0100 Subject: [PATCH 11/12] fix: fix order of bytes in decimal write bytes methods --- CHANGELOG.md | 1 + X10D.Tests/src/IO/DecimalTests.cs | 54 ++++++++++++++ X10D.Tests/src/IO/StreamTests.WriteDecimal.cs | 3 +- X10D/src/IO/DecimalExtensions.cs | 71 +++++-------------- 4 files changed, 75 insertions(+), 54 deletions(-) create mode 100644 X10D.Tests/src/IO/DecimalTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index c3d26dc..759b016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ TypeInitializationException. ### Fixed +- X10D: Fixed `decimal.TryWriteBigEndianBytes` and `decimal.TryWriteLittleEndianBytes`. - X10D.Hosting: Fixed `AddHostedSingleton` not accepting an interface as the service type. ## [3.3.0] - 2023-08-21 diff --git a/X10D.Tests/src/IO/DecimalTests.cs b/X10D.Tests/src/IO/DecimalTests.cs new file mode 100644 index 0000000..93dea74 --- /dev/null +++ b/X10D.Tests/src/IO/DecimalTests.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestFixture] +internal class DecimalTests +{ + [Test] + public void GetBigEndianBytes_ShouldReturnBytes_InBigEndian() + { + const decimal value = 1234m; + byte[] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 210]; + + byte[] bytes = value.GetBigEndianBytes(); + + CollectionAssert.AreEqual(expected, bytes); + } + + [Test] + public void GetLittleEndianBytes_ShouldReturnBytes_InLittleEndian() + { + const decimal value = 1234m; + byte[] expected = [210, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + byte[] bytes = value.GetLittleEndianBytes(); + + CollectionAssert.AreEqual(expected, bytes); + } + + [Test] + public void TryWriteBigEndianBytes_ShouldWriteBytes_InBigEndian() + { + const decimal value = 1234m; + byte[] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 210]; + + Span bytes = stackalloc byte[16]; + value.TryWriteBigEndianBytes(bytes); + + CollectionAssert.AreEqual(expected, bytes.ToArray()); + } + + [Test] + public void TryWriteLittleEndianBytes_ShouldWriteBytes_InLittleEndian() + { + const decimal value = 1234m; + byte[] expected = [210, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + Span bytes = stackalloc byte[16]; + value.TryWriteLittleEndianBytes(bytes); + + CollectionAssert.AreEqual(expected, bytes.ToArray()); + } +} diff --git a/X10D.Tests/src/IO/StreamTests.WriteDecimal.cs b/X10D.Tests/src/IO/StreamTests.WriteDecimal.cs index 911dd5d..15e7538 100644 --- a/X10D.Tests/src/IO/StreamTests.WriteDecimal.cs +++ b/X10D.Tests/src/IO/StreamTests.WriteDecimal.cs @@ -48,9 +48,10 @@ internal partial class StreamTests Span actual = stackalloc byte[16]; ReadOnlySpan expected = stackalloc byte[] { - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x10, 0x00, 0x00 + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x68 }; int read = stream.Read(actual); + Trace.WriteLine(string.Join(' ', actual.ToArray())); Assert.That(read, Is.EqualTo(16)); CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray()); diff --git a/X10D/src/IO/DecimalExtensions.cs b/X10D/src/IO/DecimalExtensions.cs index 68b9a6e..e01f591 100644 --- a/X10D/src/IO/DecimalExtensions.cs +++ b/X10D/src/IO/DecimalExtensions.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; @@ -13,11 +12,11 @@ public static class DecimalExtensions /// Converts the current decimal number into an array of bytes, as little endian. /// /// The value. - /// An array of bytes with length 4. + /// An array of bytes with length 16. [Pure] public static byte[] GetBigEndianBytes(this decimal value) { - Span buffer = stackalloc byte[4]; + Span buffer = stackalloc byte[16]; value.TryWriteBigEndianBytes(buffer); return buffer.ToArray(); } @@ -26,11 +25,11 @@ public static class DecimalExtensions /// Converts the current decimal number into an array of bytes, as little endian. /// /// The value. - /// An array of bytes with length 4. + /// An array of bytes with length 16. [Pure] public static byte[] GetLittleEndianBytes(this decimal value) { - Span buffer = stackalloc byte[4]; + Span buffer = stackalloc byte[16]; value.TryWriteLittleEndianBytes(buffer); return buffer.ToArray(); } @@ -44,23 +43,17 @@ public static class DecimalExtensions public static bool TryWriteBigEndianBytes(this decimal value, Span destination) { Span buffer = stackalloc int[4]; - GetBits(value, buffer); + decimal.GetBits(value, buffer); - if (buffer[0].TryWriteBigEndianBytes(destination[..4]) && - buffer[1].TryWriteBigEndianBytes(destination[4..8]) && - buffer[2].TryWriteBigEndianBytes(destination[8..12]) && - buffer[3].TryWriteBigEndianBytes(destination[12..])) + Span result = stackalloc byte[16]; + MemoryMarshal.Cast(buffer).CopyTo(result); + + if (BitConverter.IsLittleEndian) { - if (BitConverter.IsLittleEndian) - { - destination.Reverse(); - } - - return true; + result.Reverse(); } - destination.Clear(); - return false; + return result.TryCopyTo(destination); } /// @@ -72,44 +65,16 @@ public static class DecimalExtensions public static bool TryWriteLittleEndianBytes(this decimal value, Span destination) { Span buffer = stackalloc int[4]; - GetBits(value, buffer); + decimal.GetBits(value, buffer); - if (buffer[0].TryWriteLittleEndianBytes(destination[..4]) && - buffer[1].TryWriteLittleEndianBytes(destination[4..8]) && - buffer[2].TryWriteLittleEndianBytes(destination[8..12]) && - buffer[3].TryWriteLittleEndianBytes(destination[12..])) + Span result = stackalloc byte[16]; + MemoryMarshal.Cast(buffer).CopyTo(result); + + if (!BitConverter.IsLittleEndian) { - if (!BitConverter.IsLittleEndian) - { - destination.Reverse(); - } - - return true; + result.Reverse(); } - destination.Clear(); - return false; + return result.TryCopyTo(destination); } - - private static void GetBits(decimal value, Span destination) - { - _ = decimal.GetBits(value, destination); - } - -#if !NET5_0_OR_GREATER - private static void WriteBits(Span destination, Span buffer) - { - var flags = MemoryMarshal.Read(buffer[..4]); - var hi = MemoryMarshal.Read(buffer[4..8]); - var lo = MemoryMarshal.Read(buffer[8..]); - - var low = (uint)lo; - var mid = (uint)(lo >> 32); - - destination[0] = (int)low; - destination[1] = (int)mid; - destination[2] = hi; - destination[3] = flags; - } -#endif } From 8eaa01b505f7449406a88af0d164e798c2ed7c4b Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 11:59:47 +0100 Subject: [PATCH 12/12] test: TryWriteBytes should return false for smol span --- X10D.Tests/src/IO/DecimalTests.cs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/X10D.Tests/src/IO/DecimalTests.cs b/X10D.Tests/src/IO/DecimalTests.cs index 93dea74..46e7077 100644 --- a/X10D.Tests/src/IO/DecimalTests.cs +++ b/X10D.Tests/src/IO/DecimalTests.cs @@ -35,7 +35,7 @@ internal class DecimalTests byte[] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 210]; Span bytes = stackalloc byte[16]; - value.TryWriteBigEndianBytes(bytes); + Assert.That(value.TryWriteBigEndianBytes(bytes)); CollectionAssert.AreEqual(expected, bytes.ToArray()); } @@ -47,8 +47,26 @@ internal class DecimalTests byte[] expected = [210, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; Span bytes = stackalloc byte[16]; - value.TryWriteLittleEndianBytes(bytes); + Assert.That(value.TryWriteLittleEndianBytes(bytes)); CollectionAssert.AreEqual(expected, bytes.ToArray()); } + + [Test] + public void TryWriteBigEndianBytes_ShouldReturnFalse_GivenSmallSpan() + { + const decimal value = 1234m; + + Span bytes = Span.Empty; + Assert.That(value.TryWriteBigEndianBytes(bytes), Is.False); + } + + [Test] + public void TryWriteLittleEndianBytes_ShouldReturnFalse_GivenSmallSpan() + { + const decimal value = 1234m; + + Span bytes = Span.Empty; + Assert.That(value.TryWriteLittleEndianBytes(bytes), Is.False); + } }