☰
goo-engine 测试工具箱解析:用批量加载、算子轰炸与事件模拟挖掘 Blender 深层缺陷
2026/10/14 20:48:32 网站建设 项目流程
  • 图形学
  • 3D渲染

【免费下载链接】goo-engine

Custom build of blender with some extra NPR features.

项目地址:https://gitcode.com/gh_mirrors/go/goo-engine
点击查看免费下载

导读

本文围绕 goo-engine(一个带额外 NPR 特性的 Blender 定制构建)中 tests/utils/readme.rst 所定位的"测试工具"目录展开。该目录下的脚本不参与常规单元测试,而是专门用于压力测试与不可预测操作——例如批量加载海量.blend文件以暴露文件读取缺陷、在各种上下文里轮番执行全部算子以暴露崩溃、用可复现的用户输入模拟支撑git bisect定位回归、以及对文件导入器做模糊测试。读完本文,你将掌握这四类工具的完整命令行用法、参数语义、底层实现原理,以及它们如何被接入 goo-engine 的 CTest 自动化体系。

一、定位:为什么这些工具不进入单元测试

readme.rst开篇就给出了明确的设计哲学:这些测试不打算作为自动化单元测试的一部分运行,而是用来通过压力测试或其他难以预测的动作暴露问题——这类动作放进单元测试并不现实。文档列举了四个典型场景:

  • 从目录批量加载大量 blend 文件,暴露文件读取层面的问题;
  • 在各种上下文中运行算子,暴露崩溃;
  • 模拟用户输入,使需要用户交互的 bug 也能用git bisect定位;
  • 对文件导入器与文件格式支持做模糊测试(fuzz testing)。

文档末尾还留下一个伏笔:这些测试的精简版未来有机会被改造成正式单元测试。这与 goo-engine 的测试整体布局相印证——tests/CMakeLists.txt 将测试划分为 Python CTests、Blender-as-Python-module 测试与 GTest 三层,而 tests/utils 目录下的工具脚本正是游离在这三层之外的"重火力武器"。

二、场景一:批量加载 blend 文件暴露读取缺陷

对应脚本为 tests/utils/batch_load_blendfiles.py。它的思路非常朴素:在一个 Blender 实例中连续调用bpy.ops.wm.open_mainfile()打开大量文件,任何一处读取崩溃都会立刻暴露。

基本用法

blender --factory-startup --python ./tests/utils/batch_load_blendfiles.py

--factory-startup保证以出厂设置启动,避免用户自定义配置干扰结果。参数通过--分隔符传递给脚本自身:

blender --factory-startup --python ./tests/utils/batch_load_blendfiles.py -- \ --sort-by=SIZE --range=0:10 --wait=0.1

参数语义(对应源码 batch_load_blendfiles.py)

参数默认值说明
--blend-dir仓库上一级目录的lib递归搜索.blend文件的根目录;默认值在源码中以LIB_DIR常量计算
--sort-byPATH加载顺序,可选PATH或SIZE,取值由SORT_BY_FN映射表约束(L28-L31)
--range0:sys.maxsize形如beg:end的整数区间,源码用argparse_handle_int_range解析,必须含:分隔符,适合只加载已知出问题的文件区间
--wait-1.0两次加载之间的等待秒数;默认-1.0表示禁用等待、立即连续加载;设为正数则隐含重绘,甚至允许加载间隙的用户交互

两种加载模式

--wait=-1.0(默认)走load_files_immediately():循环打印(序号): 文件路径后立即wm.open_mainfile。--wait为正数时走load_files_with_wait():把加载逻辑注册进bpy.app.timers,每次定时器回调加载一个文件并返回等待秒数,全部加载完调用sys.exit(0)收尾。定时器模式下每加载一个文件前都会打印带序号的分隔横幅,便于对照崩溃时的最后一条输出。

三、场景二:多上下文轰炸全部算子

bl_run_operators.py 的定位在文件头写得很直白:在所有可能上下文里运行所有算子,是"发现零散小 bug 的廉价方式,但绝非完整测试",且唯一重点检查的错误是段错误(segfault)。

黑名单机制

不是所有算子都适合轰炸。源码维护了一个op_blacklist元组(L26-L102),用fnmatch通配匹配,主要屏蔽四类:

  • 慢操作:render.render、object.bake_image、ptcache.bake_all、nla.bake、object.quadriflow_remesh等,注释明确标注 "OK but slow";
  • IO 类:export*.*、import*.*、*.save_*、*.read_*、*.open_*、*.link_append,避免测试期间改动磁盘状态;
  • 弹外部程序/网页:image.external_edit、wm.url_open、wm.doc_view_manual等;
  • 已知需要特定前置条件:依赖撤销栈的object.voxel_remesh、需要活动窗口的scene.new等,归入 "FIXME: Crashes with non-trivial fixes" 注释块。

上下文矩阵

main()先遍历bpy.ops收集全部算子、排序并过滤黑名单,然后通过run_ops在不同setup_func下跑两遍(L472-L505)。上下文覆盖:空场景、物体模式、姿态模式、权重/顶点/雕刻/纹理绘制、网格编辑(含空网格)、骨骼编辑(含空骨骼)、曲线/曲面编辑、球体、文本、晶格、体积。每个上下文函数都先构造出该模式下的最小场景,例如ctx_editmode_mesh_extra会先添加顶点组、形态键、UV、材质槽再进入编辑模式,以制造更复杂的对象状态。

执行与随机化

对每个算子依次以EXEC_DEFAULT和INVOKE_DEFAULT两种模式调用,异常被吞掉(本工具只关心段错误)。默认每次执行前都会wm.read_factory_settings()重置场景并把帧范围压到 1–5 帧以限制烘焙耗时;开启USE_RANDOM后则按RANDOM_RESET=0.1(10%)的概率跳过重置、按RANDOM_MULTIPLY=10把算子列表复制并随机洗牌,RANDOM_SEED保证崩溃可复现。USE_FILES指向一个目录时,重置动作改为随机打开其中某个.blend文件,让轰炸在真实文件状态下进行。

该脚本在 tests/python/CMakeLists.txt 中被包装为script_run_operators测试,但受USE_EXPERIMENTAL_TESTS(默认FALSE)门控——这正是readme.rst所说"不进入常规单元测试、按需手动使用"的自动化体现。

四、场景三:模拟用户输入并支撑 git bisect

这是readme.rst中技术含量最高的场景。两套工具配合使用:

4.1 事件模拟驱动脚本

bl_run_operators_event_simulate.py 的核心能力是:从命令行生成事件序列,从而无需为每个用例手写脚本;其关键差异化特性是能控制模态算子(modal operators)。文档列举的适用场景包括:为基准测试/性能剖析构造可复现的用户交互(光标运动事件之间会打印耗时,可用于衡量优化)、回放复现 bug 的交互步骤、以及编写测试。

运行前提是 Blender 需以--enable-event-simulate启动(脚本头注释与 CMake 中的用法一致):

./blender.bin --factory-startup --enable-event-simulate \ --python tests/utils/bl_run_operators_event_simulate.py -- --help

4.2 actions 迷你语言

脚本用--actions参数接收一串"动作"字符串,每个动作是一条name(args, kw=val)形式的迷你指令,在解析阶段(BlenderAction._parse_value)就被校验参数合法性与算子存在性,错误会带下标提示。可用动作由action_handlers类的静态方法自动收集(L222-L410):

动作关键参数行为
area_maximizeui_type='VIEW_3D'定位屏幕中最大(或指定类型)的区域并最大化,先发鼠标移动再发Ctrl+Alt+Space
menutext='Visual Geometry to Mesh'通过 F3 搜索菜单项并回车执行
eventtype、value、ctrl/alt/shift、repeat发单个按键事件;value支持PRESS/RELEASE/TAP(TAP是 press+release 的便捷写法),type与value均从bpy.types.Event.bl_rna枚举动态校验
cursor_motionpath='CIRCLE'、steps、radius、repeat生成圆形光标运动轨迹;每个步骤是MOUSEMOVE事件,结束后打印平均 FPS 作为事件间耗时的性能参考
operatoridname+ 任意 kw 属性通过临时注册 F24 键位映射调用算子,kw 会写入键位映射的属性,从而支持传参(如number_cuts=5)

脚本头给出了两个完整示例。编辑模式旋转:

./blender.bin --factory-startup --enable-event-simulate \ --python tests/utils/bl_run_operators_event_simulate.py -- --actions \ 'area_maximize(ui_type="VIEW_3D")' \ 'operator("object.mode_set", mode="EDIT")' \ 'operator("mesh.select_all", action="SELECT")' \ 'operator("mesh.subdivide", number_cuts=5)' \ 'operator("transform.rotate")' \ 'cursor_motion(path="CIRCLE", radius=300, steps=100, repeat=2)'

雕刻笔触:

./blender.bin --factory-startup --enable-event-simulate \ --python tests/utils/bl_run_operators_event_simulate.py -- --actions \ 'area_maximize(ui_type="VIEW_3D")' \ 'event(type="FIVE", value="TAP", ctrl=True)' \ 'menu("Visual Geometry to Mesh")' \ 'menu("Frame Selected")' \ 'menu("Toggle Sculpt Mode")' \ 'event(type="WHEELDOWNMOUSE", value="TAP", repeat=2)' \ 'event(type="LEFTMOUSE", value="PRESS")' \ 'cursor_motion(path="CIRCLE", radius=300, steps=100, repeat=5)' \ 'event(type="LEFTMOUSE", value="RELEASE")'

另有--keep-open(模拟结束后保持窗口便于检查状态)与--time-actions(打印每个动作耗时)两个开关(L490-L512)。

4.3 实现原理:为何不直接调 Python API

脚本的 Implementation 一节解释了设计动机:虽然上述操作大多能直接用 Python 执行,但普通 Python 脚本在动作之间不会处理事件循环,而定时器方案又无法正确设置执行上下文。该工具让每个动作都像用户通过快捷键发起的一样被送入win.event_simulate(),事件通过bpy.app.timers逐个推进(run_event_simulate),模态算子因此得以在真实的事件驱动状态下运行。可复现的事件序列正是git bisect需要的:同一段--actions在好/坏提交上分别回放,即可二分定位引入崩溃的改动。

4.4 配套的 UI 模拟测试框架

与上述脚本配套的是 tests/python/ui_simulate 目录。入口 run.py 支持:

./tests/python/ui_simulate/run.py --blender=blender.bin --tests '*' # 指定编辑器跟随测试进度(每个事件前后调用,{file}/{line} 会被替换): ./tests/python/ui_simulate/run.py --blender=blender.bin --tests '*' \ --step-command-pre='gvim --remote-silent +{line} "{file}"'

run.py支持--jobs/-j并行、--list-tests列出可用测试 ID,并在环境中注入LSAN_OPTIONS=exitcode=0配合内存检测。每个测试由 run_blender_setup.py 在 Blender 内部加载执行——它通过--enable-event-simulate启动,逐条运行以模块.函数形式命名的测试,如test_undo.text_editor_simple。

测试本体(如 test_undo.py)用 easy_keys.py 提供的链式事件构建器书写。EventGenerate支持e.ctrl.z(5)(按 5 次 Ctrl+Z)、e.shift.d()(组合键)、e.leftmouse.cursor_motion(coords)(拖拽轨迹)、e.text("Hello")(逐字符键入,含大小写与符号的移位映射)等写法,配合unittest.TestCase断言做验证。以view3d_simple为例:添加平面、复制旋转 3 次、全选合并、细分、反复 undo/redo 并逐步断言多边形数量——这类交互密集型回归正是单元测试难以覆盖、而git bisect又必须可复现的典型场景。

五、场景四:模糊测试文件导入器

readme.rst提到的"fuzz testing file importers & file format support"对应 tests/python/batch_import.py。它把同一个导入算子批量施加到大量文件上,文件不合法或格式边缘化时最容易触发导入器崩溃。

# 批量导入 OBJ ./blender.bin --background --python tests/python/batch_import.py -- \ --operator="bpy.ops.import_scene.obj" \ --path="/fe/obj" --match="*.obj" \ --start=0 --end=10 --save_path=/tmp/test # 批量导入 3DS ./blender.bin --background --python tests/python/batch_import.py -- \ --operator="bpy.ops.import_scene.autodesk_3ds" \ --path="/fe/" --match="*.3ds" \ --start=0 --end=1000 --save_path=/tmp/test # 带插件的 SVG 导入 ./blender.bin --background --addons io_curve_svg --python tests/python/batch_import.py -- \ --operator="bpy.ops.import_curve.svg" \ --path="/usr/" --match="*.svg" \ --start=0 --end=1000 --save_path=/tmp/test

--operator指定导入算子、--path为递归扫描根目录、--match为通配文件名过滤(不区分大小写)、--start/--end控制文件区间、--save_path指定结果落盘位置。与batch_load_blendfiles.py的"逐个打开"不同,这里是"逐个导入",直接打击格式解析链路。

六、配套设施:无头图形环境

事件模拟需要真实 GUI 上下文,但开发者往往不希望窗口弹到自己桌面上。blender_headless.py 正是为此设计:它在独立显示服务器里启动图形版 Blender。模块文档明确指出其主要用例是运行模拟事件测试(即bl_run_operators_event_simulate.py和ui_simulate系列),目前只支持 WAYLAND。

运行方式是"wrapper"式:所有参数透传给 Blender,Blender 的退出码被原样转发。环境变量约定:

  • BLENDER_BIN:要运行的 Blender 可执行文件(默认blender,须在PATH中);
  • WESTON_BIN:weston 合成器路径(默认weston);
  • WAYLAND_ROOT_DIR/WESTON_ROOT_DIR:便携式 WAYLAND/WESTON 安装的根目录前缀(可留空,留空则用系统安装版)。

实现上,backend_wayland.run()以weston --socket=wl-blender-<pid> --backend=headless --width=800 --height=600拉起 headless 合成器(--backend=headless是关键:无需物理显示),并通过轮询XDG_RUNTIME_DIR下的 socket 文件等待服务器就绪,然后把WAYLAND_DISPLAY指向该 socket 启动 Blender。pid 后缀的 socket 命名支持并行运行多组测试。便携模式还会构造 weston 配置 INI 与WESTON_MODULE_MAP、合并LD_LIBRARY_PATH,让 Blender 能找到libwayland-cursor.so等库;配置中显式去掉面板(panel-position=none)以省出更多屏幕空间。

在 tests/python/CMakeLists.txt 中,WITH_UI_TESTS开启时通过add_blender_test_headless把这一整套接线起来:移除--background、用BLENDER_BIN指向测试安装的 Blender、必要时注入WESTON_BIN/WAYLAND_ROOT_DIR/WESTON_ROOT_DIR环境变量,并以-p 0 0 800 600固定窗口尺寸(注释说明:最大化窗口在 headless weston 下有问题,窗口模式更稳)。_undo_tests列表(L1019-L1034)把test_undo中的 14 个交互测试逐一注册为ui_<test>CTest 用例。

七、使用边界与后续演进

  • 定位边界:这些工具是"测试的测试"——它们不产出稳定的通过/失败结论,而是以"没崩"为最低验收标准(bl_run_operators.py明确只查 segfault),因此不适合塞进常规 CI 的单元测试通道。
  • 可复现优先:事件模拟与随机种子(RANDOM_SEED)的设计都把"能重现崩溃现场"放在首位,这正是服务于git bisect的根本原因。
  • 平台限制:无头图形包装目前仅 WAYLAND(blender_headless.py的main()在 darwin/win32 上落到backend_base的"无可用后端"报错路径),其他系统待后续补充。
  • 演进方向:readme.rst末尾的"可将精简版转成单元测试"并非空话——ui_simulate中easy_keys+unittest断言的结构已经是向正式测试靠拢的雏形;而tests/python/CMakeLists.txt中以USE_EXPERIMENTAL_TESTS门控、默认关闭的做法,也为这类重负载工具保留了渐进转正的通道。

结语

goo-engine 的tests/utils目录提供了一整套"单元测试之外"的缺陷挖掘手段:批量加载暴露读取问题、多上下文算子轰炸暴露崩溃、事件模拟把用户交互变成可git bisect的剧本、批量导入对格式解析做模糊测试,外加 WAYLAND headless 包装让这一切可在无显示环境下运行。理解这套工具的定位与用法,不仅有助于在 goo-engine 及同类 Blender 定制构建上做稳定性验证,也为任何"交互密集型软件"的回归定位提供了一套可借鉴的工程范式。

  • 图形学
  • 3D渲染

【免费下载链接】goo-engine

Custom build of blender with some extra NPR features.

项目地址:https://gitcode.com/gh_mirrors/go/goo-engine
点击查看免费下载

相关推荐

上一篇:InterviewGuide 精选题解:LeetCode 1221 分割平衡字符串——贪心计数与栈实现的三种 C++ 解法
下一篇:OpenObserve 权限管理:3 种内置角色 + 用户组 + 自定义角色,如何快速搭好数据访问控制

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询