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
44 changes: 44 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,50 @@ jobs:
**/TestResults/**/*
retention-days: 7

native-aot-tests:
name: "Native AOT Tests (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-22.04]
steps:
- name: "Checkout"
uses: actions/checkout@v7

- name: "Setup .NET SDK"
uses: actions/setup-dotnet@v6
with:
dotnet-version: |
10.0.x
global-json-file: global.json

- name: "Start LocalStack (Linux)"
if: runner.os == 'Linux'
shell: bash
run: |
docker run -d -p 4566:4566 localstack/localstack:4.14.0
if ! timeout 120s bash -c 'until curl --fail --silent --max-time 1 http://localhost:4566/_localstack/health; do sleep 1; done'; then
echo "LocalStack did not become ready within 120 seconds"
exit 1
fi
echo "LOCALSTACK_NATIVE_AOT_HOST=localhost" >> "$GITHUB_ENV"

- name: "Publish and run Native AOT tests"
shell: pwsh
run: |
$project = 'tests/LocalStack.Client.NativeAot.Tests/LocalStack.Client.NativeAot.Tests.csproj'
$rid = '${{ runner.os == 'Windows' && 'win-x64' || 'linux-x64' }}'
$exe = if ($IsWindows) { 'LocalStack.Client.NativeAot.Tests.exe' } else { 'LocalStack.Client.NativeAot.Tests' }
# Every modern target framework the project declares (from Directory.Build.props).
$frameworks = (dotnet msbuild $project -getProperty:TargetFrameworks).Split(';', [StringSplitOptions]::RemoveEmptyEntries)
foreach ($framework in $frameworks) {
dotnet publish $project -c Release -f $framework -r $rid -o "artifacts/nativeaot/$framework"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& "./artifacts/nativeaot/$framework/$exe"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

continuous-deployment:
name: "Continuous Deployment"
runs-on: ubuntu-22.04
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
<PackageVersion Include="Testcontainers" Version="4.16.0" />
<PackageVersion Include="Testcontainers.LocalStack" Version="4.16.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.v3.aot.mtp-v2" Version="4.0.2" />
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
<PackageVersion Include="xunit.runner.console" Version="2.9.3">
<PrivateAssets>all</PrivateAssets>
Expand Down
1 change: 1 addition & 0 deletions LocalStack.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Project Path="tests\LocalStack.Client.Extensions.Tests\LocalStack.Client.Extensions.Tests.csproj" />
<Project Path="tests\LocalStack.Client.Functional.Tests\LocalStack.Client.Functional.Tests.csproj" />
<Project Path="tests\LocalStack.Client.Integration.Tests\LocalStack.Client.Integration.Tests.csproj" />
<Project Path="tests\LocalStack.Client.NativeAot.Tests\LocalStack.Client.NativeAot.Tests.csproj" />
<Project Path="tests\LocalStack.Client.Tests\LocalStack.Client.Tests.csproj" />
</Folder>
<Folder Name="/test/common/">
Expand Down
44 changes: 31 additions & 13 deletions build/LocalStack.Build/BuildContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,13 @@ public IEnumerable<ProjMetadata> GetProjMetadata()
{
string csProjPath = csProj.FullPath;

IEnumerable<string> targetFrameworks = GetProjectTargetFrameworks(csProjPath);
Dictionary<string, string> properties = GetProjectProperties(csProjPath, "TargetFrameworks", "TargetFramework", RunsAsPublishedExecutableProperty);
IEnumerable<string> targetFrameworks = SplitTargetFrameworks(properties);
string directoryPath = csProj.GetDirectory().FullPath;
string assemblyName = GetAssemblyName(csProjPath);
bool runsAsPublishedExecutable = string.Equals(properties[RunsAsPublishedExecutableProperty], "true", StringComparison.OrdinalIgnoreCase);

var testProjMetadata = new ProjMetadata(directoryPath, csProjPath, targetFrameworks, assemblyName);
var testProjMetadata = new ProjMetadata(directoryPath, csProjPath, targetFrameworks, assemblyName, runsAsPublishedExecutable);
projMetadata.Add(testProjMetadata);
}

Expand Down Expand Up @@ -385,8 +387,21 @@ private static string ToPreReleaseIdentifier(string identifier)
return NuGetVersion.TryParse($"0.0.0-{sanitised}", out NuGetVersion? _) ? sanitised : $"g{sanitised}";
}

// Asks MSBuild for the evaluated frameworks, so lists built from Directory.Build.props properties resolve.
private static string[] GetProjectTargetFrameworks(string csprojPath)
// Test projects that set this property are published and run as native executables by CI, not by 'dotnet test'.
private const string RunsAsPublishedExecutableProperty = "LocalStackTestRunsAsPublishedExecutable";

private static string[] GetProjectTargetFrameworks(string csprojPath) =>
SplitTargetFrameworks(GetProjectProperties(csprojPath, "TargetFrameworks", "TargetFramework"));

private static string[] SplitTargetFrameworks(Dictionary<string, string> properties)
{
string frameworks = string.IsNullOrWhiteSpace(properties["TargetFrameworks"]) ? properties["TargetFramework"] : properties["TargetFrameworks"];

return frameworks.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}

// Asks MSBuild for evaluated property values, so values built from Directory.Build.props properties resolve.
private static Dictionary<string, string> GetProjectProperties(string csprojPath, params string[] propertyNames)
{
string dotnetHost = System.Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ??
throw new InvalidOperationException("DOTNET_HOST_PATH is not set; run the build through the dotnet CLI.");
Expand All @@ -400,8 +415,11 @@ private static string[] GetProjectTargetFrameworks(string csprojPath)
};
startInfo.ArgumentList.Add("msbuild");
startInfo.ArgumentList.Add(csprojPath);
startInfo.ArgumentList.Add("-getProperty:TargetFrameworks");
startInfo.ArgumentList.Add("-getProperty:TargetFramework");

foreach (string propertyName in propertyNames)
{
startInfo.ArgumentList.Add($"-getProperty:{propertyName}");
}

using System.Diagnostics.Process process = System.Diagnostics.Process.Start(startInfo) ??
throw new InvalidOperationException($"Could not start '{dotnetHost} msbuild'.");
Expand All @@ -414,16 +432,16 @@ private static string[] GetProjectTargetFrameworks(string csprojPath)
throw new InvalidOperationException($"'dotnet msbuild {csprojPath}' exited with code {process.ExitCode}: {output}{error}");
}

using var document = System.Text.Json.JsonDocument.Parse(output);
System.Text.Json.JsonElement properties = document.RootElement.GetProperty("Properties");
string? frameworks = properties.GetProperty("TargetFrameworks").GetString();

if (string.IsNullOrWhiteSpace(frameworks))
// With one property MSBuild prints the bare value; with several it prints JSON.
if (propertyNames.Length == 1)
{
frameworks = properties.GetProperty("TargetFramework").GetString();
return new Dictionary<string, string>(StringComparer.Ordinal) { [propertyNames[0]] = output.Trim() };
}

return (frameworks ?? string.Empty).Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
using var document = System.Text.Json.JsonDocument.Parse(output);
System.Text.Json.JsonElement properties = document.RootElement.GetProperty("Properties");

return propertyNames.ToDictionary(name => name, name => properties.GetProperty(name).GetString() ?? string.Empty, StringComparer.Ordinal);
}

private string GetAssemblyName(string csprojPath)
Expand Down
8 changes: 8 additions & 0 deletions build/LocalStack.Build/CakeTasks/TestTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,16 @@ public override void Run(BuildContext context)

ConsoleHelper.WriteInfo($"Target Frameworks: {targetFrameworks}");

if (testProj.RunsAsPublishedExecutable)
{
ConsoleHelper.WriteWarning($"Skipping {testProj.AssemblyName}: CI publishes and runs it as a native executable");

continue;
}

foreach (string targetFramework in testProj.TargetFrameworks)
{

if (context.SkipFunctionalTest && testProj.AssemblyName == "LocalStack.Client.Functional.Tests")
{
ConsoleHelper.WriteWarning("Skipping Functional Tests");
Expand Down
2 changes: 1 addition & 1 deletion build/LocalStack.Build/Models/ProjMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

namespace LocalStack.Build.Models;

public record ProjMetadata(string DirectoryPath, string CsProjPath, IEnumerable<string> TargetFrameworks, string AssemblyName);
public record ProjMetadata(string DirectoryPath, string CsProjPath, IEnumerable<string> TargetFrameworks, string AssemblyName, bool RunsAsPublishedExecutable);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(LocalStackModernTargetFrameworks)</TargetFrameworks>
<OutputType>Exe</OutputType>
<LocalStackTestRunsAsPublishedExecutable>true</LocalStackTestRunsAsPublishedExecutable>
<PublishAot>true</PublishAot>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3.aot.mtp-v2" />
<PackageReference Include="AWSSDK.S3" />
<PackageReference Include="AWSSDK.SQS" />
<PackageReference Include="AWSSDK.DynamoDBv2" />
<PackageReference Include="AWSSDK.SimpleNotificationService" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/LocalStack.Client/LocalStack.Client.csproj" />
<ProjectReference Include="../../src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj" />
<None Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
Loading
Loading