执行摘要
- 一句话:为 shell 启动脚本新增可安全重放的测试 harness
- 推荐动作:值得精读。虽然全部是测试代码,但 harness 的设计决策很有学习价值:用 PATH shim 拦截真实命令、按 fork 顺序保证记录确定性、轮询 /proc 过滤僵尸进程、冻结环境并拒绝解冻、路径占位符替换。建议与 #1899(shell 脚本外部命令快照)和 #1901(Python 启动脚本 harness)连读,能看到同一思路向两个方向的扩展。对需要维护 launch 脚本的工程师,理解 fake_bin 拦截与占位符机制是必要的。
功能与动机
PR body 仅标注 "Part of #1837"。结合 #1837 的清单,该 PR 服务于“为未来提供保护”:shell 启动脚本直接执行 ray、pkill、torchrun、hf download 等命令,任何后续重构(如 #1897 修复、#1909 参数展开)都可能在不经意间改变行为,而此前 CI 无法安全运行这些脚本。harness 的设计目标就是让真实脚本在完全不触碰真实系统(不下载权重、不启动训练、不杀死进程)的前提下跑完,并记录所有外部命令调用。
实现拆解
- 沙箱入口与目录布局:
sh_harness.py 定义 run_launch_script(script, sandbox, extra_env, timeout) 作为统一入口,在 sandbox 下建立 fake_bin(shim 目录)、capture(命令调用记录文件)、workdir(脚本工作目录)三个关键位置。
- 命令 shim 拦截:
_write_shims() 为 22 个危险/外部命令(apt、curl、docker、git、hf、pkill、ray、torchrun、wget 等)生成 bash shim,将 argv 以控制符分隔追加到 capture 后退出 0;对 python/python3 输出 1000000、date 输出固定时间,使脚本中的 GPU 等待轮询立即满足,避免测试挂起。
- 环境冻结:
_FROZEN_ENV 固定 HOME、LANG、MASTER_ADDR、NODE_RANK、WANDB_KEY 等;PATH 前置 fake_bin;_reject_unfreezing() 断言调用方传入的 extra_env 不得覆盖冻结变量或 capture 通道,防止测试环境泄露导致快照漂移。
- 执行与记录顺序:以
start_new_session=True 启动 bash,超时后 os.killpg 清理整个进程组;_wait_until_the_script_leaves_nothing_running() 轮询 /proc 进程组并过滤 Z 状态僵尸进程,处理 & 后台命令与命令替换孤儿进程;_parse_capture() 按 pid(fork 顺序)而非追加顺序排序记录,_sanitize() 将 sandbox 与 REPO_ROOT 替换为占位符。
- 结果模型与测试覆盖:
LaunchScriptRun 提供 invocations_of 与 ray_job_submit_argv 查询 API,format_invocations() 生成快照文本;test_sh_harness.py 以真实脚本 scripts/run-qwen3-4B.sh 与合成脚本验证破坏性命令拦截、后台记录顺序(连续 20 次)、环境冻结断言、路径占位与重放确定性;__init__.py 使 tests/fast/launch_scripts 成为可导入测试包。
关键文件:
tests/fast/launch_scripts/sh_harness.py(模块 测试沙箱;类别 test;类型 test-coverage;符号 LaunchScriptRun, invocations_of, ray_job_submit_argv, run_launch_script): 整个 PR 的核心:提供 run_launch_script 沙箱执行与命令 shim 记录机制,是后续快照测试与脚本重构的安全网。
tests/fast/launch_scripts/test_sh_harness.py(模块 沙箱测试;类别 test;类型 test-coverage;符号 TestRunLaunchScriptOnARealScript, TestRunLaunchScriptOnABackgroundingScript, TestRunLaunchScriptEnvironmentFreeze, TestRunLaunchScriptOnTheShimEdgeCases): harness 的自测套件,覆盖真实脚本与合成脚本的边界(后台命令、命令替换、GPU 等待循环、环境冻结、路径占位),验证 harness 的确定性与安全性。
tests/fast/launch_scripts/__init__.py(模块 测试包;类别 test;类型 test-coverage): 将 launch_scripts 提升为可导入测试包,供 harness 与用例之间模块化引用。
关键符号:run_launch_script, _write_shims, _parse_capture, _sanitize, _reject_unfreezing, _wait_until_the_script_leaves_nothing_running, _live_pids_of_group, format_invocations, LaunchScriptRun.invocations_of, LaunchScriptRun.ray_job_submit_argv
关键源码片段
tests/fast/launch_scripts/test_sh_harness.py
harness 的自测套件,覆盖真实脚本与合成脚本的边界(后台命令、命令替换、GPU 等待循环、环境冻结、路径占位),验证 harness 的确定性与安全性。
import pytest
from tests.fast.launch_scripts.sh_harness import run_launch_script
class TestRunLaunchScriptOnABackgroundingScript:
@pytest.fixture
def script(self, tmp_path):
# 一个会后台启动 sglang server 的合成脚本,用于验证 shim 对 `&` 子进程的记录
script = tmp_path / "backgrounding.sh"
script.write_text(
"""#!/bin/bash
set -ex
python3 -m sglang.launch_server --port 13141 >/dev/null 2>&1 &
curl -sf http://127.0.0.1:13141/health_generate
ray job submit --address="http://127.0.0.1:8265" -- python3 train.py
"""
)
return script
def test_a_backgrounded_command_is_still_recorded(self, script, tmp_path):
# bash 退出时不回收 `&` 子进程,过早读 capture 会漏掉它,这里验证必须记录到
run = run_launch_script(script, sandbox=tmp_path / "sandbox", timeout=30)
assert run.returncode == 0
assert run.invocations_of("python3")[0][1:3] == ["-m", "sglang.launch_server"]
@pytest.mark.parametrize("attempt", range(20))
def test_backgrounding_does_not_perturb_the_recorded_order(self, script, tmp_path, attempt):
# 快照断言精确序列,`&` 不得打乱记录的先后顺序,连续 20 次验证稳定性
run = run_launch_script(script, sandbox=tmp_path / f"sandbox-{attempt}", timeout=30)
assert [argv[0] for argv in run.invocations] == ["python3", "curl", "ray"]
def test_a_command_backgrounded_inside_a_substitution_does_not_hang_the_run(self, tmp_path):
# 命令替换中后台的命令会被孤儿化,若 PID 1 不回收则以僵尸形式残留,killpg 仍可见;
# harness 必须能识别这种情况并正常结束而不是一直等待
script = tmp_path / "substitution.sh"
script.write_text(
"""#!/bin/bash
set -ex
start_server() {
python3 -m sglang.launch_server --port "$1" >/dev/null 2>&1 &
echo "/tmp/server-$1.log"
}
LOG=$(start_server 13141)
curl -sf http://127.0.0.1:13141/health_generate
ray job submit --address="http://127.0.0.1:8265" -- python3 train.py "$LOG"
"""
)
run = run_launch_script(script, sandbox=tmp_path / "sandbox", timeout=30)
assert run.returncode == 0
assert [argv[0] for argv in run.invocations] == ["python3", "curl", "ray"]
评论区精华
本 PR 没有产生实质性 review 讨论:审核人 yueming-yuan 直接批准(APPROVED),评论区内无提问或争议。harness 中的关键设计权衡——按 fork 顺序而非追加顺序记录、跳过僵尸进程、拒绝覆盖冻结环境——并非讨论产物,而是由作者在代码注释与测试用例中固化(例如 test_sh_harness.py 中专门测试 & 后台命令不扰乱记录顺序的用例)。因此本节的洞察主要来自代码证据而非对话。
风险与影响
- 风险:
sh_harness.py 的 _live_pids_of_group() 直接遍历 /proc,是 Linux 专属实现,在 macOS 等非 Linux 开发环境下 fast 测试会失败或行为异常。
_SHIMMED_COMMANDS 覆盖有限:脚本若调用未拦截命令(如 sudo、ssh、kill)且带副作用,harness 可能误放行真实系统操作。
_parse_capture() 按 pid 排序假设 fork 顺序稳定,对复杂脚本中多线程同时 fork 的场景可能不成立。
_sanitize() 只替换 sandbox 与 REPO_ROOT,若脚本输出时间戳或其他绝对路径,快照仍会漂移。
test_sh_harness.py 对 run-qwen3-4B.sh 的具体断言(--num-layers 36、/root/Qwen3-4B)与真实脚本强耦合,脚本演进时需同步更新。
- 整体仅新增测试文件,无生产路径回归风险。
- 影响:对用户无运行时影响(纯测试资产)。对团队而言,shell 启动脚本的修改将进入快照保护范围:后续任何改动导致命令序列、环境变量或路径变化时,测试会立即失败,合并成本上升但回归风险下降。对 CI 系统,fast 测试套件新增 shell 脚本执行测试,且依赖 Linux 环境的 /proc 机制。该 PR 是 #1837 系列中“启动脚本可测试化”的起点,直接影响 #1897、#1898、#1899、#1901 等后续 PR 的实施方式。
- 风险标记:测试基础设施首次引入, 依赖 Linux /proc, 快照与真实脚本强耦合, shim 列表需随脚本演进
关联脉络
- PR #1899 Snapshot the external commands of every shell launch script: 该 PR 的直接消费者:用 sh_harness 对每个 shell 启动脚本建立外部命令快照,本 PR 为其提供安全执行与记录基础。
- PR #1901 Snapshot the commands and generated configs of every python launch script: 同系列测试基建:将相同思路扩展到 Python 启动脚本(py_harness),与本 PR 的 sh_harness 互补。
- PR #1898 Derive the miles checkout location instead of hardcoding it in launch scripts: 同属 #1837 的启动脚本重构线,与本 PR 一样服务于脚本可复现性与快照化。
- PR #1897 Fix various launch scripts errors about missing line concatenations or paths: 同一批次对 launch 脚本的修复,harness 可防止此类错误再次引入。
参与讨论