128 lines
9.2 KiB
PowerShell
128 lines
9.2 KiB
PowerShell
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
|
|
& (Join-Path $PSScriptRoot 'android/inspect-apk.ps1') -ApkPath $apkPath -EvidenceDir $evidenceDir -SourceCommit $record.sourceCommit -SourceDirty $record.sourceDirty -MetadataName 'apk-metadata.json'
|
|
$record.apkMetadata = Join-Path $evidenceDir 'apk-metadata.json'
|
|
# Unity rewrites these files with LF; restore only when Git proves their content is unchanged.
|
|
$record.normalizedUnchangedSettings = @()
|
|
$record.settingsContentChecks = @()
|
|
if (!$record.sourceDirty) {
|
|
foreach ($settingsPath in @('ProjectSettings/GraphicsSettings.asset','ProjectSettings/ProjectAuditorSettings.asset','ProjectSettings/ProjectSettings.asset','ProjectSettings/ShaderGraphSettings.asset','Assets/Settings/Mobile_RPAsset.asset','Assets/Settings/UniversalRenderPipelineGlobalSettings.asset')) {
|
|
$workingBlob = & git hash-object --path=$settingsPath -- $settingsPath
|
|
$workingExit = $LASTEXITCODE
|
|
$sourceBlob = & git rev-parse "HEAD:$settingsPath"
|
|
$sourceExit = $LASTEXITCODE
|
|
$record.settingsContentChecks += @{ path=$settingsPath; workingBlob=$workingBlob; sourceBlob=$sourceBlob; hashObjectExit=$workingExit; revParseExit=$sourceExit }
|
|
if ($workingExit -eq 0 -and $sourceExit -eq 0 -and $workingBlob -eq $sourceBlob -and [bool](& git status --porcelain -- $settingsPath)) {
|
|
& git restore --source=HEAD --worktree -- $settingsPath
|
|
if ($LASTEXITCODE -ne 0) { throw "Cannot restore unchanged generated setting: $settingsPath" }
|
|
$record.normalizedUnchangedSettings += $settingsPath
|
|
}
|
|
}
|
|
}
|
|
$record.sourceCommitEnd = (& git rev-parse HEAD)
|
|
& git update-index --refresh | Out-Null
|
|
$record.sourceDirtyEnd = [bool](& git status --porcelain)
|
|
$record.trackedDiffEnd = @(& git diff --name-only HEAD)
|
|
if ($record.sourceDirty -or $record.sourceDirtyEnd -or $record.sourceCommitEnd -ne $record.sourceCommit) { throw 'Source changed during the Android build or is dirty.' }
|
|
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 *> $logPath
|
|
$record.exitCode = $LASTEXITCODE
|
|
Get-Content -LiteralPath $logPath | Select-Object -Last 5 | Write-Host
|
|
if ($LASTEXITCODE -ne 0) { throw "Android smoke verification exited $LASTEXITCODE." }
|
|
}
|
|
# Edit/Play tests may strip this Editor-generated runtime list. Restore only that exact
|
|
# transformation after proving every other serialized byte (apart from CRLF) is unchanged.
|
|
if ($Phase -in @('EditMode','PlayMode') -and !$record.sourceDirty) {
|
|
$record.testSourceDirtyBeforeNormalization = [bool](& git status --porcelain)
|
|
$record.testGeneratedSettings = @()
|
|
foreach ($settingsPath in @('Assets/Settings/UniversalRenderPipelineGlobalSettings.asset','ProjectSettings/EditorBuildSettings.asset','ProjectSettings/ProjectAuditorSettings.asset','ProjectSettings/ShaderGraphSettings.asset')) {
|
|
$workingBlob = & git hash-object --path=$settingsPath -- $settingsPath
|
|
$sourceBlob = & git rev-parse "HEAD:$settingsPath"
|
|
$same = $workingBlob -eq $sourceBlob
|
|
$runtimeListStripped = $false
|
|
if (!$same -and $settingsPath -eq 'Assets/Settings/UniversalRenderPipelineGlobalSettings.asset') {
|
|
$sourceText = ((& git show "HEAD:$settingsPath") -join "`n") + "`n"
|
|
$workingText = (Get-Content -LiteralPath $settingsPath -Raw).Replace("`r`n","`n")
|
|
$pattern = '(?m)^ m_RuntimeSettings:\n m_List:\n(?: - rid: \d+\n)+'
|
|
$expected = [regex]::Replace($sourceText,$pattern," m_RuntimeSettings:`n m_List: []`n")
|
|
$runtimeListStripped = $expected -ne $sourceText -and $expected -ceq $workingText
|
|
if ($runtimeListStripped) { (& git diff -- $settingsPath) | Set-Content -LiteralPath (Join-Path $evidenceDir "$phaseName-generated-runtime-list.diff") -Encoding utf8 }
|
|
}
|
|
if ($same -or $runtimeListStripped) {
|
|
if ([bool](& git status --porcelain -- $settingsPath)) {
|
|
& git restore --source=HEAD --worktree -- $settingsPath
|
|
if ($LASTEXITCODE -ne 0) { throw "Cannot normalize proven generated setting: $settingsPath" }
|
|
}
|
|
}
|
|
$record.testGeneratedSettings += @{path=$settingsPath;workingBlob=$workingBlob;sourceBlob=$sourceBlob;identicalContent=$same;exactRuntimeListStripping=$runtimeListStripped}
|
|
}
|
|
}
|
|
$record.sourceCommitEnd = (& git rev-parse HEAD)
|
|
& git update-index --refresh | Out-Null
|
|
$record.sourceDirtyEnd = [bool](& git status --porcelain)
|
|
$record.trackedDiffEnd = @(& git diff --name-only HEAD)
|
|
if ($record.sourceDirty -or $record.sourceDirtyEnd -or $record.sourceCommitEnd -ne $record.sourceCommit) { throw 'Source changed during verification or is dirty.' }
|
|
$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
|