Strengthen PR test trust baseline #81
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI — dotnet | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| branches: [main, master] | |
| jobs: | |
| build-and-test: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Restore | |
| run: dotnet restore | |
| - name: Build | |
| run: dotnet build --no-restore -c Release | |
| - name: Test (unit + MCP, excluding Integration) | |
| run: | | |
| dotnet test tests/Unityctl.Shared.Tests --no-build -c Release --verbosity normal | |
| dotnet test tests/Unityctl.Core.Tests --no-build -c Release --verbosity normal | |
| dotnet test tests/Unityctl.Cli.Tests --no-build -c Release --verbosity normal | |
| dotnet test tests/Unityctl.Mcp.Tests --no-build -c Release --verbosity normal | |
| - name: Publish CLI | |
| run: > | |
| dotnet publish src/Unityctl.Cli/Unityctl.Cli.csproj | |
| -c Release | |
| --self-contained false | |
| -o publish/cli | |
| - name: Pack CLI tool | |
| run: > | |
| dotnet pack src/Unityctl.Cli/Unityctl.Cli.csproj | |
| -c Release | |
| --no-build | |
| -o publish/packages | |
| - name: Smoke published CLI (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| function Invoke-UnityctlSmoke { | |
| param( | |
| [string]$Exe, | |
| [string[]]$Arguments, | |
| [int[]]$AllowedExitCodes, | |
| [string]$OutputPath, | |
| [string]$Name | |
| ) | |
| $psi = [System.Diagnostics.ProcessStartInfo]::new() | |
| $psi.FileName = $Exe | |
| foreach ($argument in $Arguments) { | |
| [void]$psi.ArgumentList.Add($argument) | |
| } | |
| $psi.RedirectStandardOutput = $true | |
| $psi.RedirectStandardError = $true | |
| $psi.UseShellExecute = $false | |
| $process = [System.Diagnostics.Process]::Start($psi) | |
| $stdout = $process.StandardOutput.ReadToEnd() | |
| $stderr = $process.StandardError.ReadToEnd() | |
| $process.WaitForExit() | |
| if ($OutputPath) { | |
| $stdout | Set-Content -Path $OutputPath -Encoding utf8 | |
| } elseif ($stdout) { | |
| Write-Host $stdout | |
| } | |
| if ($stderr) { | |
| Write-Host $stderr | |
| } | |
| if ($AllowedExitCodes -notcontains $process.ExitCode) { | |
| throw "$Name exited with unexpected code $($process.ExitCode)" | |
| } | |
| } | |
| Invoke-UnityctlSmoke -Exe "./publish/cli/unityctl.exe" -Arguments @("--help") -AllowedExitCodes @(0, 1) -Name "published CLI help smoke" | |
| Invoke-UnityctlSmoke -Exe "./publish/cli/unityctl.exe" -Arguments @("schema", "--format", "json") -AllowedExitCodes @(0) -OutputPath "publish/schema.json" -Name "published CLI schema smoke" | |
| Invoke-UnityctlSmoke -Exe "./publish/cli/unityctl.exe" -Arguments @("tools", "--json") -AllowedExitCodes @(0) -OutputPath "publish/tools.json" -Name "published CLI tools smoke" | |
| $schema = Get-Content publish/schema.json -Raw | ConvertFrom-Json | |
| $tools = Get-Content publish/tools.json -Raw | ConvertFrom-Json | |
| $schemaNames = @($schema.commands | ForEach-Object { $_.name } | Sort-Object) | |
| $toolNames = @($tools | ForEach-Object { $_.name } | Sort-Object) | |
| if (($schemaNames -join "`n") -ne ($toolNames -join "`n")) { | |
| throw "schema and tools command names drifted" | |
| } | |
| foreach ($required in @("doctor", "check", "workflow-verify", "scene-snapshot", "scene-diff", "player-settings")) { | |
| if ($schemaNames -notcontains $required) { | |
| throw "published CLI schema is missing required command '$required'" | |
| } | |
| } | |
| New-Item -ItemType Directory -Force publish/smoke-project/Packages, publish/smoke-project/ProjectSettings | Out-Null | |
| '{"dependencies":{}}' | Set-Content -Path publish/smoke-project/Packages/manifest.json -Encoding utf8 | |
| 'm_EditorVersion: 6000.0.64f1' | Set-Content -Path publish/smoke-project/ProjectSettings/ProjectVersion.txt -Encoding utf8 | |
| Invoke-UnityctlSmoke -Exe "./publish/cli/unityctl.exe" -Arguments @("doctor", "--project", "publish/smoke-project", "--json") -AllowedExitCodes @(0, 1) -OutputPath "publish/doctor.json" -Name "doctor smoke" | |
| $doctor = Get-Content publish/doctor.json -Raw | ConvertFrom-Json | |
| foreach ($property in @("editor", "plugin", "ipc", "summary")) { | |
| if (-not $doctor.PSObject.Properties[$property]) { | |
| throw "doctor JSON is missing '$property'" | |
| } | |
| } | |
| Invoke-UnityctlSmoke -Exe "./publish/cli/unityctl.exe" -Arguments @("check", "--project", "publish/smoke-project", "--type", "compile", "--json") -AllowedExitCodes @(0, 1) -OutputPath "publish/check.json" -Name "check smoke" | |
| $check = Get-Content publish/check.json -Raw | ConvertFrom-Json | |
| foreach ($property in @("statusCode", "success", "message")) { | |
| if (-not $check.PSObject.Properties[$property]) { | |
| throw "check JSON is missing '$property'" | |
| } | |
| } | |
| '{"name":"smoke","steps":[{"id":"validate","kind":"projectValidate"}]}' | Set-Content -Path publish/smoke-verify.json -Encoding utf8 | |
| Invoke-UnityctlSmoke -Exe "./publish/cli/unityctl.exe" -Arguments @("workflow", "verify", "--file", "publish/smoke-verify.json", "--project", "publish/smoke-project", "--artifacts-dir", "publish/smoke-artifacts", "--json") -AllowedExitCodes @(0, 1) -OutputPath "publish/workflow.json" -Name "workflow verify smoke" | |
| $workflow = Get-Content publish/workflow.json -Raw | ConvertFrom-Json | |
| foreach ($property in @("passed", "summary", "steps", "artifacts")) { | |
| if (-not $workflow.PSObject.Properties[$property]) { | |
| throw "workflow verify JSON is missing '$property'" | |
| } | |
| } | |
| - name: Smoke local dotnet tool install (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| function Invoke-UnityctlSmoke { | |
| param( | |
| [string]$Exe, | |
| [string[]]$Arguments, | |
| [int[]]$AllowedExitCodes, | |
| [string]$OutputPath, | |
| [string]$Name | |
| ) | |
| $psi = [System.Diagnostics.ProcessStartInfo]::new() | |
| $psi.FileName = $Exe | |
| foreach ($argument in $Arguments) { | |
| [void]$psi.ArgumentList.Add($argument) | |
| } | |
| $psi.RedirectStandardOutput = $true | |
| $psi.RedirectStandardError = $true | |
| $psi.UseShellExecute = $false | |
| $process = [System.Diagnostics.Process]::Start($psi) | |
| $stdout = $process.StandardOutput.ReadToEnd() | |
| $stderr = $process.StandardError.ReadToEnd() | |
| $process.WaitForExit() | |
| if ($OutputPath) { | |
| $stdout | Set-Content -Path $OutputPath -Encoding utf8 | |
| } elseif ($stdout) { | |
| Write-Host $stdout | |
| } | |
| if ($stderr) { | |
| Write-Host $stderr | |
| } | |
| if ($AllowedExitCodes -notcontains $process.ExitCode) { | |
| throw "$Name exited with unexpected code $($process.ExitCode)" | |
| } | |
| } | |
| $package = Get-ChildItem publish/packages/unityctl.*.nupkg | Select-Object -First 1 | |
| if (-not ($package.BaseName -match '^unityctl\.(.+)$')) { | |
| throw "Could not infer unityctl package version from $($package.Name)" | |
| } | |
| $version = $Matches[1] | |
| dotnet tool install unityctl --tool-path publish/tool --add-source publish/packages --version $version --no-cache | |
| Invoke-UnityctlSmoke -Exe "./publish/tool/unityctl.exe" -Arguments @("--help") -AllowedExitCodes @(0, 1) -Name "installed tool help smoke" | |
| Invoke-UnityctlSmoke -Exe "./publish/tool/unityctl.exe" -Arguments @("schema", "--format", "json") -AllowedExitCodes @(0) -OutputPath "publish/tool-schema.json" -Name "installed tool schema smoke" | |
| Invoke-UnityctlSmoke -Exe "./publish/tool/unityctl.exe" -Arguments @("tools", "--json") -AllowedExitCodes @(0) -OutputPath "publish/tool-tools.json" -Name "installed tool tools smoke" | |
| Get-Content publish/tool-schema.json -Raw | ConvertFrom-Json | Out-Null | |
| Get-Content publish/tool-tools.json -Raw | ConvertFrom-Json | Out-Null | |
| Invoke-UnityctlSmoke -Exe "./publish/tool/unityctl.exe" -Arguments @("doctor", "--project", "publish/smoke-project", "--json") -AllowedExitCodes @(0, 1) -OutputPath "publish/tool-doctor.json" -Name "installed tool doctor smoke" | |
| Get-Content publish/tool-doctor.json -Raw | ConvertFrom-Json | Out-Null | |
| Invoke-UnityctlSmoke -Exe "./publish/tool/unityctl.exe" -Arguments @("check", "--project", "publish/smoke-project", "--type", "compile", "--json") -AllowedExitCodes @(0, 1) -OutputPath "publish/tool-check.json" -Name "installed tool check smoke" | |
| Get-Content publish/tool-check.json -Raw | ConvertFrom-Json | Out-Null | |
| Invoke-UnityctlSmoke -Exe "./publish/tool/unityctl.exe" -Arguments @("workflow", "verify", "--file", "publish/smoke-verify.json", "--project", "publish/smoke-project", "--artifacts-dir", "publish/smoke-artifacts-tool", "--json") -AllowedExitCodes @(0, 1) -OutputPath "publish/tool-workflow.json" -Name "installed tool workflow verify smoke" | |
| Get-Content publish/tool-workflow.json -Raw | ConvertFrom-Json | Out-Null | |
| - name: Smoke published CLI (Unix) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| chmod +x ./publish/cli/unityctl | |
| ./publish/cli/unityctl --help | |
| ./publish/cli/unityctl schema --format json > publish/schema.json | |
| ./publish/cli/unityctl tools --json > publish/tools.json | |
| python3 - <<'PY' | |
| import json | |
| with open("publish/schema.json", encoding="utf-8") as f: | |
| schema = json.load(f) | |
| with open("publish/tools.json", encoding="utf-8") as f: | |
| tools = json.load(f) | |
| schema_names = sorted(command["name"] for command in schema["commands"]) | |
| tool_names = sorted(command["name"] for command in tools) | |
| if schema_names != tool_names: | |
| raise SystemExit("schema and tools command names drifted") | |
| for required in ("doctor", "check", "workflow-verify", "scene-snapshot", "scene-diff", "player-settings"): | |
| if required not in schema_names: | |
| raise SystemExit(f"published CLI schema is missing required command '{required}'") | |
| PY | |
| mkdir -p publish/smoke-project/Packages publish/smoke-project/ProjectSettings | |
| printf '{"dependencies":{}}\n' > publish/smoke-project/Packages/manifest.json | |
| printf 'm_EditorVersion: 6000.0.64f1\n' > publish/smoke-project/ProjectSettings/ProjectVersion.txt | |
| set +e | |
| doctor_json="$(./publish/cli/unityctl doctor --project publish/smoke-project --json)" | |
| doctor_exit=$? | |
| set -e | |
| if [ "$doctor_exit" -ne 0 ] && [ "$doctor_exit" -ne 1 ]; then | |
| echo "doctor smoke exited with unexpected code $doctor_exit" >&2 | |
| exit 1 | |
| fi | |
| DOCTOR_JSON="$doctor_json" python3 - <<'PY' | |
| import json | |
| import os | |
| doctor = json.loads(os.environ["DOCTOR_JSON"]) | |
| for key in ("editor", "plugin", "ipc", "summary"): | |
| if key not in doctor: | |
| raise SystemExit(f"doctor JSON is missing '{key}'") | |
| PY | |
| set +e | |
| check_json="$(./publish/cli/unityctl check --project publish/smoke-project --type compile --json)" | |
| check_exit=$? | |
| set -e | |
| if [ "$check_exit" -ne 0 ] && [ "$check_exit" -ne 1 ]; then | |
| echo "check smoke exited with unexpected code $check_exit" >&2 | |
| exit 1 | |
| fi | |
| CHECK_JSON="$check_json" python3 - <<'PY' | |
| import json | |
| import os | |
| check = json.loads(os.environ["CHECK_JSON"]) | |
| for key in ("statusCode", "success", "message"): | |
| if key not in check: | |
| raise SystemExit(f"check JSON is missing '{key}'") | |
| PY | |
| printf '{"name":"smoke","steps":[{"id":"validate","kind":"projectValidate"}]}\n' > publish/smoke-verify.json | |
| set +e | |
| workflow_json="$(./publish/cli/unityctl workflow verify --file publish/smoke-verify.json --project publish/smoke-project --artifacts-dir publish/smoke-artifacts --json)" | |
| workflow_exit=$? | |
| set -e | |
| if [ "$workflow_exit" -ne 0 ] && [ "$workflow_exit" -ne 1 ]; then | |
| echo "workflow verify smoke exited with unexpected code $workflow_exit" >&2 | |
| exit 1 | |
| fi | |
| WORKFLOW_JSON="$workflow_json" python3 - <<'PY' | |
| import json | |
| import os | |
| workflow = json.loads(os.environ["WORKFLOW_JSON"]) | |
| for key in ("passed", "summary", "steps", "artifacts"): | |
| if key not in workflow: | |
| raise SystemExit(f"workflow verify JSON is missing '{key}'") | |
| PY | |
| - name: Smoke local dotnet tool install (Unix) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| package="$(find publish/packages -maxdepth 1 -name 'unityctl.*.nupkg' | head -n 1)" | |
| version="$(basename "$package" .nupkg)" | |
| version="${version#unityctl.}" | |
| dotnet tool install unityctl --tool-path publish/tool --add-source publish/packages --version "$version" --no-cache | |
| ./publish/tool/unityctl --help >/dev/null | |
| ./publish/tool/unityctl schema --format json >/dev/null | |
| ./publish/tool/unityctl tools --json >/dev/null | |
| set +e | |
| doctor_json="$(./publish/tool/unityctl doctor --project publish/smoke-project --json)" | |
| doctor_exit=$? | |
| set -e | |
| if [ "$doctor_exit" -ne 0 ] && [ "$doctor_exit" -ne 1 ]; then | |
| echo "installed tool doctor smoke exited with unexpected code $doctor_exit" >&2 | |
| exit 1 | |
| fi | |
| DOCTOR_JSON="$doctor_json" python3 - <<'PY' | |
| import json | |
| import os | |
| json.loads(os.environ["DOCTOR_JSON"]) | |
| PY | |
| set +e | |
| check_json="$(./publish/tool/unityctl check --project publish/smoke-project --type compile --json)" | |
| check_exit=$? | |
| set -e | |
| if [ "$check_exit" -ne 0 ] && [ "$check_exit" -ne 1 ]; then | |
| echo "installed tool check smoke exited with unexpected code $check_exit" >&2 | |
| exit 1 | |
| fi | |
| CHECK_JSON="$check_json" python3 - <<'PY' | |
| import json | |
| import os | |
| json.loads(os.environ["CHECK_JSON"]) | |
| PY | |
| set +e | |
| workflow_json="$(./publish/tool/unityctl workflow verify --file publish/smoke-verify.json --project publish/smoke-project --artifacts-dir publish/smoke-artifacts-tool --json)" | |
| workflow_exit=$? | |
| set -e | |
| if [ "$workflow_exit" -ne 0 ] && [ "$workflow_exit" -ne 1 ]; then | |
| echo "installed tool workflow verify smoke exited with unexpected code $workflow_exit" >&2 | |
| exit 1 | |
| fi | |
| WORKFLOW_JSON="$workflow_json" python3 - <<'PY' | |
| import json | |
| import os | |
| json.loads(os.environ["WORKFLOW_JSON"]) | |
| PY |