From 08db4b718d58620c728c35a113e94e992390109e Mon Sep 17 00:00:00 2001 From: Blind-Striker Date: Fri, 23 Oct 2026 18:27:15 +0300 Subject: [PATCH 1/2] ci: publish a Native AOT consumer from the packed packages The native-aot-tests job now packs Core and Extensions and publishes tests/package-consumer from those packages with Native AOT on Windows and Linux, using a fresh NuGet cache. The project sits outside the solution and stops the repository's Directory.Build.* and central package management from applying, so it sees only what a NuGet consumer sees. It runs the existing consumer calls plus a Type-based API, which needs the source generator to arrive through the package's buildTransitive target, and checks that it runs as a native executable. Linux also calls LocalStack. --- .github/workflows/ci-cd.yml | 19 ++++++++ CHANGELOG.md | 2 +- docs/engineering/quality-gates.md | 2 +- tests/package-consumer/Consumer.csproj | 27 ++++++++++++ tests/package-consumer/Directory.Build.props | 3 ++ .../package-consumer/Directory.Build.targets | 3 ++ .../package-consumer/Directory.Packages.props | 6 +++ tests/package-consumer/Program.cs | 44 +++++++++++++++++++ tests/package-consumer/appsettings.json | 12 +++++ tests/package-consumer/nuget.config | 17 +++++++ 10 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 tests/package-consumer/Consumer.csproj create mode 100644 tests/package-consumer/Directory.Build.props create mode 100644 tests/package-consumer/Directory.Build.targets create mode 100644 tests/package-consumer/Directory.Packages.props create mode 100644 tests/package-consumer/Program.cs create mode 100644 tests/package-consumer/appsettings.json create mode 100644 tests/package-consumer/nuget.config diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 25a6648..f599064 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -174,6 +174,25 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } + # Packs both libraries and publishes tests/package-consumer from those packages with Native AOT, + # so the source generator is proven to reach a consumer through the package (buildTransitive). + - name: "Pack and run the package consumer with Native AOT" + shell: pwsh + run: | + dotnet pack src/LocalStack.Client/LocalStack.Client.csproj -c Release -o artifacts/packages + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + dotnet pack src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj -c Release -o artifacts/packages + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $version = dotnet msbuild src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj -getProperty:Version + # A fresh package cache, so a same-version package from an earlier run cannot be reused. + $env:NUGET_PACKAGES = Join-Path $env:RUNNER_TEMP 'package-consumer-nuget' + $rid = '${{ runner.os == 'Windows' && 'win-x64' || 'linux-x64' }}' + dotnet publish tests/package-consumer/Consumer.csproj -c Release -r $rid -o artifacts/package-consumer "-p:LocalStackVersion=$version" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $exe = if ($IsWindows) { 'Consumer.exe' } else { 'Consumer' } + & "./artifacts/package-consumer/$exe" + exit $LASTEXITCODE + continuous-deployment: name: "Continuous Deployment" runs-on: ubuntu-22.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2363240..b67a6e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ Planned as **3.0.0** for both packages; not yet published. ### Build, tests and CI - The repository now builds with the .NET 11 SDK pinned in `global.json`; test projects and CI cover `net10.0` and `net11.0`. -- Added the xUnit v3 `LocalStack.Client.NativeAot.Tests` lane and the `native-aot-tests` CI job, which publish with `PublishAot` and run native executables on Windows and Linux for both modern targets; Linux also runs live S3/SQS round trips against LocalStack 4.14.0. Cake skips this project. +- Added the xUnit v3 `LocalStack.Client.NativeAot.Tests` lane and the `native-aot-tests` CI job, which publish with `PublishAot` and run native executables on Windows and Linux for both modern targets; Linux also runs live S3/SQS round trips against LocalStack 4.14.0. Cake skips this project. The same job packs both libraries and publishes `tests/package-consumer` from those packages with Native AOT on both operating systems. - With the .NET 11 SDK, .NET Framework (`net472`) tests run on Windows only; Cake skips them elsewhere with a warning, and CI no longer installs Mono on macOS. - Refreshed build, test, and analyzer dependencies, including Cake.Frosting 6.3.0, NuGet.Versioning 7.9.0, SourceLink 10.0.402, Testcontainers and Testcontainers.LocalStack 4.16.0, and `System.Security.Cryptography.Pkcs` 10.0.13. Restore resolves Microsoft.Build.Tasks.Git 10.0.402 and SSH.NET 2026.0.0; repository-wide `NuGetAuditMode=all` remains enabled. - Test and sandbox consumers use centrally managed Microsoft.Extensions 10.0.13. Refreshed 112 AWS service SDK pins and retained six already-latest entries, with no catalog additions or removals. Core-only projects resolve 4.0.102.6; Extensions and service-package consumers resolve 4.0.102.11 under their dependency ranges. diff --git a/docs/engineering/quality-gates.md b/docs/engineering/quality-gates.md index 6b5b8ed..0005626 100644 --- a/docs/engineering/quality-gates.md +++ b/docs/engineering/quality-gates.md @@ -29,6 +29,6 @@ The [main CI workflow](../../.github/workflows/ci-cd.yml) ignores Markdown-only In the main CI workflow only the Linux leg runs functional tests. Its test step receives the Actions secret `LOCALSTACK_AUTH_TOKEN`, an organization secret shared with this repository. A pull-request run that cannot read that secret (a fork, a Dependabot run without its own copy, or any run before the secret exists) excludes the token-gated tests through the Cake flag and writes a workflow warning. A push run without the secret still runs them, so it fails at the fixture instead of passing. LocalStack expects a workspace CI Auth Token in CI, not a developer's personal token. -CI's separate `native-aot-tests` job publishes/runs both targets on `windows-latest` (`win-x64`) and `ubuntu-22.04` (`linux-x64`). Linux waits for LocalStack 4.14.0 health and sets `LOCALSTACK_NATIVE_AOT_HOST`; live cases otherwise skip. The [project](../../tests/LocalStack.Client.NativeAot.Tests/LocalStack.Client.NativeAot.Tests.csproj) and pinned xUnit v3 AOT package define the runner; this lane qualifies the [Native AOT boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming), not every service/deployment. +CI's separate `native-aot-tests` job publishes/runs both targets on `windows-latest` (`win-x64`) and `ubuntu-22.04` (`linux-x64`). Linux waits for LocalStack 4.14.0 health and sets `LOCALSTACK_NATIVE_AOT_HOST`; live cases otherwise skip. The [project](../../tests/LocalStack.Client.NativeAot.Tests/LocalStack.Client.NativeAot.Tests.csproj) and pinned xUnit v3 AOT package define the runner; this lane qualifies the [Native AOT boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming), not every service/deployment. The same job then packs both libraries into `artifacts/packages` and publishes [`tests/package-consumer`](../../tests/package-consumer/) from those packages with a fresh NuGet cache; that project is outside the solution and isolated from the repository's build props, so it proves the generator reaches a consumer through the package's `buildTransitive` target on both operating systems. The badge-update step is `continue-on-error`, and its action logs a non-200 Gist response without turning that response into a failed step. Neither a badge nor a fixed test count in prose proves that the current revision passed. Report the actual command, target frameworks, result, and any tests deliberately not run. Do not infer NuGet publication, Native AOT support, or full LocalStack service behavior from a successful build or client-construction test. diff --git a/tests/package-consumer/Consumer.csproj b/tests/package-consumer/Consumer.csproj new file mode 100644 index 0000000..4b2294f --- /dev/null +++ b/tests/package-consumer/Consumer.csproj @@ -0,0 +1,27 @@ + + + + + Exe + net10.0 + enable + enable + true + true + false + + + + + + + + + + + + diff --git a/tests/package-consumer/Directory.Build.props b/tests/package-consumer/Directory.Build.props new file mode 100644 index 0000000..0c0e3c1 --- /dev/null +++ b/tests/package-consumer/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/tests/package-consumer/Directory.Build.targets b/tests/package-consumer/Directory.Build.targets new file mode 100644 index 0000000..f175977 --- /dev/null +++ b/tests/package-consumer/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/tests/package-consumer/Directory.Packages.props b/tests/package-consumer/Directory.Packages.props new file mode 100644 index 0000000..6242951 --- /dev/null +++ b/tests/package-consumer/Directory.Packages.props @@ -0,0 +1,6 @@ + + + + false + + diff --git a/tests/package-consumer/Program.cs b/tests/package-consumer/Program.cs new file mode 100644 index 0000000..32976b1 --- /dev/null +++ b/tests/package-consumer/Program.cs @@ -0,0 +1,44 @@ +using Amazon.S3; +using Amazon.SQS; +using LocalStack.Client.Contracts; +using LocalStack.Client.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +if (System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported) +{ + Console.Error.WriteLine("Expected a Native AOT executable."); + return 1; +} + +var configuration = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile("appsettings.json") + .Build(); + +// The consumer contract, unchanged from earlier versions. +var services = new ServiceCollection(); +services.AddLocalStack(configuration); +services.AddAwsService(); +using var provider = services.BuildServiceProvider(); +var s3 = provider.GetRequiredService(); + +// A Type-based API: served by the source generator that arrives through the package. +var session = provider.GetRequiredService(); +using var sqs = (IAmazonSQS)session.CreateClientByInterface(typeof(IAmazonSQS)); +string serviceId = provider.GetRequiredService().ExtractServiceMetadata(typeof(AmazonSQSClient)).ServiceId; + +if (!((AmazonS3Config)s3.Config).ForcePathStyle || serviceId != "SQS" || sqs.Config.ProxyPort != 4566) +{ + Console.Error.WriteLine($"Unexpected configuration: pathStyle={((AmazonS3Config)s3.Config).ForcePathStyle} serviceId={serviceId} sqsPort={sqs.Config.ProxyPort}"); + return 1; +} + +if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LOCALSTACK_NATIVE_AOT_HOST"))) +{ + await s3.ListBucketsAsync(); + await sqs.ListQueuesAsync(new Amazon.SQS.Model.ListQueuesRequest()); +} + +Console.WriteLine("Package consumer passed."); +return 0; diff --git a/tests/package-consumer/appsettings.json b/tests/package-consumer/appsettings.json new file mode 100644 index 0000000..d9bab94 --- /dev/null +++ b/tests/package-consumer/appsettings.json @@ -0,0 +1,12 @@ +{ + "LocalStack": { + "UseLocalStack": true, + "Session": { + "RegionName": "eu-central-1" + }, + "Config": { + "LocalStackHost": "localhost", + "EdgePort": 4566 + } + } +} diff --git a/tests/package-consumer/nuget.config b/tests/package-consumer/nuget.config new file mode 100644 index 0000000..25a4f42 --- /dev/null +++ b/tests/package-consumer/nuget.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + From aba0b5ac9fafd0e3ddb1ded3e5a34309129c076c Mon Sep 17 00:00:00 2001 From: Blind-Striker Date: Fri, 23 Oct 2026 22:35:48 +0300 Subject: [PATCH 2/2] test: name the package consumer like the other test projects Rename tests/package-consumer/Consumer.csproj to LocalStack.Client.PackageConsumer.Tests. It now marks itself with LocalStackTestRunsAsPublishedExecutable, so Cake skips it like the Native AOT lane, and CI publishes it for every framework in LocalStackModernTargetFrameworks instead of a hardcoded net10.0. --- .github/workflows/ci-cd.yml | 16 ++++++++++------ CHANGELOG.md | 2 +- docs/engineering/quality-gates.md | 2 +- .../Directory.Build.props | 0 .../Directory.Build.targets | 0 .../Directory.Packages.props | 0 ...calStack.Client.PackageConsumer.Tests.csproj} | 4 +++- .../Program.cs | 0 .../appsettings.json | 0 .../nuget.config | 0 10 files changed, 15 insertions(+), 9 deletions(-) rename tests/{package-consumer => LocalStack.Client.PackageConsumer.Tests}/Directory.Build.props (100%) rename tests/{package-consumer => LocalStack.Client.PackageConsumer.Tests}/Directory.Build.targets (100%) rename tests/{package-consumer => LocalStack.Client.PackageConsumer.Tests}/Directory.Packages.props (100%) rename tests/{package-consumer/Consumer.csproj => LocalStack.Client.PackageConsumer.Tests/LocalStack.Client.PackageConsumer.Tests.csproj} (80%) rename tests/{package-consumer => LocalStack.Client.PackageConsumer.Tests}/Program.cs (100%) rename tests/{package-consumer => LocalStack.Client.PackageConsumer.Tests}/appsettings.json (100%) rename tests/{package-consumer => LocalStack.Client.PackageConsumer.Tests}/nuget.config (100%) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f599064..c060ec8 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -174,7 +174,7 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } - # Packs both libraries and publishes tests/package-consumer from those packages with Native AOT, + # Packs both libraries and publishes the package consumer from those packages with Native AOT, # so the source generator is proven to reach a consumer through the package (buildTransitive). - name: "Pack and run the package consumer with Native AOT" shell: pwsh @@ -184,14 +184,18 @@ jobs: dotnet pack src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj -c Release -o artifacts/packages if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $version = dotnet msbuild src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj -getProperty:Version + $frameworks = (dotnet msbuild src/LocalStack.Client/LocalStack.Client.csproj -getProperty:LocalStackModernTargetFrameworks).Split(';', [StringSplitOptions]::RemoveEmptyEntries) # A fresh package cache, so a same-version package from an earlier run cannot be reused. $env:NUGET_PACKAGES = Join-Path $env:RUNNER_TEMP 'package-consumer-nuget' + $project = 'tests/LocalStack.Client.PackageConsumer.Tests/LocalStack.Client.PackageConsumer.Tests.csproj' $rid = '${{ runner.os == 'Windows' && 'win-x64' || 'linux-x64' }}' - dotnet publish tests/package-consumer/Consumer.csproj -c Release -r $rid -o artifacts/package-consumer "-p:LocalStackVersion=$version" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - $exe = if ($IsWindows) { 'Consumer.exe' } else { 'Consumer' } - & "./artifacts/package-consumer/$exe" - exit $LASTEXITCODE + $exe = if ($IsWindows) { 'LocalStack.Client.PackageConsumer.Tests.exe' } else { 'LocalStack.Client.PackageConsumer.Tests' } + foreach ($framework in $frameworks) { + dotnet publish $project -c Release -r $rid -o "artifacts/package-consumer/$framework" "-p:LocalStackConsumerTargetFramework=$framework" "-p:LocalStackVersion=$version" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & "./artifacts/package-consumer/$framework/$exe" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } continuous-deployment: name: "Continuous Deployment" diff --git a/CHANGELOG.md b/CHANGELOG.md index b67a6e5..12bca02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ Planned as **3.0.0** for both packages; not yet published. ### Build, tests and CI - The repository now builds with the .NET 11 SDK pinned in `global.json`; test projects and CI cover `net10.0` and `net11.0`. -- Added the xUnit v3 `LocalStack.Client.NativeAot.Tests` lane and the `native-aot-tests` CI job, which publish with `PublishAot` and run native executables on Windows and Linux for both modern targets; Linux also runs live S3/SQS round trips against LocalStack 4.14.0. Cake skips this project. The same job packs both libraries and publishes `tests/package-consumer` from those packages with Native AOT on both operating systems. +- Added the xUnit v3 `LocalStack.Client.NativeAot.Tests` lane and the `native-aot-tests` CI job, which publish with `PublishAot` and run native executables on Windows and Linux for both modern targets; Linux also runs live S3/SQS round trips against LocalStack 4.14.0. Cake skips this project. The same job packs both libraries and publishes `LocalStack.Client.PackageConsumer.Tests` from those packages with Native AOT on both operating systems. - With the .NET 11 SDK, .NET Framework (`net472`) tests run on Windows only; Cake skips them elsewhere with a warning, and CI no longer installs Mono on macOS. - Refreshed build, test, and analyzer dependencies, including Cake.Frosting 6.3.0, NuGet.Versioning 7.9.0, SourceLink 10.0.402, Testcontainers and Testcontainers.LocalStack 4.16.0, and `System.Security.Cryptography.Pkcs` 10.0.13. Restore resolves Microsoft.Build.Tasks.Git 10.0.402 and SSH.NET 2026.0.0; repository-wide `NuGetAuditMode=all` remains enabled. - Test and sandbox consumers use centrally managed Microsoft.Extensions 10.0.13. Refreshed 112 AWS service SDK pins and retained six already-latest entries, with no catalog additions or removals. Core-only projects resolve 4.0.102.6; Extensions and service-package consumers resolve 4.0.102.11 under their dependency ranges. diff --git a/docs/engineering/quality-gates.md b/docs/engineering/quality-gates.md index 0005626..448f9a8 100644 --- a/docs/engineering/quality-gates.md +++ b/docs/engineering/quality-gates.md @@ -29,6 +29,6 @@ The [main CI workflow](../../.github/workflows/ci-cd.yml) ignores Markdown-only In the main CI workflow only the Linux leg runs functional tests. Its test step receives the Actions secret `LOCALSTACK_AUTH_TOKEN`, an organization secret shared with this repository. A pull-request run that cannot read that secret (a fork, a Dependabot run without its own copy, or any run before the secret exists) excludes the token-gated tests through the Cake flag and writes a workflow warning. A push run without the secret still runs them, so it fails at the fixture instead of passing. LocalStack expects a workspace CI Auth Token in CI, not a developer's personal token. -CI's separate `native-aot-tests` job publishes/runs both targets on `windows-latest` (`win-x64`) and `ubuntu-22.04` (`linux-x64`). Linux waits for LocalStack 4.14.0 health and sets `LOCALSTACK_NATIVE_AOT_HOST`; live cases otherwise skip. The [project](../../tests/LocalStack.Client.NativeAot.Tests/LocalStack.Client.NativeAot.Tests.csproj) and pinned xUnit v3 AOT package define the runner; this lane qualifies the [Native AOT boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming), not every service/deployment. The same job then packs both libraries into `artifacts/packages` and publishes [`tests/package-consumer`](../../tests/package-consumer/) from those packages with a fresh NuGet cache; that project is outside the solution and isolated from the repository's build props, so it proves the generator reaches a consumer through the package's `buildTransitive` target on both operating systems. +CI's separate `native-aot-tests` job publishes/runs both targets on `windows-latest` (`win-x64`) and `ubuntu-22.04` (`linux-x64`). Linux waits for LocalStack 4.14.0 health and sets `LOCALSTACK_NATIVE_AOT_HOST`; live cases otherwise skip. The [project](../../tests/LocalStack.Client.NativeAot.Tests/LocalStack.Client.NativeAot.Tests.csproj) and pinned xUnit v3 AOT package define the runner; this lane qualifies the [Native AOT boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming), not every service/deployment. The same job then packs both libraries into `artifacts/packages` and publishes [`LocalStack.Client.PackageConsumer.Tests`](../../tests/LocalStack.Client.PackageConsumer.Tests/) from those packages for every modern target framework with a fresh NuGet cache; that project is outside the solution and isolated from the repository's build props, so it proves the generator reaches a consumer through the package's `buildTransitive` target on both operating systems. The badge-update step is `continue-on-error`, and its action logs a non-200 Gist response without turning that response into a failed step. Neither a badge nor a fixed test count in prose proves that the current revision passed. Report the actual command, target frameworks, result, and any tests deliberately not run. Do not infer NuGet publication, Native AOT support, or full LocalStack service behavior from a successful build or client-construction test. diff --git a/tests/package-consumer/Directory.Build.props b/tests/LocalStack.Client.PackageConsumer.Tests/Directory.Build.props similarity index 100% rename from tests/package-consumer/Directory.Build.props rename to tests/LocalStack.Client.PackageConsumer.Tests/Directory.Build.props diff --git a/tests/package-consumer/Directory.Build.targets b/tests/LocalStack.Client.PackageConsumer.Tests/Directory.Build.targets similarity index 100% rename from tests/package-consumer/Directory.Build.targets rename to tests/LocalStack.Client.PackageConsumer.Tests/Directory.Build.targets diff --git a/tests/package-consumer/Directory.Packages.props b/tests/LocalStack.Client.PackageConsumer.Tests/Directory.Packages.props similarity index 100% rename from tests/package-consumer/Directory.Packages.props rename to tests/LocalStack.Client.PackageConsumer.Tests/Directory.Packages.props diff --git a/tests/package-consumer/Consumer.csproj b/tests/LocalStack.Client.PackageConsumer.Tests/LocalStack.Client.PackageConsumer.Tests.csproj similarity index 80% rename from tests/package-consumer/Consumer.csproj rename to tests/LocalStack.Client.PackageConsumer.Tests/LocalStack.Client.PackageConsumer.Tests.csproj index 4b2294f..f1a2436 100644 --- a/tests/package-consumer/Consumer.csproj +++ b/tests/LocalStack.Client.PackageConsumer.Tests/LocalStack.Client.PackageConsumer.Tests.csproj @@ -4,10 +4,12 @@ A consumer of the packed LocalStack.Client.Extensions package, published with Native AOT by the native-aot-tests CI job. It is deliberately outside the solution and isolated from the repository's build props, central package versions and generator wiring, so it sees only what a NuGet consumer sees. + CI passes LocalStackConsumerTargetFramework (each of LocalStackModernTargetFrameworks) and LocalStackVersion. --> Exe - net10.0 + $(LocalStackConsumerTargetFramework) + true enable enable true diff --git a/tests/package-consumer/Program.cs b/tests/LocalStack.Client.PackageConsumer.Tests/Program.cs similarity index 100% rename from tests/package-consumer/Program.cs rename to tests/LocalStack.Client.PackageConsumer.Tests/Program.cs diff --git a/tests/package-consumer/appsettings.json b/tests/LocalStack.Client.PackageConsumer.Tests/appsettings.json similarity index 100% rename from tests/package-consumer/appsettings.json rename to tests/LocalStack.Client.PackageConsumer.Tests/appsettings.json diff --git a/tests/package-consumer/nuget.config b/tests/LocalStack.Client.PackageConsumer.Tests/nuget.config similarity index 100% rename from tests/package-consumer/nuget.config rename to tests/LocalStack.Client.PackageConsumer.Tests/nuget.config