关于 implement-conformance-testing-script
用于生成单个可执行脚本,以针对生成构建目录和独立测试目录运行一致性测试。脚本会根据是否存在准备脚本,选择内联安装或仅激活环境的模式。
主要功能
- 01选择脚本模式
- 02支持 Bash 和 PowerShell
- 03在临时目录暂存构建
- 04隔离依赖
- 05从独立目录运行测试
适用场景
- 01渲染后运行一致性测试
- 02复用已准备好的测试环境
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ plainlang/ plain-forge --skill implement-conformance-testing-script安装后在 Claude Code 中输入/implement-conformance-testing-script确认已加载。
详细介绍
实现符合性测试脚本
此技能会生成一个可执行的脚本,用于对生成的构建文件夹运行符合性测试,并遵循一致且与语言无关的模式。
参考实现:
- assets/run_conformance_tests_java.sh — Java,内联安装变体。
- assets/run_conformance_tests_python.sh — Python,内联安装变体。
- assets/run_conformance_tests_.ps1 — Windows PowerShell 对应版本。
开始编写前,请先阅读两者——你生成的每个脚本都必须忠实地将相同模式转换为目标语言的工具链以及用户的 shell 环境。
符合性测试脚本与单元测试脚本的区别
符合性测试脚本在结构上与单元测试脚本非常相似(参见同级技能 implement-unit-testing-script),但有两个重要区别:
- 位置参数有两个,而不是一个。 符合性测试脚本同时接收构建文件夹(待测试的源代码)和单独的符合性测试文件夹(要针对该构建运行的测试)。当前渲染器传入的都是绝对路径(旧版渲染器传入的是相对路径)——脚本必须同时兼容两者。
- 测试从工作文件夹之外加载。 构建内容会暂存到工作文件夹
<temp>/<lang>_<basename>(系统临时目录 + 语言 ID +$1的基本名称——见约定),脚本随后会在该文件夹中执行cd,但测试命令指向的是预先解析为绝对路径的符合性测试文件夹(见下文的$CONFORMANCE_TESTS_FOLDER)。测试绝不会复制到暂存区域。
其他所有方面——工具链检查、构建暂存、依赖隔离、退出代码——都相同。
变体选择:内联安装还是仅激活
开始编写前,先决定要生成哪种变体。 两种变体都包含工具链检查、参数验证、当前工作目录捕获、测试执行和退出代码处理——区别仅在中间部分(下文模式的第 4–7 步)。
| 查找现有内容 | 生成内容 |
|---|---|
项目的 test_scripts/ 文件夹中存在 prepare_environment_<lang>.sh / .ps1(或 config.yaml 的 prepare-environment-script: 键所指向的位置) | 仅激活变体。 验证已准备好的环境,激活该环境并运行测试。不会暂存构建内容或安装依赖——这些工作已由准备步骤完成。 |
| 不存在——没有准备脚本 | 内联安装变体。 暂存构建内容、安装依赖并一次性运行测试。 |
为什么要采用这种拆分
渲染器会针对每个功能规格调用一次一致性测试运行器。模块中的每个功能规格都有自己的 plain_modules/<module>/tests/<spec>/ 文件夹。渲染器为新规格生成代码后,会运行同一模块中所有先前规格的一致性测试,以检测回归。对于包含 N 个功能规格的模块,每次渲染期间,此脚本会被调用大约 N 次,而不是一次。
这种按规格调用的模式使安装步骤代价高昂。朴素的运行器每次调用都执行 pip install / npm ci / mvn install -DskipTests / cargo build,每次渲染就要支付 N 次安装成本。对于任何非玩具项目,这项成本都会占据大部分实际运行时间。
这两种变体正是为此而设计的:
- 安装内联仅在 N 较小(几个规格)或依赖项安装成本低时才合适。它是自包含的:每次调用都从头开始暂存、安装、运行,然后重复。
- 仅激活是生产环境的解决方案。
prepare_environment_<lang>每次渲染运行一次,只支付一次安装成本,并将预热后的环境填充到<temp>/<lang>_<basename>/。之后,N 次一致性测试调用只需连接到该工作文件夹并运行测试——无需安装,无需编译,只需激活即可运行。
清理约定由这种拆分决定: 安装内联变体拥有自己的工作文件夹,并会在退出时将其删除(trap 'rm -rf "$WORKING_FOLDER"' EXIT / 在 finally 中使用 Remove-Item)。仅激活变体绝不会删除工作文件夹——无论成功还是失败。准备脚本负责管理该文件夹的生命周期;如果仅激活脚本删除了它,N 次调用中的第二次就会因“准备好的环境缺失”检查而失败,摊销优势也会荡然无存。
**为什么选择正确的变体很重要:**如果在已有准备脚本的情况下输出安装内联变体,那么每次运行都会清除准备脚本的工作(通过脚本暂存时的 rm -rf 和退出清理),或者重复执行安装,从而破坏准备脚本的整体目的。反过来,如果输出仅激活变体却没有准备脚本,那么每次运行都会因没有任何内容填充工作文件夹而无法通过“验证准备好的环境”检查。参见反模式。
先选择 Shell
开始编写之前,先确定脚本必须使用哪种 Shell——这取决于用户的环境,而不是语言:
- Bash(
.sh)——macOS、Linux、WSL、Linux 上的 CI 运行器。除非用户使用原生 Windows,否则默认选择此项。 - PowerShell(
.ps1)——原生 Windows / 仅支持 PowerShell 的环境。
两种变体采用相同的模式,只有语法不同。
模式
两种变体的步骤 1–3和步骤 8完全相同。步骤 4–7有所不同——请选择下方与所选变体对应的小节。
两种变体的通用步骤
-
检查工具链。 确认所需的语言运行时/构建工具(以及任何必需的版本)已安装。如果未安装,打印错误并以代码
69退出。 -
验证参数。 要求提供 两个位置参数:
<build_folder>和<conformance_tests_folder>。如果缺少任意一个,打印用法并以代码69退出。 -
将一致性测试文件夹解析为绝对路径。 当前渲染器会将
$2作为绝对路径传入;旧版渲染器则传入相对于调用目录的路径。请在前面一次性解析该路径,并存入步骤 8 中测试命令会使用的变量:- Bash:
case "$2" in /*) CONFORMANCE_TESTS_FOLDER="$2" ;; *) CONFORMANCE_TESTS_FOLDER="$(pwd)/$2" ;; esac - PowerShell:
if (-not [System.IO.Path]::IsPathRooted($ConformanceTestsFolder)) { $ConformanceTestsFolder = Join-Path (Get-Location) $ConformanceTestsFolder }
切勿在使用时拼接
$current_dir/$2——如果$2是绝对路径,这会产生重复路径(/project//abs/path/...);典型症状是ImportError: Start directory is not importable,并且报错路径中项目根目录出现两次。 - Bash:
步骤 4–7——内联安装变体(无准备脚本)
- 设置工作目录。 将工作文件夹定义为
<temp>/<lang>_<basename>(Bash 中为/tmp/<lang>_$(basename "$1"),PowerShell 中为Join-Path ([System.IO.Path]::GetTempPath()) "<lang>_$(Split-Path $BuildFolder -Leaf)")。清空该文件夹(使用rm -rf/Remove-Item -Recurse -Force)并重新创建。注册清理操作,以便脚本退出时删除该文件夹——Bash 中使用trap 'rm -rf "$WORKING_FOLDER"' EXIT,PowerShell 中在finally块中使用Remove-Item -Recurse -Force。该文件夹是后续每次写入的唯一目标位置。 - 复制构建内容。 将
<build_folder>($1)中的所有内容递归复制到工作文件夹。不要复制一致性测试;它们应保留在原处。完成此步骤后,脚本剩余部分应将$1(构建文件夹)和$2(一致性测试文件夹)视为只读。 - 进入工作目录。 使用
cd/Set-Location进入<temp>/<lang>_<basename>。如果失败,以代码69退出。所有剩余步骤都必须在工作文件夹内运行;绝不能写回$1或$2。 - 在
<temp>/<lang>_<basename>内的隔离环境中安装依赖项。 为每个工作文件夹设置独立的依赖项位置(例如,Python venv 放在./.venv,本地./node_modules,项目专用的 Maven 仓库放在./.m2等),并在其中安装/解析所有依赖项。绝不要将依赖项安装到源构建文件夹($1)、一致性测试文件夹($2)、用户的全局缓存(~/.m2、系统级pip、~/.cargo、~/.npm等)或<temp>/<lang>_<basename>之外的任何位置。如果安装命令失败,立即传递其退出代码,不要继续执行步骤 8。参见依赖项隔离(内联安装)。
步骤 4–7 — 仅激活变体(prepare 脚本已存在)
-
验证已准备好的环境。 检查以下两项:
- 检查工作文件夹
<temp>/<lang>_<basename>是否存在 — 其路径的生成方式必须与 prepare 完全一致(系统临时目录 +<lang>_+$1的基本名称)。 - 检查其中该语言的隔离位置是否存在(例如,Python 的
.venv/bin/activate、Java 的.m2/、Node 的node_modules/、Go 的.gocache/、Rust 的.cargo/)。
如果任一检查失败,打印有帮助的错误信息(
"Error: prepared environment missing — did you run prepare_environment_<lang>.<sh|ps1> first?")并以状态码69退出。不要悄悄改为在脚本中创建环境 — 那会掩盖实际的配置错误,并使此脚本伪装成安装内联变体。完成此步骤后,在脚本剩余部分中,$1和$2均视为只读。此变体不注册清理操作。 工作文件夹归 prepare 所有;此脚本绝不能删除它 — 无论是通过
trap、finally块,还是在失败时删除。请参阅清理约定。 - 检查工作文件夹
-
进入工作目录。 使用
cd/Set-Location进入<temp>/<lang>_<basename>。如果失败,以状态码69退出。所有后续步骤均在工作文件夹内运行;绝不能写回$1或$2。 -
激活已准备好的依赖环境。 按语言处理:
- 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 已将内容放入<temp>/<lang>_<basename>/...,这是唯一应连接到的位置。 - Python:
-
(此变体没有步骤 7 — 安装是 prepare 的工作。直接跳到步骤 8。)
通用步骤 8(两种变体均适用)
-
运行一致性测试。 调用该语言的标准测试命令,并将其指向
$CONFORMANCE_TESTS_FOLDER(在步骤 3 中解析出的绝对路径)。脚本的最终退出码取决于测试命令的返回值 — 但以下“未发现测试”情况除外。测试命令对
$CONFORMANCE_TESTS_FOLDER只读。它从该目录加载测试文件,但运行器生成的任何产物(缓存、JUnit XML、覆盖率报告、已编译的测试类等)都必须写入<temp>/<lang>_<basename>,不能写在测试文件旁边。如果所选运行器默认将输出写在测试文件旁边,请传递显式的输出目录标志,将输出指向工作文件夹内(例如,pytest --basetemp=./.pytest_tmp、jest --cacheDirectory=./.jest_cache,或通过mvn -f "$CONFORMANCE_TESTS_FOLDER/pom.xml" -Dproject.build.directory="$(pwd)/target" test将 Maventarget/放在工作文件夹内)。
只读输入 — 硬性规则
一致性脚本有两个只读输入:源构建文件夹($1)和一致性测试文件夹($2)。在任何情况下都不得写入这两个文件夹。脚本绝不能:
- 在
$1或$2中安装依赖(不得在$1/$2中运行pip install,不得在其中运行npm install,不得运行会向其中写入内容的mvn install,不得让 Cargo 构建产物落在其中), - 在
$1或$2中创建 virtualenv /node_modules/.m2/.gocache/.cargo目录, - 将测试命令的
cwd设置为$1或$2(每条测试命令都必须在步骤 6 / 仅激活步骤 5 中执行cd后,从<temp>/<lang>_<basename>中运行), - 在
$1或$2中创建日志、缓存、构建输出、JUnit XML、覆盖率报告、已编译的测试类或临时文件。
输入只读的原因:
- **
$1(构建文件夹)**与渲染器(默认位置为plain_modules/<module>/code)及下游工具共享。向其中写入内容会破坏渲染器对“已生成内容”的认知,并导致后续渲染失败。将文件暂存到系统临时目录的目的,就是让源文件夹保持为渲染结果的干净、可复现产物。 - **
$2(一致性测试文件夹)**是用户编写的测试源代码,通常会纳入版本控制。向其中写入内容会污染工作树、导致 git 状态不断变化,而且(对于会自动发现测试的框架)可能让后续运行把生成的文件当作测试。
如果你准备执行任何 cwd 为 $1 或 $2,或目标路径以 $1/ 或 $2/ 开头的命令,立即停止。应将操作移至 <temp>/<lang>_<basename> 中,否则你执行的就是脚本绝不能执行的操作。
“未发现测试”检测
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:通常本身就会明确报错;无需额外检查。
零测试却静默通过是最危险的一致性运行器故障模式 — 务必对此进行防范。这适用于两个变体。
约定
适用于两种 shell 风格和两个变体:
- 退出代码:
69— 不可恢复的调用错误:缺少参数、缺少工具链、无法进入工作目录、无法创建虚拟环境(install-inline),或预先准备的环境缺失或损坏(activate-only)。与参考脚本中的UNRECOVERABLE_ERROR_EXIT_CODE相同。1— 触发了“未发现测试”保护机制(见上文)。- 任何其他非零代码 — 由底层测试命令返回。
- 工作文件夹命名:
<temp>/<lang>_<basename>— 系统临时目录(Bash 中为/tmp,PowerShell 中为[System.IO.Path]::GetTempPath()),加上语言的简短标识符(java、python、node、go、rust……),再加上第一个参数(构建文件夹)的基本名称($(basename "$1")/Split-Path $BuildFolder -Leaf);绝不能使用符合性测试文件夹,也绝不能使用原始参数(它可能是绝对路径;拼接后会产生错误的重复路径)。所有依赖项安装、构建输出、缓存、测试运行器产物以及测试调用本身都必须位于此文件夹中。脚本在步骤 5(install-inline)/步骤 4(activate-only)之后不得再访问$1,且任何时候都不得访问$2。 - 清理责任: install-inline 变体会在退出时删除其工作文件夹,无论成功还是失败(Bash 中使用
trap 'rm -rf "$WORKING_FOLDER"' EXIT;PowerShell 中在finally块里Pop-Location之后使用Remove-Item -Recurse -Force)。activate-only 变体绝不删除工作文件夹 — prepare 负责其生命周期,并在每次渲染时重新创建;在此处删除会导致之后每次按规范调用都出错。 - 日志记录 — 尽可能详细。 在一次渲染与绿色/红色的符合性结果之间,操作人员只能看到脚本的输出;脚本失败时,唯一可用的取证信息就是它的 stdout/stderr。应将脚本视为生产运行手册,而不是安静运行的辅助程序。具体来说:
- 执行每个步骤前都要宣布该步骤,并提供涉及的每个变量的确切值 — 解析后的构建文件夹、解析后的符合性测试文件夹、工作文件夹、语言、检测到的工具链版本、正在激活(install-inline)或验证(activate-only)的隔离根目录、所用变体,以及即将运行的测试命令。只说“正在运行测试”毫无用处;“在已激活位于
./.venv的虚拟环境(Python 3.11.6)下,从工作文件夹/tmp/python_build/运行python -m unittest discover -b -s /abs/path/to/tests”才有助于排查问题。 - 执行每条重要命令前都要回显该命令。 在 Bash 中,对脚本主体使用
set -x(或在每次调用前立即执行echo "+ <cmd>");在 PowerShell 中,将$VerbosePreference设为'Continue',并使用Write-Verbose/Write-Host打印带参数的每条命令行。 - 打印清晰的章节横幅。 每个模式步骤都应有类似
===== [6/8] Activate prepared environment =====(activate-only)或===== [7/8] Install dependencies =====(install-inline)的横幅,让人一眼就能浏览长日志;横幅还必须注明变体,让操作人员立即知道正在执行哪个分支。 - 打印解析后的绝对路径,而不只是相对名称。操作人员在另一台机器上查看日志时,需要知道虚拟环境、
.m2、node_modules、.gocache、.cargo实际位于何处,以及$2解析后的路径。 - 在步骤 1 中打印工具链自身的
--version输出(以及解析到的路径),即使成功也要打印。当符合性测试在本地通过、却在 CI 中失败时,这是最有用的取证信息。 - 原样输出安装/激活过程的内容 — 不要将其通过管道传到
/dev/null,也不要重定向到工作文件夹内的日志文件。操作人员必须能在脚本自身的输出流中看到依赖项解析器的每一行内容(install-inline),或每条“虚拟环境缺失”/“.m2 缺失”保护提示(activate-only)。删除 Python 参考实现中受VERBOSE控制的包装;符合性测试输出是取证信息最有价值的地方。 - 打印脚本所属的变体(install-inline 或 activate-only)以及原因 — 例如:“activate-only 变体:检测到此前运行
prepare_environment_python.sh所创建的/tmp/python_build/.venv/bin/activate”。验证步骤触发保护机制时,操作人员必须同时看到已检查的路径和预期来源(“是否先运行了prepare_environment_python.sh <build>?”)。 - 失败时,退出前打印即将运行的内容 — 确切的测试命令、工作目录、解析后的
$CONFORMANCE_TESTS_FOLDER,以及相关环境变量(PYTHONPATH、NODE_PATH、GOMODCACHE、CARGO_HOME、MAVEN_OPTS、PATH,Python 还包括VIRTUAL_ENV)。 - 打印最终摘要行,其中注明变体、测试命令、退出代码、解析后的符合性测试文件夹和工作文件夹,以便操作人员确切知道需要手动重新运行什么。 这里的详细日志并非噪音 — 能在自身输出中说明情况的详尽脚本,可以让排查时间从 30 秒缩短到 30 分钟。绝不要为了简洁而牺牲详细程度。
- 执行每个步骤前都要宣布该步骤,并提供涉及的每个变量的确切值 — 解析后的构建文件夹、解析后的符合性测试文件夹、工作文件夹、语言、检测到的工具链版本、正在激活(install-inline)或验证(activate-only)的隔离根目录、所用变体,以及即将运行的测试命令。只说“正在运行测试”毫无用处;“在已激活位于
- 在任何
cd之前解析$2。 这是手写符合性测试脚本时最常见的错误:切换目录后仍使用符合性测试参数。请在步骤 3 中将其解析为绝对路径(绝对路径 → 原样使用;相对路径 → 加上调用时所在的目录前缀),之后只使用解析后的变量。按照上述日志规则,解析后的值也必须出现在详细日志中,以便一眼发现$2解析错误。
依赖隔离(install-inline)
本节仅适用于 install-inline 脚本。对于仅激活的脚本,prepare 会设置隔离位置;你只需让测试命令指向该位置即可——参见激活已准备好的环境。
依赖环境必须位于 $WORKING_FOLDER 内部,以免测试运行受到用户全局缓存的影响,也避免污染这些缓存。请选择该语言最惯用的隔离机制:
| 语言 | 隔离机制 | 安装命令(在 $WORKING_FOLDER 内运行) | 测试命令(指向 $CONFORMANCE_TESTS_FOLDER) |
|---|---|---|---|
| Python | venv,位于 ./.venv | python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt | python -m unittest discover -b -s "$CONFORMANCE_TESTS_FOLDER"(或 pytest "$CONFORMANCE_TESTS_FOLDER") |
| Node.js | 本地 ./node_modules(默认) | npm ci(优先)或 npm install | npx jest --rootDir "$CONFORMANCE_TESTS_FOLDER" |
| Java | 项目专用 Maven 仓库,位于 ./.m2 | mvn -Dmaven.repo.local=./.m2 install -DskipTests(构建并安装构件,以便测试 pom 能解析它) | mvn -f "$CONFORMANCE_TESTS_FOLDER/pom.xml" -Dmaven.repo.local="$(pwd)/.m2" test |
| Go | 模块缓存,位于 ./.gocache | GOMODCACHE="$PWD/.gocache" go mod download(可选预热) | GOMODCACHE="$PWD/.gocache" go test "$CONFORMANCE_TESTS_FOLDER/..." |
| Rust | cargo 主目录,位于 ./.cargo | CARGO_HOME="$PWD/.cargo" cargo fetch(可选预热) | CARGO_HOME="$PWD/.cargo" cargo test --manifest-path "$CONFORMANCE_TESTS_FOLDER/Cargo.toml" |
注意:
- **安装命令和测试命令中的每个路径都相对于
<temp>/<lang>_<basename>。**因此,脚本会在第 6 步cd到工作文件夹——从这一步起,./.venv、./node_modules、./.m2等路径都会指向<temp>/<lang>_<basename>下,而绝不会指向$1或$2。 - **始终在安装命令和测试命令中传入隔离标记或环境变量。**两者必须使用相同的依赖存放位置,否则测试命令会悄悄回退到全局缓存,或者(更糟的是)写入
$1/$2。 - 只有 Python 生态系统必须使用 venv,才能从字面上满足“安装到虚拟环境中”的要求。其他语言使用能实现相同隔离效果的原生机制。
- **立即传递安装命令的退出代码。**在 Bash 中:
<install cmd> || exit $?。在 PowerShell 中:检查$LASTEXITCODE,如果非零则执行exit $LASTEXITCODE。 - 使用
date +%s.%N(Bash)/Get-Date(PowerShell)计时依赖设置过程,并打印"Requirements setup completed in X.XX seconds"。如果耗时较长,这就说明应添加prepare_environment_<lang>脚本(并将此脚本切换为仅激活的变体)。
激活已准备好的环境(仅激活)
本节仅适用于仅激活脚本。隔离位置已由 prepare 创建;一致性测试只需连接到该位置,并向测试命令传入正确的标志。
在下表中,$WORKING_FOLDER 为 <temp>/<lang>_<basename>——例如,Bash 中的 /tmp/python_$(basename "$1")。
| 语言 | 第 4 步:验证是否存在 | 第 6 步:激活 | 第 8 步:测试命令(指向 $CONFORMANCE_TESTS_FOLDER) |
|---|---|---|---|
| Python | $WORKING_FOLDER/.venv/bin/activate | source .venv/bin/activate(先 cd 到工作文件夹) | python -m unittest discover -b -s "$CONFORMANCE_TESTS_FOLDER" |
| Node.js | $WORKING_FOLDER/node_modules/ | (无) | npx jest --rootDir "$CONFORMANCE_TESTS_FOLDER" |
| Java | $WORKING_FOLDER/.m2/ | MAVEN_LOCAL_REPO="$(pwd)/.m2" | mvn -f "$CONFORMANCE_TESTS_FOLDER/pom.xml" -Dmaven.repo.local="$MAVEN_LOCAL_REPO" test |
| Go | $WORKING_FOLDER/.gocache/ | export GOMODCACHE="$(pwd)/.gocache" | go test "$CONFORMANCE_TESTS_FOLDER/..." |
| Rust | $WORKING_FOLDER/.cargo/ | export CARGO_HOME="$(pwd)/.cargo" | cargo test --manifest-path "$CONFORMANCE_TESTS_FOLDER/Cargo.toml" |
注意:
- 验证,不要重建。 如果缺少
.venv,请显示明确的“是否先运行了 prepare_environment?”消息并以69退出——不要静默地退回到内联创建环境的方式。否则,配置错误的项目会被静默地降级为内联安装流程,并掩盖真正的问题。 - 与 prepare 的隔离路径完全一致。 如果 prepare 将虚拟环境放在
.venv,而你在venv中查找,验证步骤就会一直失败。请阅读implement-prepare-environment-script获取规范路径。 - 此变体不要计时。 耗时阶段是 prepare;一致性测试只运行测试。在这里添加耗时日志会产生误导,让脚本看起来像是在执行安装,实际却没有。
Bash 相关说明
- Shebang:
#!/bin/bash。 - 文件命名:
run_conformance_tests_<lang>.sh,放在assets/(技能参考)或test_scripts/(目标项目)中。 - 参数:
$1= 构建文件夹,$2= 一致性测试文件夹。 - 设置为可执行: 对生成的脚本运行
chmod +x。 cd失败检查: 参考脚本使用cd ... 2>/dev/null+[ $? -ne 0 ]模式。请保留此模式。
PowerShell 相关说明
- 不使用 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。请为路径加引号,以处理其中包含空格的情况。 - 在任何
Set-Location调用之前,使用[System.IO.Path]::IsPathRooted+Join-Path (Get-Location) ...将$ConformanceTestsFolder转换为绝对路径。 - 无需
chmod步骤。 如果执行策略可能会阻止脚本,请向用户说明Set-ExecutionPolicy -Scope CurrentUser RemoteSigned,不要将其写入脚本。
工作流
- 确定变体。 在项目中查找
prepare_environment_<lang>.sh/.ps1(先检查test_scripts/,然后检查config.yaml中是否有prepare-environment-script:键)。如果存在 → 生成仅激活变体。如果不存在 → 生成内联安装变体。请参阅变体选择。 - 确认目标语言、Shell 类型(Bash 或 PowerShell)和依赖清单(
pom.xml、requirements.txt/pyproject.toml、package.json、go.mod、Cargo.toml等)。如有不清楚之处,请提问。 - 阅读 assets/run_conformance_tests_java.sh 和 assets/run_conformance_tests_python.sh,熟悉准确的结构。两者都是内联安装的参考;对于仅激活变体,请遵循仅激活变体的第 4–7 步以及激活已准备好的环境表格。
- 将每个步骤转换为目标语言和Shell 对应的命令。工具链检查、依赖安装/激活和测试调用是与语言相关的部分;其余内容只需在 Bash 和 PowerShell 语法之间机械转换。
- 选择对应语言的行:
- **内联安装:**使用依赖隔离(内联安装)表格——第 7 步和第 8 步使用相同的标志/环境变量。
- **仅激活:**使用激活已准备好的环境表格——第 4、6、8 步分别使用相应的验证、激活和测试命令列。
- 添加未发现测试的检测中适用于该语言的“未发现测试”保护逻辑。
- 保存新脚本。对于 Bash 脚本,执行
chmod +x。 - 更新
config.yaml以引用新脚本。 添加或更新conformance-tests-script:键,并填入新建脚本的路径(例如conformance-tests-script: test_scripts/run_conformance_tests_<lang>.sh)。这是必需的——渲染器需要此引用,才能在渲染每个功能规格后调用一致性测试脚本。 - 将脚本作为链接资源添加到基础
.plain文件中。 脚本写入磁盘后,必须在项目的基础.plain模块中引用它——通常是在承载一致性测试策略的***test reqs***模块中——以便渲染器将脚本内容视为一致性契约的事实依据。使用add-resource技能,在规格的相关部分添加标准 Markdown 链接(例如[conformance test runner](test_scripts/run_conformance_tests_<lang>.sh)),路径相对于.plain文件解析。脚本是单个文本文件,因此符合链接资源契约——不要链接test_scripts/目录,不要将脚本改述进规格,也不要在规格中重复内嵌其命令。如果多个基础.plain模块引用了一致性契约(例如宿主模块和继承自它的集成模块),则在每个需要它的模块中链接该脚本;链接资源应放在拥有测试要求的规格旁,而不是默认放在继承链顶端。添加链接后,重新读取规格,确认链接路径指向刚写入的脚本。如果是仅激活变体,还要在同一规格中添加对应的prepare_environment_<lang>脚本链接——一致性契约的两部分应放在一起。 - 仅适用于仅激活脚本: 通过运行
prepare_environment_<lang>.<sh|ps1> <build> && run_conformance_tests_<lang>.<sh|ps1> <build> <tests>进行冒烟测试。如果准备步骤成功后,一致性脚本立即报错“prepared environment missing”,则两个脚本对工作文件夹路径或隔离位置的设置不一致——在宣布完成前修正此问题。
Anti-Patterns
- (严重错误)不要在源构建文件夹(
$1)或一致性测试文件夹($2)中安装、构建或以其他方式写入。 这两个参数都是只读输入。所有安装内容、缓存、构建产物、日志、JUnit XML、覆盖率报告、编译后的测试类和临时文件都必须写入<temp>/<lang>_<basename>。这包括:绝不能将$1或$2作为cwd或目标运行pip install、npm install、mvn install或cargo build;绝不能让虚拟环境、node_modules、.m2或.gocache/.cargo目录出现在$1或$2中;也绝不能从这两个文件夹中的任何一个运行测试命令。将内容暂存到系统临时目录的目的,就是让构建文件夹保持为渲染生成的干净产物,并让一致性测试文件夹在用户的版本控制中保持为干净目录树;写入其中任何一个都会破坏这些保证。 - (严重错误)不要通过拼接原始参数来构建路径。 当前渲染器传入的
$1和$2都是绝对路径。诸如WORKING_FOLDER=.tmp/$1、WORKING_FOLDER=<lang>_$1或"$current_dir/$2"这样的写法会悄悄生成重复路径(/project//abs/path/...)——典型症状是ImportError: Start directory is not importable,或者“文件夹不存在”,且路径中项目根目录重复出现。始终使用$(basename "$1")/Split-Path -Leaf生成工作文件夹,并使用步骤 3 中解析出的$CONFORMANCE_TESTS_FOLDER。 - (严重错误——仅激活模式)不要删除工作文件夹。 不要使用
trap 'rm -rf …' EXIT,不要在finally中使用Remove-Item,也不要在失败时清理。工作文件夹是准备步骤的交付物,并会在每次渲染中被多次附加;第一次调用后将其删除,会导致后续每次调用都无法通过“已准备环境缺失”检查。清理操作只属于内联安装模式。 - 如果已有
prepare_environment_<lang>脚本,不要生成内联安装模式。 一致性脚本中的暂存rm -rf和退出时清理会清除准备步骤完成的所有内容,而内联安装会在每次运行时从头重做。务必先执行模式选择检查。 - 如果没有准备脚本,不要生成仅激活模式。 每次运行时,“验证已准备环境”检查都会失败,因为没有任何步骤填充工作文件夹。
- 已准备环境缺失时,不要静默地从仅激活模式回退到内联安装。 应以退出码
69退出并给出明确错误,以便发现配置错误。静默回退会掩盖真正的问题,并导致不同运行之间的行为不一致。 - 不要将一致性测试文件夹复制到工作文件夹中。 只暂存构建文件夹(且仅限内联安装模式)。测试文件夹应从
$CONFORMANCE_TESTS_FOLDER原地读取。 - 不要在
cd之后计算测试路径。 在步骤 3 中将$2解析为绝对路径;否则,相对路径$2会相对于工作文件夹解析,导致无法找到测试且不易察觉。 - 不要跳过“未发现测试”检查。 一致性测试套件发现零个测试却以
0退出,是最糟糕的失败模式——它在 CI 中看起来像是成功。 - 不要跳过工具链检查,即使“所有人都已安装”也是如此——调用系统依赖退出码
69来检测缺失的运行时。 - 不要原地复用源文件夹(内联安装模式)。务必先将其复制到
<temp>/<lang>_<basename>;渲染器依赖这种隔离。 - 不要更改退出码约定。 系统的其他部分会针对
69和1执行分支逻辑——Bash 和 PowerShell 版本必须使用完全相同的退出码。 - 不要编写跨 Shell 混合脚本(例如检测 PowerShell 的
.sh脚本,或反过来)。每种 Shell 都应提供一个独立脚本,并使用相应的扩展名。 - 内联安装模式下,不要将依赖安装到用户的全局位置(
~/.m2、系统级pip、~/.cargo等)。务必将其隔离在$WORKING_FOLDER中,以免并发运行和其他项目相互干扰。 - 运行测试命令前,务必先验证安装或激活是否成功。 安装失败(或缺少已准备的环境)后仍运行“测试”,会产生看似测试失败的误导性错误。
- 不要忘记更新
config.yaml。 创建一致性测试脚本后,务必在config.yaml中添加或更新conformance-tests-script:键,使其指向新脚本。缺少此项,渲染器就不知道在哪里查找一致性测试脚本。 - 不要忘记在基础
.plain文件中将脚本链接为资源。 如果脚本只在config.yaml中引用,它对规范约定来说就是不可见的——渲染器将conformance-tests-script:视为构建系统指针,而不是模型所读取的测试需求约定的一部分。使用add-resource技能,从负责一致性测试需求的.plain模块添加指向该脚本的 Markdown 链接(参见工作流步骤 9)。省略此资源链接意味着模型在编写和审查一致性测试代码时,始终看不到它必须满足的实际运行器;对于仅激活脚本,模型也无法了解测试运行前必须已就绪的隔离路径。 - 不要编写过于简略的脚本。 静默步骤、安装或激活输出被重定向到
>/dev/null、缺少--version输出、没有模式横幅、没有记录解析出的$2,以及缺少最终摘要,都会让脚本比它运行的测试更难调试。请严格遵循约定下的日志记录——尽可能详尽规则:每个步骤都要说明自身操作,每条命令都要回显,横幅中要注明模式,打印解析出的$CONFORMANCE_TESTS_FOLDER,每次失败时都要说明即将运行的命令及其位置,最终摘要要列出模式、测试命令、退出码和工作文件夹。