diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c02cbc..fc1a6a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,7 +153,7 @@ jobs: - name: Verify Zig installation run: zig version - - name: Generate Code using OpenAPI v3.0 Petstore example - run: | - zig build run-generate - zig run generated/main.zig + - name: Run smoke tests + shell: pwsh + timeout-minutes: 15 + run: ./test/smoke-tests.ps1 diff --git a/README.md b/README.md index 63c2db6..356fc75 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,16 @@ zig build-exe generated/main.zig -fno-emit-bin zig build test-package ``` +### Smoke tests + +The pull request workflow runs `test/smoke-tests.ps1` to generate and compile Zig clients from each supported JSON OpenAPI example under `openapi/`. The script prints the current specification before each operation, making it easy to identify which input file caused a failure. Expect the full suite to take several minutes because it builds the CLI and compiles generated code for every supported example. + +```powershell +pwsh ./test/smoke-tests.ps1 +``` + +YAML examples are skipped by default because YAML input is not implemented yet. Use `-IncludeYaml` when YAML generation support is added. + ## Using as a Library openapi2zig can also be used as a Zig library for parsing OpenAPI/Swagger specifications and generating code programmatically. diff --git a/test/smoke-tests.ps1 b/test/smoke-tests.ps1 new file mode 100644 index 0000000..9608b08 --- /dev/null +++ b/test/smoke-tests.ps1 @@ -0,0 +1,178 @@ +param ( + [Parameter(Mandatory=$false)] + [switch] + $UseInstalled = $false, + + [Parameter(Mandatory=$false)] + [switch] + $IncludeYaml = $false, + + [Parameter(Mandatory=$false)] + [string] + $OutputRoot = "./.zig-cache/smoke-tests" +) + +$ErrorActionPreference = "Stop" + +function Invoke-NativeCommand { + param ( + [Parameter(Mandatory=$true)] + [string] + $FilePath, + + [Parameter(Mandatory=$false)] + [string[]] + $Arguments = @(), + + [Parameter(Mandatory=$false)] + [string] + $FailureMessage = "Native command failed" + ) + + if (-not (Get-Command $FilePath -ErrorAction SilentlyContinue)) { + throw "$FailureMessage. Command not found: $FilePath" + } + + Write-Host "> $FilePath $($Arguments -join ' ')" + & $FilePath @Arguments + if ($LASTEXITCODE -ne 0) { + throw "$FailureMessage (exit code: $LASTEXITCODE)" + } +} + +function Test-IsOpenApiExampleSpec { + param ( + [Parameter(Mandatory=$true)] + [System.IO.FileInfo] + $File, + + [Parameter(Mandatory=$true)] + [string[]] + $Extensions + ) + + # The json-schema folder contains reference schemas, not API examples that openapi2zig can generate clients from. + $normalizedPath = $File.FullName.Replace('\', '/') + return $Extensions -contains $File.Extension.ToLowerInvariant() -and + $normalizedPath -notlike "*/json-schema/*" +} + +function Get-OpenApiSpecs { + param ( + [Parameter(Mandatory=$true)] + [string] + $OpenApiRoot, + + [Parameter(Mandatory=$true)] + [bool] + $IncludeYamlSpecs + ) + + $extensions = @(".json") + if ($IncludeYamlSpecs) { + $extensions += @(".yaml", ".yml") + } + + Get-ChildItem -Path $OpenApiRoot -Recurse -File | + Where-Object { Test-IsOpenApiExampleSpec -File $_ -Extensions $extensions } | + Sort-Object FullName +} + +function Get-SafeName { + param ( + [Parameter(Mandatory=$true)] + [string] + $Value + ) + + return ($Value -replace '[^A-Za-z0-9_]', '_') +} + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$openApiRoot = Join-Path $repoRoot "openapi" +$outputRootPath = Join-Path $repoRoot $OutputRoot + +if (-not (Test-Path -Path $openApiRoot -PathType Container)) { + throw "OpenAPI examples directory not found: $openApiRoot" +} + +Push-Location $repoRoot +try { + Invoke-NativeCommand -FilePath "zig" -Arguments @("version") -FailureMessage "Zig is required to run smoke tests" + + $openapi2zig = "openapi2zig" + if (-not $UseInstalled) { + Write-Host "`n=== Building openapi2zig ===`n" + Write-Host "Initial Zig builds can take 2-5 minutes, and generated code compilation across all specs can take several more minutes; do not cancel these steps." + Invoke-NativeCommand -FilePath "zig" -Arguments @("build", "-Doptimize=Debug") -FailureMessage "Failed to build openapi2zig" + + $isWindowsPlatform = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT + $binaryName = if ($isWindowsPlatform) { "openapi2zig.exe" } else { "openapi2zig" } + $openapi2zig = Join-Path $repoRoot (Join-Path "zig-out/bin" $binaryName) + if (-not (Test-Path -Path $openapi2zig -PathType Leaf)) { + throw "Built openapi2zig binary not found: $openapi2zig" + } + } + + if (Test-Path -Path $outputRootPath) { + Remove-Item -Path $outputRootPath -Recurse -Force + } + New-Item -Path $outputRootPath -ItemType Directory -Force | Out-Null + + $allSpecs = @(Get-OpenApiSpecs -OpenApiRoot $openApiRoot -IncludeYamlSpecs $true) + $specs = if ($IncludeYaml) { + $allSpecs + } else { + @($allSpecs | Where-Object { $_.Extension.ToLowerInvariant() -eq ".json" }) + } + if ($specs.Count -eq 0) { + throw "No OpenAPI specification examples found under $openApiRoot" + } + + if (-not $IncludeYaml) { + $yamlCount = @($allSpecs | Where-Object { $_.Extension.ToLowerInvariant() -in @(".yaml", ".yml") }).Count + if ($yamlCount -gt 0) { + Write-Host "Skipping $yamlCount YAML specs because YAML input has not been implemented by openapi2zig yet. Use -IncludeYaml to verify YAML support when it is available." + } + } + + Write-Host "`n=== Smoke testing $($specs.Count) OpenAPI specification examples ===`n" + Write-Host "Generated code compilation runs once per specification and can take several minutes across the full suite; CI allows up to 15 minutes for this job." + + for ($i = 0; $i -lt $specs.Count; $i++) { + $spec = $specs[$i] + $relativeSpec = [System.IO.Path]::GetRelativePath($repoRoot, $spec.FullName) + $safeName = Get-SafeName -Value ([System.IO.Path]::ChangeExtension($relativeSpec, $null)) + $caseOutputRoot = Join-Path $outputRootPath $safeName + $generatedFile = Join-Path $caseOutputRoot "client.zig" + $compileFile = Join-Path $caseOutputRoot "compile.zig" + + New-Item -Path $caseOutputRoot -ItemType Directory -Force | Out-Null + + Write-Host "`n[$($i + 1)/$($specs.Count)] Generating $relativeSpec" + Invoke-NativeCommand ` + -FilePath $openapi2zig ` + -Arguments @("generate", "-i", $spec.FullName, "-o", $generatedFile) ` + -FailureMessage "Generation failed for $relativeSpec" + + @' +const std = @import("std"); +const generated = @import("client.zig"); + +test "generated client compiles" { + std.testing.refAllDecls(generated); +} +'@ | Set-Content -Path $compileFile -Encoding utf8NoBOM + + Write-Host "[$($i + 1)/$($specs.Count)] Compiling generated Zig for $relativeSpec" + Invoke-NativeCommand ` + -FilePath "zig" ` + -Arguments @("test", $compileFile) ` + -FailureMessage "Generated Zig failed to compile for $relativeSpec" + } + + Write-Host "`nSmoke tests passed for $($specs.Count) OpenAPI specification examples." +} +finally { + Pop-Location +}