ci: win-ci 검증 출력을 호스트 파일로 회수한다 (증거 기준선)
Some checks failed
Windows / verify (push) Has been cancelled
Some checks failed
Windows / verify (push) Has been cancelled
VM 을 만든 이유가 "리눅스에서 통과한 게 Windows 에서 깨지는 것을 재현
가능하게 본다" 인데, 정작 실패 트레이스백이 호스트에 한 줄도 안 남았다.
로그 채널은 마지막 몇 줄만 흘렸고(Select-Object -Last 30), serial.log 는
VM 이 뜰 때마다 truncate 되고, guest.log 는 stage2 가 다시 돌 때만 채워진다.
그래서 실패 내용을 화면에서 읽어 옮겨 적는 상태였고 — 고친 뒤에 고쳐졌다는
것을 증명할 기준선이 없었다.
- serve-host.py: POST /artifact/<name> 추가. results/<name> 에 파일로 저장
(경로는 basename 으로만 취하고 32MB 상한)
- stage2.ps1: Step 이 각 단계의 출력 전문을 StreamWriter(AutoFlush)로 받아
호스트로 업로드한다. 중간에 예외로 죽어도 거기까지는 남는다.
출력 자르기를 없애고 pytest 는 --tb=long, 검증 리비전을 박은
summary.log 도 함께 올린다
- sync.sh: 스크립트만 /home/claude/win-ci 로 반영한다. build.sh 를 다시
돌리면 지워둔 Windows ISO 4.8GB 를 다시 내려받는다
첫 수집 결과(rev 162540b)로 실제 원인 두 개를 특정했다.
This commit is contained in:
@@ -211,18 +211,59 @@ Log "=== stage2 완료 ==="
|
||||
# agent 가 무한 재시도에 빠지지 않는다. 결과는 로그로 남는다.
|
||||
# ==================================================================
|
||||
$Repo = 'C:\win-ci\live-app-translator'
|
||||
function Step($name, $block) {
|
||||
Log "--- [$name] 시작"
|
||||
$OutDir = 'C:\win-ci\out'
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
# 출력 원본을 호스트 파일로 회수한다.
|
||||
#
|
||||
# 왜 필요한가: 로그 채널로는 마지막 몇 줄만 흘렸고 serial.log 는 VM 이 뜰 때마다
|
||||
# truncate 된다. 그래서 pytest 실패 트레이스백이 호스트에 한 줄도 안 남았고
|
||||
# 화면에서 읽어 옮겨 적는 상태였다. 파일로 된 기준선이 없으면 고친 뒤에
|
||||
# 고쳐졌다는 증명을 할 수 없다.
|
||||
function Send-Artifact($name, $path) {
|
||||
if (-not (Test-Path $path)) { return }
|
||||
try {
|
||||
& $block
|
||||
Log "--- [$name] 통과"
|
||||
return $true
|
||||
$bytes = [IO.File]::ReadAllBytes($path)
|
||||
# 백틱 줄바꿈을 쓰지 않는다. LF 로 저장된 스크립트에서 토크나이저가
|
||||
# 어긋나 스크립트 전체가 무효가 된 전례가 있다 (NOTES.md 참고).
|
||||
$req = @{ Uri = "http://10.0.2.2:8099/artifact/$name"; Method = 'Post'; TimeoutSec = 120; Body = $bytes; ContentType = 'application/octet-stream' }
|
||||
Invoke-RestMethod @req | Out-Null
|
||||
Log " -> 호스트로 $name ($($bytes.Length) bytes)"
|
||||
} catch {
|
||||
Log "--- [$name] 실패: $($_.Exception.Message)"
|
||||
return $false
|
||||
Log " -> $name 업로드 실패: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
function Step($name, $block) {
|
||||
$log = Join-Path $OutDir "$name.log"
|
||||
Remove-Item $log -Force -ErrorAction SilentlyContinue
|
||||
Log "--- [$name] 시작"
|
||||
|
||||
# StreamWriter + AutoFlush 로 한 줄씩 즉시 디스크에 쓴다. 중간에 예외로
|
||||
# 죽어도 거기까지의 출력이 남아야 한다 (Tee-Object 는 그 보장이 없다).
|
||||
$sw = [IO.StreamWriter]::new($log, $false, [Text.UTF8Encoding]::new($false))
|
||||
$sw.AutoFlush = $true
|
||||
$tail = [System.Collections.Generic.Queue[string]]::new()
|
||||
$ok = $true
|
||||
try {
|
||||
& $block 2>&1 | ForEach-Object {
|
||||
$line = "$_"
|
||||
$sw.WriteLine($line)
|
||||
$tail.Enqueue($line)
|
||||
if ($tail.Count -gt 25) { [void]$tail.Dequeue() }
|
||||
}
|
||||
} catch {
|
||||
$ok = $false
|
||||
$sw.WriteLine("[step 예외] $($_.Exception.Message)")
|
||||
}
|
||||
$sw.Close()
|
||||
|
||||
foreach ($l in $tail) { Log " $l" }
|
||||
if ($ok) { Log "--- [$name] 통과" } else { Log "--- [$name] 실패" }
|
||||
Send-Artifact "$name.log" $log
|
||||
return $ok
|
||||
}
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
Sync-Path
|
||||
@@ -251,16 +292,29 @@ try {
|
||||
# 죽는다. 검증 스크립트가 한글을 찍으므로 UTF-8 로 고정한다.
|
||||
$env:PYTHONIOENCODING = 'utf-8'
|
||||
$env:PYTHONUTF8 = '1'
|
||||
$r = @{}
|
||||
$r['pytest'] = Step 'pytest' { & $py -m pytest tests -q 2>&1 | Select-Object -Last 30 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
$r['ruff'] = Step 'ruff' { & $py -m ruff check src tests scripts 2>&1 | Select-Object -Last 5 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
$r['windows경로'] = Step 'windows_smoke' { & $py packaging\windows_smoke.py 2>&1 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
$r['exe빌드'] = Step 'pyinstaller' { & $py -m pip install pyinstaller --quiet; & $py -m PyInstaller packaging\livesub.spec --noconfirm 2>&1 | Select-Object -Last 4 | ForEach-Object { Log " $_" }; if (-not (Test-Path 'dist\LiveSub.exe')) { throw 'dist\LiveSub.exe 없음' }; Log " exe 크기 $([math]::Round((Get-Item 'dist\LiveSub.exe').Length/1MB,1)) MB" }
|
||||
$r['exe기동'] = Step 'exe 실행' { & $py packaging\windows_smoke.py --exe dist\LiveSub.exe 2>&1 | ForEach-Object { Log " $_" }; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
# 출력을 자르지 않는다. 전문은 Step 이 호스트 results/<name>.log 로 올린다.
|
||||
# 여기서 truncate 하면 실패 원인이 있는 트레이스백 본문이 날아간다.
|
||||
$r = [ordered]@{}
|
||||
$r['pytest'] = Step 'pytest' { & $py -m pytest tests -q -rf --tb=long; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
$r['ruff'] = Step 'ruff' { & $py -m ruff check src tests scripts; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
$r['windows경로'] = Step 'windows_smoke' { & $py packaging\windows_smoke.py; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
$r['exe빌드'] = Step 'pyinstaller' { & $py -m pip install pyinstaller --quiet; & $py -m PyInstaller packaging\livesub.spec --noconfirm; if (-not (Test-Path 'dist\LiveSub.exe')) { throw 'dist\LiveSub.exe 없음' }; "exe 크기 $([math]::Round((Get-Item 'dist\LiveSub.exe').Length/1MB,1)) MB" }
|
||||
$r['exe기동'] = Step 'exe_smoke' { & $py packaging\windows_smoke.py --exe dist\LiveSub.exe; if ($LASTEXITCODE -ne 0) { throw "exit $LASTEXITCODE" } }
|
||||
|
||||
Log "=== Windows 검증 결과 ==="
|
||||
foreach ($k in $r.Keys) { Log ("{0,-14} {1}" -f $k, $(if ($r[$k]) { 'PASS' } else { 'FAIL' })) }
|
||||
Log "=== 검증 끝 (rev2: .gitignore 수정 반영) ==="
|
||||
# 판정 요약도 파일로 남긴다. 어떤 리비전을 검증한 결과인지 같이 박아야
|
||||
# 나중에 "언제 통과한 건지" 를 되짚을 수 있다.
|
||||
$rev = (& git rev-parse --short HEAD 2>&1 | Select-Object -First 1)
|
||||
$sum = Join-Path $OutDir 'summary.log'
|
||||
$lines = @("rev=$rev", "utc=$([DateTime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss'))")
|
||||
Log "=== Windows 검증 결과 (rev $rev) ==="
|
||||
foreach ($k in $r.Keys) {
|
||||
$line = "{0,-14} {1}" -f $k, $(if ($r[$k]) { 'PASS' } else { 'FAIL' })
|
||||
Log $line
|
||||
$lines += $line
|
||||
}
|
||||
Set-Content -Path $sum -Value $lines -Encoding UTF8
|
||||
Send-Artifact 'summary.log' $sum
|
||||
Log "=== 검증 끝 ==="
|
||||
Pop-Location
|
||||
} catch {
|
||||
Log "검증 준비 단계에서 실패: $($_.Exception.Message)"
|
||||
|
||||
Reference in New Issue
Block a user