ARTICLE DETAIL

资讯详情

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

waspc e2e 测试:基于黑盒 Shell 命令与 Golden 快照的 Wasp CLI 验证体系

waspc e2e 测试:基于黑盒 Shell 命令与 Golden 快照的 Wasp CLI 验证体系 waspc e2e 测试基于黑盒 Shell 命令与 Golden 快照的 Wasp CLI 验证体系【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp本篇技术文章以 waspc/e2e-tests/README.md 为核心完整解析 Wasp 编译器waspc端到端测试的设计思想与实现细节如何把 Wasp CLI 当作黑盒来验证其全部命令行为如何通过test-outputs/下的测试用例目录与current/golden双快照目录组织测试产物以及如何用文件存在性清单manifest加内容 diff 双重比对来保证生成应用的确定性输出。读完本文你将掌握这套 e2e 测试的运行方式run test:waspc:e2e等命令、目录布局约定、快照比对的忽略规则与确定性技巧以及 golden 快照的更新流程。设计目标只验证接口与输出不关心内部实现根据 README 的说明waspce2e 测试的目的是验证 Wasp 二进制按预期工作而不关心其内部实现。这个定位决定了整个测试体系的两个关键约束接口Interface通过 Wasp CLI 暴露。测试要覆盖所有 Wasp CLI 命令的行为每条命令都被当作黑盒black box。输出Outputswaspc的主要输出是 Wasp 应用。测试要验证 CLI 命令能正确生成或修改 Wasp 应用此外还覆盖次要输出例如 CLI 本身的安装/卸载、bash补全等。测试入口是 waspc/e2e-tests/Main.hs它是一个 Tasty 测试套件在 waspc.cabal 中声明为test-suite waspc-e2e-testsmain-is: Main.hs并声明了build-tool-depends: waspc:wasp-cli即构建测试前会先编译出wasp-cli可执行文件。从 Main.hs 的main函数可以看到几个环境层面的事实Windows 上直接跳过main首先判断os mingw32若是则打印 Skipping end-to-end tests on Windows due to tests using *nix-only commands 并退出。这是由测试大量使用 shell 命令决定的适用限制。被测 CLI 由环境变量指定ensureE2eTestsEnvironment在未设置WASP_CLI_CMD时将其默认指向 waspc/tools/wasp-cli-dev即当前waspc源码树构建出的开发版 CLI而不是全局安装的发布版。串行预热构建warmUpWaspCli会在并发跑快照测试之前先执行一次$WASP_CLI_CMD version。注释解释了原因开发版 CLI 通过cabal run执行Cabal 不支持多个并发调用共享同一个dist-newstyle否则会出现 package.conf.inplace already exists 的竞态失败。先串行执行一次可强制构建完成后续并发调用只运行已构建好的 CLI。最终的测试树被组织成三个分组testGroup E2E tests [ testGroup Snapshot Tests snapshotTestTrees, testGroup Shell tests shellTestTrees, testGroup Tests [sdkPackageExportsTestTree] ]其中 Shell tests 覆盖了wasp-new、wasp-telemetry、wasp-completion、wasp-version、wasp-compile、wasp-build、wasp-clean、wasp-project-lock、wasp-show、wasp-install、wasp-deps、wasp-dockerfile、wasp-db-start、wasp-db-seed、wasp-db-reset、wasp-db-migrate-dev等命令wasp-start、wasp-build start、wasp studio、wasp db studio这几个长驻进程类命令在 Main.hs 中以FIXME注释形式挂起等待重构为纯 Haskell 测试后补齐代码中引用了重构 issue。变体一Shell Tests——输出可丢弃的命令行为测试README 将Tests定义为输出不需要保存的测试测试那些结果可以直接丢弃的 Wasp CLI 命令。这类测试的执行框架在 waspc/e2e-tests/Test.hsdata Test Test { name :: String, testCases :: [TestCase] } data TestCase TestCase { name :: String, shellCommandBuilder :: ShellCommandBuilder TestContext [ShellCommand] }一个Test由name和一组TestCase组成每个用例本质上是一个有上下文的 shell 命令构建器。以 Tests/WaspNewTest.hs 为例wasp-new测试包含 4 个用例create-minimalwasp new wasp-app -t minimal、create-minimal-interactive通过管道把应用名和模板名喂给交互式wasp new、create-basic、create-basic-interactive分别验证非交互与交互式创建项目的两条路径。执行流程在testTreeFromTest中getTestCaseDir test.name testCase.name取得该用例的TestCaseDir路径规则见下节setupTestCase先rm -rf再mkdir -p保证每个用例从干净目录开始executeTestCaseCommand把构建出的完整 shell 命令以testCaseDir为工作目录执行std_out和std_err都重定向到用例目录下的output.log日志文件写入逻辑在 TestLogging.hs它会先写入 测试名 、 Command 、 Output 三段头最后只看退出码ExitSuccess通过ExitFailure code则expectationFailure并把日志文件路径和全文拼进失败信息formatCommandFailure。这种退出码即断言的模式要求用例自身负责把断言编码进 shell 命令。ShellCommands.hs 提供了几种通用断言手段assertCommandOutputContains把命令输出重定向到.wasp-e2e-output.log再用grep -qF marker断言输出包含指定字符串。例如版本匹配测试 Tests/WaspVersionTest.hs 执行$WASP_CLI_CMD version | { read ver; [ $ver waspVersion ]; }其中waspVersion直接来自 Haskell 模块Wasp.Version从而验证 CLI 报告版本与 waspc 源码版本一致wasp show spec --json测试则用~| jq .管道强制 stdout 必须是合法 JSON~|、~、~?三个中缀操作符分别生成|、和if cond; then cmd; fi结构的命令链用例作者可以用 Haskell 的 Monad 语法拼出任意 shell 流程。测试产物目录布局TestCaseDir 与 SnapshotDirFileSystem.hs 用强类型 newtypeTestCaseDir、SnapshotDir、TestOutputsDir等把整个测试文件系统的布局固化成路径类型防止误操作。其根目录约定在 README 中写明所有测试输出创建在waspc/e2e-tests/test-outputs/testsOutputsDirInWaspcDir [reldir|e2e-tests/test-outputs|]。Shell Test 的 TestCaseDirtest-outputs/test-name/test-case-name/通常包含e2e-tests/ └── test-outputs/ └── test-name/test-case-name/ # 测试用例目录 ├── wasp-app/ # 该测试用 Wasp 应用约定项目名即 wasp-app ├── output.log # 命令执行日志 └── ...其中wasp-app/是测试中创建 Wasp 项目时的固定项目名createTestCaseCommand里把waspProjectName硬编码为wasp-app所有项目内命令都cd进这个目录执行。Snapshot Test 的 SnapshotDirtest-outputs/snapshots/name-current|golden/的命名规则是快照测试名-快照类型例如wasp-build-current与wasp-build-goldene2e-tests/ └── test-outputs/ └── snapshots/ └── name-snapshot-type/ # 例如 wasp-build-current、wasp-build-golden ├── wasp-app/ # 该快照测试的 Wasp 应用 └── snapshot-file-list.manifest # 声明快照目录中应存在的文件清单当前仓库 waspc/e2e-tests/test-outputs/snapshots/ 下实际提交了 4 组 golden 快照wasp-new-golden、wasp-compile-golden、wasp-build-golden、kitchen-sink-golden对应 Main.hs 中注册的 5 个快照测试里的 4 个wasp-migrate的 golden 同样参与比对。另有一个细节快照测试的日志文件放在snapshots/目录下作为快照目录的兄弟文件name.log见snapshotLogFileInSnapshotsDirFileSystem.hs 注释明确说明这是so it doesnt pollute the snapshots file-list / content comparison against golden——日志不进入快照目录就不会干扰与 golden 的文件清单和内容比对。变体二Snapshot Tests——生成应用的确定性比对README 指出Wasp 应用输出主要用快照测试来验证把当前测试输出current与期望输出golden比较比较方式有两种——按存在性文件必须同时出现在 current 与 golden 中和按内容两快照中文件内容必须完全一致。具体实现在 waspc/e2e-tests/SnapshotTest.hs核心流程由prepareSnapshotTestData串联setupSnapshotTestEnvironment currentSnapshotDir goldenSnapshotDir -- 1. 环境准备 executeSnapshotTestCommand snapshotTest currentSnapshotDir logFile -- 2. 执行被测命令 generateSnapshotFileListManifest currentDir manifestFile -- 3. 生成存在性清单 getNormalizedSnapshotFilesForContentCheck currentDir -- 4. 归一化内容比对文件随后createSnapshotTestTree为 current 快照中的每一个文件生成一个 TastygoldenVsFileDiff用例diff 命令是diff -u golden-file current-filegolden 路径通过将 current 文件的相对路径映射到 golden 目录得到mapCurrentToGoldenSnapshotFile。也就是说存在性由文件必须能在 golden 中找到同名同相对路径文件保证内容由逐文件diff -u保证失败时测试输出就是可读的 unified diff。存在性与内容比对的忽略规则生成应用的输出里天然混有大量不确定性文件SnapshotTest.hs 分别维护了两份过滤清单忽略项存在性检查manifest内容检查源码注释中的原因.DS_Store、node_modules是是系统噪音/依赖目录.wasp/spec子树是是拷贝进.wasp/spec的wasp.sh/spec包与仓库waspc/data/packages/spec完全相同单独在包测试中覆盖.wasp/.projectlock是是项目锁文件按设计持久存在且包含上次 Wasp 命令的 PID不确定*.tgz是是本地 tarball 依赖CLAUDE.md、dev.db、dev.db-journal、.gitignore、.waspinfo、package-lock.json、tsconfig.*.tsbuildinfo、dist否是数据库文件、增量构建信息、lockfile 等与命令行为无直接关系或内容不确定manifest 本身是确定性的文件清单递归遍历快照目录getDirFiltered→ 过滤目录只留文件 → 相对化路径 → 排序 → 逐行写入snapshot-file-list.manifest。这样即使文件被增删diff 也能精确定位到多生成了哪个文件/漏生成了哪个文件。package.json 归一化消除 JSON 排版噪音内容比对前还有一步归一化formatPackageJsonFiles对快照中所有package.json文件执行aeson解码后用aeson-pretty重新编码2 空格缩进、键排序、confTrailingNewline True。SnapshotTest.hs 中的注释引用了 issue #482 说明动机——不同 npm 版本写入package.json的排版/键序可能不同归一化后快照比对只关心语义内容。让被测输出本身变得确定性wasp-build 快照为例确定性不只靠忽略还靠让生成过程本身稳定。Tests/SnapshotTests/WaspBuildSnapshotTest.hs 是一个完整的快照测试样例其命令序列是createSnapshotWaspProjectFromMinimalStarter用 minimal 模板wasp new wasp-appsetWaspDbToPSQL把schema.prisma第 2 行替换为provider postgresql源码注释自认 Fragile, assumes line numbers do not changewaspCliBuild执行wasp buildbuildAndRemoveWaspProjectDockerImagedocker build --build-arg BUILDKIT_DOCKERFILE_CHECKerrortrue -t waspc-e2e-tests-wasp-app .后立刻docker image rm只验证生成的 Dockerfile 可构建wrapViteConfigForDeterministicBuildviteBuild生成vite.config.wrapper.ts包装原始vite.config.ts注入三项确定性配置——minify: false输出可读、便于 diff、entryFileNames/chunkFileNames/assetFileNames去掉内容哈希跨运行文件名稳定、以及一个externalizeNodeModules插件把所有解析进node_modules的 import 标记为 external构建产物只含应用代码diff 更干净最后执行REACT_APP_API_URLhttp://localhost:3001 npx vite build --config vite.config.wrapper.ts。另一个确定性技巧在 ShellCommands.hs 的waspCliDbMigrateDevwasp db migrate-dev --name name默认生成date-name迁移目录测试随后用mv把date-name改名为no-date-name同时处理项目内migrations/与生成输出.wasp/out中的db/migrations/两处保证快照比对不受时间戳影响。源码注释同时说明mv失败被2/dev/null || true抑制是因为没有可迁移内容时命令成功但不创建文件大括号; }用于把|| true的作用域限定在mv上避免吞掉链上前序命令的失败。命令构建 DSLShellCommandBuilder 与三种上下文所有测试共享同一个命令 DSLShellCommands.hsShellCommandBuilder context a ShellCommandBuilder (Reader context a)是一个带上下文的 Reader monadbuildShellCommand时把具体上下文代入。上下文分三层WaspProjectContextwaspProjectDirwaspProjectName假设命令在 Wasp 项目内执行TestContext额外携带testCaseDir命令的工作目录是TestCaseDirinTestWaspProjectDir帮助器负责cd wasp-app/ ... cd 回用例目录SnapshotTestContext额外携带snapshotDirinSnapshotWaspProjectDir以同样方式在快照目录与项目目录之间切换。基于该 DSL 的常用构件还包括waspCliNew/waspCliNewInteractive$WASP_CLI_CMD new wasp-app -t template或以printf wasp-app\ntemplate\n | $WASP_CLI_CMD new模拟交互输入waspCliDbStart、waspCliDbSeed、waspCliDbResetdb reset --force、waspCliShowSpecshow spec、waspCliInstallwasp install等项目命令writeToFile文件内容先 base64 编码再base64 -d file落盘注释说明这是为了escape dealing with special characters避免 shell 引号转义地狱replaceLineInFile则用awk按行号整行替换setWaspDbToPSQL基于它实现skipIfDockerDisabled把 Docker 相关命令包进if [ -z $WASP_E2E_TESTS_SKIP_DOCKER ]; then ...; else echo SKIPPED: ...; fi通过WASP_E2E_TESTS_SKIP_DOCKER环境变量在无 Docker 环境跳过 Docker 用例copyContentsOfGitTrackedDirToSnapshotWaspProjectDir用git ls-files srcDir | sed 去前缀 | rsync -a --files-from-只拷贝 git 跟踪文件供快照测试复用仓库内现成的示例工程如 kitchen-sink 快照。运行、并发控制与 golden 更新运行方式仓库在 waspc/run 脚本中封装了常用命令e2e 相关入口命令实际执行run test:waspc:e2ecabal test waspc-e2e-tests --test-options--hide-successes先构建run test:waspc:e2e:accept-allrm -rf e2e-tests/test-outputs/snapshots/*-golden cabal test waspc-e2e-tests ...即删除全部 golden 输出后重跑快照测试把 current 产物固化为新 goldenrun test:waspc单元测试 e2e 测试运行前提是在waspc/目录下FileSystem.hs 的getWaspcDirPath有防护unless (takeFileName absCwd waspc) (error Expecting test process to be invoked from waspc dir)因为 Cabal 总是从 waspc 目录启动cabal test系统为 *nixNode 工具链可用快照测试会 shell 出npm install、tsc、vite buildDocker 相关用例可用WASP_E2E_TESTS_SKIP_DOCKER环境变量跳过。并发度控制Main.hs 中 5 个快照测试通过pooledMapConcurrentlyN受控并发准备。源码注释解释了取舍每个快照测试都会 shell 出 Node 工具链而这些工具自身已经跑满所有核心全量并发会导致 CPU 超订oversubscribe。默认并发度为max 1 (numCores div 4)可用环境变量WASP_E2E_TEST_MAX_JOBS覆盖为正整数例如设为 1 完全串行准备。进程组清理防止残留子进程快照测试的命令执行不走普通callCommand而是 SnapshotTest.hs 中的callCommandInProcessGroup以create_group True创建独立进程组bracket结束时无论正常还是异步异常/线程取消执行interruptProcessGroupOf。注释说明动机当并发测试中另一个测试失败导致本线程被取消时必须终止整个子进程树npm、tsc等派生的进程而不是让它们继续跑。Shell Test 侧的执行路径相对简单普通createProcesswaitForProcess但同样把 stdout/stderr 完整落盘为日志。小结这套体系对测试代码生成器的启示waspce2e 测试README、Main.hs、Test.hs、SnapshotTest.hs、FileSystem.hs、ShellCommands.hs展示了验证CLI 代码生成器类工具的一套可复用模式双层测试结构行为可丢弃的用 shell 退出码断言Shell tests生成物重要的用 golden 快照逐文件比对Snapshot tests职责分明确定性工程固定项目名wasp-app、迁移名去日期前缀、package.json语义归一化、Vite 去哈希去压缩、外部化依赖配合精细的两级忽略清单让比对生成应用全目录成为可能可审计的执行每个用例/快照的命令与完整输出都落盘output.log、name.log失败信息直接指向日志强类型目录布局FileSystem.hs用 newtype 路径区分TestCaseDir/SnapshotDir/TestOutputsDir从类型层面杜绝写错目录的风险受控并发与清理WASP_E2E_TEST_MAX_JOBS限流 进程组级中断 WASP_CLI_CMD指向本地构建的开发版 CLI保证并发跑批稳定且被测对象就是当前源码树。若你的项目也是命令生成/修改整个目录树的工具这套黑盒命令 目录级 golden 快照 归一化与忽略清单的组合可以直接作为落地模板参考相关源码全部位于 waspc/e2e-tests/ 目录golden 样例见 waspc/e2e-tests/test-outputs/snapshots/。【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表