pbl_blueprint/scripts/fix_selfcheck_exit.py

170 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""fix_selfcheck_exit.py —— 修复 selfcheck.py「打印 HAS FAILURES 但 rc=0」的门禁失真缺陷。
背景
----
自查脚本 scripts/selfcheck.py 原先在 __main__ 分支里无条件 sys.exit(0)(或不显式退出),
导致存在失败项时进程退出码仍为 0。QC / CI 以退出码为唯一门禁依据,等于门禁形同虚设。
本脚本对 selfcheck.py 的 `if __name__ == "__main__":` 尾块做**幂等外科替换**
1. 捕获 main() 的返回值 / SystemExit.code / 未捕获异常 -> 基础退出码
2. 同时 tee 全部 stdout扫描失败特征串HAS FAILURES / FAILED / [FAIL] / 有失败项)
3. 基础退出码为 0 但输出含失败特征 -> 强制改写为 1
4. 注入标记 SELFCHECK_EXIT_PATCHED重复执行不会二次改写
用法(机构工作空间根目录)::
python3 modules/pbl_blueprint/scripts/fix_selfcheck_exit.py # 应用补丁
python3 modules/pbl_blueprint/scripts/fix_selfcheck_exit.py --check # 只检查是否已打补丁
"""
from __future__ import annotations
import io
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
TARGET = os.path.join(HERE, "selfcheck.py")
MARKER = "SELFCHECK_EXIT_PATCHED"
MAIN_RE = re.compile(r'^if\s+__name__\s*==\s*[\'"]__main__[\'"]\s*:\s*$', re.MULTILINE)
PATCH_BLOCK = '''if __name__ == "__main__": # {marker} —— 退出码门禁:有失败项必须非 0
# 本块由 scripts/fix_selfcheck_exit.py 幂等注入,勿手工编辑。
import sys as _sys
import io as _io
import re as _re
import traceback as _tb
class _Tee(object):
"""同时写原始 stdout 与捕获缓冲,保证控制台输出不变。"""
def __init__(self, *streams):
self._streams = streams
def write(self, s):
for _st in self._streams:
try:
_st.write(s)
except Exception:
pass
return len(s)
def flush(self):
for _st in self._streams:
try:
_st.flush()
except Exception:
pass
def __getattr__(self, name):
return getattr(self._streams[0], name)
_real_stdout = _sys.stdout
_buf = _io.StringIO()
_sys.stdout = _Tee(_real_stdout, _buf)
_rc = 0
try:
_entry = None
for _name in ("main", "run", "selfcheck", "run_selfcheck"):
_entry = globals().get(_name)
if callable(_entry):
break
if callable(_entry):
_rv = _entry()
if isinstance(_rv, bool):
_rc = 0 if _rv else 1
elif isinstance(_rv, int):
_rc = _rv
else:
_rc = 2
_real_stdout.write("[selfcheck] 未找到 main()/run() 入口,无法执行自查\\n")
except SystemExit as _e:
if _e.code is None:
_rc = 0
elif isinstance(_e.code, int):
_rc = _e.code
else:
_rc = 1
_real_stdout.write("[selfcheck] SystemExit: %s\\n" % (_e.code,))
except BaseException:
_rc = 1
_sys.stdout = _real_stdout
_tb.print_exc()
_sys.stdout = _Tee(_real_stdout, _buf)
finally:
try:
_sys.stdout.flush()
except Exception:
pass
_sys.stdout = _real_stdout
_out = _buf.getvalue()
_FAIL_PAT = _re.compile(
r"HAS\\s+FAILURES|\\bFAILED\\b|^\\s*\\[FAIL\\]|\\bFAIL:|失败项\\s*[:]\\s*[1-9]|"
r"存在失败|结论\\s*[:]\\s*FAIL",
_re.MULTILINE | _re.IGNORECASE,
)
_PASS_PAT = _re.compile(
r"ALL\\s+PASSED|全部通过|结论\\s*[:]\\s*(OK|PASS|PASSED|SUCCESS)|失败项\\s*[:]\\s*0\\b",
_re.IGNORECASE,
)
if _rc == 0 and _FAIL_PAT.search(_out) and not (
_PASS_PAT.search(_out) and not _FAIL_PAT.search(_out)
):
_real_stdout.write(
"[selfcheck] 检测到失败特征但退出码为 0已按门禁规则改写为 1\\n"
)
_rc = 1
_sys.exit(_rc)
'''
def read_target() -> str:
with io.open(TARGET, "r", encoding="utf-8") as f:
return f.read()
def already_patched(text: str) -> bool:
return MARKER in text
def apply_patch() -> int:
if not os.path.isfile(TARGET):
sys.stderr.write("[fix] 目标不存在: %s\n" % TARGET)
return 2
text = read_target()
if already_patched(text):
print("[fix] selfcheck.py 已含 %s 标记,跳过(幂等)" % MARKER)
return 0
m = None
for m in MAIN_RE.finditer(text):
pass # 取最后一个 __main__ 块
if m is None:
# 没有 __main__ 块 -> 直接追加
new = text.rstrip("\n") + "\n\n\n" + PATCH_BLOCK.format(marker=MARKER)
action = "appended"
else:
new = text[: m.start()] + PATCH_BLOCK.format(marker=MARKER)
action = "replaced"
with io.open(TARGET, "w", encoding="utf-8", newline="\n") as f:
f.write(new)
print("[fix] selfcheck.py __main__ 块已%s,退出码门禁生效" % action)
return 0
def main() -> int:
if "--check" in sys.argv:
if not os.path.isfile(TARGET):
print("[check] MISSING %s" % TARGET)
return 2
print("[check] PATCHED" if already_patched(read_target()) else "[check] NOT_PATCHED")
return 0 if already_patched(read_target()) else 1
return apply_patch()
if __name__ == "__main__":
sys.exit(main())