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
53 changes: 53 additions & 0 deletions .github/workflows/aot-smoke.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: AOT Smoke

# Native-AOT publish-and-run smoke consumer (#132).
#
# Publishes a tiny console consumer with PublishAot and runs the native binary,
# so the build fails if the library stops compiling or running under native AOT
# (e.g. the async-enumerable pipeline path stops being trimmer-safe). This is the
# *runtime* half of AOT verification: it exercises TestExtractor / TestTransformer
# / TestLoader end-to-end in a trimmed, natively-compiled binary.
#
# windows-latest ships the MSVC "Desktop development with C++" workload that the
# native linker requires, so no extra toolchain install is needed.
#
# Runs on every PR so AOT regressions block before merge.

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: aot-smoke-${{ github.ref }}
cancel-in-progress: true

jobs:
aot-smoke:
name: "AOT publish + run"
runs-on: windows-latest
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: '10.0.x'

- name: Publish (native AOT, win-x64)
run: dotnet publish aot-smoke/Wolfgang.Etl.TestKit.AotSmoke/Wolfgang.Etl.TestKit.AotSmoke.csproj -c Release -r win-x64

- name: Run native binary
shell: bash
run: |
EXE="aot-smoke/Wolfgang.Etl.TestKit.AotSmoke/bin/Release/net10.0/win-x64/publish/Wolfgang.Etl.TestKit.AotSmoke.exe"
echo "Running: $EXE"
"$EXE"
14 changes: 14 additions & 0 deletions aot-smoke/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Isolation stub — companion to Directory.Build.props/.targets in this folder.
#
# .editorconfig is discovered by walking UP the directory tree, independently of
# MSBuild's Directory.Build.props isolation, so without this the repo-root
# .editorconfig's analyzer policy (CA2007 etc.) would still apply to the
# throwaway AOT-smoke consumer. `root = true` stops that walk-up so the fixture
# is fully exempt from repo lint — it is a publish-and-run smoke fixture, not
# shipped code.
root = true

[*.cs]
# The consumer is a console app with no synchronization context, so ConfigureAwait
# is irrelevant; silence CA2007 explicitly in case SDK defaults ever enable it.
dotnet_diagnostic.CA2007.severity = none
9 changes: 9 additions & 0 deletions aot-smoke/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!--
Isolation stub. MSBuild imports only the NEAREST Directory.Build.props when
walking up, so this empty project stops the repo-root Directory.Build.props
(analyzers, BannedSymbols, TreatWarningsAsErrors, CA2007, multi-TFM, PublicAPI,
…) from applying to the throwaway AOT-smoke consumer. The consumer is a
publish-and-run fixture, not shipped code, and must not inherit library policy.
-->
<Project>
</Project>
3 changes: 3 additions & 0 deletions aot-smoke/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!-- Isolation stub — see Directory.Build.props in this folder. -->
<Project>
</Project>
60 changes: 60 additions & 0 deletions aot-smoke/Wolfgang.Etl.TestKit.AotSmoke/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Native-AOT smoke consumer for Wolfgang.Etl.TestKit (#132).
//
// Exercises the public surface after native-AOT publish and returns a non-zero
// exit code if any invariant is violated, so the CI job fails if the library
// stops being AOT-safe at publish/run time. Kept deliberately simple — this is a
// smoke test, not a behavioural test suite (those live in the unit-test project).

using Wolfgang.Etl.TestKit;

static int Fail(string message)
{
Console.Error.WriteLine($"AOT smoke FAILED: {message}");
return 1;
}

// Extractor: yields the seeded items through the async-enumerable pipeline.
var extractor = new TestExtractor<int>(new[] { 1, 2, 3 });
var extracted = new List<int>();
await foreach (var item in extractor.ExtractAsync())
{
extracted.Add(item);
}
if (extracted.Count != 3)
{
return Fail($"TestExtractor yielded {extracted.Count} items, expected 3");
}

// Transformer: pass-through over the extracted items.
var transformer = new TestTransformer<int>();
var transformed = new List<int>();
await foreach (var item in transformer.TransformAsync(ToAsync(extracted)))
{
transformed.Add(item);
}
if (transformed.Count != extracted.Count)
{
return Fail($"TestTransformer yielded {transformed.Count} items, expected {extracted.Count}");
}

// Loader: collects everything the pipeline pushed.
var loader = new TestLoader<int>(collectItems: true);
await loader.LoadAsync(ToAsync(transformed));
var collected = loader.GetCollectedItems();
if (collected is null || collected.Count != transformed.Count)
{
return Fail($"TestLoader collected {(collected is null ? "null" : collected.Count.ToString())} items, expected {transformed.Count}");
}

Console.WriteLine($"AOT smoke OK: extracted {extracted.Count}, transformed {transformed.Count}, loaded {collected.Count}");
return 0;

static async IAsyncEnumerable<int> ToAsync(IEnumerable<int> items)
{
foreach (var item in items)
{
yield return item;
}

await Task.CompletedTask;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<!--
Native-AOT smoke consumer for #132. A tiny console app that references the
library and exercises its public surface (TestExtractor / TestTransformer /
TestLoader), published with PublishAot and executed in CI (see
.github/workflows/aot-smoke.yaml). This gives a *runtime* AOT guarantee: it
fails if AOT publish or execution ever breaks — e.g. the async-enumerable
pipeline path stops being trimmer-safe. Not part of the solution / not packed.
-->
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<PublishAot>true</PublishAot>
<IsPackable>false</IsPackable>
<IsTestProject>false</IsTestProject>
<!-- Invariant globalization keeps the AOT binary small and avoids ICU; the app only
emits ASCII diagnostics. -->
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Wolfgang.Etl.TestKit\Wolfgang.Etl.TestKit.csproj" />
</ItemGroup>

</Project>