fix: Windows 점검기 SIGABRT 수정 + 러너 안내 정확하게
Some checks failed
Windows / verify (push) Has been cancelled

리뷰 지적을 확인해보니 두 건은 실제 결함이었다.

1) 점검기가 화면 없는 환경에서 그냥 죽음 (SIGABRT, 종료코드 134)
   QT_QPA_PLATFORM 을 "windows" 로 하드코딩해 둔 탓에, 표시할 화면이 없으면
   첫 QApplication 생성에서 플러그인 로드에 실패하며 아무 결과도 못 남기고
   abort 했다. 점검기가 그렇게 죽으면 존재 이유가 없다.
   - 화면이 없으면 offscreen 으로 떨어지게 하고, 이 판단을 Qt import 전에 한다
   - QApplication 생성을 _qt_app() 한 곳으로 모음
   - 실행 플랫폼을 출력해 리눅스에서 돌아간 경우가 바로 보이게 함
   이제 환경변수 없이 돌려도 항목별 판정과 종료코드가 정상으로 나온다.

2) 보안 설명이 과장돼 있었음 — 가장 중요한 수정
   "커밋된 워크플로만 실행하니 임의 명령은 불가"라고 썼는데, 나는 이 저장소
   main 에 push 할 수 있으므로 워크플로를 고쳐 커밋하면 그 PC에서 원하는
   명령을 돌릴 수 있다. 개인 PC를 내주는 판단에 쓰일 설명을 틀리게 준 것이라
   그대로 바로잡았다. 실제로 보장되는 것(숨길 수 없음·언제든 끌 수 있음·
   계정 권한으로만 동작)만 적고, VM 또는 전용 계정을 권하도록 바꿨다.
   서비스 등록(SYSTEM 권한) 안내도 로그온 작업(계정 권한)으로 교체 —
   "관리자로 띄우지 말라"는 권고와 모순됐다.

3) 라벨 오타 안전장치 (공식 문서 확인 중 발견)
   Gitea 는 runs-on 이 어떤 라벨과도 안 맞으면 실패시키지 않고 기본 우분투
   컨테이너에서 돌린다. Windows 검증이 조용히 리눅스에서 돌아가 "통과"로
   보일 수 있어, 워크플로 첫 단계에서 RUNNER_OS 를 확인해 막는다.
   문서의 등록 라벨도 windows:host 로 명시 (생략 시 기본값이 host 이지만
   명시가 낫다 — docs.gitea.com/runner/labels).

4) 트리거 설명 정정: "아무거나 push" -> main push 또는 수동 실행.

검증: pytest 189개 통과, ruff clean, 워크플로 트리거/스텝 파싱 확인,
      QT_QPA_PLATFORM 없이 점검기 실행해 abort 없이 종료코드 1 확인

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-09-23 03:17:11 +09:00
parent 2fcd961f8f
commit 4af97a68f2
3 changed files with 85 additions and 29 deletions

View File

@@ -24,9 +24,24 @@ ROOT = Path(__file__).resolve().parents[1]
ARTIFACTS = ROOT / "artifacts"
sys.path.insert(0, str(ROOT / "src"))
# Qt 플랫폼 플러그인은 QApplication 을 만들기 *전에* 정해야 한다.
# 표시할 화면이 없는데 그냥 두면 플러그인 로드 실패로 SIGABRT 가 나면서
# 아무 결과도 못 남기고 죽는다. 점검기가 그렇게 죽으면 쓸모가 없다.
if sys.platform != "win32" and not (
os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")
):
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
_results: list[tuple[str, bool, str]] = []
def _qt_app():
"""QApplication 을 한 번만 만들어 재사용한다."""
from PySide6.QtWidgets import QApplication
return QApplication.instance() or QApplication([])
def check(name: str):
"""점검 하나. 예외가 나도 나머지는 계속 돈다."""
@@ -47,7 +62,9 @@ def check(name: str):
def run_source_checks() -> None:
print("\n=== Windows 전용 경로 ===")
print(f"\n=== Windows 전용 경로 (실행 플랫폼: {sys.platform}) ===")
if sys.platform != "win32":
print(" 주의: Windows 가 아니라 Windows 전용 항목은 당연히 실패합니다.")
@check("WASAPI 루프백 장치 열거")
def _devices():
@@ -78,11 +95,9 @@ def run_source_checks() -> None:
@check("전역 단축키 등록/해제")
def _hotkey():
from PySide6.QtWidgets import QApplication
from livesub.ui.hotkeys import GlobalHotkeys
app = QApplication.instance() or QApplication([])
_qt_app()
keys = GlobalHotkeys()
if not keys.supported:
raise RuntimeError("이 플랫폼은 전역 단축키 미지원")
@@ -90,20 +105,16 @@ def run_source_checks() -> None:
if not keys.register("Ctrl+Alt+F12", lambda: fired.append(1)):
raise RuntimeError(keys.last_error)
keys.unregister_all()
_ = app
return "Ctrl+Alt+F12 등록·해제 성공"
@check("모니터 열거")
def _screens():
from PySide6.QtWidgets import QApplication
from livesub.ui.placement import list_screens
app = QApplication.instance() or QApplication([])
_qt_app()
screens = list_screens()
if not screens:
raise RuntimeError("모니터를 찾지 못함")
_ = app
return " / ".join(s.label for s in screens)
@check("GPU 인식")
@@ -125,13 +136,10 @@ def run_source_checks() -> None:
@check("자막 오버레이 실제 렌더링")
def _overlay():
os.environ.setdefault("QT_QPA_PLATFORM", "windows")
from PySide6.QtWidgets import QApplication
from livesub.config import AppConfig
from livesub.ui.main_window import MainWindow
app = QApplication.instance() or QApplication([])
app = _qt_app()
cfg = AppConfig()
cfg.models.preload_on_start = False
win = MainWindow(cfg)