十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ray 文档代码片段编写与 CI 自动化测试完整指南:doctest / testcode / literalinclude 三种示例格式全解析

Ray 文档代码片段编写与 CI 自动化测试完整指南:doctest / testcode / literalinclude 三种示例格式全解析 Ray 文档代码片段编写与 CI 自动化测试完整指南doctest / testcode / literalinclude 三种示例格式全解析【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray本文是 Ray 开源仓库中《How to write code snippets》writing-code-snippets.md的深度技术指南。它面向所有为 Ray 文档docstring 或用户指南贡献代码示例的开发者系统讲解如何编写可开箱即跑、并在 CI 中被自动化执行的代码片段三种示例格式doctest-style、code-output-style、literalinclude的语法与取舍、难以测试/输出不稳定的场景处理、GPU 示例的 Bazel 配置、本地验证方法以及失败示例的三类根因诊断。读完本文你将能写出与 Ray 官方文档同等质量、持续被 CI 守护的示例代码。前提说明本文示例的指令语法基于 reStructuredText.rst与文档 ray-contribute 系列 保持一致若使用 Markdown 编写则采用 MyST 语法可参考 MyST 官方文档中关于 directives 的说明。本仓库自 2.10.0 版本起新页面统一使用 MyST Markdown。一、三种示例格式定义与渲染效果Ray 文档的示例分为三种类型doctest-style交互式会话风格、code-output-style普通代码 独立输出块和literalinclude从外部模块文件引用。它们都会被 CI 执行但语法与适用场景不同。1. doctest-style 示例doctest-style模拟 Python 交互式会话代码行以开头预期输出紧随其后。在.rst中使用.. doctest::指令.. doctest:: def is_even(x): ... return (x % 2) 0 is_even(0) True is_even(1) False在 MyST Markdown 中渲染效果如下 def is_even(x): ... return (x % 2) 0 is_even(0) True is_even(1) False编写 docstring 时的简化写法如果你是在写 Python 模块/类的 docstring而非文档页面可以省略.. doctest::指令直接写缩进的块。pytest 的--doctest-modules会自动拾取代码更简洁def is_even(x): Return True if x is even. Example: def is_even(x): ... return (x % 2) 0 is_even(0) True is_even(1) False return (x % 2) 0这种写法在仓库源码中随处可见例如 python/ray/data/read_api.py 中range等数据读取 API 的 docstring import ray ds ray.data.from_items([1, 2, 3, 4, 5]) ds # doctest: ELLIPSIS ds.schema()2. code-output-style 示例code-output-style由一对指令组成.. testcode::存放普通 Python 代码.. testoutput::存放该代码的标准输出stdout.. testcode:: def is_even(x): return (x % 2) 0 print(is_even(0)) print(is_even(1)) .. testoutput:: True False渲染效果def is_even(x): return (x % 2) 0 print(is_even(0)) print(is_even(1))True False要点testcode中的代码不依赖提示符适合较长、面向过程的多行代码testoutput必须与testcode的实际 stdout逐字符一致包括换行与空白否则 CI 报错。3. literalinclude 示例literalinclude直接引用仓库中真实的.py模块文件用:start-after:/:end-before:按标记截取片段从源头上杜绝文档代码与示例文件不一致.. literalinclude:: ./doc_code/example_module.py :language: python :start-after: __is_even_begin__ :end-before: __is_even_end__其引用的实际文件是 doc/source/ray-contribute/doc_code/example_module.py# example_module.py # fmt: off # __is_even_begin__ def is_even(x): return (x % 2) 0 # __is_even_end__ # fmt: on渲染时只展示两个标记之间的代码def is_even(x): return (x % 2) 0注意doc_code/目录下的.py文件本身也是 CI 测试对象见下文构建层面的测试规则因此 literalinclude 是展示即测试——读者看到的每一行代码都真实存在于仓库并被 CI 跑过。二、如何选择示例类型没有硬性规则选择最能说明你 API 的风格即可。如果你不确定指南给出的默认建议是优先使用 code-output-styletestcodetestoutput因为它对输出格式的约束最宽松。什么场景用 doctest-style当示例很短且重点是展示对象的 repr 表示比如打印中间对象、展示 schema 结构时用 doctest-style。例如展示ray.data.range的 schema 与take结果.. doctest:: import ray ds ray.data.range(100) ds.schema() Column Type ------ ---- id int64 ds.take(5) [{id: 0}, {id: 1}, {id: 2}, {id: 3}, {id: 4}]这类对象表示即输出的用例逐行对比的格式天然适合。什么场景用 code-output-style当示例较长或对象的 repr 与示例主题无关时用 code-output-style。典型如端到端的批处理变换此例来自 Ray Data 文档展示了map_batches的真实输出.. testcode:: from typing import Dict import numpy as np import ray ds ray.data.read_csv(s3://anonymousair-example-data/iris.csv) # Compute a petal area attribute. def transform_batch(batch: Dict[str, np.ndarray]) - Dict[str, np.ndarray]: vec_a batch[petal length (cm)] vec_b batch[petal width (cm)] batch[petal area (cm^2)] np.round(vec_a * vec_b, 2) return batch transformed_ds ds.map_batches(transform_batch) print(transformed_ds.materialize()) .. testoutput:: shape: (150, 6) ╭───────────────────┬──────────────────┬───────────────────┬──────────────────┬────────┬───────────────────╮ │ sepal length (cm) ┆ sepal width (cm) ┆ petal length (cm) ┆ petal width (cm) ┆ target ┆ petal area (cm^2) │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ double ┆ double ┆ double ┆ double ┆ int64 ┆ double │ ╞═══════════════════╪══════════════════╪═══════════════════╪══════════════════╪════════╪═══════════════════╡ │ 5.1 ┆ 3.5 ┆ 1.4 ┆ 0.2 ┆ 0 ┆ 0.28 │ │ 4.9 ┆ 3.0 ┆ 1.4 ┆ 0.2 ┆ 0 ┆ 0.28 │ │ 4.7 ┆ 3.2 ┆ 1.3 ┆ 0.2 ┆ 0 ┆ 0.26 │ │ 4.6 ┆ 3.1 ┆ 1.5 ┆ 0.2 ┆ 0 ┆ 0.3 │ │ 5.0 ┆ 3.6 ┆ 1.4 ┆ 0.2 ┆ 0 ┆ 0.28 │ │ … ┆ … ┆ … ┆ … ┆ … ┆ … │ │ 6.7 ┆ 3.0 ┆ 5.2 ┆ 2.3 ┆ 2 ┆ 11.96 │ │ 6.3 ┆ 2.5 ┆ 5.0 ┆ 1.9 ┆ 2 ┆ 9.5 │ │ 6.5 ┆ 3.0 ┆ 5.2 ┆ 2.0 ┆ 2 ┆ 10.4 │ │ 6.2 ┆ 3.4 ┆ 5.4 ┆ 2.3 ┆ 2 ┆ 12.42 │ │ 5.9 ┆ 3.0 ┆ 5.1 ┆ 1.8 ┆ 2 ┆ 9.18 │ ╰───────────────────┴──────────────────┴───────────────────┴──────────────────┴────────┴───────────────────╯ (Showing 10 of 150 rows)注意这里的表格输出来自 polars 风格的 repr当输出格式随依赖库版本变化时更稳妥的做法是配合下文第 4 节的省略号策略只断言稳定片段。什么场景用 literalinclude当编写端到端示例、且示例本身不含输出时用 literalinclude。它最适合完整可运行脚本场景读者可以打开真实文件查看全貌CI 直接执行该文件双份维护成本为零。三、难以测试的示例怎么办何时允许不测试依赖外部系统的示例可以不测试例如需要 Weights Biases 账号的集成示例。判断标准是示例是否依赖网络、凭据或外部服务。跳过 doctest-style 示例在 Python 代码行尾追加# doctest: SKIP即可跳过该行该行代码不会被执行、其输出不会被校验.. doctest:: import ray ray.data.read_images(s3://private-bucket) # doctest: SKIPSKIP是 Python 标准 doctest 指令ELLIPSIS允许...模糊匹配等其它指令同样可用——read_api.py 中大量使用了这两种指令组合 ds ray.data.read_images(path) # doctest: SKIP ds ray.data.read_zarr( # doctest: SKIP ... s3://bucket/path.zarr, ... ... ... )跳过 code-output-style 示例给testcode块加:skipif: True选项整段代码将被跳过且不渲染输出断言.. testcode:: :skipif: True from ray.air.integrations.wandb import WandbLoggerCallback callback WandbLoggerCallback( projectOptimization_Project, api_key_file..., log_configTrue )api_key_file...需要替换为真实的凭据文件路径——正因为依赖外部服务才需要用:skipif: True跳过 CI 执行同时把代码留在文档中供用户参考。四、长输出或非确定性输出怎么处理当代码本身非确定如随机数、时间戳、分布式对象地址或输出过长时有三种策略省略号模糊匹配、模拟输出MOCK、完全省略输出块。doctest-style用省略号忽略部分输出把不稳定的部分替换为... import ray ray.data.read_images(s3://anonymousray-example-data/image-datasets/simple) Dataset(num_rows..., schema...)num_rows、schema的具体内容被...替代CI 只校验稳定的前缀与括号结构。需要说明的是doctest 要启用...通配需依赖ELLIPSIS指令标准 doctest 默认关闭实际项目中通常显式标注# doctest: ELLIPSIS见 read_api.py。要完全忽略输出不展示也不校验指南的明确建议是改写为 code-output-style 并省略testoutput块而不要使用# doctest: SKIP——因为 SKIP 是给依赖外部系统的场景用的滥用会掩盖真实的回归详见第六节。code-output-style三种输出策略策略 A——省略号模糊匹配把输出中长或不确定的部分替换为..... testcode:: import ray ds ray.data.read_images(s3://anonymousray-example-data/image-datasets/simple) print(ds) .. testoutput:: Dataset(num_rows..., schema...)策略 B——展示样例输出MOCK输出非确定、但你希望读者看到样例时给testoutput加:options: MOCK。此时 CI 不校验内容页面仍展示样例.. testcode:: import random print(random.random()) .. testoutput:: :options: MOCK 0.969461416250246策略 C——完全隐藏输出输出难测且无需展示时直接省略testoutput块代码仍会被执行、但 stdout 不做断言.. testcode:: print(This output is hidden and untested)五、用 GPU 测试示例Bazel doctest 规则配置当示例需要 GPU例如 Ray Data 的 GPU 批推理、Ray Train 的分布式训练时必须把它从默认的 CPU doctest 规则中排除并加入独立的 GPU doctest 规则。操作分五步第 1 步定位 BUILD 文件。示例位于doc/目录下则打开 doc/BUILD.bazel示例位于 Python 库目录如python/ray/train/则打开对应的 python/ray/train/BUILD.bazel。第 2 步找到doctest规则。它形如仓库中 doc/BUILD.bazel 的全局规则即如此doctest( files glob( include[source/**/*.rst], ), size large, tags [team:none] )第 3 步把你的示例文件加入 exclude 列表使其脱离 CPU 默认规则doctest( files glob( include[source/**/*.rst], exclude[source/data/requires-gpus.rst] ), tags [team:none] )第 4 步新建或复用gpu True的 doctest 规则doctest( files [], tags [team:none], gpu True )第 5 步把示例文件加入该 GPU 规则并视需要设置sizedoctest( files [source/data/requires-gpus.rst] size large, tags [team:none], gpu True )仓库中的真实对照在 doc/BUILD.bazel 中可以同时看到 CPU 与 GPU 规则的完整形态。doctest_each宏为 data 库的每个文档单独建一个测试目标并把batch_inference.rst、transforming-data.rst从 CPU 规则中排除后单独放进doctest[data-gpu]doctest_each( files glob( include [source/data/**/*.md, source/data/**/*.rst], exclude [ source/data/batch_inference.rst, source/data/transforming-data.rst, source/data/api/**/*.rst, ], ), pytest_plugin_file //python/ray/data:tests/doctest_pytest_plugin.py, tags [team:data], ) doctest( name doctest[data-gpu], files [ source/data/batch_inference.rst, source/data/transforming-data.rst, ], gpu True, pytest_plugin_file //python/ray/data:tests/doctest_pytest_plugin.py, tags [team:data], )Python 库侧同理python/ray/train/BUILD.bazel 中py_doctest[train]排除了 GPU 相关文件py_doctest[train-gpu]则用gpu True单独承接doctest( name py_doctest[train], size large, env {RAY_TRAIN_V2_ENABLED: 1, TF_USE_LEGACY_KERAS: 1}, files glob( [**/*.py], exclude [ examples/**, tests/**, horovod/**, mosaic/**, tensorflow/tensorflow_trainer.py, _internal/session.py, context.py, ], ), tags [team:ml], ) doctest( name py_doctest[train-gpu], size large, env {RAY_TRAIN_V2_ENABLED: 0}, files [_internal/session.py, context.py, tensorflow/tensorflow_trainer.py], gpu True, tags [team:ml], )底层原理doctest宏定义在 bazel/python.bzl 中。当gpu True时宏会把规则名追加[gpu]后缀、给标签追加gputag否则追加cpu并最终生成一个py_test目标其 pytest 参数为--doctest-modules拾取 docstring 中的示例--doctest-glob*.md额外拾取 Markdown/MyST 文档中的testcode块--disable-warnings、-v-c NO_PYTEST_CONFIG避免全局 pytest.ini 干扰 doctest-p pytest_plugin_file注入仓库自研的 pytest 插件默认是 bazel/default_doctest_pytest_plugin.pyRay Data 使用自己的 python/ray/data/tests/doctest_pytest_plugin.py。这些插件为示例执行提供确定性环境默认插件注册了 module 级别的ray.shutdown()fixture 保证测试间状态隔离Data 插件还把RAY_DATA_PARQUET_FOOTER_NUM_ACTORS设为 1避免 32 个 actor 的默认 footer 读取池触发 Ray 的worker 进程过多告警从而污染testoutput断言、固定preserve_order True、关闭执行启动横幅——这也是文档示例输出必须可复现的原因CI 已为输出确定性做了大量铺垫。六、本地验证pytest-sphinx 与 pytest --doctest-modulesCI 只是最后一道关提交 PR 前应先在本地跑通示例。Ray 使用自维护的pytest-sphinxfork正是它把.. testcode::/.. testoutput::翻译成 pytest 可执行的测试pip install githttps://github.com/ray-project/pytest-sphinx然后对模块、docstring 或用户指南分别运行 pytest# 测试整个模块的 docstring 示例 pytest --doctest-modules python/ray/data/read_api.py # 只测试某个函数/类的 docstring 示例 pytest --doctest-modules python/ray/data/read_api.py::ray.data.read_api.range # 测试文档页面.rst中的 testcode/doctest 块 pytest --doctest-modules doc/source/data/getting-started.rst注意第三条命令的路径映射doc/source/data/getting-started.rst正是仓库 doc/source/data 目录下的真实文件运行前需确保本地已安装 Raypip install -e .或使用编译好的 wheel以及示例所需的第三方依赖。七、调试失败的示例两类问题、三种意图CI 中示例失败时先回答两个问题这是什么类型的失败这个示例原本在保护什么第一步判断失败类型失败类型表现处理方式输出不匹配示例运行成功但 stdout 与testoutput或预期不符普通测试失败。本地pytest --doctest-modules file复现对比实际输出与预期块要么代码行为变了要么预期输出写错了修正其一构建期失败示例还没运行构建就中止import 错误、conf.py报错先修复构建错误再重读日志——中止之后的日志不可靠Sphinx 警告渲染网关将警告视为错误导致构建失败文档站点宿主Read the Docs的渲染门禁将警告视为错误指令格式错误或交叉引用失效都会使构建失败即使代码本身正确。这是标记语言问题而非代码问题参见 Read the Docs render gate第二步判断示例的保护意图失败示例是一个信号正确应对取决于它原本要防什么1. 破坏性变更探测器breaking-change detector。片段是用户可见代码因库行为变更而失效。此时应把失败当作真实信号要么不放行该变更要么带着破坏性变更通知发布。变更获批后必须同步更新示例使其匹配新行为保证页面与随版本发布的内容一致。真正错误的是只改示例不告知变更——那会向复制了旧示例的用户隐藏变更。2. 示例校验器example validator。示例本身错了笔误、坏合并、过期 import。直接修复示例即可不要误判为代码回归。3. 漂移指示器drift indicator。示例仍能运行但其周围的叙述文字已与代码实际行为脱节。此时更新叙述而不只是示例。同一个示例在不同时期可能以不同方式失败坏合并引入的IndentationError属于示例校验器失败修片段上游 API 变更导致的ImportError则是破坏性变更信号要修代码或对外沟通变更。失败文本会告诉你属于哪种情况。关于跳过与 docs-go 标签的边界让意图指导是否跳过# doctest: SKIP或:skipif: True只适用于依赖外部系统的示例用它们应对破坏性变更探测器是错误的——那会把真实破坏从用户眼前藏起来。同样的推理适用于docs-go标签它跳过整个 PR 的各库示例测试步骤是给纯叙述改动、不触碰任何示例的 PR 提供的便利通道不是绕过红色示例测试的捷径。标签由守卫步骤lint: validate docs-go scope约束仅当 PR 改动全部落在文档内容doc/下的.md/.rst与图片、仓库根的.vale.ini/.vale/、或 API 一致性检查器源码ci/ray_ci/doc/时才有效且需要写权限才能添加。八、CI 中的完整链路从 PR 到合入理解上述规则后可以串起 Ray 文档 CI 的完整视图详见 CI 测试工作流按路径路由文档示例测试按库路由——改动doc/source/ray-core/、ray-observability/跑core: docs example testsdata/、ray-more-libs/跑data: docs example teststrain/、tune/、ray-air/跑ml: docs example testsrllib/、serve/各有专属步骤。这些步骤执行的就是本文所述的doctest、testcode、literalinclude片段Per-library docs example tests。渲染门禁Read the Docs 以fail_on_warning: true构建站点任何 Sphinx 警告都会失败所以指令格式必须正确。纯叙述改动只改.md/.rst/图片的 PR 不触发任何库测试步骤可执行资产.py/.ipynb与示例消费的配置资产.yaml/.sh仍会路由到所属库。合入门禁合入前完整测试套件必须通过外部贡献者的 PR 需 committer 添加go标签触发全量测试。九、仓库实践速查三种格式的规范定义writing-code-snippets.mdCI 路由与 docs-go 标签ci.mddoctest/doctest_each宏实现bazel/python.bzl文档侧 doctest 规则含 contenteditable="false">【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表