Skip to content
Merged
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
23 changes: 23 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,29 @@ jobs:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

# 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
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
$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' }}'
$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"
runs-on: ubuntu-22.04
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `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.
Expand Down
2 changes: 1 addition & 1 deletion docs/engineering/quality-gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [`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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<!-- Stops the repository's Directory.Build.props from applying: this project must build like an external consumer. -->
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<!-- Stops tests/Directory.Build.targets from adding the generator as a project reference: the package must deliver it. -->
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<!-- Stops the repository's central package management from applying to this external-consumer project. -->
<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<!--
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.
-->
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(LocalStackConsumerTargetFramework)</TargetFramework>
<LocalStackTestRunsAsPublishedExecutable>true</LocalStackTestRunsAsPublishedExecutable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>true</PublishAot>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LocalStack.Client.Extensions" Version="$(LocalStackVersion)" />
<PackageReference Include="AWSSDK.S3" Version="4.0.104.4" />
<PackageReference Include="AWSSDK.SQS" Version="4.0.100.18" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.13" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.13" />
<None Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

</Project>
44 changes: 44 additions & 0 deletions tests/LocalStack.Client.PackageConsumer.Tests/Program.cs
Original file line number Diff line number Diff line change
@@ -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<IAmazonS3>();
using var provider = services.BuildServiceProvider();
var s3 = provider.GetRequiredService<IAmazonS3>();

// A Type-based API: served by the source generator that arrives through the package.
var session = provider.GetRequiredService<ISession>();
using var sqs = (IAmazonSQS)session.CreateClientByInterface(typeof(IAmazonSQS));
string serviceId = provider.GetRequiredService<ISessionReflection>().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;
12 changes: 12 additions & 0 deletions tests/LocalStack.Client.PackageConsumer.Tests/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"LocalStack": {
"UseLocalStack": true,
"Session": {
"RegionName": "eu-central-1"
},
"Config": {
"LocalStackHost": "localhost",
"EdgePort": 4566
}
}
}
17 changes: 17 additions & 0 deletions tests/LocalStack.Client.PackageConsumer.Tests/nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local-packages" value="../../artifacts/packages" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="local-packages">
<package pattern="LocalStack.Client" />
<package pattern="LocalStack.Client.Extensions" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
Loading