fix: exe 점검이 자식 프로세스를 안 죽여 임시폴더 정리에서 실패하던 것
Some checks failed
Windows / verify (push) Has been cancelled
Some checks failed
Windows / verify (push) Has been cancelled
onefile 의 부모는 압축을 푸는 부트로더일 뿐이고 창과 로그 파일을 쥐고 있는 것은 자식이다. 부모만 terminate 하니 자식이 livesub.log 를 계속 잡고 있어 임시 APPDATA 를 지울 때 PermissionError 가 났다. exe 의 문제가 아니라 점검이 뒷정리를 못 한 것인데 exe 기동 실패로 보고됐다. - 프로세스 트리를 통째로 정리한다 - 임시폴더 삭제는 실패해도 점검을 실패시키지 않는다 - 두 번째 실행에서 저장 위치 화면이 또 뜨면 실패로 본다. "기억한다"는 약속이 깨진 것이므로 본 화면이 아닌 창은 통과가 아니다
This commit is contained in:
@@ -14,8 +14,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -440,6 +442,37 @@ def _visible_windows(pids: set[int]) -> list[str]:
|
||||
return titles
|
||||
|
||||
|
||||
def _terminate_tree(proc: subprocess.Popen) -> None:
|
||||
"""부모만 죽이면 안 된다.
|
||||
|
||||
onefile 의 부모는 압축을 푸는 부트로더일 뿐이고, 창과 로그 파일을 쥐고 있는
|
||||
것은 자식이다. 부모만 terminate 하면 자식이 살아남아 로그 파일을 계속
|
||||
잡고 있고, 임시 폴더를 지울 수 없어 점검이 엉뚱한 곳에서 실패한다.
|
||||
"""
|
||||
children = []
|
||||
try:
|
||||
import psutil
|
||||
|
||||
children = psutil.Process(proc.pid).children(recursive=True)
|
||||
except Exception: # noqa: BLE001 - psutil 이 없으면 부모만이라도 정리한다
|
||||
pass
|
||||
|
||||
for child in children:
|
||||
with contextlib.suppress(Exception):
|
||||
child.terminate()
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=15)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
for child in children:
|
||||
with contextlib.suppress(Exception):
|
||||
child.wait(timeout=10)
|
||||
with contextlib.suppress(Exception):
|
||||
if child.is_running():
|
||||
child.kill()
|
||||
|
||||
|
||||
def _launch_and_wait_for_window(exe: Path, appdata: Path, timeout_s: int = 90) -> str:
|
||||
"""exe 를 띄우고 **실제 창이 뜰 때까지** 기다린다.
|
||||
|
||||
@@ -469,11 +502,22 @@ def _launch_and_wait_for_window(exe: Path, appdata: Path, timeout_s: int = 90) -
|
||||
time.sleep(1)
|
||||
raise RuntimeError(f"{timeout_s}초 안에 창이 뜨지 않았습니다")
|
||||
finally:
|
||||
proc.terminate()
|
||||
_terminate_tree(proc)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _scratch_appdata():
|
||||
"""점검용 %APPDATA%.
|
||||
|
||||
치울 때 실패해도 점검을 실패시키지 않는다. Windows 는 방금 죽인 프로세스가
|
||||
파일 핸들을 놓는 데 잠깐 시간이 걸려서, 임시 폴더 삭제가 PermissionError 로
|
||||
튀는 일이 있다. 그건 exe 의 문제가 아니라 치우는 쪽 사정이다.
|
||||
"""
|
||||
tmp = Path(tempfile.mkdtemp(prefix="livesub-smoke-"))
|
||||
try:
|
||||
proc.wait(timeout=15)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
yield tmp
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
def run_exe_check(exe: Path) -> None:
|
||||
@@ -491,17 +535,17 @@ def run_exe_check(exe: Path) -> None:
|
||||
@check("exe 첫 실행 — 저장 위치 화면")
|
||||
def _first_run():
|
||||
# APPDATA 를 빈 폴더로 돌려 "처음 켜는 사람" 을 그대로 재현한다.
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
title = _launch_and_wait_for_window(exe, appdata=Path(tmp))
|
||||
with _scratch_appdata() as tmp:
|
||||
title = _launch_and_wait_for_window(exe, appdata=tmp)
|
||||
return f"창 제목 {title!r}"
|
||||
|
||||
@check("exe 두 번째 실행 — 본 화면")
|
||||
def _second_run():
|
||||
# 위치를 이미 고른 사람. 묻지 않고 곧바로 본 화면이 떠야 한다.
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
data = Path(tmp) / "data"
|
||||
with _scratch_appdata() as tmp:
|
||||
data = tmp / "data"
|
||||
data.mkdir()
|
||||
pointer = Path(tmp) / "LiveSub"
|
||||
pointer = tmp / "LiveSub"
|
||||
pointer.mkdir()
|
||||
(pointer / "location.json").write_text(
|
||||
json.dumps({"data_dir": str(data)}), encoding="utf-8"
|
||||
@@ -511,7 +555,9 @@ def run_exe_check(exe: Path) -> None:
|
||||
(data / "config.json").write_text(
|
||||
json.dumps({"models": {"preload_on_start": False}}), encoding="utf-8"
|
||||
)
|
||||
title = _launch_and_wait_for_window(exe, appdata=Path(tmp))
|
||||
title = _launch_and_wait_for_window(exe, appdata=tmp)
|
||||
if "저장 위치" in title:
|
||||
raise RuntimeError(f"위치를 이미 골랐는데 또 물어봤습니다: {title!r}")
|
||||
return f"창 제목 {title!r}"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user