ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

inngest 的 Go 测试并行拆分利器:gotesplit 分片原理、CI 集成与源码级解析

inngest 的 Go 测试并行拆分利器:gotesplit 分片原理、CI 集成与源码级解析 inngest 的 Go 测试并行拆分利器gotesplit 分片原理、CI 集成与源码级解析【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest本篇以 inngest 仓库中随依赖引入的gotesplit工具说明文档为主体完整讲解这个Go 测试分片运行器的命令用法、参数语义、分片算法与 JUnit 报告机制并结合 inngest 仓库的 CI 工作流和 vendored 源码展示它如何把一份go test集合拆成 N 份在 CI 矩阵上并行执行。读完你可以掌握gotesplit的完整命令行参数与 CI 接入方式、它在源码层面如何枚举/排序/切分测试、以及在 inngest 的 E2E 流水线中-total/-index/-junit-dir三个参数是如何串起 Codecov 报告上传的。一、gotesplit 是什么定位与核心工作流gotesplit README 对它的定义一句话gotesplit splits the testng in Go into a subset and run it把 Go 的测试集拆分成子集再运行它最典型的场景是在 CI 环境中并行跑测试。从源码看其核心工作流分两步入口在 Run枚举测试getTestListsFromPkgs 先执行go test -list . pkgs...解析输出中每个包下以Test或Example开头的行得到包 → 测试名列表的结构切分后逐包执行把所有测试摊平后按分片序号取一段再为每个涉及到的包生成一条go test ... -run ^(?:TestA|TestB|...)$命令依次执行切分与命令构造逻辑在 run 中。README 给出的最小示例直观体现了这一流程% gotesplit -total10 -index0 -- -v -short go test -v -short -run ^(?:TestAA|TestBB)$即声明总共 10 份、我是第 0 份gotesplit 会替你把第 0 份对应的测试名拼进-run正则并真正执行。二、命令用法与完整参数说明基本语法% gotesplit [options] [pkgs...] [-- go-test-arguments...]--之前的参数归 gotesplit 自己之后的全部透传给go test。参数一览README 列出的选项如下并补充了源码中实际注册的默认值与取值约束见 flag 定义参数说明默认值 / 约束-total uint测试分片总数若设置了环境变量CIRCLE_NODE_TOTAL会自动读取1-index uint分片序号从 0 开始若设置了CIRCLE_NODE_INDEX会自动读取0-junit-dir string将测试结果以 JUnit 格式写入该目录可选空不输出报告-coverprofile-dir string收集 coverprofile 的临时目录README 未列源码中存在.cover几个值得注意的实现细节均可在源码中验证CircleCI 环境变量自动探测Run 在解析命令行前会遍历total/index两个 flag若对应的大写环境变量CIRCLE_NODE_TOTAL、CIRCLE_NODE_INDEX非空则直接覆盖 flag 值——这正是 README 中在 CircleCI 上无需显式传-total/-index的原因。序号合法性校验run 开头即校验index total否则报错 indexshould be the range from 0 tototal-1。-tags与-race的感知detectTags 和 detectRace 会扫描--之后透传的 go test 参数。若检测到-race枚举阶段也会给go test -list加上-race见 getTestListsFromPkgs 注释避免同一代码编译两遍--tagsxxx同理透传给 list 命令保证枚举结果与真实构建一致。三、分片算法深度解析测试如何被均分这是理解 gotesplit 的关键。所有测试先被摊平成一个全局序列testListStrs每项为pkg:::testName见 run切分计算在 run.go#L72-L79testNum : uint(len(testListStrs)) minMemberPerGroup : testNum / total mod : testNum % total getOffset : func(i uint) uint { return minMemberPerGroup*i uint(math.Min(float64(i), float64(mod))) } from : getOffset(idx) to : getOffset(idx 1)算法要点每份至少testNum / total个测试余数mod由序号靠前的分片各多拿 1 个min(i, mod)的效果是前mod份各 1。因此当测试总数不能被份数整除时第 0 号分片永远最重第total-1号最轻。在 CI 上这意味着各矩阵任务的完成时间可能略有参差但不会差出一个完整测试以上的量级最多差 1 个测试。全局序列的排序规则决定了第 N 个测试落在哪一份定义在 getTestLists每个包内部的测试名按字典序排列sort.Strings(list)见 L132包与包之间按测试数量降序排列数量相同时再按包名升序L140-L146。测试多的大包会占据序列头部从而被前面的分片优先消化。整包优化切分之后addList 会比较某个包被分到的测试数与该包完整测试列表是否相等若相等说明该分片整包拿走就直接go test pkg不加-run过滤避免生成冗长正则testArgsList 构造。部分包则拼出run : ^(?: strings.Join(tl.list, |) )$ args append(args, -run, run, tl.pkg)四、JUnit 报告与覆盖率合并这是 inngest 在 CI 中实际用到的能力对应 README 的-junit-dir选项强制 verbose指定-junit-dir后run 会检测透传参数里是否已有-v没有就自动补上同时明确禁止与-json混用-json output and -junitDir cannot be specified at the same time。输出文件命名每次内部go test调用都会把输出经 TeeReader 同时写到终端和内存缓冲解析后落盘为junit-index-i.xmlrun.go#L161-L176其中index是本次分片序号、i是该分片内第几个包的调用。inngest 的 Codecov 上传步骤正是按此命名规则取junit-${{ matrix.index }}-0.xml。覆盖率合并若透传参数里有-coverprofilefilegotesplit 会把它从参数中剥离为每份go test生成独立的coverprofile_i写入-coverprofile-dir临时目录全部跑完后由 mergeCoverprofiles 合并回原目标文件——合并时会用正则^mode: [a-zA-Z]\n去掉后续文件重复的 mode 行run.go#L205-L216最后删除临时目录。五、安装方式与在 inngest 中的引入方式README 给出的通用安装途径保留原文语义# 安装最新版默认装到 ./bin/ % curl -sfL https://raw.githubusercontent.com/Songmu/gotesplit/main/install.sh | sh -s # 指定安装目录与版本 % curl -sfL https://raw.githubusercontent.com/Songmu/gotesplit/main/install.sh | sh -s -- -b $(go env GOPATH)/bin [vX.Y.Z] # go get % go get github.com/Songmu/gotesplit/cmd/gotesplitinngest 仓库采用的不是 curl 脚本而是 Go 原生的tool 指令方式且版本锁定为 v0.4.0go.mod#L297 声明tool github.com/Songmu/gotesplit/cmd/gotesplit同时 go.mod#L127 以// indirect形式依赖库本体github.com/Songmu/gotesplit v0.4.0CI 中用一行go install tool安装该工具见 .github/workflows/e2e.yml#L163-L164无需下载任何外部脚本。六、inngest 实战E2E 流水线中的分片矩阵inngest 的 .github/workflows/e2e.yml 是 README 中GitHub Actions 并行测试示例的落地版。Go SDK 的 E2E 任务矩阵如下e2e.yml#L117-L126golang: name: Go SDK / OS: (${{ matrix.os }}), ... / split: ${{ matrix.index }} strategy: fail-fast: false matrix: os: [depot-ubuntu-22.04] experimentalKeyQueues: [false, true] database: [sqlite, postgres] parallelism: [5] index: [0, 1, 2, 3, 4]parallelism: [5]对应-total5index: [0..4]让 Actions 矩阵展开出 5 个并行任务。测试执行步骤e2e.yml#L166-L178gotesplit -total ${{ matrix.parallelism }} -index ${{ matrix.index }} -junit-dir test-results ./tests/golang -- -v -count1这条命令完整覆盖了 gotesplit 的三大能力-total/-index声明分片身份、-junit-dir test-results输出 JUnit 报告、./tests/golang指定包、-- -v -count1透传给go test。报告随后被 Codecov 消费e2e.yml#L209-L216- name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/codecov-action... with: files: ./test-results/junit-${{ matrix.index }}-0.xml report_type: test_results文件名中的junit-index-0.xml与第四节讲的junit-index-i.xml命名规则严格对应inngest 对./tests/golang单个包执行整包或分包调用落在i0。另外注意该矩阵还叠加了database: [sqlite, postgres]与experimentalKeyQueues维度——每个数据库变体各有一套 5 路分片fail-fast: false保证某一份失败不拖累其他分片。七、README 官方 CI 集成示例除 inngest 实践外README 本身给出了两种平台的标准接法可直接参考。CircleCI自动读取环境变量parallelism: 5 docker: - image: circleci/golang:1.15.3 steps: - checkout - run: command: | curl -sfL https://raw.githubusercontent.com/Songmu/gotesplit/main/install.sh | sh -s bin/gotesplit ./... -- -v因为 CircleCI 会注入CIRCLE_NODE_TOTAL/CIRCLE_NODE_INDEX命令里不必显式传-total/-index。GitHub Actions矩阵显式传参name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: parallelism: [3] index: [0,1,2] steps: - uses: actions/setup-gov4 - uses: actions/checkoutv3 - name: Run tests parallelly run: | curl -sfL https://raw.githubusercontent.com/Songmu/gotesplit/main/install.sh | sh -s bin/gotesplit -total ${{ matrix.parallelism }} -index ${{ matrix.index }} ./... -- -v八、附加能力regexp子命令源码中 runner.go 注册了名为regexp的子命令README 未提及。用法形如gotesplit regexp total index [pkgs...]它不执行测试只打印该分片对应的-run正则cmd_regexp.go#L37-L66便于调试我这一份到底会跑哪些测试若分片为空则输出0^匹配不到任何测试保证go test -run 0^直接跳过。注意其实现目前只取第一个包的测试列表cmd_regexp.go#L48-L51多包场景下的输出可以推断仅反映首个包。九、使用要点小结-total默认 1、-index默认 0本地不带参数运行时等价于完整跑一遍index必须落在[0, total-1]越界直接报错。分片是按测试数均分而非按包或按耗时均分且前mod份多拿 1 个测试测试耗时分布不均时各分片时长会有自然波动。序列顺序 包内字典序 包间测试数降序、包名升序因此分片划分对新增测试敏感新增测试会平移后续测试的归属分片。-junit-dir与-json互斥需要覆盖率时在透传参数中写-coverprofilegotesplit 会自动分片收集并合并。inngest 的接入范式go.modtool 指令 go install tool 矩阵parallelism/index 按junit-index-i.xml命名上传报告是一个可复用的 CI 模板相关入口见 go.mod 与 .github/workflows/e2e.yml。【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表