Testing
tests/runtime.spec.ts在加载期拒绝缺失、不可执行、非 CPython、低于 3.10 或无响应的解释器配置;在激活后更改PATH,证明已解析的可执行文件保持固定;在激活后删除该文件,保留迟到的worker-exit路径;并断言运行中的程序能看到TMPDIR,但看不到PATH、HOME或DEEPSEEK_API_KEY。原生输出用例分别固定每个来源流内的顺序,而不要求独立通道之间存在总顺序;Darwin 资源限制用例明确说明或跳过平台特有的RLIMIT_AS行为。snapshots/session/ptc-python-turn通过真实 Loader 把 headless PTC worker 提供方替换为私有 Python 提供方,经真实 bash binding 重放一个 Pythonrun_code程序,并固定 Python SDK prompt、tool schema、dispatch event、捕获日志与完成值。tests/boot-write-failure.spec.ts对spawn做 mock,使 fd-3 管道在引导写入时抛出异常(这是真实子进程无法被迫进入的唯一路径),并断言run()resolve 出一个worker-exit而非 reject。一个同级用例让被 mock 的spawn同步抛出,并断言run()仍然 resolve 出一个worker-exit,且会移除它的暂存目录——以被 mock 的spawn在其 argv 中收到的确切引导路径为准,因此一个同级 worker 的并发暂存不会让它变得不稳定。两者都被隔离在这个 spec 中,因此真实子进程测试套件不受影响。tests/residual-detach.spec.ts对detachResidual做单元测试:向前传递的副本与残余数据相等、拥有一个大小与其自身长度一致的底层存储(fixture 保持在 Node 的 Buffer 池阈值之上),并且不与源帧的ArrayBuffer共享。tests/runtime.spec.ts:output-cap 用例断言parse-cap - envelope上界(67108800)及其消息。一个 daemon 线程用例驱动四个线程穿过结算的 flush 发出未结束的写入。一个 native-write 用例在抬高后的maxLogBytes之下,通过os.write写入 200 KiB 且不含换行符,断言它回读时恰好是一条日志条目(证明散逸输出是按行聚合的,而不是在管道分片边界处被切开);一个配套用例写入b"one\ntwo\nthree",断言得到三条条目(证明真正的换行符仍然起分隔作用)。一个 newline-free-flood 用例在一个 4 KiB 的maxLogBytes之下写入 2 MiB,断言捕获终止于截断标记且保持在预算之内(证明残余数据受账本约束,而不是被整体缓冲);一个 NUL-flood 配套用例在同一预算之下写入 4000 个不含换行符的 NUL,断言发生截断(证明残余数据是按序列化开销计费的,约为原始的 6 倍,且在度量时不分配转义后的副本);一个 illegal-UTF-8 用例在一个 3072 字节的预算之下控速发出单字节\xff写入,并对Buffer.concat做包装以度量峰值合并缓冲区,断言它保持在 2048 之下(按 U+FFFD 宽度 3 计费时残余数据在约 1024 原始字节处冲刷;一次原始字节的少计会让它达到约 3072,因此该界限具有区分力);一个 CESU-8/overlong 用例把结构良构但非法的ED A0 80一次一个字节地控速发出,断言同样的峰值界限(按每序列真实的 9 计费时它提前冲刷;按结构宽度 3 计费会使峰值增至三倍,因此把逐前导字节范围检查回退会使它变红);一个 broken-multibyte 用例在分开的分片里先写入一个 3 字节的前导字节、再写入一个新的 ASCII 字节,断言同时捕获到一个A和一个 U+FFFD(覆盖accrueStrayCost的跨分片断裂序列分支);一个 post-truncation 用例写入一个 108 字节的载荷(小于最小的 PIPE_BUF,因此是一次原子写入),其首行耗尽一个 64 字节的预算,断言第二行被丢弃(覆盖单次data回调中的截断后准入空操作,无需 v8-ignore);一个 short-escape 用例写入一行混合了制表符、引号、反斜杠、一个\uXXXX控制字符、一个多字节字符和 ASCII 的内容,断言它原样完成往返(覆盖jsonStringCostUpTo的每一条分支);一个 reassembly 用例写入一个跨越每个合法多字节前导字节类别(E0 范围、普通 3 字节、F0 和 F4)、越过管道缓冲区的载荷,断言它原样完成往返且不含 U+FFFD(覆盖accrueStrayCost的逐前导字节范围与跨分片重组);一个 lone-surrogate 用例在一个 4 KiB 预算之下伪造一个以 1000 个\ud800转义洪泛的 fd-3log帧,断言发生截断(该计数正落在计 3 字节会放行、计 6 字节则截断的窗口内,证明该代理项是按其完整转义宽度计费的);一个 stray-sealing 用例在抬高后的预算之下控速发出 60000 次单字节、不含换行符的os.write(1, …)调用,并对Buffer.concat做包装以度量复制量,断言这股细流合并为一条条目、且累积复制量保持在一个实测的 256 KiB 阈值之下(封存后的形态复制约 120 KB,重新合并的形态复制约 538 KB,因此把封存回退成重新合并会使该断言变红——证明分片列表在越过MAX_PENDING_CHUNKS后封存为块)。一个 closeDeadline-flush 用例让 leader 写入一段不含换行符的诊断,随后 spawn 一个持有管道不放的setsid孤儿进程,断言该诊断在logs中存留下来(证明残余数据在截止时间销毁流之前被冲刷)。same-group 回收用例 spawn 一个忽略 SIGTERM 的同进程组后代,它释放管道并递增一个心跳文件;该测试断言在宽限窗口的 SIGKILL 之后心跳停止:无论被杀死的后代是被回收还是作为僵尸进程滞留,这个断言都成立,因此它在 PID 1 不 wait() 孤儿进程的环境下同样成立。一个 dispose-after-resolve 用例断言,对一个已完成、且存在同进程组存活者的运行调用dispose(),只有在该存活者停止执行之后才返回(证明该运行会一直留在live中,直到它的进程组被回收),并带有一个expect(afterDispose).toBeGreaterThan(0)守卫,使得当心跳文件从未被写入时,冻结心跳的断言不会被空洞地通过。一个 deadline 用例忙阻塞事件循环越过两个定时器,断言该存活者的心跳冻结(证明轮询的截止时间分支自身发送 SIGKILL,而不是取消尚未触发的升级)。cross-loop 用例在主协程通过await asyncio.sleep让出时,从一个工作线程自己的asyncio.run事件循环运行一个绑定,断言该回复完成往返而不是超时;一个配套用例放弃某个线程的调用,使其事件循环关闭,随后在一个后续绑定之前回答它——断言 pump 在关闭事件循环上的call_soon_threadsafe之后仍然存活(由宿主门控的顺序使其具有确定性,未修复时会把后续绑定拖到墙钟上挂起)。inherited-soft-limit 用例通过一个ulimit -S -t包装脚本运行解释器,将 CPU 软限制设为低于cpuSeconds,并断言实际应用的RLIMIT_CPU软限制是继承来的值,而不是配置的值(用 CPU 而非地址空间,因为 macOS 忽略ulimit -v)。一个配套用例继承 1 秒的 CPU 软限制,让程序捕获 SIGXCPU 并忙循环越过它,断言结算复查报告 timeout——证明复查用的是实际生效的软限制,而不是配置的cpuSeconds。一个 control-heavy-diagnostic 用例在一个较小的maxValueBytes之下抛出一个 NUL 洪泛异常,断言序列化后的帧能放得下(证明该诊断是按序列化开销计量的)。一个 tail-copy 用例(maxLogBytes: 256、addressSpaceMb: 384)让程序在一个变量里构建一个尾部并写入"\n" + tail,其中tail为 150 MiB——构建峰值约 2 倍(约 300 MiB,落在地址空间之内,因此模型自身的分配会成功,任何 OOM 都属于缺陷路径),而修复前的整尾重新缓冲会加上第三份约 150 MiB 的副本、越过 384 MiB;切片后的前缀让该次运行得以截断并完成(仅 Linux 的 RLIMIT_AS 复现,macOS 走顺利路径——fixture 自身的构建必须放进地址空间,这是这些 RLIMIT_AS 用例的一条通用规则)。一个 output-budget/address-space 用例断言一个 50 MB 的maxLogBytes和一个 50 MB 的maxValueBytes各自对照一个 256 MiB 的addressSpaceMb在加载期被拒绝(乘以最坏情况的 12 之后超过解释器基线之后剩下的余量),而默认的各项上限对照 512 MiB 则加载成功,对两项预算对称地门控;一个具区分力的用例断言一个 48 MiB 的maxLogBytes对照一个 512 MiB 的addressSpaceMb被拒绝——48×8 = 384 MiB 放得进 448 MiB 的可预算余量(旧的 8× 倍数会错误放行),但 48×12 = 576 MiB 放不进。该倍数覆盖的约 12× 峰值来自换行路径上一次接近预算的写入——调用方自己的字符串、行切片与 encode 副本同时存活;结算期 flush 已不再是承重者,因为flush_line在 push 之前就丢弃了 pending 分块,因此只持有两份副本而非三份。一个 inherited-RLIMIT_AS 用例通过一个ulimit -v 131072包装层运行解释器,配以一个配置的 512 MiBaddressSpaceMb所允许的 32 MiBmaxLogBytes,断言引导期的重新检查把它作为exception拒绝、且其消息点名了继承的 RLIMIT_AS(128 MiB 的继承限制在基线之后剩下的太少;仅 Linux,macOS 忽略ulimit -v,该次运行会继续)。一个 non-integer-budget 用例断言一个小数的maxLogBytes/maxValueBytes在加载期被拒绝。一个 combined-peak 用例(maxLogBytes: 32 MiB、maxValueBytes: 32 MiB、addressSpaceMb: 512——每项预算单独都被 12× 门放行)写入约 33M 个不含换行符的星芒面字符(缓冲、未冲刷)后返回约 33M 个星芒面字符,断言该次运行以output-limit结算(该值本身就超过它 32 MiB 的预算);修复前未冲刷的日志 pending 加上值的构建加编码峰值会一起越过 512 MiB 地址空间而 OOM,因此在分帧值之前先冲刷日志正是让值检查得以完成的原因(仅 Linux 的 RLIMIT_AS 复现;在 macOS 上超预算的值在两种顺序下都报 output-limit)。一个宽完成值用例(maxValueBytes: 20 MiB、addressSpaceMb: 384)返回[0] * 6_000_000——JSON 约 12 MB、低于预算,因此必须成功往返;修复前 O(width) 的遍历为每个元素分配遍历元组与编码器栈项(约为序列化尺寸的 28×,超出门保留的 12×),在一个计量器已放行的值上 OOM,而 O(depth) 游标使唯一按宽度分配的只剩输出字符串本身。一个宽 binding 实参用例(addressSpaceMb: 384)以[0] * 6_000_000调用 binding 并断言长度回传:_lossless_json_violation运行在模型构造的实参上,子进程侧没有任何字节预算先行约束,其逐元素元组实测 459.1 MiB,而游标为 0.0 MiB。一个回溯用例从 binding 返回一个 4 MiB 字符串并断言其成功往返:旧的标量正则保留的引擎状态与字符串宽度成正比(1 MiB 时 146 MiB,4 MiB 时 557.8 MiB,超过默认的 512 MiB),会在_pump_replies内抛出 MemoryError 并把该次调用搁置到墙钟。一个 control-heavy 计费用例在 16 MiB 的maxValueBytes之下返回 8M 个 NUL,断言得到output-limit而非exception:以计数替代物化转义形式来计费,在字节数完全相同的前提下实测 19.1 MiB 对 228.9 MiB。一个 addressSpaceMb 下界用例断言 64 MiB 与 32 MiB 在加载期被拒绝,且消息点名addressSpaceMb,而不是预算循环给出的负数上限。一个进程身份用例断言 leader 的启动时刻在 Linux 上可稳定读取、在 Darwin 上报告 undefined,这正是使被复用的 pgid 不会收到本次运行 SIGTERM 的那道守卫。一个 paced-replies 用例在一轮asyncio.gather中 resolve 八条 4 MiB 的值,断言这些帧能够往返;本修复移除的峰值(32.0 MiB 缓冲 → 0.0 MiB)位于宿主 fd-3 可写缓冲内部、透过 seam 不可见,因此该用例钉住的是往返与无回归匹配,其峰值只能在树外度量。一个 late-drop 用例让该次运行在maxWallMs上结算,随后才 resolve 那个 pending 的 binding,断言得到一个timeout结果、一个 undefined 值、且迟到路径确实被执行过——这三条断言在修复前也全部成立,因为sendReply本就丢弃结算之后的值、只是更晚,因此该用例钉住的是顺序,而非一个透过 seam 可观测的行为。一个"绑定全部用名"用例(rebinds every name the failure path uses)断言一个真实的ValueError在 send-done 绑定之后仍然存活——这是一个有测修复,由一个逐名重绑__main__.ProtocolChannel.send_sync、__main__.ProtocolChannel.write_encoded与__main__._encode_json_plain的用例钉住——这三个名字正是 shipped 的send_done若做调用时刻查找时会迟解析的那三个——并钉住done帧不被一次调用时刻的查找跳过;而完成值的 TOCTOU 预编码、stray UTF-8 预算冲刷的扣留与结算后到达的迟到拒绝的 settled 先查,都被计入那十处无 fail-before 修复(理由见 Problem 段),不由 fail-before 测试钉住。 一个 fragment-cap 滴灌用例写入 200 000 次单字符无换行的sys.stdout.write调用,断言该次运行以截断标记完成而非 MemoryError(no-fail-before:25 M 规模的 OOM 无法在 CI 中确定性构造)。一个 dual-limit CPU 用例通过一个ulimit -t 2包装脚本运行解释器并忙循环越过它,断言得到timeout(软限制被降到 1,因此 SIGXCPU 触发,而非worker-exit)。一个传递名重绑用例重绑__main__._dump_scalar、__main__.os、__main__._os_write、__main__._memoryview与__main__._FALLBACK_DONE_FRAME,断言仍有一帧 done 以exception落地,而非worker-exit(真实消息被固定兜底字面量替换)。 一个 BaseException 重绑用例把__main__.BaseException重绑为RuntimeError并抛出ValueError,断言该次运行仍报告exception,而非worker-exit(catch 用的是程序运行前的局部异常类)。一个 RuntimeError 重绑闭环用例把__main__.RuntimeError重绑为ValueError作为程序首条语句,并驱动闭环 worker 模式,断言泵存活于死循环回复、投递后续 binding(泵的_RuntimeError是 def 期默认参数,因此在重绑前捕获原始值)。 一个_done_with_value重绑用例把__main__._done_with_value重绑为一个抛出函数并返回合法值,断言该次运行仍报告成功(入口名是程序运行前绑定的_run局部)。 一个sys.__stdout__flush 用例不经显式 flush 直接通过sys.__stdout__/sys.__stderr__写入,断言两个字节都出现在logs中(-u无缓冲子进程加上结算对原始 std 流的排空)。 宿主在 spawn 后立即关闭子进程的 stdin 写句柄(程序是不读 fd 0 的 async 函数体;存活的管道会在运行结束后继续持有宿主侧句柄,让继承 fd 0 的 setsid 逃逸后代拖住宿主进程)。通道的帧读取器把解码原语(_decode_json_plain、os.read、_READ_CHUNK_BYTES、bytes,以及len;异步读取器还有asyncio.get_event_loop)绑定为 def 期默认参数,因此__main__重绑无法杀死回复泵;_decode_json_plain自身以同样方式捕获json.loads/两个正则/len/isinstance/str/list。回复泵的帧读取器是_run在程序运行前捕获的绑定方法,以显式参数传入_pump_replies(函数体内的channel.read_frame_async查找会解析被重绑的类属性,因为泵在程序顶层语句之后才启动)。send_done的帧形判别使用_run绑定的_str/_isinstance(程序重绑__main__.isinstance无法让合法成功落入固定字面量兜底)。_make_error_class把Exception与setattr捕获为 def 期默认值,dispatch 把_lossless_json_violation/asyncio.get_event_loop/通道的 send 与 write 原语绑定进_run局部(帧写入走 def 期绑定的write_encoded+_encode_json_plain,而非send_sync的调用期函数体;日志 sink 直接走绑定的 encode+write 原语,而非 send_sync)——在首次 binding 调用前重绑这些名字无法破坏合法调用。compile(wrapped, ..., dont_inherit=True)阻止本模块的from __future__ import annotations把程序的类型注解字符串化。裸名pythonBin在 CURRENT 进程 PATH 上无法解析时现在于 LOAD 期失败('does not resolve on PATH',与空/NUL 检查一致):子进程以env: \{\}spawn,回退到裸名会让 execvp 从平台默认 PATH 静默启动一个调用方从未要求的系统解释器——这是从旧的运行期 ENOENT worker-exit 到早期、响亮的配置错误的可见行为变更。bootstrap 在任何模型代码运行前把 SIGXCPU 重置为SIG_DFL并解除屏蔽:子进程继承宿主的处置与掩码,忽略或屏蔽 SIGXCPU 的宿主会让程序越过软RLIMIT_CPU一直跑到硬限的 SIGKILL——把确定的超限分类成worker-exit而非timeout。(结算期 enforcer 已为在运行中 trap 或屏蔽信号的程序恢复SIG_DFL;这里补上继承态的缺口。)浮点编码器的Decimal(repr(value)).normalize()运行在模块加载期构造的固定_FLOAT_CONTEXT = Context(prec=28)上(在任何模型代码之前):进程全局 decimal context 否则会让合法程序的getcontext().prec = 2静默舍入完成值的数字,或让traps[Inexact] = True使编码抛异常、把成功运行误判为 exception。一个回归用例同时改动两个旋钮并断言浮点完成值精确往返。 宿主在toString/JSON.parse之前把 fd-3 帧的原始长度限制在 64 MiB(FRAME_PARSE_CAP_BYTES):256 MiB 线上上限约束的是字节而非解码后的结构,接近它的紧凑宽帧解码后可能占用远超线上字节的宿主内存。maxLogBytes/maxValueBytes在加载期被限制到该解析器上限,因此诚实子进程的帧总能放得下;模型构造的超限 binding 实参被丢弃(已登记在 README)。一个帧上限用例写入 65 MiB 的A加一个换行,断言得到携带 protocol-frame-exceeded 消息的worker-exit(pre-join 计数拒绝无换行的单帧;第一帧检查在 join 之前拒绝带换行的帧,使峰值保持在线上字节的一份拷贝——通过回退到无条件计数验证 fail-before)。一个_decode_json_plain重绑用例断言 binding 回复仍能往返;一个 stdin-EOF 用例让程序读取 fd 0 并断言它立即看到 EOF(被销毁的写句柄),通过禁用销毁验证 fail-before。一个帧上限用例写入 65 MiB 的A加一个换行,断言得到携带 protocol-frame-exceeded 消息的worker-exit(pre-join 计数拒绝无换行的单帧;第一帧检查在 join 之前拒绝带换行的帧,使峰值保持在线上字节的一份拷贝——通过回退到无条件计数验证 fail-before)。一个多帧用例让两个都在上限内、但合并缓冲越过上限的帧都存活(由第一帧检查而非字节计数决定);一个 sealing 阈值用例写入 64 MiB 的 4 KiB 原子无换行写,再加首个换行前的 12289 字节,断言该次运行为 worker-exit(sealing 是换行分支的 ELSE 半支,因此带换行的 chunk 总是抵达第一帧检查)。一个 pythonBin 用例把一个裸名解析到 PATH 首项为相对条目(.)的路径,断言使用绝对条目。 一个 exact-limit 用例(maxLogBytes: 64)写入一个 60 字符行(62 字节 JSON + 1 分隔符 = 63 = 预留后的账本)与一个 61 字符行(64 > 63),断言前者放行、后者截断为仅标记——钉住外层数组外壳预留的精确边界;一个配套用例断言maxLogBytes: 61在构造期被拒绝。一个语法标签用例断言解析期语法错误携带File "<model>"(ast.parse与 compile 及运行期 traceback 过滤使用同一来源标签)。一个 SIGXCPU 屏蔽用例屏蔽 SIGXCPU(pthread_sigmask)、越过软限并返回,断言得到timeout(复查在重投递前解除屏蔽);一个 trap+mask 配套用例安装一个重新屏蔽的自定义 handler 并断言同样的timeout(SIG_DFL 在 unblock 前恢复,因此挂起信号在内核内致死)。