60 lines
7.9 KiB
PowerShell
60 lines
7.9 KiB
PowerShell
param([Parameter(Mandatory)][string]$ApkPath,[Parameter(Mandatory)][string]$EvidenceDir,[string]$Serial='emulator-5554')
|
|
$ErrorActionPreference='Stop'
|
|
$EvidenceDir=(Resolve-Path -LiteralPath $EvidenceDir).Path
|
|
$projectRoot=Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
$checks=[Collections.Generic.List[object]]::new();$children=[Collections.Generic.List[object]]::new()
|
|
$record=[ordered]@{milestone='Final';sourceCommit=(& git rev-parse HEAD);sourceDirty=[bool](& git status --porcelain);apkSha256=(Get-FileHash -LiteralPath $ApkPath).Hash;status='failed';excludedAudio='SFX and BGM excluded by user2026-10-06; all44 approved Korean voices mandatory'}
|
|
function Check([string]$Id,[bool]$Passed,[string]$Actual){$checks.Add(@{id=$Id;passed=$Passed;actual=$Actual});if(!$Passed){throw "Final check failed: $Id ($Actual)"}}
|
|
try {
|
|
Check 'clean-frozen-source' (!$record.sourceDirty) $record.sourceCommit
|
|
& pwsh -NoProfile -File (Join-Path $PSScriptRoot 'test-final-syntax.ps1') -EvidenceDir (Join-Path $EvidenceDir 'ScriptSyntax')
|
|
Check 'native-script-parser-clean' ($LASTEXITCODE-eq0) 'All Android verification PowerShell files parsed without errors before native launch'
|
|
$build=Get-Content -LiteralPath (Join-Path $EvidenceDir 'androidbuild-evidence.json') -Raw|ConvertFrom-Json
|
|
Check 'exact-final-build' ($build.status-eq'passed'-and$build.exitCode-eq0-and!$build.sourceDirty-and!$build.sourceDirtyEnd-and$build.sourceCommit-eq$record.sourceCommit-and$build.sourceCommitEnd-eq$record.sourceCommit-and$build.apkSha256-eq$record.apkSha256) $record.apkSha256
|
|
$standard=Join-Path $projectRoot 'artifacts/android/MagnetismHeroes.apk'
|
|
Check 'final-and-deliverable-apk-identical' ((Get-FileHash -LiteralPath $standard).Hash-eq$record.apkSha256) $standard
|
|
$adoption=Get-Content -LiteralPath (Join-Path $projectRoot 'SourceAssets/Audio/Voice/active-adoption.json') -Raw|ConvertFrom-Json
|
|
Check 'approved44-active-voices' ($adoption.approvedCount-eq44-and$adoption.draftCount-eq0-and$adoption.clips.Count-eq44-and@($adoption.clips.id|Sort-Object -Unique).Count-eq44) 'All active voices approved, no draft'
|
|
& pwsh -NoProfile -File (Join-Path $PSScriptRoot 'inspect-voice-payload.ps1') -ApkPath $ApkPath -EvidenceDir (Join-Path $EvidenceDir 'VoicePayload')
|
|
$voiceExit=$LASTEXITCODE;$voicePayload=Get-Content -LiteralPath (Join-Path $EvidenceDir 'VoicePayload/apk-voice-payload.json') -Raw|ConvertFrom-Json
|
|
Check 'actual-apk44-voice-payloads' ($voiceExit-eq0-and$voicePayload.status-eq'passed'-and$voicePayload.apkSha256-eq$record.apkSha256-and$voicePayload.actualClipCount-eq44-and$voicePayload.checks.Count-eq44-and@($voicePayload.checks|Where-Object passed -ne $true).Count-eq0) 'Exact actual APK PCM bytes for all44, independent of incremental BuildReport'
|
|
& (Join-Path $projectRoot 'artifacts/tools/apk-inspector/Scripts/python.exe') -I (Join-Path $PSScriptRoot 'test-voice-payload-negative.py') --apk $ApkPath --project $projectRoot --evidence (Join-Path $EvidenceDir 'VoicePayloadNegative')
|
|
Check 'actual-apk-payload-negative-controls' ($LASTEXITCODE-eq0) 'Same inspector accepts actual44; rejects missing serialized clips and altered actual PCM copies'
|
|
foreach($clip in $adoption.clips){
|
|
foreach($entry in @(@{kind='source';path=(Join-Path $projectRoot ('SourceAssets/Audio/Voice/'+$clip.sourceFile));expected=$clip.sourceSha256},@{kind='edited';path=(Join-Path $projectRoot ('SourceAssets/Audio/Voice/'+$clip.editedFile));expected=$clip.editedSha256},@{kind='runtime';path=(Join-Path $projectRoot $clip.runtimeFile);expected=$clip.runtimeSha256})){
|
|
Check "voice-$($clip.id)-$($entry.kind)-exact-sha" ((Get-FileHash -LiteralPath $entry.path).Hash-eq$entry.expected) $entry.expected
|
|
}
|
|
Check "voice-$($clip.id)-packed-in-built-apk" (@($voicePayload.checks|Where-Object {$_.id-eq$clip.id-and$_.passed-and$_.runtimeSha256-eq$clip.runtimeSha256}).Count-eq1) 'Deserialized actual APK AudioClip PCM and format exactly match approved runtime WAV'
|
|
}
|
|
$zip=[IO.Compression.ZipFile]::OpenRead($ApkPath)
|
|
try {
|
|
foreach($name in @('NotoSansCJK-OFL.txt','TTS-Apache-2.0.txt','TTS-model-card.txt')){
|
|
$entry=$zip.GetEntry('assets/Notices/'+$name);Check "apk-notice-present-$name" ($null-ne$entry) $name
|
|
$stream=$entry.Open();try{$sha=[Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($stream))}finally{$stream.Dispose()}
|
|
Check "apk-notice-exact-bytes-$name" ($sha-eq(Get-FileHash -LiteralPath (Join-Path $projectRoot ('Assets/StreamingAssets/Notices/'+$name))).Hash) $sha
|
|
}
|
|
}finally{$zip.Dispose()}
|
|
foreach($suite in @(@{name='Fresh30';script='exercise-final-progression.ps1';report='android-final-progression-evidence.json'},@{name='FinalBoundary';script='exercise-final-boundaries.ps1';report='android-final-boundary-evidence.json'},@{name='RapidBoundary';script='exercise-final-rapid.ps1';report='android-final-rapid-evidence.json'},@{name='ContentBoundary';script='exercise-content.ps1';report='android-content-evidence.json'})){
|
|
$childDir=Join-Path $EvidenceDir $suite.name;New-Item -ItemType Directory -Path $childDir -Force|Out-Null
|
|
Copy-Item -LiteralPath (Join-Path $EvidenceDir 'androidbuild-evidence.json') -Destination (Join-Path $childDir 'androidbuild-evidence.json')
|
|
$log=Join-Path $childDir 'outer.log';$command="pwsh -NoProfile -File tools/android/$($suite.script) -ApkPath $ApkPath -EvidenceDir $childDir -Serial $Serial"
|
|
& pwsh -NoProfile -File (Join-Path $PSScriptRoot $suite.script) -ApkPath $ApkPath -EvidenceDir $childDir -Serial $Serial *> $log
|
|
$actualExit=$LASTEXITCODE;$child=Get-Content -LiteralPath (Join-Path $childDir $suite.report) -Raw|ConvertFrom-Json
|
|
$children.Add(@{suite=$suite.name;command=$command;exitCode=$actualExit;log=$log;report=$child})
|
|
Check "suite-$($suite.name)-actual-exit-status-checks" ($actualExit-eq0-and$child.status-eq'passed'-and$child.sourceCommit-eq$record.sourceCommit-and!$child.sourceDirty-and$child.apkSha256-eq$record.apkSha256-and$child.checks.Count-gt0-and@($child.checks|Where-Object passed -ne $true).Count-eq0) "$actualExit / $($child.checks.Count) checks"
|
|
if($suite.name-eq'Fresh30'){
|
|
foreach($stage in 1..30){Check "fresh30-required-success-$stage" (@($child.checks|Where-Object {$_.id-eq"stage-$stage-actual-success"-and$_.passed}).Count-eq1) 'Native sequential checkpoint present'}
|
|
Check 'fresh30-native-commands-no-fixture-write' (@($child.commands|Where-Object {$_.command-match'\bpush\b'}).Count-eq0) 'Native progression never pushes save data'
|
|
}
|
|
}
|
|
& pwsh -NoProfile -File (Join-Path $PSScriptRoot 'test-native-audio.ps1') -ReferencePath (Join-Path $projectRoot 'Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_collect.wav') -CapturedPath (Join-Path $EvidenceDir 'ContentBoundary/audio-one-offline.wav') -WrongReferencePath (Join-Path $projectRoot 'Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/guide_split.wav') -EvidenceDir (Join-Path $EvidenceDir 'pcm-negative-controls')
|
|
Check 'pcm-full-speech-negative-controls' ($LASTEXITCODE-eq0) 'Actual fullspeech positive and clipped front/back/middle/duplicate/noise/wrongclip negatives'
|
|
$record.sourceCommitEnd=(& git rev-parse HEAD);$record.sourceDirtyEnd=[bool](& git status --porcelain)
|
|
Check 'unchanged-source-end' (!$record.sourceDirtyEnd-and$record.sourceCommitEnd-eq$record.sourceCommit) $record.sourceCommitEnd
|
|
$record.status='passed'
|
|
}catch{$record.error=$_.Exception.Message;Write-Host $record.error -ForegroundColor Red}
|
|
finally{$record.checks=$checks.ToArray();$record.children=$children.ToArray();$record.finishedUtc=[DateTime]::UtcNow.ToString('o');$record|ConvertTo-Json -Depth 18|Set-Content -LiteralPath (Join-Path $EvidenceDir 'android-final-evidence.json') -Encoding utf8}
|
|
if($record.status-ne'passed'){exit 1}
|
|
Write-Host "Final Android passed: $($checks.Count) package/suite gates plus fresh30 and separately labelled Content boundaries."
|
|
exit 0
|