Skip to content

Commit 51110f1

Browse files
committed
Run Windows backend tests with native runtime present
Windows CI previously compiled the Rust lib test binary without executing it because the clean runner lacked the native runtime DLLs needed for the Windows test process to start. This prepares Windows App Runtime plus the Foundry DLL set, then compiles once with --no-run so CI can prepend both Foundry and build-script native output directories before executing the same Rust lib test harness. Constraint: Keep the default Windows feature set under test instead of adding a test-only feature split. Rejected: Keep --no-run permanently | it leaves Windows-specific unit regressions unexecuted. Rejected: Disable Foundry for tests | it would test a different Windows binary shape from the app. Confidence: medium Scope-risk: moderate Directive: If the Windows runtime preparation flakes, inspect the CI logs before reverting to --no-run; the intent is to execute Windows lib tests, not only link them. Tested: git diff --check; local cargo test/check/npm build were run before this CI-only path amend. Not-tested: PowerShell runtime-prep script on a Windows GitHub runner before PR CI.
1 parent 7d77044 commit 51110f1

2 files changed

Lines changed: 148 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
if: matrix.preflight
7777
shell: pwsh
7878
run: |
79-
foreach ($script in @("./scripts/windows-preflight.ps1", "./scripts/windows-build-gnu.ps1", "./scripts/windows-runtime-smoke.ps1")) {
79+
foreach ($script in @("./scripts/windows-preflight.ps1", "./scripts/windows-build-gnu.ps1", "./scripts/windows-runtime-smoke.ps1", "./scripts/windows-prepare-foundry-runtime-ci.ps1")) {
8080
$errors = $null
8181
[System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw $script), [ref]$errors) | Out-Null
8282
if ($errors) {
@@ -91,16 +91,49 @@ jobs:
9191
- name: Check Tauri backend (cargo check)
9292
run: cargo check --manifest-path src-tauri/Cargo.toml
9393

94+
- name: Prepare Windows native runtime for tests
95+
if: runner.os == 'Windows'
96+
shell: pwsh
97+
run: ./scripts/windows-prepare-foundry-runtime-ci.ps1
98+
9499
- name: Run Rust backend unit tests
95100
if: runner.os != 'Windows'
96101
run: cargo test --manifest-path src-tauri/Cargo.toml --lib
97102

98-
- name: Compile Rust backend unit tests (Windows)
99-
# Windows runner 能链接 lib test binary,但干净镜像缺少可选 native runtime
100-
# DLL entrypoint 时,进程会在 test harness 启动前退出。这里保留 cfg/link
101-
# 覆盖;共享单测在 macOS / Linux 上实际执行。
103+
- name: Run Rust backend unit tests (Windows)
102104
if: runner.os == 'Windows'
103-
run: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-run
105+
shell: pwsh
106+
run: |
107+
# 先编译测试二进制,再把 build.rs 产物目录补进 PATH。cargo test
108+
# 会在同一目录复用已编译好的 harness,但这时我们能定位 WebView2 /
109+
# Foundry 的原生 DLL,避免 Windows loader 命中 runner 上的旧版本。
110+
cargo test --manifest-path src-tauri/Cargo.toml --lib --no-run
111+
112+
$extraPath = New-Object System.Collections.Generic.List[string]
113+
$nativeDir = Join-Path (Get-Location) "src-tauri\target\foundry-native-ci"
114+
if (Test-Path $nativeDir) { $extraPath.Add($nativeDir) }
115+
116+
$buildRoot = Join-Path (Get-Location) "src-tauri\target\debug\build"
117+
if (Test-Path $buildRoot) {
118+
Get-ChildItem -Path $buildRoot -Recurse -Filter WebView2Loader.dll -ErrorAction SilentlyContinue |
119+
Where-Object { $_.FullName -match "\\x64\\WebView2Loader\.dll$" } |
120+
ForEach-Object { $extraPath.Add($_.DirectoryName) }
121+
Get-ChildItem -Path $buildRoot -Recurse -Filter Microsoft.AI.Foundry.Local.Core.dll -ErrorAction SilentlyContinue |
122+
ForEach-Object { $extraPath.Add($_.DirectoryName) }
123+
}
124+
125+
$orderedPath = @($extraPath | Select-Object -Unique)
126+
if ($orderedPath.Count -gt 0) {
127+
Write-Host "[ci] Native test PATH prefix:"
128+
$orderedPath | ForEach-Object { Write-Host " $_" }
129+
$env:PATH = ($orderedPath -join ";") + ";$env:PATH"
130+
}
131+
132+
foreach ($dll in @("WebView2Loader.dll", "onnxruntime.dll", "Microsoft.AI.Foundry.Local.Core.dll")) {
133+
where.exe $dll 2>$null
134+
if ($LASTEXITCODE -ne 0) { Write-Host "[ci] $dll not found on PATH before test run" }
135+
}
136+
cargo test --manifest-path src-tauri/Cargo.toml --lib
104137
105138
- name: Verify version sync across all 5 files
106139
# 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
$ErrorActionPreference = "Stop"
2+
3+
# CI-only runtime preparation for executing the Windows Rust lib test binary.
4+
# The production app still downloads/validates these pieces lazily at runtime;
5+
# this script only makes the GitHub runner look like a machine with Foundry
6+
# native audio dependencies installed so `cargo test --lib` can start.
7+
8+
$NativeDir = Join-Path (Get-Location) "src-tauri\target\foundry-native-ci"
9+
New-Item -ItemType Directory -Force -Path $NativeDir | Out-Null
10+
11+
$WindowsAppRuntimeInstallerUrl = "https://aka.ms/windowsappsdk/1.8/1.8.260416003/windowsappruntimeinstall-x64.exe"
12+
$Packages = @(
13+
@{ Name = "Microsoft.AI.Foundry.Local.Core.WinML"; Version = "1.0.0" },
14+
@{ Name = "Microsoft.ML.OnnxRuntime.Foundry"; Version = "1.23.2.3" },
15+
@{ Name = "Microsoft.ML.OnnxRuntimeGenAI.Foundry"; Version = "0.13.2" }
16+
)
17+
$RequiredDlls = @(
18+
"Microsoft.AI.Foundry.Local.Core.dll",
19+
"Microsoft.WindowsAppRuntime.Bootstrap.dll",
20+
"onnxruntime.dll",
21+
"onnxruntime-genai.dll"
22+
)
23+
24+
function Test-WindowsAppRuntimeReady {
25+
$min = [version]'1.8.1.0'
26+
$pkgs = @(Get-AppxPackage -Name '*AppRuntime*' -ErrorAction SilentlyContinue)
27+
function Test-Package($NamePattern, $Architecture, $IsFramework) {
28+
return @($pkgs | Where-Object {
29+
$_.Name -match $NamePattern `
30+
-and "$($_.Architecture)" -ieq $Architecture `
31+
-and [version]$_.Version -ge $min `
32+
-and "$($_.Status)" -eq 'Ok' `
33+
-and $_.IsPartiallyStaged -ne $true `
34+
-and ([bool]$_.IsFramework -eq $IsFramework)
35+
}).Count -gt 0
36+
}
37+
38+
$frameworkX64 = Test-Package '^Microsoft\.WindowsAppRuntime\.1\.8$' 'X64' $true
39+
$mainX64 = Test-Package '^MicrosoftCorporationII\.(WinAppRuntime|WindowsAppRuntime)\.Main\.1\.8$' 'X64' $false
40+
$singletonX64 = Test-Package '^(MicrosoftCorporationII\.(WinAppRuntime|WindowsAppRuntime)\.Singleton|Microsoft\.WindowsAppRuntime\.Singleton)$' 'X64' $false
41+
$ddlmX64 = Test-Package '^Microsoft\.WinAppRuntime\.DDLM\..*-x6$' 'X64' $false
42+
return ($frameworkX64 -and $mainX64 -and $singletonX64 -and $ddlmX64)
43+
}
44+
45+
function Install-WindowsAppRuntimeIfNeeded {
46+
if (Test-WindowsAppRuntimeReady) {
47+
Write-Host "[ok] Windows App Runtime 1.8 already available"
48+
return
49+
}
50+
51+
$installer = Join-Path $env:RUNNER_TEMP "WindowsAppRuntimeInstall-x64.exe"
52+
Write-Host "[ci] Downloading Windows App Runtime 1.8 installer"
53+
Invoke-WebRequest -Uri $WindowsAppRuntimeInstallerUrl -OutFile $installer
54+
55+
Write-Host "[ci] Installing Windows App Runtime 1.8"
56+
$process = Start-Process -FilePath $installer -ArgumentList @("--quiet", "--force") -Wait -PassThru
57+
Remove-Item -Force $installer -ErrorAction SilentlyContinue
58+
if ($process.ExitCode -ne 0) {
59+
throw "Windows App Runtime installer failed with exit code $($process.ExitCode)"
60+
}
61+
62+
for ($i = 0; $i -lt 20; $i++) {
63+
if (Test-WindowsAppRuntimeReady) {
64+
Write-Host "[ok] Windows App Runtime 1.8 installed"
65+
return
66+
}
67+
Start-Sleep -Seconds 1
68+
}
69+
throw "Windows App Runtime 1.8 did not become ready after installation"
70+
}
71+
72+
function Save-NuGetNativeDlls($PackageName, $Version) {
73+
$lower = $PackageName.ToLowerInvariant()
74+
$url = "https://api.nuget.org/v3-flatcontainer/$lower/$Version/$lower.$Version.nupkg"
75+
$packagePath = Join-Path $env:RUNNER_TEMP "$lower.$Version.nupkg"
76+
77+
Write-Host "[ci] Downloading $PackageName $Version"
78+
Invoke-WebRequest -Uri $url -OutFile $packagePath
79+
80+
Add-Type -AssemblyName System.IO.Compression.FileSystem
81+
$archive = [System.IO.Compression.ZipFile]::OpenRead($packagePath)
82+
try {
83+
foreach ($entry in $archive.Entries) {
84+
if (-not $entry.FullName.StartsWith("runtimes/win-x64/native/")) { continue }
85+
if (-not $entry.FullName.EndsWith(".dll")) { continue }
86+
$target = Join-Path $NativeDir ([System.IO.Path]::GetFileName($entry.FullName))
87+
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $target, $true)
88+
Write-Host "[ok] extracted $([System.IO.Path]::GetFileName($entry.FullName))"
89+
}
90+
} finally {
91+
$archive.Dispose()
92+
Remove-Item -Force $packagePath -ErrorAction SilentlyContinue
93+
}
94+
}
95+
96+
Install-WindowsAppRuntimeIfNeeded
97+
foreach ($package in $Packages) {
98+
Save-NuGetNativeDlls $($package["Name"]) $($package["Version"])
99+
}
100+
101+
foreach ($dll in $RequiredDlls) {
102+
$path = Join-Path $NativeDir $dll
103+
if (-not (Test-Path $path)) {
104+
throw "Required Foundry native DLL missing after extraction: $dll"
105+
}
106+
}
107+
108+
Write-Host "[ok] Foundry native DLLs ready in $NativeDir"
109+
Add-Content -Path $env:GITHUB_PATH -Value $NativeDir

0 commit comments

Comments
 (0)