From fc6798eb444313b92e9ba288aba785ed5db1f1bb Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Thu, 1 Oct 2026 08:35:33 -0700 Subject: [PATCH 1/5] test(aot): execute selected dependency injection flows natively --- .github/workflows/analyzer-audit.yml | 33 ++++++++++++++++--- CONTRIBUTING.md | 13 ++++++++ ...ns.DependencyInjection.TrimmedSmoke.csproj | 2 ++ .../Program.cs | 7 +++- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/.github/workflows/analyzer-audit.yml b/.github/workflows/analyzer-audit.yml index 8544404d30d..005737352da 100644 --- a/.github/workflows/analyzer-audit.yml +++ b/.github/workflows/analyzer-audit.yml @@ -194,24 +194,47 @@ jobs: --output $output & (Join-Path $output 'Orleans.Serialization.TrimmedSmoke') - dependency-injection-trimmed-smoke: - name: Orleans.DependencyInjection trimmed smoke for selected generic DI constructor flows (net10.0) + dependency-injection-smoke: + name: Orleans.DependencyInjection ${{ matrix.mode }} smoke for selected generic DI constructor flows (net10.0) runs-on: ubuntu-latest timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - mode: trimmed + native-aot: false + - mode: NativeAOT + native-aot: true + env: + RESULTS_DIRECTORY: Artifacts/AnalyzerAudit/DependencyInjection/${{ matrix.mode }} steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Setup .NET uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 with: global-json-file: global.json - - name: Publish and run selected generic DI constructor flows trimmed smoke + - name: Publish and run selected generic DI constructor flows smoke shell: pwsh run: | - $output = Join-Path $env:RUNNER_TEMP 'orleans-dependency-injection-trimmed-smoke' + $output = Join-Path $env:RUNNER_TEMP 'orleans-dependency-injection-${{ matrix.mode }}-smoke' + New-Item -ItemType Directory -Force $env:RESULTS_DIRECTORY | Out-Null dotnet publish test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj ` --framework net10.0 ` --configuration Release ` --runtime linux-x64 ` --self-contained true ` - --output $output + --output $output ` + -bl:$env:RESULTS_DIRECTORY/publish.binlog ` + -p:NativeAotSmoke=${{ matrix.native-aot }} + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } & (Join-Path $output 'Orleans.DependencyInjection.TrimmedSmoke') + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Upload dependency injection smoke diagnostics + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: dependency-injection-${{ matrix.mode }}-smoke + retention-days: 1 + if-no-files-found: warn + path: ${{ env.RESULTS_DIRECTORY }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70650f4f22e..173c766e19b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,6 +63,19 @@ dotnet test --solution Orleans.slnx --framework net10.0 --filter-trait "Category dotnet test --project test/Orleans.Core.Tests/Orleans.Core.Tests.csproj --framework net10.0 --filter-class "*MyTestClass*" --filter-method "*MyTestMethod*" --minimum-expected-tests 1 ``` +### NativeAOT smoke tests + +The dependency-injection smoke exercises placement strategy and director registration, keyed service resolution, constructor dependency identity, and lifecycle-driven startup task activation. The static analysis workflow publishes and executes these flows with both trimming and NativeAOT. + +To execute the native smoke on Windows, install the [NativeAOT C++ toolchain prerequisites](https://learn.microsoft.com/dotnet/core/deploying/native-aot/), then run: + +```console +dotnet publish test\Orleans.DependencyInjection.TrimmedSmoke\Orleans.DependencyInjection.TrimmedSmoke.csproj --framework net10.0 --configuration Release --runtime win-x64 --self-contained true -p:NativeAotSmoke=true --output Artifacts\DependencyInjectionNativeAotSmoke +Artifacts\DependencyInjectionNativeAotSmoke\Orleans.DependencyInjection.TrimmedSmoke.exe +``` + +`NativeAotSmoke=true` enables `PublishAot` within the smoke project, keeping build-time analyzers and source generators on their managed target frameworks. The executable verifies that runtime code generation is disabled before exercising the selected DI flows. Use `NativeAotSmoke=false` to execute the same flows in a self-contained trimmed application. + ### Work on an application with local source For an application which needs to exercise an unreleased change, reference the relevant project under `src` instead of the published NuGet package. For example, reference `src/Orleans.Core/Orleans.Core.csproj` from the application project and build the application together with the Orleans solution. This lets the debugger step into the local Orleans source. diff --git a/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj b/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj index 626451d590d..b300c5c49ea 100644 --- a/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj +++ b/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj @@ -6,6 +6,8 @@ false true link + true + $(DefineConstants);NATIVE_AOT_SMOKE diff --git a/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs b/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs index aa87cc437e7..29f6925ebf4 100644 --- a/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs +++ b/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs @@ -15,11 +15,16 @@ internal static class Program private static async Task Main() { +#if NATIVE_AOT_SMOKE + Ensure( + !System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported, + "The NativeAOT smoke must execute with runtime code generation disabled."); +#endif ValidateCoreRegistration(); ValidateRuntimeRegistration(); await ValidateStartupTaskActivation(); - Console.WriteLine("Selected generic DI constructor flows survived the self-contained trimmed smoke."); + Console.WriteLine("Selected generic DI constructor flows activated successfully."); } private static void ValidateCoreRegistration() From ff54bd9efeebd1d22735cf9da500891b6d6cf7f3 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Thu, 1 Oct 2026 09:05:46 -0700 Subject: [PATCH 2/5] test(aot): add a reusable native smoke harness --- .../NativeAotEnvironment.cs | 16 ++++++++++ .../Orleans.NativeAotSmoke.csproj | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 test/Orleans.NativeAotSmoke/NativeAotEnvironment.cs create mode 100644 test/Orleans.NativeAotSmoke/Orleans.NativeAotSmoke.csproj diff --git a/test/Orleans.NativeAotSmoke/NativeAotEnvironment.cs b/test/Orleans.NativeAotSmoke/NativeAotEnvironment.cs new file mode 100644 index 00000000000..0836f129876 --- /dev/null +++ b/test/Orleans.NativeAotSmoke/NativeAotEnvironment.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Orleans.NativeAotSmoke; + +internal static class NativeAotEnvironment +{ + [ModuleInitializer] + internal static void Validate() + { + if (RuntimeFeature.IsDynamicCodeSupported) + { + throw new InvalidOperationException("NativeAOT smoke scenarios require runtime code generation to be disabled."); + } + } +} diff --git a/test/Orleans.NativeAotSmoke/Orleans.NativeAotSmoke.csproj b/test/Orleans.NativeAotSmoke/Orleans.NativeAotSmoke.csproj new file mode 100644 index 00000000000..85055b5328d --- /dev/null +++ b/test/Orleans.NativeAotSmoke/Orleans.NativeAotSmoke.csproj @@ -0,0 +1,32 @@ + + + + Exe + net10.0 + true + false + DependencyInjection + true + false + $(DefineConstants);NATIVE_AOT_SMOKE + + + + + + + + + + + + + + + + From 75c91f92edff149b28d3d3ea1ad0f461aadf8ae2 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Thu, 1 Oct 2026 09:09:41 -0700 Subject: [PATCH 3/5] test(aot): run dependency injection in the shared native harness --- .github/workflows/analyzer-audit.yml | 13 +++++++------ CONTRIBUTING.md | 8 ++++---- .../Orleans.DependencyInjection.TrimmedSmoke.csproj | 2 -- .../Program.cs | 5 ----- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/.github/workflows/analyzer-audit.yml b/.github/workflows/analyzer-audit.yml index 005737352da..84313a2df95 100644 --- a/.github/workflows/analyzer-audit.yml +++ b/.github/workflows/analyzer-audit.yml @@ -203,9 +203,11 @@ jobs: matrix: include: - mode: trimmed - native-aot: false + project: test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj + executable: Orleans.DependencyInjection.TrimmedSmoke - mode: NativeAOT - native-aot: true + project: test/Orleans.NativeAotSmoke/Orleans.NativeAotSmoke.csproj + executable: Orleans.NativeAotSmoke env: RESULTS_DIRECTORY: Artifacts/AnalyzerAudit/DependencyInjection/${{ matrix.mode }} steps: @@ -219,16 +221,15 @@ jobs: run: | $output = Join-Path $env:RUNNER_TEMP 'orleans-dependency-injection-${{ matrix.mode }}-smoke' New-Item -ItemType Directory -Force $env:RESULTS_DIRECTORY | Out-Null - dotnet publish test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj ` + dotnet publish ${{ matrix.project }} ` --framework net10.0 ` --configuration Release ` --runtime linux-x64 ` --self-contained true ` --output $output ` - -bl:$env:RESULTS_DIRECTORY/publish.binlog ` - -p:NativeAotSmoke=${{ matrix.native-aot }} + -bl:$env:RESULTS_DIRECTORY/publish.binlog if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - & (Join-Path $output 'Orleans.DependencyInjection.TrimmedSmoke') + & (Join-Path $output '${{ matrix.executable }}') if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Upload dependency injection smoke diagnostics if: always() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 173c766e19b..935489a0139 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,16 +65,16 @@ dotnet test --project test/Orleans.Core.Tests/Orleans.Core.Tests.csproj --framew ### NativeAOT smoke tests -The dependency-injection smoke exercises placement strategy and director registration, keyed service resolution, constructor dependency identity, and lifecycle-driven startup task activation. The static analysis workflow publishes and executes these flows with both trimming and NativeAOT. +The reusable `test\Orleans.NativeAotSmoke` project publishes selected scenarios as native executables. Its default dependency-injection scenario shares the existing trimmed smoke's source and exercises placement strategy and director registration, keyed service resolution, constructor dependency identity, and lifecycle-driven startup task activation. The static analysis workflow publishes and executes these flows with both trimming and NativeAOT. To execute the native smoke on Windows, install the [NativeAOT C++ toolchain prerequisites](https://learn.microsoft.com/dotnet/core/deploying/native-aot/), then run: ```console -dotnet publish test\Orleans.DependencyInjection.TrimmedSmoke\Orleans.DependencyInjection.TrimmedSmoke.csproj --framework net10.0 --configuration Release --runtime win-x64 --self-contained true -p:NativeAotSmoke=true --output Artifacts\DependencyInjectionNativeAotSmoke -Artifacts\DependencyInjectionNativeAotSmoke\Orleans.DependencyInjection.TrimmedSmoke.exe +dotnet publish test\Orleans.NativeAotSmoke\Orleans.NativeAotSmoke.csproj --configuration Release --runtime win-x64 --output Artifacts\NativeAotSmoke +Artifacts\NativeAotSmoke\Orleans.NativeAotSmoke.exe ``` -`NativeAotSmoke=true` enables `PublishAot` within the smoke project, keeping build-time analyzers and source generators on their managed target frameworks. The executable verifies that runtime code generation is disabled before exercising the selected DI flows. Use `NativeAotSmoke=false` to execute the same flows in a self-contained trimmed application. +`PublishAot` is enabled within the native smoke project, keeping build-time analyzers and source generators on their managed target frameworks. The executable verifies that runtime code generation is disabled before exercising the selected flows. Additional scenarios use `-p:NativeAotSmokeScenario=` and share the same project; each build compiles that scenario's `*.cs` files. The existing `Orleans.DependencyInjection.TrimmedSmoke` project exercises the same DI flows in a self-contained trimmed application. ### Work on an application with local source diff --git a/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj b/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj index b300c5c49ea..626451d590d 100644 --- a/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj +++ b/test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj @@ -6,8 +6,6 @@ false true link - true - $(DefineConstants);NATIVE_AOT_SMOKE diff --git a/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs b/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs index 29f6925ebf4..6ae07a2f557 100644 --- a/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs +++ b/test/Orleans.DependencyInjection.TrimmedSmoke/Program.cs @@ -15,11 +15,6 @@ internal static class Program private static async Task Main() { -#if NATIVE_AOT_SMOKE - Ensure( - !System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported, - "The NativeAOT smoke must execute with runtime code generation disabled."); -#endif ValidateCoreRegistration(); ValidateRuntimeRegistration(); await ValidateStartupTaskActivation(); From bf455da99525b2e2df0e3a3bf8238416cc2f9112 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Thu, 1 Oct 2026 10:19:59 -0700 Subject: [PATCH 4/5] test(aot): centralize native smoke discovery and execution --- .github/scripts/run-native-aot-smoke.ps1 | 184 ++++++++++++++++++ .../DependencyInjection.smoke.json | 5 + 2 files changed, 189 insertions(+) create mode 100644 .github/scripts/run-native-aot-smoke.ps1 create mode 100644 test/Orleans.NativeAotSmoke/DependencyInjection.smoke.json diff --git a/.github/scripts/run-native-aot-smoke.ps1 b/.github/scripts/run-native-aot-smoke.ps1 new file mode 100644 index 00000000000..bf3855c982d --- /dev/null +++ b/.github/scripts/run-native-aot-smoke.ps1 @@ -0,0 +1,184 @@ +[CmdletBinding(DefaultParameterSetName = 'Run')] +param( + [Parameter(Mandatory, ParameterSetName = 'List')] + [switch] $List, + + [Parameter(Mandatory, ParameterSetName = 'Run')] + [string] $Scenario, + + [Parameter(Mandatory, ParameterSetName = 'Run')] + [ValidateSet('win-x64', 'linux-x64')] + [string] $RuntimeIdentifier, + + [Parameter(ParameterSetName = 'Run')] + [string] $ResultsDirectory, + + [Parameter(ParameterSetName = 'Run')] + [switch] $UseEnvironmentalTools +) + +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $false +$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +$projectDirectory = Join-Path (Join-Path $repositoryRoot 'test') 'Orleans.NativeAotSmoke' +$manifests = @(Get-ChildItem -LiteralPath $projectDirectory -Filter '*.smoke.json' -File | Sort-Object Name) +if ($manifests.Count -eq 0) +{ + throw "No native smoke scenario manifests were found in '$projectDirectory'." +} + +$names = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) +$scenarios = foreach ($file in $manifests) +{ + $manifest = Get-Content -LiteralPath $file.FullName -Raw | ConvertFrom-Json -AsHashtable + foreach ($property in @('scenario', 'diagnostics', 'rejectDiagnostics')) + { + if (-not $manifest.ContainsKey($property)) + { + throw "Manifest '$($file.Name)' is missing '$property'." + } + } + + foreach ($property in $manifest.Keys) + { + if ($property -notin @('scenario', 'diagnostics', 'rejectDiagnostics')) + { + throw "Manifest '$($file.Name)' has unknown property '$property'." + } + } + + if ($manifest.scenario -isnot [string] -or + $manifest.scenario -cnotmatch '^[A-Za-z][A-Za-z0-9]*$' -or + $file.Name -cne "$($manifest.scenario).smoke.json") + { + throw "Manifest '$($file.Name)' must name its alphanumeric scenario exactly." + } + + if (-not $names.Add($manifest.scenario)) + { + throw "Native smoke scenario '$($manifest.scenario)' is registered more than once." + } + + if ($manifest.diagnostics -notin @('strict', 'legacy-visible') -or + $manifest.rejectDiagnostics -isnot [array]) + { + throw "Manifest '$($file.Name)' must specify a diagnostic policy and an array of rejection patterns." + } + + if ($manifest.diagnostics -eq 'legacy-visible' -and $manifest.rejectDiagnostics.Count -eq 0) + { + throw "Manifest '$($file.Name)' must gate its supported path while retaining legacy diagnostics." + } + + foreach ($pattern in $manifest.rejectDiagnostics) + { + if ($pattern -isnot [string] -or [string]::IsNullOrWhiteSpace($pattern)) + { + throw "Manifest '$($file.Name)' contains an empty diagnostic rejection pattern." + } + + [void] [regex]::new($pattern) + } + + if ($manifest.scenario -ne 'DependencyInjection' -and + @(Get-ChildItem -LiteralPath $projectDirectory -Filter "$($manifest.scenario)*.cs" -File).Count -eq 0) + { + throw "Manifest '$($file.Name)' has no matching scenario source files." + } + + $manifest +} + +if ($List) +{ + $include = foreach ($manifest in $scenarios) + { + [ordered] @{ scenario = $manifest.scenario; runner = 'ubuntu-latest'; runtime = 'linux-x64' } + [ordered] @{ scenario = $manifest.scenario; runner = 'windows-latest'; runtime = 'win-x64' } + } + + [ordered] @{ include = @($include) } | ConvertTo-Json -Depth 4 -Compress + return +} + +$selected = @($scenarios | Where-Object { $_.scenario -ceq $Scenario }) +if ($selected.Count -ne 1) +{ + throw "Native smoke scenario '$Scenario' is not registered." +} + +$selected = $selected[0] +if ([string]::IsNullOrWhiteSpace($ResultsDirectory)) +{ + $ResultsDirectory = Join-Path (Join-Path (Join-Path $repositoryRoot 'Artifacts') 'NativeAotSmoke') "$Scenario-$RuntimeIdentifier" +} + +$ResultsDirectory = [System.IO.Path]::GetFullPath($ResultsDirectory) +New-Item -ItemType Directory -Force -Path $ResultsDirectory | Out-Null +$output = Join-Path $ResultsDirectory 'native' +$publishLog = Join-Path $ResultsDirectory 'publish.log' +$runtimeLog = Join-Path $ResultsDirectory 'runtime.log' +$publishArguments = @( + 'publish', (Join-Path $projectDirectory 'Orleans.NativeAotSmoke.csproj'), + '--configuration', 'Release', + '--framework', 'net10.0', + '--runtime', $RuntimeIdentifier, + '--self-contained', 'true', + '--output', $output, + "-p:NativeAotSmokeScenario=$Scenario", + '-p:TrimmerSingleWarn=false', + '-m:1', + "-bl:$(Join-Path $ResultsDirectory 'publish.binlog')" +) + +if ($UseEnvironmentalTools) +{ + $publishArguments += '-p:IlcUseEnvironmentalTools=true' +} + +if ($selected.diagnostics -eq 'legacy-visible') +{ + $publishArguments += @( + '-p:ILLinkTreatWarningsAsErrors=false', + '-p:IlcTreatWarningsAsErrors=false', + '-p:WarningsNotAsErrors=IL2026%3BIL2055%3BIL2057%3BIL2060%3BIL2067%3BIL2070%3BIL2071%3BIL2077%3BIL2090%3BIL2091%3BIL2096%3BIL3050%3BIL4000' + ) +} +else +{ + $publishArguments += @( + '-p:TreatWarningsAsErrors=true', + '-p:ILLinkTreatWarningsAsErrors=true', + '-p:IlcTreatWarningsAsErrors=true' + ) +} + +& dotnet @publishArguments *>&1 | Tee-Object -FilePath $publishLog +$publishExitCode = $LASTEXITCODE +if ($publishExitCode -ne 0) +{ + throw "Native smoke '$Scenario' publish failed with exit code $publishExitCode. See '$publishLog'." +} + +$diagnostics = @(Select-String -LiteralPath $publishLog -Pattern '(warning|error)\s+IL[0-9]+') +foreach ($pattern in $selected.rejectDiagnostics) +{ + $rejected = @($diagnostics | Where-Object { $_.Line -match $pattern }) + if ($rejected.Count -gt 0) + { + throw "Native smoke '$Scenario' emitted supported-path diagnostics:`n$($rejected.Line -join [Environment]::NewLine)" + } +} + +$executable = Join-Path $output $(if ($IsWindows) { 'Orleans.NativeAotSmoke.exe' } else { 'Orleans.NativeAotSmoke' }) +if (-not (Test-Path -LiteralPath $executable -PathType Leaf)) +{ + throw "Native smoke '$Scenario' publish produced no executable at '$executable'." +} + +& $executable *>&1 | Tee-Object -FilePath $runtimeLog +$runtimeExitCode = $LASTEXITCODE +if ($runtimeExitCode -ne 0) +{ + throw "Native smoke '$Scenario' execution failed with exit code $runtimeExitCode. See '$runtimeLog'." +} diff --git a/test/Orleans.NativeAotSmoke/DependencyInjection.smoke.json b/test/Orleans.NativeAotSmoke/DependencyInjection.smoke.json new file mode 100644 index 00000000000..e5fae9a9189 --- /dev/null +++ b/test/Orleans.NativeAotSmoke/DependencyInjection.smoke.json @@ -0,0 +1,5 @@ +{ + "scenario": "DependencyInjection", + "diagnostics": "strict", + "rejectDiagnostics": [] +} From 556364c3dd0d680b3bf5b2fd435977d9a08df3c0 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Thu, 1 Oct 2026 10:22:55 -0700 Subject: [PATCH 5/5] ci(aot): discover all native scenarios in one shared matrix --- .github/scripts/run-native-aot-smoke.ps1 | 12 +++- .github/workflows/analyzer-audit.yml | 76 ++++++++++++++++++------ CONTRIBUTING.md | 9 +++ 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/.github/scripts/run-native-aot-smoke.ps1 b/.github/scripts/run-native-aot-smoke.ps1 index bf3855c982d..d7ec439640d 100644 --- a/.github/scripts/run-native-aot-smoke.ps1 +++ b/.github/scripts/run-native-aot-smoke.ps1 @@ -14,12 +14,20 @@ param( [string] $ResultsDirectory, [Parameter(ParameterSetName = 'Run')] - [switch] $UseEnvironmentalTools + [switch] $UseEnvironmentalTools, + + [Parameter()] + [string] $RepositoryRoot ) $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $false -$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) +{ + $RepositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +} + +$RepositoryRoot = [System.IO.Path]::GetFullPath($RepositoryRoot) $projectDirectory = Join-Path (Join-Path $repositoryRoot 'test') 'Orleans.NativeAotSmoke' $manifests = @(Get-ChildItem -LiteralPath $projectDirectory -Filter '*.smoke.json' -File | Sort-Object Name) if ($manifests.Count -eq 0) diff --git a/.github/workflows/analyzer-audit.yml b/.github/workflows/analyzer-audit.yml index 84313a2df95..9467b5c5a1d 100644 --- a/.github/workflows/analyzer-audit.yml +++ b/.github/workflows/analyzer-audit.yml @@ -194,34 +194,24 @@ jobs: --output $output & (Join-Path $output 'Orleans.Serialization.TrimmedSmoke') - dependency-injection-smoke: - name: Orleans.DependencyInjection ${{ matrix.mode }} smoke for selected generic DI constructor flows (net10.0) + dependency-injection-trimmed-smoke: + name: Orleans.DependencyInjection trimmed smoke for selected generic DI constructor flows (net10.0) runs-on: ubuntu-latest timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - include: - - mode: trimmed - project: test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj - executable: Orleans.DependencyInjection.TrimmedSmoke - - mode: NativeAOT - project: test/Orleans.NativeAotSmoke/Orleans.NativeAotSmoke.csproj - executable: Orleans.NativeAotSmoke env: - RESULTS_DIRECTORY: Artifacts/AnalyzerAudit/DependencyInjection/${{ matrix.mode }} + RESULTS_DIRECTORY: Artifacts/AnalyzerAudit/DependencyInjection/trimmed steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Setup .NET uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 with: global-json-file: global.json - - name: Publish and run selected generic DI constructor flows smoke + - name: Publish and run selected generic DI constructor flows trimmed smoke shell: pwsh run: | - $output = Join-Path $env:RUNNER_TEMP 'orleans-dependency-injection-${{ matrix.mode }}-smoke' + $output = Join-Path $env:RUNNER_TEMP 'orleans-dependency-injection-trimmed-smoke' New-Item -ItemType Directory -Force $env:RESULTS_DIRECTORY | Out-Null - dotnet publish ${{ matrix.project }} ` + dotnet publish test/Orleans.DependencyInjection.TrimmedSmoke/Orleans.DependencyInjection.TrimmedSmoke.csproj ` --framework net10.0 ` --configuration Release ` --runtime linux-x64 ` @@ -229,13 +219,63 @@ jobs: --output $output ` -bl:$env:RESULTS_DIRECTORY/publish.binlog if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - & (Join-Path $output '${{ matrix.executable }}') + & (Join-Path $output 'Orleans.DependencyInjection.TrimmedSmoke') if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Upload dependency injection smoke diagnostics if: always() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: - name: dependency-injection-${{ matrix.mode }}-smoke + name: dependency-injection-trimmed-smoke retention-days: 1 if-no-files-found: warn path: ${{ env.RESULTS_DIRECTORY }} + + native-aot-smoke-matrix: + name: Discover NativeAOT smoke scenarios + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + matrix: ${{ steps.scenarios.outputs.matrix }} + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - name: Discover registered scenarios + id: scenarios + shell: pwsh + run: | + $matrix = ./.github/scripts/run-native-aot-smoke.ps1 -List + "matrix=$matrix" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8NoBOM + + native-aot-smoke: + name: NativeAOT ${{ matrix.scenario }} smoke (${{ matrix.runtime }}, net10.0) + needs: native-aot-smoke-matrix + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.native-aot-smoke-matrix.outputs.matrix) }} + env: + RESULTS_DIRECTORY: Artifacts/AnalyzerAudit/NativeAot/${{ matrix.scenario }}/${{ matrix.runtime }} + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - name: Setup .NET + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 + with: + global-json-file: global.json + - name: Publish, check diagnostics, and execute native scenario + shell: pwsh + run: | + ./.github/scripts/run-native-aot-smoke.ps1 ` + -Scenario '${{ matrix.scenario }}' ` + -RuntimeIdentifier '${{ matrix.runtime }}' ` + -ResultsDirectory $env:RESULTS_DIRECTORY + - name: Upload native smoke diagnostics + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: native-aot-${{ matrix.scenario }}-${{ matrix.runtime }} + retention-days: 1 + if-no-files-found: warn + path: | + ${{ env.RESULTS_DIRECTORY }}/publish.binlog + ${{ env.RESULTS_DIRECTORY }}/publish.log + ${{ env.RESULTS_DIRECTORY }}/runtime.log diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 935489a0139..58a3de1c2c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,6 +76,15 @@ Artifacts\NativeAotSmoke\Orleans.NativeAotSmoke.exe `PublishAot` is enabled within the native smoke project, keeping build-time analyzers and source generators on their managed target frameworks. The executable verifies that runtime code generation is disabled before exercising the selected flows. Additional scenarios use `-p:NativeAotSmokeScenario=` and share the same project; each build compiles that scenario's `*.cs` files. The existing `Orleans.DependencyInjection.TrimmedSmoke` project exercises the same DI flows in a self-contained trimmed application. +Register each native scenario with a `.smoke.json` manifest beside its source. The existing static analysis workflow discovers these manifests and runs one centralized matrix on Windows and Linux. The shared runner publishes the scenario, checks linker/AOT diagnostics, executes the native binary, and retains diagnostic logs and a binlog: + +```powershell +.\.github\scripts\run-native-aot-smoke.ps1 -List +.\.github\scripts\run-native-aot-smoke.ps1 -Scenario DependencyInjection -RuntimeIdentifier win-x64 +``` + +Each manifest declares `scenario`, `diagnostics`, and `rejectDiagnostics`. Use `strict` for warning-free native paths. A bounded compatibility rollout can use `legacy-visible` with nonempty diagnostic rejection patterns for the supported path; the runner retains the existing backlog visibly and rejects matching new-path warnings and errors. Feature PRs add scenario sources and manifests to this shared harness. + ### Work on an application with local source For an application which needs to exercise an unreleased change, reference the relevant project under `src` instead of the published NuGet package. For example, reference `src/Orleans.Core/Orleans.Core.csproj` from the application project and build the application together with the Orleans solution. This lets the debugger step into the local Orleans source.