tests/_isolation.py
Rama main · réplica de lectura del repositorio original.
"""在乾淨的環境裡跑,而不是在沒有環境的地方跑。
這些測試要證明的是「不會偷偷讀到主機上的憑證或設定」。在 Linux 上,
「把環境變數清光」剛好等於那件事;在 Windows 上不等於,而且在 Linux
上其實也不完全等於:
- 子行程拿到空環境時,python.exe 連 hash 亂數都取不到就死了
(Fatal Python error: _Py_HashRandomization_Init),returncode 1、
stderr 全空——看起來像被測的腳本壞掉,其實它一行都沒跑到。
- Path.home() 與 expanduser("~") 在 Windows 看 USERPROFILE。清掉之後
直接 RuntimeError: Could not determine home directory.
- 反過來,POSIX 上把 HOME 清掉,expanduser 會退回 /etc/passwd 裡的
真實家目錄。那不是隔離,那是隔離破洞——只是它不會報錯,所以沒人發現。
所以要清掉的是「主機的身分與憑證」,不是「作業系統本身」,而家目錄要
指到一個空目錄、不能不設。
"""
from __future__ import annotations
import os
import tempfile
import unittest
from pathlib import Path
# 作業系統自己要用的變數,跟被測的行為無關。少了它們,Windows 上的
# 子行程根本開不起來。
_WINDOWS_ESSENTIALS = (
"SystemRoot", "SystemDrive", "WINDIR", "COMSPEC", "PATHEXT",
"TEMP", "TMP", "NUMBER_OF_PROCESSORS", "PROCESSOR_ARCHITECTURE",
)
_SCRATCH_HOME: str | None = None
def scratch_home() -> str:
"""一個空的家目錄,整個測試行程共用。不是主機的家目錄。"""
global _SCRATCH_HOME
if _SCRATCH_HOME is None:
_SCRATCH_HOME = tempfile.mkdtemp(prefix="xkb-test-home-")
return _SCRATCH_HOME
def clean_env() -> dict[str, str]:
"""作業系統開得起來的最小集合,加上一個空的家目錄。
用法是把它展開在測試自己的變數前面:
with mock.patch.dict(os.environ, {**clean_env(), "XKB_ENV_FILE": ...}, clear=True):
"""
env = {name: os.environ[name] for name in _WINDOWS_ESSENTIALS if name in os.environ}
home = scratch_home()
env["HOME"] = home
env["USERPROFILE"] = home
return env
def isolated_env(home: Path | str | None = None, **overrides: str | None) -> dict[str, str]:
"""clean_env() 加上指定的變數;home 同時設 HOME 與 USERPROFILE。
兩個都要設,因為 expanduser 在 POSIX 讀 HOME、在 Windows 讀
USERPROFILE——只設一個,隔離就只在其中一個平台成立。
"""
env = clean_env()
if home is not None:
env["HOME"] = str(home)
env["USERPROFILE"] = str(home)
env.update({key: value for key, value in overrides.items() if value is not None})
return env
# fixture 用沒有副檔名、靠 shebang 執行的腳本假裝成 bun / git。Windows 沒有
# 那個 exec 語意,跑不起來的是「假裝」那一層,不是被測的契約。明講跳過,
# 不要讓它以紅燈的樣子留著——紅燈留久了就沒人看了。
needs_posix_exec = unittest.skipIf(
os.name == "nt",
"fixture 需要 POSIX 的 shebang 執行語意;這條契約在 VPS / CI 上驗",
)