implement-conformance-testing-script

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Implement Conformance Testing Script

实现Conformance测试脚本

This skill produces a single executable script that runs the conformance tests for a generated build folder, following a consistent, language-agnostic pattern.
The reference implementations are:
  • assets/run_conformance_tests_java.sh — Java, install-inline variant.
  • assets/run_conformance_tests_python.sh — Python, install-inline variant.
  • assets/run_conformance_tests_<lang>.ps1 — Windows PowerShell equivalents.
Read both before writing anything — every script you produce must be a faithful translation of the same pattern into the target language's tooling and the user's shell environment.
本Skill生成一个可执行脚本,用于为生成的构建文件夹运行conformance测试,遵循一致的、与语言无关的模式。
参考实现如下:
  • assets/run_conformance_tests_java.sh — Java,内置安装型变体。
  • assets/run_conformance_tests_python.sh — Python,内置安装型变体。
  • assets/run_conformance_tests_<lang>.ps1 — Windows PowerShell等效版本。
在编写任何内容之前,请先阅读上述所有脚本——你生成的每个脚本都必须将相同的模式忠实地转换为目标语言的工具链以及用户的Shell环境。

How conformance scripts differ from unit-test scripts

Conformance脚本与单元测试脚本的区别

A conformance script is structurally very close to a unit-test script (see the sibling skill
implement-unit-testing-script
) but with two important differences:
  1. Two positional arguments instead of one. A conformance script takes both the build folder (source under test) and a separate conformance tests folder (the tests to execute against that build).
  2. Tests are loaded from outside the working folder. The build is staged into
    .tmp/<lang>_<arg>
    and the script
    cd
    s into it, but the test command is pointed at the original
    $current_dir/<conformance_tests_folder>
    . Tests are never copied into the staging area.
Everything else — toolchain check, build staging, dependency isolation, exit codes — is the same.
Conformance脚本在结构上与单元测试脚本非常相似(请查看姊妹Skill
implement-unit-testing-script
),但有两个重要区别:
  1. 两个位置参数而非一个。Conformance脚本同时接收构建文件夹(被测源码)和一个独立的conformance测试文件夹(用于针对该构建执行的测试)。
  2. 测试从工作文件夹外部加载。构建被暂存到
    .tmp/<lang>_<arg>
    中,脚本
    cd
    进入该文件夹,但测试命令指向原始
    $current_dir/<conformance_tests_folder>
    。测试永远不会被复制到暂存区域。
其他所有内容——工具链检查、构建暂存、依赖隔离、退出码——都是相同的。

Variant decision: install-inline vs. activate-only

变体选择:内置安装型 vs 仅激活型

Before writing anything, decide which variant to emit. Both variants share toolchain check, arg validation, cwd capture, test execution, and exit-code handling — they differ only in the middle (steps 4–7 of the pattern below).
Look for an existingEmit
prepare_environment_<lang>.sh
/
.ps1
in the project's
test_scripts/
folder (or wherever
config.yaml
's
prepare-environment-script:
key points)
Activate-only variant. Verifies the prepared env, activates it, and runs tests. Does not stage the build or install deps — prepare already did.
Nothing — no prepare scriptInstall-inline variant. Stages the build, installs deps, and runs tests in one shot.
在编写任何内容之前,请确定要生成的变体。两种变体共享工具链检查、参数验证、当前工作目录捕获、测试执行和退出码处理——它们仅在中间步骤(下文模式中的第4-7步)有所不同。
查找是否存在已有脚本生成对应变体
项目
test_scripts/
文件夹中(或
config.yaml
prepare-environment-script:
键指向的任何位置)存在
prepare_environment_<lang>.sh
/
.ps1
仅激活型变体。验证已准备好的环境,激活它并运行测试。会暂存构建或安装依赖——prepare脚本已完成这些操作。
无——不存在prepare脚本内置安装型变体。一次性完成构建暂存、依赖安装和测试运行。

Why this split exists

为何要拆分这两种变体

The conformance runner is invoked once per functional spec by the renderer. Each functional spec in a module has its own
conformance_tests/<module>/<spec>/
folder, and after the renderer finishes generating code for a new spec, it runs the conformance tests of every previous spec in the same module to detect regressions. For a module with N functional specs, this script is called on the order of N times per render — not once per render.
That per-spec invocation pattern is what makes the install step expensive. A naive runner that does
pip install
/
npm ci
/
mvn install -DskipTests
/
cargo build
on every invocation pays the install cost N times per render. For anything beyond a toy project, that cost dominates wall-clock time.
The two variants are a direct response to this:
  • Install-inline is correct only when N is small (a few specs) or dependencies are cheap. It is self-contained: stage, install, run, repeat from scratch every invocation.
  • Activate-only is the production answer.
    prepare_environment_<lang>
    runs once per render and pays the install cost a single time, populating
    .tmp/<lang>_<arg>/
    with the warmed environment. Each of the N conformance invocations then just attaches to that working folder and runs the tests — no install, no compile, just activate-and-go.
Why picking the right variant matters: if you emit the install-inline variant alongside an existing prepare script, prepare's work is wiped (by the script's
rm -rf .tmp/<lang>_$1
) or duplicated (by re-running install) on every run — defeating prepare's whole purpose. Conversely, emitting activate-only without a prepare script means the "verify prepared environment" check fails on every run because nothing has populated the working folder. See Anti-Patterns.
Conformance运行器由渲染器针对每个功能规范调用一次。模块中的每个功能规范都有自己的
conformance_tests/<module>/<spec>/
文件夹,当渲染器完成新规范的代码生成后,它会运行同一模块中所有先前规范的conformance测试以检测回归问题。对于一个包含N个功能规范的模块,该脚本在每次渲染时会被调用大约N次——而非每次渲染调用一次。
这种按规范调用的模式使得安装步骤变得昂贵。如果在每次调用时都执行
pip install
/
npm ci
/
mvn install -DskipTests
/
cargo build
的朴素运行器,每次渲染都会付出N次安装成本。对于非玩具项目,这一成本会占据大部分运行时间。
两种变体正是针对这一问题的解决方案:
  • 内置安装型仅在N较小(几个规范)或依赖安装成本较低时适用。它是自包含的:暂存、安装、运行,每次调用都从头重复。
  • 仅激活型是生产环境的解决方案。
    prepare_environment_<lang>
    每次渲染时运行一次,只需付出一次安装成本,将预热后的环境填充到
    .tmp/<lang>_<arg>/
    中。随后的N次conformance调用只需连接到该工作文件夹并运行测试——无需安装、无需编译,只需激活即可运行。
选择正确变体的重要性:如果在已有prepare脚本的情况下生成内置安装型变体,prepare脚本的工作成果会在每次运行时被脚本的
rm -rf .tmp/<lang>_$1
清除(或通过重新运行安装重复执行)——完全违背了prepare脚本的初衷。反之,如果在没有prepare脚本的情况下生成仅激活型变体,“验证已准备环境”检查会在每次运行时失败,因为没有任何内容填充工作文件夹。请查看反模式

Pick the Shell First

先选择Shell

Before writing anything, decide which shell flavor the script must target — it depends on the user's environment, not on the language:
  • Bash (
    .sh
    )
    — macOS, Linux, WSL, CI runners on Linux. Default unless the user is on native Windows.
  • PowerShell (
    .ps1
    )
    — native Windows / PowerShell-only environments.
If you can't tell from the project (no obvious OS hints, no existing scripts), ask the user.
The same pattern applies to both. Only the syntax changes.
在编写任何内容之前,请确定脚本必须针对的Shell类型——这取决于用户的环境,而非编程语言:
  • Bash(
    .sh
    — macOS、Linux、WSL、Linux环境下的CI运行器。默认选择,除非用户使用原生Windows环境。
  • PowerShell(
    .ps1
    — 原生Windows / 仅PowerShell环境。
如果无法从项目中判断(没有明显的操作系统提示,没有现有脚本),请询问用户。
两种Shell适用相同的模式,仅语法有所不同。

The Pattern

模式

Steps 1–3 and step 8 are identical in both variants. Steps 4–7 differ — pick the subsection below that matches the variant you decided on.
第1-3步第8步在两种变体中完全相同。第4-7步有所不同——请选择与你确定的变体匹配的子章节。

Common steps (both variants)

通用步骤(两种变体)

  1. Toolchain check. Verify that the required language runtime / build tool (and the required version, if any) is installed. If not, print an error and exit with code
    69
    .
  2. Argument validation. Require two positional arguments:
    <build_folder>
    and
    <conformance_tests_folder>
    . If either is missing, print usage and exit with code
    69
    .
  3. Capture original cwd. Store
    pwd
    in a variable (
    current_dir
    /
    $PWD
    ) before changing directories — the test command in step 8 needs it to resolve the conformance tests folder.
  1. 工具链检查。验证所需的语言运行时/构建工具(以及任何所需的版本)已安装。如果未安装,打印错误信息并以代码
    69
    退出。
  2. 参数验证。需要两个位置参数:
    <build_folder>
    <conformance_tests_folder>
    。如果任一参数缺失,打印使用说明并以代码
    69
    退出。
  3. 捕获原始工作目录。在更改目录之前,将
    pwd
    存储到变量中(
    current_dir
    /
    $PWD
    )——第8步中的测试命令需要它来解析conformance测试文件夹。

Steps 4–7 — install-inline variant (no prepare script)

第4-7步——内置安装型变体(无prepare脚本)

  1. Working directory setup. Define a working folder at
    .tmp/<lang>_<arg1>
    . Wipe it (
    rm -rf
    /
    Remove-Item -Recurse -Force
    ) and recreate it. This folder — and only this folder — is where every subsequent write must land.
  2. Copy the build. Recursively copy everything from
    <build_folder>
    (
    $1
    ) into the working folder. Do not copy the conformance tests — they stay where they are. After this step both
    $1
    (build folder) and
    $2
    (conformance tests folder) are treated as read-only for the rest of the script.
  3. Enter the working directory.
    cd
    /
    Set-Location
    into
    .tmp/<lang>_<arg1>
    . If that fails, exit with code
    69
    . All remaining steps run from inside the working folder; they must never write back to
    $1
    or
    $2
    .
  4. Install dependencies into an isolated environment inside
    .tmp/<lang>_<arg1>
    .
    Set up a per-working-folder dependency location (a Python venv at
    ./.venv
    , a local
    ./node_modules
    , a project-scoped Maven repo at
    ./.m2
    , etc.) and install/resolve all dependencies into it. Never install into the source build folder (
    $1
    ), the conformance tests folder (
    $2
    ), the user's global cache (
    ~/.m2
    , system-wide
    pip
    ,
    ~/.cargo
    ,
    ~/.npm
    , ...), or anywhere outside
    .tmp/<lang>_<arg1>
    . If the install command fails, propagate its exit code immediately and do not proceed to step 8. See Dependency isolation (install-inline).
  1. 工作目录设置。定义工作文件夹为
    .tmp/<lang>_<arg1>
    。清除该文件夹(
    rm -rf
    /
    Remove-Item -Recurse -Force
    )并重新创建。此后所有写入操作都必须仅在该文件夹中进行。
  2. 复制构建内容。将
    <build_folder>
    $1
    )中的所有内容递归复制到工作文件夹中。不要复制conformance测试——它们保持原位。此步骤完成后,
    $1
    (构建文件夹)和
    $2
    (conformance测试文件夹)在脚本剩余部分中均被视为只读
  3. 进入工作目录。使用
    cd
    /
    Set-Location
    进入
    .tmp/<lang>_<arg1>
    。如果失败,以代码
    69
    退出。剩余所有步骤均在工作文件夹内运行;绝不能写回
    $1
    $2
  4. .tmp/<lang>_<arg1>
    内的隔离环境中安装依赖
    。设置每个工作文件夹的依赖位置(Python的venv位于
    ./.venv
    ,本地
    ./node_modules
    ,项目范围的Maven仓库位于
    ./.m2
    等)并将所有依赖安装/解析到其中。绝不能安装到源码构建文件夹(
    $1
    )、conformance测试文件夹(
    $2
    )、用户的全局缓存(
    ~/.m2
    、系统级
    pip
    ~/.cargo
    ~/.npm
    等)或
    .tmp/<lang>_<arg1>
    之外的任何位置。如果安装命令失败,立即传播其退出码,不要继续执行第8步。请查看依赖隔离(内置安装型)

Steps 4–7 — activate-only variant (prepare script exists)

第4-7步——仅激活型变体(存在prepare脚本)

  1. Verify the prepared environment. Both:
    • Check that the working folder
      .tmp/<lang>_<arg1>
      exists.
    • Check that the language's isolation location inside it exists (e.g.
      .venv/bin/activate
      for Python,
      .m2/
      for Java,
      node_modules/
      for Node,
      .gocache/
      for Go,
      .cargo/
      for Rust).
    If either check fails, print a helpful error (
    "Error: prepared environment missing — did you run prepare_environment_<lang>.<sh|ps1> first?"
    ) and exit
    69
    . Do not silently fall back to creating it inline — that would mask a real misconfiguration and turn this script into the install-inline variant in disguise. After this step both
    $1
    and
    $2
    are treated as read-only for the rest of the script.
  2. Enter the working directory.
    cd
    /
    Set-Location
    into
    .tmp/<lang>_<arg1>
    . If that fails, exit
    69
    . All remaining steps run from inside the working folder; they must never write back to
    $1
    or
    $2
    .
  3. Activate the prepared dependency environment. Per-language:
    • Python:
      source .venv/bin/activate
      (must succeed; exit
      69
      on failure).
    • Java: set
      MAVEN_LOCAL_REPO="$(pwd)/.m2"
      so it can be passed as
      -Dmaven.repo.local="$MAVEN_LOCAL_REPO"
      to
      mvn
      in step 8.
    • Node.js / Go / Rust: nothing to activate explicitly — the test command in step 8 just needs to receive the same isolation flag/env var that prepare used (
      ./node_modules
      is found by default; pass
      GOMODCACHE
      /
      CARGO_HOME
      ).
    Activation is always relative to the working folder, never to
    $1
    or
    $2
    — prepare populated
    .tmp/<lang>_<arg1>/...
    , and that is the only place to attach to.
  4. (There is no step 7 in this variant — install was prepare's job. Skip straight to step 8.)
  1. 验证已准备环境。需同时检查:
    • 工作文件夹
      .tmp/<lang>_<arg1>
      是否存在。
    • 其中的语言隔离位置是否存在(例如Python的
      .venv/bin/activate
      ,Java的
      .m2/
      ,Node的
      node_modules/
      ,Go的
      .gocache/
      ,Rust的
      .cargo/
      )。
    如果任一检查失败,打印清晰的错误信息(
    "错误:已准备环境缺失——你是否先运行了prepare_environment_<lang>.<sh|ps1>?"
    )并以
    69
    退出。不要静默回退到内置创建环境——这会掩盖真实的配置错误,并将此脚本变相转换为内置安装型变体。此步骤完成后,
    $1
    $2
    在脚本剩余部分中均被视为只读
  2. 进入工作目录。使用
    cd
    /
    Set-Location
    进入
    .tmp/<lang>_<arg1>
    。如果失败,以
    69
    退出。剩余所有步骤均在工作文件夹内运行;绝不能写回
    $1
    $2
  3. 激活已准备的依赖环境。针对不同语言:
    • Python:
      source .venv/bin/activate
      (必须成功;失败则以
      69
      退出)。
    • Java:设置
      MAVEN_LOCAL_REPO="$(pwd)/.m2"
      ,以便在第8步中作为
      -Dmaven.repo.local="$MAVEN_LOCAL_REPO"
      传递给
      mvn
    • Node.js / Go / Rust:无需显式激活——第8步中的测试命令只需接收与prepare脚本相同的隔离标志/环境变量(默认会找到
      ./node_modules
      ;传递
      GOMODCACHE
      /
      CARGO_HOME
      )。
    激活操作始终相对于工作文件夹,绝不能相对于
    $1
    $2
    ——prepare脚本已填充
    .tmp/<lang>_<arg1>/...
    ,这是唯一可连接的位置。
  4. (此变体无第7步——安装是prepare脚本的工作。直接跳至第8步。)

Common step 8 (both variants)

通用第8步(两种变体)

  1. Run the conformance tests. Invoke the language's standard test command, pointed at
    $current_dir/<conformance_tests_folder>
    (the original cwd from step 3 + the second arg). The script's final exit code is whatever the test command returns — except for the "no tests discovered" case below.
    The test command is read-only with respect to
    $current_dir/$2
    . It loads test files from there, but any artifacts the runner produces (caches, JUnit XML, coverage reports, compiled test classes, etc.) must land inside
    .tmp/<lang>_<arg1>
    , not next to the test files. If your chosen runner defaults to writing output beside the tests, pass an explicit output-directory flag pointing inside the working folder (e.g.
    pytest --basetemp=./.pytest_tmp
    ,
    jest --cacheDirectory=./.jest_cache
    , Maven
    target/
    under
    .tmp
    via
    mvn -f "$current_dir/$2/pom.xml" -Dproject.build.directory="$(pwd)/target" test
    ).
  1. 运行conformance测试。调用语言的标准测试命令,指向
    $current_dir/<conformance_tests_folder>
    (第3步中捕获的原始工作目录 + 第二个参数)。脚本的最终退出码即为测试命令的返回码——除了下文的“未发现测试用例”情况。
    测试命令相对于
    $current_dir/$2
    必须是只读的。它从该位置加载测试文件,但运行器生成的任何工件(缓存、JUnit XML、覆盖率报告、编译后的测试类等)必须存放在
    .tmp/<lang>_<arg1>
    内,而非测试文件旁边。如果所选运行器默认将输出写入测试文件旁边,请传递显式的输出目录标志,指向工作文件夹内部(例如
    pytest --basetemp=./.pytest_tmp
    jest --cacheDirectory=./.jest_cache
    ,通过
    mvn -f "$current_dir/$2/pom.xml" -Dproject.build.directory="$(pwd)/target" test
    将Maven的
    target/
    放在
    .tmp
    下)。

Read-only inputs — hard rule

只读输入——硬性规则

A conformance script has two read-only inputs: the source build folder (
$1
) and the conformance tests folder (
$2
). Neither one may be written to under any circumstances. The script must never:
  • install dependencies into
    $1
    or
    $2
    (no
    pip install
    inside
    $1
    /
    $2
    , no
    npm install
    inside them, no
    mvn install
    writing into them, no Cargo build artifacts ending up under them),
  • write a virtualenv /
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    directory inside
    $1
    or
    $2
    ,
  • run the test command with its
    cwd
    set to
    $1
    or
    $2
    (every test command runs from inside
    .tmp/<lang>_<arg1>
    after the
    cd
    in step 6 / activate-only step 5),
  • create logs, caches, build outputs, JUnit XML, coverage reports, compiled test classes, or temp files inside
    $1
    or
    $2
    .
Why each input is read-only:
  • $1
    (build folder)
    is shared with the renderer (
    plain_modules/...
    by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. The whole point of staging into
    .tmp/
    is so the source folder stays a clean, reproducible artifact of the render.
  • $2
    (conformance tests folder)
    is the user's authored test source — typically checked into version control. Writing into it pollutes the working tree, churns git status, and (with frameworks that auto-discover) can make subsequent runs pick up generated files as if they were tests.
If you find yourself about to issue any command whose
cwd
is
$1
or
$2
, or whose target path starts with
$1/
or
$2/
, stop. Either move the operation into
.tmp/<lang>_<arg1>
, or you're doing something the script must not do.
Conformance脚本有两个只读输入:源码构建文件夹(
$1
)和conformance测试文件夹(
$2
)。在任何情况下都不得写入这两个文件夹。脚本绝不能:
  • 将依赖安装到
    $1
    $2
    中(不得在
    $1
    /
    $2
    内运行
    pip install
    npm install
    mvn install
    ,不得让Cargo构建产物存放在它们下方),
  • $1
    $2
    内创建虚拟环境 /
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    目录,
  • $1
    $2
    作为当前工作目录的情况下运行测试命令(所有测试命令均在第6步/仅激活型第5步的
    cd
    操作后,从
    .tmp/<lang>_<arg1>
    内部运行),
  • $1
    $2
    内创建日志、缓存、构建输出、JUnit XML、覆盖率报告、编译后的测试类或临时文件。
为何每个输入都是只读的:
  • **
    $1
    (构建文件夹)**与渲染器(默认是
    plain_modules/...
    )和下游工具共享。写入其中会破坏渲染器对“生成内容”的认知,并影响后续渲染。将内容暂存到
    .tmp/
    的核心目的就是保持源码文件夹为渲染生成的干净、可复现的产物。
  • **
    $2
    (conformance测试文件夹)**是用户编写的测试源码——通常已纳入版本控制。写入其中会污染工作树,导致git状态频繁变动,并且(对于自动发现测试的框架)可能使后续运行将生成的文件当作测试用例。
如果你发现自己要执行的命令的当前工作目录是
$1
$2
,或者目标路径以
$1/
$2/
开头,请停止。要么将操作移至
.tmp/<lang>_<arg1>
内,要么你正在执行脚本不允许的操作。

"No tests discovered" detection

“未发现测试用例”检测

The Python reference script grep's the test runner output for
"Ran 0 tests in"
and exits
1
if no tests ran. Replicate the equivalent check for the target language wherever that language's test runner silently passes when given an empty test set:
  • Python
    unittest
    :
    "Ran 0 tests in"
  • Node.js
    jest
    :
    "No tests found"
  • Go
    go test
    :
    "no test files"
    /
    "no tests to run"
  • Rust
    cargo test
    :
    "running 0 tests"
  • Java
    mvn test
    : usually fails loudly already; no extra check needed.
A silently-passing zero-test run is the most dangerous failure mode of a conformance runner — always guard against it. This applies to both variants.
Python参考脚本会在测试运行器输出中搜索
"Ran 0 tests in"
,如果没有测试运行则以
1
退出。对于目标语言,请复制等效的检查逻辑,当该语言的测试运行器在给定空测试集时静默通过的情况:
  • Python
    unittest
    "Ran 0 tests in"
  • Node.js
    jest
    "No tests found"
  • Go
    go test
    "no test files"
    /
    "no tests to run"
  • Rust
    cargo test
    "running 0 tests"
  • Java
    mvn test
    :通常会直接报错;无需额外检查。
静默通过的零测试运行是conformance运行器最危险的失败模式——始终要对此进行防范。此规则适用于两种变体

Conventions

约定

Shared across both shell flavors and both variants:
  • Exit codes:
    • 69
      — unrecoverable invocation error: missing argument, missing toolchain, can't enter working folder, can't create venv (install-inline), or prepared environment missing/broken (activate-only). Matches the reference scripts'
      UNRECOVERABLE_ERROR_EXIT_CODE
      .
    • 1
      — "no tests discovered" guard tripped (see above).
    • Any other non-zero code — propagated from the underlying test command.
  • Working folder naming:
    .tmp/<lang>_<arg1>
    where
    <lang>
    is a short identifier for the language (
    java
    ,
    python
    ,
    node
    ,
    go
    ,
    rust
    , ...). Use the first argument (the build folder) in the path, never the conformance tests folder. All dependency installs, build outputs, caches, test runner artifacts, and the test invocation itself live inside this folder. Nothing the script does should touch
    $1
    after step 5 (install-inline) / step 4 (activate-only), or
    $2
    at any point.
  • Logging: print short progress lines (
    "Preparing <lang> build subfolder: ..."
    ,
    "Activating prepared virtual environment..."
    ,
    "Running <lang> conformance tests..."
    ) so failures are easy to triage. Wrap noisy "preparing" lines in a
    VERBOSE
    check if matching the Python reference.
  • Capture
    current_dir
    before
    cd
    .
    This is the single most common bug in hand-written conformance scripts: forgetting that the conformance tests folder argument is relative to the invocation directory, not the working folder.
适用于两种Shell类型以及两种变体:
  • 退出码
    • 69
      — 不可恢复的调用错误:缺失参数、缺失工具链、无法进入工作文件夹、无法创建venv(内置安装型),或已准备环境缺失/损坏(仅激活型)。与参考脚本的
      UNRECOVERABLE_ERROR_EXIT_CODE
      一致。
    • 1
      — 触发“未发现测试用例”防护(见上文)。
    • 任何其他非零码 — 从底层测试命令传播而来。
  • 工作文件夹命名
    .tmp/<lang>_<arg1>
    ,其中
    <lang>
    是语言的短标识符(
    java
    python
    node
    go
    rust
    等)。使用第一个参数(构建文件夹)作为路径的一部分,绝不能使用conformance测试文件夹。所有依赖安装、构建输出、缓存、测试运行器工件和测试调用本身都存放在此文件夹内。脚本执行的任何操作在第5步(内置安装型)/第4步(仅激活型)后都不得触碰
    $1
    ,或在任何时候触碰
    $2
  • 日志:打印简短的进度行(
    "准备<lang>构建子文件夹:..."
    "激活已准备的虚拟环境..."
    "运行<lang> conformance测试..."
    ),以便轻松排查故障。如果匹配Python参考脚本,可将嘈杂的“准备”行包装在
    VERBOSE
    检查中。
  • cd
    前捕获
    current_dir
    。这是手写conformance脚本中最常见的错误:忘记conformance测试文件夹参数是相对于调用目录的,而非工作文件夹。

Dependency isolation (install-inline)

依赖隔离(内置安装型)

This section applies to install-inline scripts only. For activate-only scripts, the isolation location is set up by prepare; you just need to point the test command at it — see Activating a prepared environment.
The dependency environment must live inside
$WORKING_FOLDER
so the test run can't be polluted by — or pollute — the user's global caches. Pick the most idiomatic isolation mechanism for the language:
LanguageIsolation mechanismInstall command (run inside
$WORKING_FOLDER
)
Test command (point at
$current_dir/$2
)
Python
venv
at
./.venv
python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
python -m unittest discover -b -s "$current_dir/$2"
(or
pytest "$current_dir/$2"
)
Node.jslocal
./node_modules
(default)
npm ci
(preferred) or
npm install
npx jest --rootDir "$current_dir/$2"
Javaproject-scoped Maven repo at
./.m2
mvn -Dmaven.repo.local=./.m2 install -DskipTests
(build + install artifact so the test pom can resolve it)
mvn -f "$current_dir/$2/pom.xml" -Dmaven.repo.local="$(pwd)/.m2" test
Gomodule cache at
./.gocache
GOMODCACHE="$PWD/.gocache" go mod download
(optional pre-warm)
GOMODCACHE="$PWD/.gocache" go test "$current_dir/$2/..."
Rustcargo home at
./.cargo
CARGO_HOME="$PWD/.cargo" cargo fetch
(optional pre-warm)
CARGO_HOME="$PWD/.cargo" cargo test --manifest-path "$current_dir/$2/Cargo.toml"
Notes:
  • Every path in the install command and test command is relative to
    .tmp/<lang>_<arg1>
    .
    That's why the script
    cd
    s into the working folder in step 6 — from that point on,
    ./.venv
    ,
    ./node_modules
    ,
    ./.m2
    , etc. all resolve under
    .tmp/<lang>_<arg1>
    , never under
    $1
    or
    $2
    .
  • Always pass the isolation flag/env var to both the install command and the test command. They must agree on where deps live, otherwise the test command will silently fall back to the global cache or (worse) write into
    $1
    /
    $2
    .
  • Python is the only ecosystem where the venv is mandatory to satisfy "into a virtual environment" literally. The others use language-native equivalents that achieve the same isolation.
  • Propagate the install exit code immediately. In Bash:
    <install cmd> || exit $?
    . In PowerShell: check
    $LASTEXITCODE
    and
    exit $LASTEXITCODE
    if non-zero.
  • Time the dependency setup with
    date +%s.%N
    (Bash) /
    Get-Date
    (PowerShell) and print
    "Requirements setup completed in X.XX seconds"
    . If this number is large, that's the signal to add a
    prepare_environment_<lang>
    script (and switch this script to the activate-only variant).
本节仅适用于内置安装型脚本。对于仅激活型脚本,隔离位置由prepare脚本设置;你只需将测试命令指向该位置即可——请查看激活已准备环境(仅激活型)
依赖环境必须存放在**
$WORKING_FOLDER
内部**,以确保测试运行不会被用户的全局缓存污染,也不会污染用户的全局缓存。为语言选择最符合惯例的隔离机制:
语言隔离机制安装命令(在
$WORKING_FOLDER
内运行)
测试命令(指向
$current_dir/$2
Python
venv
位于
./.venv
python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
python -m unittest discover -b -s "$current_dir/$2"
(或
pytest "$current_dir/$2"
Node.js本地
./node_modules
(默认)
npm ci
(首选)或
npm install
npx jest --rootDir "$current_dir/$2"
Java项目范围的Maven仓库位于
./.m2
mvn -Dmaven.repo.local=./.m2 install -DskipTests
(构建并安装工件,以便测试pom可以解析它)
mvn -f "$current_dir/$2/pom.xml" -Dmaven.repo.local="$(pwd)/.m2" test
Go模块缓存位于
./.gocache
GOMODCACHE="$PWD/.gocache" go mod download
(可选预加载)
GOMODCACHE="$PWD/.gocache" go test "$current_dir/$2/..."
Rustcargo home位于
./.cargo
CARGO_HOME="$PWD/.cargo" cargo fetch
(可选预加载)
CARGO_HOME="$PWD/.cargo" cargo test --manifest-path "$current_dir/$2/Cargo.toml"
注意事项:
  • 安装命令和测试命令中的所有路径均相对于
    .tmp/<lang>_<arg1>
    。这就是脚本在第6步中
    cd
    进入工作文件夹的原因——从那时起,
    ./.venv
    ./node_modules
    ./.m2
    等都解析到
    .tmp/<lang>_<arg1>
    下,绝不会解析到
    $1
    $2
    下。
  • 始终将隔离标志/环境变量传递给安装命令和测试命令。它们必须就依赖的存放位置达成一致,否则测试命令会静默回退到全局缓存(更糟)写入
    $1
    /
    $2
  • Python是唯一强制要求使用venv的生态系统,以严格满足“进入虚拟环境”的要求。其他语言使用原生等效机制实现相同的隔离效果。
  • 立即传播安装退出码。在Bash中:
    <install cmd> || exit $?
    。在PowerShell中:检查
    $LASTEXITCODE
    ,如果非零则
    exit $LASTEXITCODE
  • 为依赖设置计时,使用
    date +%s.%N
    (Bash)/
    Get-Date
    (PowerShell)并打印
    "依赖设置完成,耗时X.XX秒"
    。如果此数值较大,则表明需要添加
    prepare_environment_<lang>
    脚本(并将此脚本切换为仅激活型变体)。

Activating a prepared environment (activate-only)

激活已准备环境(仅激活型)

This section applies to activate-only scripts only. The isolation location was created by prepare; conformance just needs to attach to it and pass the right flags to the test command.
LanguageVerify exists in step 4Activate in step 6Test command in step 8 (point at
$current_dir/$2
)
Python
.tmp/<lang>_$1/.venv/bin/activate
source .venv/bin/activate
(after
cd
-ing into the working folder)
python -m unittest discover -b -s "$current_dir/$2"
Node.js
.tmp/<lang>_$1/node_modules/
(nothing)
npx jest --rootDir "$current_dir/$2"
Java
.tmp/<lang>_$1/.m2/
MAVEN_LOCAL_REPO="$(pwd)/.m2"
mvn -f "$current_dir/$2/pom.xml" -Dmaven.repo.local="$MAVEN_LOCAL_REPO" test
Go
.tmp/<lang>_$1/.gocache/
export GOMODCACHE="$(pwd)/.gocache"
go test "$current_dir/$2/..."
Rust
.tmp/<lang>_$1/.cargo/
export CARGO_HOME="$(pwd)/.cargo"
cargo test --manifest-path "$current_dir/$2/Cargo.toml"
Notes:
  • Verify, don't recreate. If
    .venv
    is missing, exit
    69
    with a clear "did you run prepare_environment first?" message — do not silently fall back to creating it inline. That would silently degrade a misconfigured project into the install-inline path and mask the real problem.
  • Match prepare's isolation paths exactly. If prepare puts the venv at
    .venv
    and you look for it at
    venv
    , the verify step will always fail. Read
    implement-prepare-environment-script
    for the canonical paths.
  • Don't time anything in this variant. The slow phase is prepare; conformance just runs the tests. Adding a duration log here is misleading — it makes the script look like it's doing the install when it isn't.
本节仅适用于仅激活型脚本。隔离位置由prepare脚本创建;conformance脚本只需连接到该位置并向测试命令传递正确的标志即可。
语言第4步中需验证存在的路径第6步中的激活操作第8步中的测试命令(指向
$current_dir/$2
Python
.tmp/<lang>_$1/.venv/bin/activate
source .venv/bin/activate
cd
进入工作文件夹后)
python -m unittest discover -b -s "$current_dir/$2"
Node.js
.tmp/<lang>_$1/node_modules/
(无操作)
npx jest --rootDir "$current_dir/$2"
Java
.tmp/<lang>_$1/.m2/
MAVEN_LOCAL_REPO="$(pwd)/.m2"
mvn -f "$current_dir/$2/pom.xml" -Dmaven.repo.local="$MAVEN_LOCAL_REPO" test
Go
.tmp/<lang>_$1/.gocache/
export GOMODCACHE="$(pwd)/.gocache"
go test "$current_dir/$2/..."
Rust
.tmp/<lang>_$1/.cargo/
export CARGO_HOME="$(pwd)/.cargo"
cargo test --manifest-path "$current_dir/$2/Cargo.toml"
注意事项:
  • 仅验证,不重新创建。如果
    .venv
    缺失,以
    69
    退出并显示清晰的“你是否先运行了prepare_environment?”信息——不要静默回退到内置创建环境。这会将配置错误的项目变相降级为内置安装型路径,并掩盖真实问题。
  • 与prepare脚本的隔离路径完全匹配。如果prepare脚本将venv放在
    .venv
    ,而你在
    venv
    中查找,验证步骤将始终失败。请阅读
    implement-prepare-environment-script
    以获取标准路径。
  • 此变体无需计时。耗时阶段是prepare脚本的工作;conformance脚本仅运行测试。在此处添加时长日志会产生误导——它会让脚本看起来在执行安装操作,但实际上并没有。

Bash specifics

Bash特定事项

  • Shebang:
    #!/bin/bash
    .
  • File naming:
    run_conformance_tests_<lang>.sh
    , placed in
    assets/
    (skill reference) or
    test_scripts/
    (target project).
  • Arguments:
    $1
    = build folder,
    $2
    = conformance tests folder.
  • Make it executable:
    chmod +x
    the produced script.
  • cd
    failure check:
    the reference scripts use the
    cd ... 2>/dev/null
    +
    [ $? -ne 0 ]
    pattern. Keep it.
  • Shebang
    #!/bin/bash
  • 文件命名
    run_conformance_tests_<lang>.sh
    ,放置在
    assets/
    (Skill参考)或
    test_scripts/
    (目标项目)中。
  • 参数
    $1
    = 构建文件夹,
    $2
    = conformance测试文件夹。
  • 设置可执行权限:为生成的脚本执行
    chmod +x
  • cd
    失败检查
    :参考脚本使用
    cd ... 2>/dev/null
    +
    [ $? -ne 0 ]
    模式。请保持此模式。

PowerShell specifics

PowerShell特定事项

  • No shebang. Use a
    param([Parameter(Mandatory=$true)][string]$BuildFolder, [Parameter(Mandatory=$true)][string]$ConformanceTestsFolder)
    block at the top instead.
  • File naming:
    run_conformance_tests_<lang>.ps1
    .
  • Exit codes: use
    exit 69
    etc. (PowerShell honors them just like Bash).
  • Toolchain check: prefer
    Get-Command <tool> -ErrorAction SilentlyContinue
    and, where a specific version is needed, parse the tool's
    --version
    output.
  • Filesystem: use
    Test-Path
    ,
    Remove-Item -Recurse -Force
    ,
    New-Item -ItemType Directory
    ,
    Copy-Item -Recurse
    ,
    Set-Location
    . Quote paths to handle spaces.
  • Capture original cwd:
    $currentDir = (Get-Location).Path
    before any
    Set-Location
    call.
  • No
    chmod
    step needed.
    If execution policy is likely to block the script, mention
    Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
    to the user — don't bake it into the script.
  • 无Shebang。在顶部使用
    param([Parameter(Mandatory=$true)][string]$BuildFolder, [Parameter(Mandatory=$true)][string]$ConformanceTestsFolder)
    块替代。
  • 文件命名
    run_conformance_tests_<lang>.ps1
  • 退出码:使用
    exit 69
    等(PowerShell与Bash一样会遵守这些退出码)。
  • 工具链检查:优先使用
    Get-Command <tool> -ErrorAction SilentlyContinue
    ,当需要特定版本时,解析工具的
    --version
    输出。
  • 文件系统操作:使用
    Test-Path
    Remove-Item -Recurse -Force
    New-Item -ItemType Directory
    Copy-Item -Recurse
    Set-Location
    。为路径添加引号以处理空格。
  • 捕获原始工作目录
    $currentDir = (Get-Location).Path
    必须在任何
    Set-Location
    调用之前执行。
  • 无需
    chmod
    步骤
    。如果执行策略可能阻止脚本运行,请告知用户执行
    Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
    ——不要将此步骤写入脚本。

Workflow

工作流程

  1. Decide the variant. Look in the project for
    prepare_environment_<lang>.sh
    /
    .ps1
    (check
    test_scripts/
    , then any
    prepare-environment-script:
    key in
    config.yaml
    ). If present → emit activate-only. If absent → emit install-inline. See Variant decision.
  2. Confirm the target language, shell flavor (Bash or PowerShell), and dependency manifest (
    pom.xml
    ,
    requirements.txt
    /
    pyproject.toml
    ,
    package.json
    ,
    go.mod
    ,
    Cargo.toml
    , ...). Ask if any is unclear.
  3. Read assets/run_conformance_tests_java.sh and assets/run_conformance_tests_python.sh to refresh the exact structure. Both are install-inline references — for activate-only, follow steps 4–7 of the activate-only variant and the Activating a prepared environment table.
  4. Translate each step into the equivalent commands for the target language and shell. The toolchain check, dependency install/activate, and test invocation are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
  5. Pick the right per-language row:
  6. Add the language-appropriate "no tests discovered" guard from No tests discovered detection.
  7. Save the new script. For Bash,
    chmod +x
    it.
  8. For activate-only scripts only: smoke-test by running
    prepare_environment_<lang>.<sh|ps1> <build> && run_conformance_tests_<lang>.<sh|ps1> <build> <tests>
    . If the conformance script errors with "prepared environment missing" right after a successful prepare, the two scripts disagree on either the working-folder path or the isolation location — fix that before declaring done.
  1. 确定变体。在项目中查找
    prepare_environment_<lang>.sh
    /
    .ps1
    (先检查
    test_scripts/
    ,再检查
    config.yaml
    中的
    prepare-environment-script:
    键)。如果存在 → 生成仅激活型。如果不存在 → 生成内置安装型。请查看变体选择
  2. 确认目标语言Shell类型(Bash或PowerShell)和依赖清单
    pom.xml
    requirements.txt
    /
    pyproject.toml
    package.json
    go.mod
    Cargo.toml
    等)。如有任何不清楚的地方,请询问用户。
  3. 阅读assets/run_conformance_tests_java.shassets/run_conformance_tests_python.sh以刷新对精确结构的认知。两者都是内置安装型参考——对于仅激活型,请遵循仅激活型变体的第4-7步和激活已准备环境表格。
  4. 将每个步骤转换为目标语言以及Shell的等效命令。工具链检查、依赖安装/激活和测试调用是语言特定部分;其余部分是Bash与PowerShell语法之间的机械转换。
  5. 选择正确的语言对应行:
  6. 添加未发现测试用例检测中适合该语言的防护逻辑。
  7. 保存新脚本。对于Bash脚本,执行
    chmod +x
  8. 仅针对仅激活型脚本:通过运行
    prepare_environment_<lang>.<sh|ps1> <build> && run_conformance_tests_<lang>.<sh|ps1> <build> <tests>
    进行冒烟测试。如果在成功运行prepare脚本后,conformance脚本报错“已准备环境缺失”,则两个脚本在工作文件夹路径或隔离位置上存在不一致——在完成前修复此问题。

Anti-Patterns

反模式

  • (Hard mistake) Don't install into, build into, or otherwise write to the source build folder (
    $1
    ) or the conformance tests folder (
    $2
    ).
    Both arguments are read-only input. Every install, cache, build artifact, log, JUnit XML, coverage report, compiled test class, and temp file must land in
    .tmp/<lang>_<arg1>
    . This includes never running
    pip install
    ,
    npm install
    ,
    mvn install
    , or
    cargo build
    with
    $1
    or
    $2
    as their
    cwd
    or target; never letting a venv /
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    directory appear inside
    $1
    or
    $2
    ; and never running the test command from inside either folder. The whole point of staging into
    .tmp/
    is so the build folder remains a clean artifact of the render and the conformance tests folder remains a clean tree under the user's version control — writing to either one corrupts those guarantees.
  • Don't emit the install-inline variant when a
    prepare_environment_<lang>
    script already exists.
    The conformance script's
    rm -rf .tmp/<lang>_$1
    will wipe everything prepare did, and the inline install will redo it from scratch on every run. Always run the Variant decision check first.
  • Don't emit the activate-only variant when no prepare script exists. The "verify prepared environment" check will fail on every run because nothing has populated the working folder.
  • Don't silently fall back from activate-only to install-inline when the prepared environment is missing. Exit
    69
    with a clear error so the misconfiguration is visible. Silent fallback hides the real bug and produces inconsistent behavior between runs.
  • Don't copy the conformance tests folder into
    .tmp/
    .
    Only the build folder is staged (and only in install-inline). The test folder is read in place from
    $current_dir/$2
    .
  • Don't compute the test path after
    cd
    .
    Capture
    current_dir
    first; otherwise
    $2
    will be resolved relative to the working folder and silently miss the tests.
  • Don't skip the "no tests discovered" check. A conformance suite that finds zero tests and exits
    0
    is the worst possible failure mode — it looks like success in CI.
  • Don't skip the toolchain check, even when "everyone has it installed" — exit code
    69
    is what the calling system relies on to detect a missing runtime.
  • Don't reuse the source folder in place (install-inline). Always copy into
    .tmp/<lang>_<arg1>
    first; the renderer relies on this isolation.
  • Don't change the exit-code contract. Other parts of the system branch on
    69
    and
    1
    specifically — and these codes must be identical between the Bash and PowerShell variants.
  • Don't write a cross-shell hybrid (e.g. a
    .sh
    that detects PowerShell, or vice versa). Ship one script per shell, named with the appropriate extension.
  • Don't install dependencies into the user's global location (
    ~/.m2
    , system-wide
    pip
    ,
    ~/.cargo
    , etc.) in the install-inline variant. Always isolate inside
    $WORKING_FOLDER
    so concurrent runs and other projects can't interfere.
  • Don't run the test command without first verifying the install / activation succeeded. A failed install (or missing prepared env) followed by a "test" run produces misleading errors that look like test failures.
  • (严重错误)不要安装到、构建到或写入源码构建文件夹(
    $1
    )或conformance测试文件夹(
    $2
    。这两个参数都是只读输入。所有安装、缓存、构建产物、日志、JUnit XML、覆盖率报告、编译后的测试类和临时文件都必须存放在
    .tmp/<lang>_<arg1>
    中。这包括绝不能在
    $1
    $2
    作为当前工作目录或目标的情况下运行
    pip install
    npm install
    mvn install
    cargo build
    ;绝不能让虚拟环境 /
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    目录出现在
    $1
    $2
    内;绝不能在任一文件夹内运行测试命令。将内容暂存到
    .tmp/
    的核心目的是保持构建文件夹为渲染生成的干净产物,保持conformance测试文件夹为用户版本控制下的干净树——写入任一文件夹都会破坏这些保证。
  • prepare_environment_<lang>
    脚本已存在时,不要生成内置安装型变体
    。conformance脚本的
    rm -rf .tmp/<lang>_$1
    会清除prepare脚本完成的所有工作,而内置安装会在每次运行时从头重做。请始终先执行变体选择检查。
  • 当不存在prepare脚本时,不要生成仅激活型变体。“验证已准备环境”检查会在每次运行时失败,因为没有任何内容填充工作文件夹。
  • 当已准备环境缺失时,不要从仅激活型静默回退到内置安装型。以
    69
    退出并显示清晰的错误信息,以便暴露配置错误。静默回退会掩盖真实的bug,并导致运行之间的行为不一致。
  • 不要将conformance测试文件夹复制到
    .tmp/
    。仅暂存构建文件夹(且仅在内置安装型中)。测试文件夹从
    $current_dir/$2
    原位读取。
  • 不要在
    cd
    后计算测试路径
    。先捕获
    current_dir
    ;否则
    $2
    会相对于工作文件夹解析,导致静默找不到测试用例。
  • 不要跳过“未发现测试用例”检查。一个找到零测试用例并以
    0
    退出的conformance套件是最糟糕的失败模式——在CI中看起来像是成功。
  • 不要跳过工具链检查,即使“每个人都已安装”——退出码
    69
    是调用系统检测缺失运行时的依据。
  • 不要原地复用源码文件夹(内置安装型)。始终先复制到
    .tmp/<lang>_<arg1>
    ;渲染器依赖此隔离机制。
  • 不要更改退出码约定。系统的其他部分会专门针对
    69
    1
    进行分支处理——这些代码在Bash和PowerShell变体中必须完全相同。
  • 不要编写跨Shell混合脚本(例如检测PowerShell的
    .sh
    脚本,反之亦然)。为每个Shell单独提供一个脚本,使用适当的扩展名命名。
  • 在内置安装型变体中,不要将依赖安装到用户的全局位置
    ~/.m2
    、系统级
    pip
    ~/.cargo
    等)。始终在
    $WORKING_FOLDER
    内隔离,以便并发运行和其他项目不会相互干扰。
  • 不要在未验证安装/激活成功的情况下运行测试命令。安装失败(或缺失已准备环境)后运行“测试”会产生误导性错误,看起来像是测试失败。