testing_util.py — 这是代码评测的底层执行引擎¶
模块路径: verl.utils.reward_score.prime_code.testing_util
文件概述¶
这是代码评测的底层执行引擎,实现了在受限环境中运行生成代码并比较输出的逻辑。它支持两种代码类型:函数调用型(call-based)和标准输入型(standard_input),并包含一套安全机制(reliability_guard)防止恶意代码。
这是整个 prime_code 子包中最长、最复杂的文件。
关键代码讲解¶
1. 代码类型枚举¶
class CODE_TYPE(Enum):
call_based = 0 # 通过函数调用测试(如 LeetCode 风格)
standard_input = 1 # 通过标准输入/输出测试(如竞赛编程风格)
2. 标准输出捕获器¶
class Capturing(list):
"""捕获 sys.stdout 的输出"""
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
self._stringio.close = lambda x: 1 # 防止代码关闭 stdout
return self
def __exit__(self, *args):
self.append(self._stringio.getvalue())
sys.stdout = self._stdout
3. 核心测试函数 run_test¶
函数调用型的测试流程:
if which_type == CODE_TYPE.call_based:
sol += test # 拼接用户代码
tmp_sol = RuntimeModule.from_string("tmp_sol", "", sol)
method = getattr(tmp_sol, method_name)
# 对每个测试用例
for index, inputs in enumerate(in_outs["inputs"]):
inputs = [json.loads(line) for line in inputs.split("\n")]
output = method(*inputs) # 调用函数
tmp_result = output == in_outs["outputs"][index] # 比较输出
标准输入型的测试流程更复杂,需要将代码包装成函数并 mock 标准输入:
elif which_type == CODE_TYPE.standard_input:
# 将代码包装成 code() 函数
new_test = "def code():\n" + indented_code
# Mock 标准输入
with Capturing() as output:
call_method(method, inputs)
# 比较输出(多种比较策略)
4. 输出比较策略¶
代码对输出的比较非常宽容,有多层回退策略:
1. 直接字符串比较
2. 去掉首尾空格后比较
3. 按行分割后比较
4. 转为浮点数用 np.allclose 比较
5. 按单词分割后用集合比较
5. 安全保护 reliability_guard¶
def reliability_guard(maximum_memory_bytes=None):
"""禁用危险的系统函数"""
faulthandler.disable()
builtins.exit = None
builtins.quit = None
os.kill = None
os.system = None
os.remove = None
os.fork = None
shutil.rmtree = None
subprocess.Popen = None
# ... 禁用大量危险函数
这个函数禁用了大量危险的系统操作(如删除文件、创建进程、修改权限等),防止生成的代码对系统造成破坏。注意这不是安全沙箱,只是减少风险。
6. 输入 Mock¶
def call_method(method, inputs):
@patch("builtins.open", mock_open(read_data=inputs))
@patch("sys.stdin", StringIO(inputs))
@patch("sys.stdin.readline", lambda *args: next(inputs_line_iterator))
@patch("sys.stdin.read", lambda *args: inputs)
def _inner_call_method(_method):
return _method()
return _inner_call_method(method)
使用 unittest.mock.patch 模拟标准输入和文件读取,使代码能从"虚拟"输入中读取数据。
核心类/函数列表¶
| 类/函数名 | 作用 |
|---|---|
run_test |
核心测试执行函数 |
CODE_TYPE |
代码类型枚举 |
Capturing |
stdout 捕获器 |
call_method |
带输入 mock 的函数调用 |
reliability_guard |
安全保护(禁用危险操作) |
custom_compare_ |
宽容的输出比较 |
truncatefn |
截断长字符串用于调试 |
与其他模块的关系¶
- 被
prime_code/utils.py的check_correctness在子进程中调用 - 使用
pyext.RuntimeModule动态编译执行代码 - 使用
numpy.allclose做浮点数近似比较
小结¶
这是一个功能完整但代码量较大的代码执行引擎。它的设计面临两个核心挑战:(1) 如何安全地执行不可信的代码 -- 通过进程隔离 + reliability_guard;(2) 如何宽容地比较输出 -- 通过多层回退的比较策略。标准输入型代码的处理尤其复杂,需要动态改造代码结构并 mock 输入源。