param( [Parameter(Mandatory)][ValidateSet('EditMode','PlayMode','AndroidBuild','AndroidSmoke')][string]$Phase, [ValidateSet('FirstSlice','Composition','Persistence','Content','Final')][string]$Milestone = 'Final' ) $ErrorActionPreference = 'Stop' $projectRoot = Split-Path $PSScriptRoot -Parent Set-Location -LiteralPath $projectRoot $evidenceDir = Join-Path $projectRoot "artifacts/verification/$Milestone" New-Item -ItemType Directory -Path $evidenceDir -Force | Out-Null $phaseName = $Phase.ToLowerInvariant() $logPath = Join-Path $evidenceDir "$phaseName.log" $record = [ordered]@{ phase = $Phase; milestone = $Milestone; startedUtc = [DateTime]::UtcNow.ToString('o') sourceCommit = (& git rev-parse HEAD); sourceDirty = [bool](& git status --porcelain) command = $null; exitCode = $null; status = 'not-run'; log = $logPath } try { $record.editor = (Get-Content 'ProjectSettings/ProjectVersion.txt' | Select-String '^m_EditorVersion:').ToString().Split(':',2)[1].Trim() $record.packages = (Get-Content 'Packages/manifest.json' -Raw | ConvertFrom-Json).dependencies $apkPath = Join-Path $projectRoot "artifacts/android/$Milestone/MagnetismHeroes.apk" if ($Phase -in @('EditMode','PlayMode')) { $xmlPath = Join-Path $evidenceDir "$phaseName.xml" if (Test-Path -LiteralPath $xmlPath) { Remove-Item -LiteralPath $xmlPath } $cliArgs = @('test', $projectRoot, '--mode', $Phase, '--output', $xmlPath, '--timeout', '900', '--', '-logFile', $logPath) $record.command = 'unity ' + (($cliArgs | ForEach-Object { '"' + $_ + '"' }) -join ' ') & unity @cliArgs $record.exitCode = $LASTEXITCODE if (!(Test-Path -LiteralPath $xmlPath)) { throw "Unity $Phase exited $($record.exitCode) without a test report. See $logPath" } [xml]$report = Get-Content -LiteralPath $xmlPath -Raw $cases = @($report.SelectNodes('//test-case')) $record.total = $cases.Count $record.passed = @($cases | Where-Object { $_.result -eq 'Passed' }).Count $record.failed = @($cases | Where-Object { $_.result -eq 'Failed' }).Count $record.notPassed = $record.total - $record.passed if ($record.exitCode -ne 0) { throw "Unity $Phase exited $($record.exitCode). See $logPath" } if ($record.total -eq 0 -or $record.notPassed -ne 0) { throw "Test report contains $($record.total) tests and $($record.notPassed) non-passing cases." } } elseif ($Phase -eq 'AndroidBuild') { New-Item -ItemType Directory -Path (Split-Path $apkPath -Parent) -Force | Out-Null if (Test-Path -LiteralPath $apkPath) { Remove-Item -LiteralPath $apkPath } $env:MAGNETISM_APK_PATH = $apkPath $cliArgs = @('run', $projectRoot, '--timeout', '1800', '--log-file', $logPath, '--no-tail', '--', '-buildTarget', 'Android', '-executeMethod', 'MagnetismHeroes.Editor.ProjectBootstrap.BuildAndroid') $record.command = 'unity ' + (($cliArgs | ForEach-Object { '"' + $_ + '"' }) -join ' ') & unity @cliArgs $record.exitCode = $LASTEXITCODE if ($LASTEXITCODE -ne 0 -or !(Test-Path -LiteralPath $apkPath)) { throw 'Android build failed or produced no APK.' } $record.apk = $apkPath $record.apkSha256 = (Get-FileHash -LiteralPath $apkPath -Algorithm SHA256).Hash if ($Milestone -eq 'Final') { Copy-Item -LiteralPath $apkPath -Destination (Join-Path $projectRoot 'artifacts/android/MagnetismHeroes.apk') } } else { $smokeScript = Join-Path $PSScriptRoot 'android/smoke.ps1' if (!(Test-Path -LiteralPath $smokeScript)) { throw 'Android smoke verification has not been implemented yet.' } $record.command = "pwsh -NoProfile -File tools/android/smoke.ps1 -ApkPath $apkPath -Milestone $Milestone -EvidenceDir $evidenceDir" & pwsh -NoProfile -File $smokeScript -ApkPath $apkPath -Milestone $Milestone -EvidenceDir $evidenceDir $record.exitCode = $LASTEXITCODE if ($LASTEXITCODE -ne 0) { throw "Android smoke verification exited $LASTEXITCODE." } } $record.status = 'passed' } catch { $record.status = 'failed' $record.error = $_.Exception.Message Write-Host $record.error -ForegroundColor Red } finally { $record.finishedUtc = [DateTime]::UtcNow.ToString('o') $record | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $evidenceDir "$phaseName-evidence.json") -Encoding utf8 } if ($record.status -ne 'passed') { exit 1 } Write-Host "$Phase / $Milestone passed. Evidence: $evidenceDir" exit 0