From 75572afbee9ab976d903e498a9e3bda020c26fc6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 21 Aug 2026 15:24:30 +1000 Subject: [PATCH] Add net10/net11 preview CI and simplify guards Updates CI to run net10.0 across all OSes and adds net11.0 preview jobs with separate setup/build/test steps gated by `sdk-preview` and `SIXLABORS_TESTING_PREVIEW`. Raises C# language version to 14.0 and updates test project target frameworks to `net10.0` by default, with `net11.0` included only in preview runs. Also modernizes guard APIs by switching `CallerArgumentExpression` to `nameof(value)` and removing redundant `Span` overloads in favor of `ReadOnlySpan`-based checks. --- .github/workflows/build-and-test.yml | 50 +++++++++++++++++-- global.json | 18 +++++++ msbuild/props/SixLabors.Global.props | 2 +- msbuild/targets/SixLabors.Src.targets | 19 +++++-- src/SharedInfrastructure/DebugGuard.cs | 45 ++--------------- src/SharedInfrastructure/Guard.cs | 49 ++---------------- tests/Directory.Build.props | 2 +- .../DebugGuardTests.cs | 10 ++-- .../SharedInfrastructure.Tests/GuardTests.cs | 10 ++-- .../SharedInfrastructure.Tests.csproj | 14 +++++- 10 files changed, 110 insertions(+), 109 deletions(-) create mode 100644 global.json diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 54b6834..fc5f2ee 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -13,15 +13,31 @@ jobs: matrix: options: - os: ubuntu-latest - framework: net8.0 + framework: net11.0 + sdk-preview: true runtime: -x64 codecov: false - os: macos-latest - framework: net8.0 + framework: net11.0 + sdk-preview: true runtime: -x64 codecov: false - os: windows-latest - framework: net8.0 + framework: net11.0 + sdk-preview: true + runtime: -x64 + codecov: false + + - os: ubuntu-latest + framework: net10.0 + runtime: -x64 + codecov: false + - os: macos-latest + framework: net10.0 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net10.0 runtime: -x64 codecov: true @@ -52,19 +68,45 @@ jobs: restore-keys: ${{ runner.os }}-nuget- - name: DotNet Setup + if: ${{ matrix.options.sdk-preview != true }} uses: actions/setup-dotnet@v4 with: dotnet-version: | - 8.0.x + 10.0.x + + - name: DotNet Setup Preview + if: ${{ matrix.options.sdk-preview == true }} + uses: actions/setup-dotnet@v4 + with: + dotnet-quality: preview + dotnet-version: | + 11.0.x - name: DotNet Build + if: ${{ matrix.options.sdk-preview != true }} + shell: pwsh + run: ./ci-build.ps1 "${{matrix.options.framework}}" + + - name: DotNet Build Preview + if: ${{ matrix.options.sdk-preview == true }} shell: pwsh run: ./ci-build.ps1 "${{matrix.options.framework}}" + env: + SIXLABORS_TESTING_PREVIEW: True - name: DotNet Test + if: ${{ matrix.options.sdk-preview != true }} + shell: pwsh + run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" + env: + XUNIT_PATH: .\tests\SharedInfrastructure.Tests # Required for xunit + + - name: DotNet Test Preview + if: ${{ matrix.options.sdk-preview == true }} shell: pwsh run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" env: + SIXLABORS_TESTING_PREVIEW: True XUNIT_PATH: .\tests\SharedInfrastructure.Tests # Required for xunit - name: Update Codecov diff --git a/global.json b/global.json new file mode 100644 index 0000000..8ff67c2 --- /dev/null +++ b/global.json @@ -0,0 +1,18 @@ +// Pins the .NET SDK to major version 10 or later for all SixLabors repositories. +// The build copies this file to consuming repositories together with .editorconfig and +// the other shared config files (see msbuild/targets/SixLabors.Src.targets). +{ + "sdk": { + // The floor, not an exact build: resolution never selects an SDK below major 10. + "version": "10.0.0", + + // Select the newest installed SDK at or above the floor. This spans feature bands, + // minor versions, and major versions, so a machine with only stable 10.0.x SDKs + // resolves the newest of those, and a machine with a newer SDK resolves that instead. + "rollForward": "latestMajor", + + // Let the CLI and Visual Studio select preview SDKs when one is installed. + // Machines and CI lanes without a preview SDK are unaffected. + "allowPrerelease": true + } +} diff --git a/msbuild/props/SixLabors.Global.props b/msbuild/props/SixLabors.Global.props index 8d9ad02..682f2dc 100644 --- a/msbuild/props/SixLabors.Global.props +++ b/msbuild/props/SixLabors.Global.props @@ -62,7 +62,7 @@ - 12.0 + 14.0 true strict;IOperation true diff --git a/msbuild/targets/SixLabors.Src.targets b/msbuild/targets/SixLabors.Src.targets index e373863..e3e4e1b 100644 --- a/msbuild/targets/SixLabors.Src.targets +++ b/msbuild/targets/SixLabors.Src.targets @@ -7,14 +7,26 @@ - + + + + + + + - + + + @@ -67,8 +79,7 @@ and '$(TargetFramework)'!='' and $([System.String]::Copy('$(PackageId)').StartsWith('SixLabors.')) and '$(PackageId)'!='SixLabors.Licensing' - and ( '$(PkgSixLabors_Licensing)'=='' - or !Exists('$(PkgSixLabors_Licensing)\build\net8.0\SixLabors.Licensing.dll') )"> + and ( '$(PkgSixLabors_Licensing)'=='' or !Exists('$(PkgSixLabors_Licensing)\build\net8.0\SixLabors.Licensing.dll') )"> diff --git a/src/SharedInfrastructure/DebugGuard.cs b/src/SharedInfrastructure/DebugGuard.cs index 34b11c8..3bc74c6 100644 --- a/src/SharedInfrastructure/DebugGuard.cs +++ b/src/SharedInfrastructure/DebugGuard.cs @@ -23,7 +23,7 @@ internal static partial class DebugGuard /// The type of the value. /// is null. [Conditional("DEBUG")] - public static void NotNull([NotNull] TValue? value, [CallerArgumentExpression("value")] string? parameterName = null) + public static void NotNull([NotNull] TValue? value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) where TValue : class => ArgumentNullException.ThrowIfNull(value, parameterName); @@ -35,7 +35,7 @@ public static void NotNull([NotNull] TValue? value, [CallerArgumentExpre /// is null. /// is empty or contains only blanks. [Conditional("DEBUG")] - public static void NotNullOrWhiteSpace([NotNull] string? value, [CallerArgumentExpression("value")] string? paramName = null) + public static void NotNullOrWhiteSpace([NotNull] string? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { ArgumentNullException.ThrowIfNull(value); @@ -204,25 +204,6 @@ public static void IsFalse(bool target, string parameterName, string message) /// [Conditional("DEBUG")] public static void MustBeSizedAtLeast(ReadOnlySpan source, int minLength, string parameterName) - { - if (source.Length < minLength) - { - ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName); - } - } - - /// - /// Verifies, that the `source` span has the length of 'minLength', or longer. - /// - /// The element type of the spans. - /// The target span. - /// The minimum length. - /// The name of the parameter that is to be checked. - /// - /// has less than items. - /// - [Conditional("DEBUG")] - public static void MustBeSizedAtLeast(Span source, int minLength, string parameterName) { if (source.Length < minLength) { @@ -241,27 +222,7 @@ public static void MustBeSizedAtLeast(Span source, int minLength, string p [Conditional("DEBUG")] public static void DestinationShouldNotBeTooShort( ReadOnlySpan source, - Span destination, - string destinationParamName) - { - if (destination.Length < source.Length) - { - ThrowArgumentException($"Destination span is too short!", destinationParamName); - } - } - - /// - /// Verifies that the 'destination' span is not shorter than 'source'. - /// - /// The source element type. - /// The destination element type. - /// The source span. - /// The destination span. - /// The name of the argument for 'destination'. - [Conditional("DEBUG")] - public static void DestinationShouldNotBeTooShort( - Span source, - Span destination, + ReadOnlySpan destination, string destinationParamName) { if (destination.Length < source.Length) diff --git a/src/SharedInfrastructure/Guard.cs b/src/SharedInfrastructure/Guard.cs index a1f843e..aeff56a 100644 --- a/src/SharedInfrastructure/Guard.cs +++ b/src/SharedInfrastructure/Guard.cs @@ -21,7 +21,7 @@ internal static partial class Guard /// The type of the value. /// is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void NotNull([NotNull]TValue? value, [CallerArgumentExpression("value")] string? parameterName = null) + public static void NotNull([NotNull] TValue? value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) where TValue : class => ArgumentNullException.ThrowIfNull(value, parameterName); @@ -33,7 +33,7 @@ public static void NotNull([NotNull]TValue? value, [CallerArgumentExpres /// is null. /// is empty or contains only blanks. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void NotNullOrWhiteSpace([NotNull]string? value, string parameterName) + public static void NotNullOrWhiteSpace([NotNull] string? value, string parameterName) { if (!string.IsNullOrWhiteSpace(value)) { @@ -221,27 +221,6 @@ public static void MustBeSizedAtLeast(ReadOnlySpan source, int minLength, ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeSizedAtLeast(minLength, parameterName); } - /// - /// Verifies, that the `source` span has the length of 'minLength', or longer. - /// - /// The element type of the spans. - /// The target span. - /// The minimum length. - /// The name of the parameter that is to be checked. - /// - /// has less than items. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MustBeSizedAtLeast(Span source, int minLength, string parameterName) - { - if (source.Length >= minLength) - { - return; - } - - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeSizedAtLeast(minLength, parameterName); - } - /// /// Verifies that the 'destination' span is not shorter than 'source'. /// @@ -253,29 +232,7 @@ public static void MustBeSizedAtLeast(Span source, int minLength, string p [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void DestinationShouldNotBeTooShort( ReadOnlySpan source, - Span destination, - string destinationParamName) - { - if (destination.Length >= source.Length) - { - return; - } - - ThrowHelper.ThrowArgumentException("Destination span is too short!", destinationParamName); - } - - /// - /// Verifies that the 'destination' span is not shorter than 'source'. - /// - /// The source element type. - /// The destination element type. - /// The source span. - /// The destination span. - /// The name of the argument for 'destination'. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void DestinationShouldNotBeTooShort( - Span source, - Span destination, + ReadOnlySpan destination, string destinationParamName) { if (destination.Length >= source.Length) diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 79e96c3..8fdd420 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -3,7 +3,7 @@ - true + true diff --git a/tests/SharedInfrastructure.Tests/DebugGuardTests.cs b/tests/SharedInfrastructure.Tests/DebugGuardTests.cs index 080e32e..9a12902 100644 --- a/tests/SharedInfrastructure.Tests/DebugGuardTests.cs +++ b/tests/SharedInfrastructure.Tests/DebugGuardTests.cs @@ -11,7 +11,7 @@ namespace SharedInfrastructure.Tests; public class DebugGuardTests { - private class Foo + private sealed class Foo { } @@ -31,7 +31,7 @@ public void AllStaticMethodsOnOnDebugGuardHaveDEBUGConditional() [Fact] public void NotNull_WhenNull_Throws() { - Foo foo = null; + Foo? foo = null; Assert.Throws(() => Guard.NotNull(foo, nameof(foo))); } @@ -48,7 +48,7 @@ public void NotNull_WhenNotNull() [InlineData(" ", true)] [InlineData("$", false)] [InlineData("lol", false)] - public void NotNullOrWhiteSpace(string str, bool shouldThrow) + public void NotNullOrWhiteSpace(string? str, bool shouldThrow) { if (shouldThrow) { @@ -205,13 +205,13 @@ public void MustBeGreaterThanOrEqualTo_IsLess_ThrowsNoException() [InlineData(new int[] { 1, 2 }, 1)] [InlineData(new int[] { 1, 2 }, 2)] public void MustBeSizedAtLeast_Array_LengthIsGreaterOrEqual_ThrowsNoException(int[] value, int minLength) - => DebugGuard.MustBeSizedAtLeast(value, minLength, "myParamName"); + => DebugGuard.MustBeSizedAtLeast(value, minLength, "myParamName"); [Fact] public void MustBeSizedAtLeast_Array_LengthIsLess_ThrowsException() { ArgumentException exception = Assert.Throws( - () => DebugGuard.MustBeSizedAtLeast(new int[] { 1, 2 }, 3, "myParamName")); + static () => DebugGuard.MustBeSizedAtLeast([1, 2], 3, "myParamName")); Assert.Equal("myParamName", exception.ParamName); Assert.Contains("The size must be at least 3.", exception.Message); diff --git a/tests/SharedInfrastructure.Tests/GuardTests.cs b/tests/SharedInfrastructure.Tests/GuardTests.cs index 7ca2de1..c18db39 100644 --- a/tests/SharedInfrastructure.Tests/GuardTests.cs +++ b/tests/SharedInfrastructure.Tests/GuardTests.cs @@ -5,14 +5,14 @@ namespace SharedInfrastructure.Tests; public class GuardTests { - private class Foo + private sealed class Foo { } [Fact] public void NotNull_WhenNull_Throws() { - Foo foo = null; + Foo? foo = null; Assert.Throws(() => Guard.NotNull(foo, nameof(foo))); } @@ -29,7 +29,7 @@ public void NotNull_WhenNotNull() [InlineData(" ", true)] [InlineData("$", false)] [InlineData("lol", false)] - public void NotNullOrWhiteSpace(string str, bool shouldThrow) + public void NotNullOrWhiteSpace(string? str, bool shouldThrow) { if (shouldThrow) { @@ -205,13 +205,13 @@ public void MustBeBetweenOrEqualTo_IsLessOrGreater_ThrowsNoException(int value, [InlineData(2, 1)] [InlineData(2, 2)] public void MustBeSizedAtLeast_Array_LengthIsGreaterOrEqual_ThrowsNoException(int valueLength, int minLength) - => Guard.MustBeSizedAtLeast(new int[valueLength], minLength, "myParamName"); + => Guard.MustBeSizedAtLeast(new int[valueLength], minLength, "myParamName"); [Fact] public void MustBeSizedAtLeast_Array_LengthIsLess_ThrowsException() { ArgumentException exception = - Assert.Throws(() => Guard.MustBeSizedAtLeast(new int[] { 1, 2 }, 3, "myParamName")); + Assert.Throws(() => Guard.MustBeSizedAtLeast([1, 2], 3, "myParamName")); Assert.Equal("myParamName", exception.ParamName); Assert.Contains("Spans must be at least of length 3", exception.Message); diff --git a/tests/SharedInfrastructure.Tests/SharedInfrastructure.Tests.csproj b/tests/SharedInfrastructure.Tests/SharedInfrastructure.Tests.csproj index 3248073..67cad0c 100644 --- a/tests/SharedInfrastructure.Tests/SharedInfrastructure.Tests.csproj +++ b/tests/SharedInfrastructure.Tests/SharedInfrastructure.Tests.csproj @@ -1,12 +1,24 @@ - net8.0; SharedInfrastructure.Tests SharedInfrastructure.Tests enable + + + + net10.0;net11.0 + + + + + net10.0 + + + +