Files
OpenShell/tasks/scripts/windows-mxc-inference-examples-e2e.ps1
Prekshi Vyas 7ba7a39d09 ci(windows): exercise MXC inference demos with mock API (#3780)
* ci(windows): exercise MXC Ollama demo with mock API

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

* ci(windows): cover both MXC inference demos

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

* fix(mxc): preserve executable extension resolution

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

* test(mxc): use absolute PowerShell in lifecycle checks

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

* test(mxc): make lifecycle write probes deterministic

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

* test(mxc): assert stable lifecycle completion

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

---------

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
2026-09-29 13:39:23 -07:00

259 lines
11 KiB
PowerShell

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# Runs both shipped MXC inference demos through the real gateway and CLI
# against the in-process wxc mock and a local HTTP API stub. This proves demo
# wiring only; it is not evidence of MXC or AppContainer enforcement.
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateSet("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc")]
[string] $Target,
[string] $GatewayPath,
[string] $CliPath,
[string] $ArtifactRoot,
[switch] $KeepArtifacts
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $false
if (-not [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) {
throw "windows-mxc-inference-examples-e2e.ps1 requires Windows."
}
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
$ExamplesRoot = Join-Path $RepoRoot "crates\openshell-driver-mxc\examples"
$TargetDir = if ([string]::IsNullOrWhiteSpace($env:CARGO_TARGET_DIR)) {
Join-Path $RepoRoot "target"
} else {
[System.IO.Path]::GetFullPath($env:CARGO_TARGET_DIR)
}
if ([string]::IsNullOrWhiteSpace($GatewayPath)) {
$GatewayPath = Join-Path $TargetDir "$Target\release\openshell-gateway.exe"
}
if ([string]::IsNullOrWhiteSpace($CliPath)) {
$CliPath = Join-Path $TargetDir "$Target\release\openshell.exe"
}
$GatewayPath = [System.IO.Path]::GetFullPath($GatewayPath)
$CliPath = [System.IO.Path]::GetFullPath($CliPath)
foreach ($artifact in @($GatewayPath, $CliPath)) {
if (-not (Test-Path -LiteralPath $artifact -PathType Leaf)) {
throw "required release artifact is missing: $artifact"
}
}
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
$ArtifactRoot = if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) {
[System.IO.Path]::GetTempPath()
} else {
$env:RUNNER_TEMP
}
}
$ArtifactRoot = [System.IO.Path]::GetFullPath($ArtifactRoot)
$StageDir = [System.IO.Path]::GetFullPath((Join-Path $ArtifactRoot "openshell-mxc-inference-examples-e2e-$Target"))
$expectedPrefix = $ArtifactRoot.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar
if (-not $StageDir.StartsWith($expectedPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
throw "refusing to use staging path outside artifact root: $StageDir"
}
if (Test-Path -LiteralPath $StageDir) {
Remove-Item -LiteralPath $StageDir -Recurse -Force
}
New-Item -ItemType Directory -Path $StageDir | Out-Null
foreach ($fixture in @(
"run-ollama-test.ps1",
"mxc-ollama.toml",
"ollama.yaml",
"run-inference-test.ps1",
"mxc-inference.toml",
"inference.yaml"
)) {
Copy-Item -LiteralPath (Join-Path $ExamplesRoot $fixture) -Destination $StageDir
}
function Get-AvailablePort {
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
try {
$listener.Start()
return ([System.Net.IPEndPoint] $listener.LocalEndpoint).Port
} finally {
$listener.Stop()
}
}
function Test-Listener([int] $Port) {
$client = [System.Net.Sockets.TcpClient]::new()
try {
$pending = $client.BeginConnect("127.0.0.1", $Port, $null, $null)
if (-not $pending.AsyncWaitHandle.WaitOne(250)) { return $false }
$client.EndConnect($pending)
return $true
} catch {
return $false
} finally {
$client.Dispose()
}
}
$ApiPort = Get-AvailablePort
$MockToken = "openshell-ci-mock-token"
$RequestLog = Join-Path $StageDir "mock-inference-requests.log"
$ServerReady = Join-Path $StageDir "mock-inference.ready"
$ServerOutLog = Join-Path $StageDir "mock-inference.out.log"
$ServerErrLog = Join-Path $StageDir "mock-inference.err.log"
$ServerScript = Join-Path $PSScriptRoot "windows-mxc-inference-stub.ps1"
$serverProcess = $null
$passed = $false
$oldAppData = $env:APPDATA
$oldLocalAppData = $env:LOCALAPPDATA
$oldNvApiKey = $env:NV_API_KEY
try {
$serverProcess = Start-Process -FilePath "powershell.exe" -ArgumentList @(
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", "`"$ServerScript`"",
"-Port", "$ApiPort",
"-RequestLog", "`"$RequestLog`"",
"-ReadyPath", "`"$ServerReady`"",
"-ExpectedBearerToken", $MockToken
) -PassThru -WindowStyle Hidden -RedirectStandardOutput $ServerOutLog -RedirectStandardError $ServerErrLog
$deadline = (Get-Date).AddSeconds(15)
while ((Get-Date) -lt $deadline -and (-not (Test-Path -LiteralPath $ServerReady) -or -not (Test-Listener $ApiPort))) {
if ($serverProcess.HasExited) {
$details = (Get-Content $ServerOutLog, $ServerErrLog -ErrorAction SilentlyContinue) -join [Environment]::NewLine
throw "mock inference server stopped before listening: $details"
}
Start-Sleep -Milliseconds 200
}
if (-not (Test-Listener $ApiPort)) {
throw "mock inference server did not listen on port $ApiPort within 15 seconds"
}
$env:APPDATA = Join-Path $StageDir "appdata"
$env:LOCALAPPDATA = Join-Path $StageDir "localappdata"
New-Item -ItemType Directory -Force -Path $env:APPDATA, $env:LOCALAPPDATA | Out-Null
$ollamaRunner = Join-Path $StageDir "run-ollama-test.ps1"
$ollamaShare = Join-Path $StageDir "ollama-share"
$ollamaArgs = @(
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", $ollamaRunner,
"-Mock",
"-GatewayPath", $GatewayPath,
"-CliPath", $CliPath,
"-ShareDir", $ollamaShare,
"-OllamaPort", "$ApiPort",
"-Model", "openshell-ci-mock",
"-Prompt", "Return the CI mock response.",
"-KeepArtifacts"
)
$output = & powershell.exe @ollamaArgs 2>&1
$exitCode = $LASTEXITCODE
$output | ForEach-Object { Write-Host $_ }
if ($exitCode -ne 0) {
throw "shipped Ollama demo failed in mock mode (exit $exitCode)"
}
$ollamaResultDir = Get-ChildItem -LiteralPath $StageDir -Directory -Filter "results-ollama-*" |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
if ($null -eq $ollamaResultDir) { throw "Ollama demo did not produce a results directory" }
$ollamaSummary = Get-Content -LiteralPath (Join-Path $ollamaResultDir.FullName "summary.txt") -Raw
if ($ollamaSummary -notmatch '(?m)^verdict=PASS\s*$' -or $ollamaSummary -notmatch '(?m)^mode=mock-wiring\s*$') {
throw "Ollama demo summary did not report a mock-wiring PASS: $ollamaSummary"
}
$ollamaResponse = Get-Content -LiteralPath (Join-Path $ollamaResultDir.FullName "ollama-response.json") -Raw | ConvertFrom-Json
if ($ollamaResponse.response -ne "Hello from the CI mock.") {
throw "Ollama demo did not retain the expected mock completion"
}
$env:NV_API_KEY = $MockToken
$cloudRunner = Join-Path $StageDir "run-inference-test.ps1"
$cloudShare = Join-Path $StageDir "cloud-share"
$cloudApiUrl = "http://127.0.0.1:$ApiPort/v1/chat/completions"
$cloudArgs = @(
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", $cloudRunner,
"-Mock",
"-GatewayPath", $GatewayPath,
"-CliPath", $CliPath,
"-ShareDir", $cloudShare,
"-ApiUrl", $cloudApiUrl,
"-Model", "openshell-ci-mock",
"-Prompt", "Return the CI mock response.",
"-KeepArtifacts"
)
$output = & powershell.exe @cloudArgs 2>&1
$exitCode = $LASTEXITCODE
$output | ForEach-Object { Write-Host $_ }
if ($exitCode -ne 0) {
throw "shipped cloud-inference demo failed in mock mode (exit $exitCode)"
}
$cloudResultDir = Get-ChildItem -LiteralPath $StageDir -Directory -Filter "results-inference-*" |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
if ($null -eq $cloudResultDir) { throw "cloud-inference demo did not produce a results directory" }
$cloudSummary = Get-Content -LiteralPath (Join-Path $cloudResultDir.FullName "summary.txt") -Raw
if ($cloudSummary -notmatch '(?m)^verdict=PASS\s*$' -or $cloudSummary -notmatch '(?m)^mode=mock-wiring\s*$') {
throw "cloud-inference demo summary did not report a mock-wiring PASS: $cloudSummary"
}
$cloudResponse = Get-Content -LiteralPath (Join-Path $cloudResultDir.FullName "inference-response.json") -Raw | ConvertFrom-Json
if ($cloudResponse.choices[0].message.content -ne "Hello from the CI mock.") {
throw "cloud-inference demo did not retain the expected mock completion"
}
$requestLines = @([System.IO.File]::ReadAllLines($RequestLog))
$expectedRequests = @(
@{ Pattern = '^GET /api/tags HTTP/\S+ authorization=absent$'; Count = 2; Description = 'host prerequisite and sandbox Ollama tag requests' },
@{ Pattern = '^POST /api/generate HTTP/\S+ authorization=absent$'; Count = 1; Description = 'sandbox Ollama generation request' },
@{ Pattern = '^POST /v1/chat/completions HTTP/\S+ authorization=synthetic$'; Count = 1; Description = 'sandbox cloud-inference request with the synthetic CI credential' }
)
foreach ($expected in $expectedRequests) {
$actualCount = @($requestLines | Where-Object { $_ -match $expected.Pattern }).Count
if ($actualCount -ne $expected.Count) {
throw "mock inference server observed $actualCount $($expected.Description); expected $($expected.Count): $($requestLines -join '; ')"
}
}
$passed = $true
Write-Host "MXC inference examples mock E2E passed for $Target"
} catch {
Write-Host "MXC inference examples mock E2E failed: $($_.Exception.Message)" -ForegroundColor Red
Get-ChildItem -LiteralPath $StageDir -File -Recurse -Include "*.log", "summary.txt" -ErrorAction SilentlyContinue |
ForEach-Object {
Write-Host "--- $($_.FullName) ---"
Get-Content -LiteralPath $_.FullName -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_ }
}
throw
} finally {
$env:APPDATA = $oldAppData
$env:LOCALAPPDATA = $oldLocalAppData
if ([string]::IsNullOrWhiteSpace($oldNvApiKey)) {
Remove-Item Env:NV_API_KEY -ErrorAction SilentlyContinue
} else {
$env:NV_API_KEY = $oldNvApiKey
}
if ($serverProcess -and -not $serverProcess.HasExited) {
Stop-Process -Id $serverProcess.Id -Force -ErrorAction SilentlyContinue
try { [void] $serverProcess.WaitForExit(5000) } catch {}
}
if ($passed -and -not $KeepArtifacts -and (Test-Path -LiteralPath $StageDir)) {
Remove-Item -LiteralPath $StageDir -Recurse -Force
} else {
Write-Host "MXC inference example artifacts: $StageDir"
}
}