feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,8 @@
|
||||
{"file": ".trellis/tasks/09-19-application-testing/prd.md", "reason": "逐项核对应用测试验收标准"}
|
||||
{"file": ".trellis/tasks/09-19-application-testing/design.md", "reason": "核对实现未偏离最终架构与隔离边界"}
|
||||
{"file": ".trellis/tasks/09-19-application-testing/implement.md", "reason": "核对分阶段交付与允许执行的验证"}
|
||||
{"file": ".trellis/spec/backend/compiler-contracts.md", "reason": "检查语言合同和生产编译路径无回归"}
|
||||
{"file": ".trellis/spec/backend/error-handling.md", "reason": "检查诊断与测试失败报告"}
|
||||
{"file": ".trellis/spec/backend/quality-guidelines.md", "reason": "检查重复、职责和定向覆盖"}
|
||||
{"file": ".trellis/spec/backend/database-guidelines.md", "reason": "检查临时 SQLite 隔离且不触达正式数据库"}
|
||||
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "检查源码到 CLI/runtime 的跨层闭环"}
|
||||
@@ -0,0 +1,143 @@
|
||||
# Dever 应用测试体系技术设计
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
module/ + test/
|
||||
-> one SourceMap with compiler-assigned Application/Test origins
|
||||
-> existing parser and Markdown extraction
|
||||
-> existing declaration registration and semantic checker
|
||||
-> Test layout/access rules
|
||||
-> filename-matched TestCase metadata
|
||||
-> test-only assert/assert_eq lowering
|
||||
-> checked Program
|
||||
-> one native suite containing every checked TestCase specialization
|
||||
-> case-id dispatch into one isolated native process + temporary project directory per case
|
||||
-> captured result + deterministic CLI summary
|
||||
```
|
||||
|
||||
There is no test interpreter, dynamic test registry, source macro, runtime reflection or production test symbol. Test behavior is a thin compiler-owned entry mode over the same checked Program and native backend.
|
||||
|
||||
## Source Ownership And Identity
|
||||
|
||||
`SourceFile` gains a compiler-assigned source origin rather than inferring test authority from a user-controlled path. Production loading remains the current `SourceMap::load(module_root)` behavior. A dedicated project-test loader loads `module/` as application sources and an existing `test/` as test sources; a missing test root is an empty set, while a non-directory or unreadable root is an explicit load error.
|
||||
|
||||
Test sources retain two paths:
|
||||
|
||||
- display path: `test/user/account/register.dever`, used by diagnostics and CLI output;
|
||||
- logical path: `user/account/register.dever`, used for package identity and Markdown package documentation.
|
||||
|
||||
The new `SourceLayout::Test { component, domain, topic }` is valid only for exactly three logical segments. It applies existing lower-snake-case and generic-bucket rules. `.dever` and `.dever.md` with the same logical stem are one duplicate test identity.
|
||||
|
||||
The checker cache identity includes source origin/logical path so a strict application source and a test source with equal bytes cannot share the wrong checked result.
|
||||
|
||||
## Test Entry Contract
|
||||
|
||||
For `test/user/account/register.dever`, the logical package is `user.account.register`; the checker selects its only ordinary `register() ()` function and records a `TestCase` containing:
|
||||
|
||||
- stable public identity `user/account/register`;
|
||||
- checked private function id;
|
||||
- source span and whether its reachable graph uses a Model.
|
||||
|
||||
Other declarations stay private to the same test file. A same-name overload, transaction/recovery entry, parameters or outputs are rejected before native compilation. Test functions are never marked public and never enter API/model snapshots.
|
||||
|
||||
`Program` exposes read-only test metadata to the CLI. Native compilation accepts an already checked test-case id through a dedicated method instead of weakening `Program::entry` or making test functions public.
|
||||
|
||||
## Visibility
|
||||
|
||||
Test resolution extends the existing role-aware symbol owner rather than adding a parallel resolver:
|
||||
|
||||
- same file: private helper functions/types;
|
||||
- same domain: App and Domain functions/types;
|
||||
- other domains: App public surface only;
|
||||
- Model values/types: readable under existing public/type rules;
|
||||
- Model operations: still rejected because only the owning App role may issue CRUD;
|
||||
- Adapter, Port, API and other Test owners: not callable.
|
||||
|
||||
All production callers reject Test targets before ordinary same-domain checks, preventing reverse dependencies. Test assertions are available only while checking a Test owner.
|
||||
|
||||
## Assertions
|
||||
|
||||
`assert` and `assert_eq` are compiler-owned test intrinsics, not bundled library functions. They reuse the existing `Intrinsic` expression shape so dependency, effect, liveness and native traversal do not gain a second assertion IR hierarchy.
|
||||
|
||||
- `assert(value)` checks one `Bool` expression.
|
||||
- `assert_eq(actual, expected)` checks the left expression first, uses its exact type as the right-hand expectation, and requires the existing `Type::comparable` contract.
|
||||
- both return Unit and are legal only as ordinary zero-output actions in Test-owned functions;
|
||||
- declarations named `assert` or `assert_eq` remain ordinary outside tests and are reserved inside a test file.
|
||||
|
||||
Native emission evaluates each operand once and preserves source order. On failure it creates a source-located assertion error. `assert_eq` renders concrete typed values through the existing `Render` implementations; generated records/choices retain their source-shaped renderer. No generic runtime `Value`, serialization round-trip or reflection is introduced.
|
||||
|
||||
## Native Execution And Reporting
|
||||
|
||||
The CLI checks the combined source set once, sorts `TestCase` metadata, then compiles one native test suite and runs each case serially. The suite unions the existing specialization graphs and dispatches one checked private entry by its stable sorted case index. It does not introduce a dynamic function registry or runtime reflection.
|
||||
|
||||
Every case still starts a fresh process from the same immutable suite executable. The runner stages that executable beside a new temporary project for each process, so runtime singletons, generated SQLite settings, Model initialization and data remain isolated without an in-process reset protocol. A suite that contains any database test links the SQLite-capable runtime once; only a selected database branch loads its generated setting and initializes Models. A non-database branch does neither.
|
||||
|
||||
Native cache identity continues to cover generated code, compiler identity, runtime bytes, toolchain, platform, environment and compiler options. Tests use a dedicated fast compilation profile because they validate behavior rather than production code generation performance; `run` and `build` retain the production optimization contract.
|
||||
|
||||
For each case the runner:
|
||||
|
||||
1. creates an owned temporary project directory;
|
||||
2. reuses the single compiled suite and selects the checked private test entry by internal case index;
|
||||
3. stages the executable in that directory;
|
||||
4. writes a generated `config/setting.json` only when the reachable graph uses Model operations;
|
||||
5. runs the executable with the temporary project as its executable/config root;
|
||||
6. captures stdout/stderr and records pass/failure;
|
||||
7. drops all owned native and runtime artifacts before continuing.
|
||||
|
||||
Successful process output is captured and omitted from runner status. Failed output is printed in clearly labelled blocks below the case result. A source/check failure prevents all execution; a per-case compile/start/run/assert/business/fault failure is recorded and later cases continue. The final exit status is nonzero when any case failed or was not run.
|
||||
|
||||
The first release has no child timeout or signal manager. Explicit cancellation may terminate the command; recoverable success/failure paths own deterministic cleanup.
|
||||
|
||||
## SQLite Isolation
|
||||
|
||||
The runner never loads the project's deployment database configuration for tests. If no test graph has a database effect, the suite uses the base runtime profile. A mixed suite links the SQLite-capable runtime once, but a selected non-database case writes no setting file and does not call database bootstrap or Model initialization.
|
||||
|
||||
If the graph has a database effect, the runner generates a temporary setting with:
|
||||
|
||||
- `database.default` as SQLite;
|
||||
- one SQLite entry for each explicit Model connection name;
|
||||
- paths below that case's `data/db/`;
|
||||
- one connection and the existing bounded page defaults.
|
||||
|
||||
Package-root selectors retain runtime resolution semantics: an undeclared root falls back to `default`; explicit selectors retain separate connections. The generated settings are parsed by `dever_runtime::config::Settings`, validated by `Program::validate_database_settings`, and consumed by the normal bootstrap, schema migration, Seed, transaction and shutdown code. No test-only database handle reaches Dever source.
|
||||
|
||||
The emitter currently initializes the application's complete Model schema once a reachable test uses the database. This preserves foreign keys, migrations and Seed as an application-wide contract. A non-database test uses no database profile even when unrelated Models exist in the module.
|
||||
|
||||
## CLI And Formatting
|
||||
|
||||
`Action::Test` is added to the existing explicit CLI argument parser; there is no generic subcommand framework. The command performs semantic checking, normal warnings and API baseline validation before execution.
|
||||
|
||||
Formatting remains syntax-only. `fmt` discovers both source roots, prepares every replacement before committing any, validates both snapshots again, then performs atomic per-file renames. A missing `test/` contributes no files. This prevents module files from changing when a test Markdown contract or file permission fails.
|
||||
|
||||
Human output follows one stable shape:
|
||||
|
||||
```text
|
||||
running 2 tests
|
||||
test news/article/publish ... ok
|
||||
test user/account/register ... FAILED
|
||||
|
||||
test result: FAILED. 1 passed; 1 failed; 0 not run
|
||||
```
|
||||
|
||||
No JSON/JUnit output or test filter is introduced.
|
||||
|
||||
## Markdown And CMS
|
||||
|
||||
Test Markdown uses the existing strict document contract. Its package metadata names the logical package (for example `user.account.register`), and its function section documents the filename-matched entry. No test-specific Markdown headings are added.
|
||||
|
||||
The maintained CMS Dever and Markdown projects receive equivalent application tests. They call existing App boundaries and assert observable business values and Model counts. They do not add test-only production App functions or duplicate CRUD.
|
||||
|
||||
## Compatibility And Trade-offs
|
||||
|
||||
- Production `check/api/run/build` continue loading only `module/`; their checked Program and native cache inputs remain unchanged.
|
||||
- `fmt` intentionally expands to the optional test root because tests are first-class Dever source.
|
||||
- One suite link removes per-case native compilation. Process-per-case dispatch retains runtime/database isolation, while the suite may be larger than one reachability-pruned test binary.
|
||||
- Real PostgreSQL application tests are deferred. Adding them later requires an explicit safe test connection and cleanup contract; it must not alter default SQLite behavior.
|
||||
- There are no fixture hooks or shared test modules. A later addition must prove that it does not introduce hidden order or shared mutable state.
|
||||
|
||||
## Cleanup And Rollback
|
||||
|
||||
All test execution artifacts are owned by RAII temporary-directory and native-program guards. Cleanup errors do not replace the primary test result. No project source, deployment setting or data path is rewritten by `dever test`.
|
||||
|
||||
The change is additive. Rollback removes the Test source origin/layout, assertion lowering, CLI action and example tests; no production source migration or database migration is required.
|
||||
@@ -0,0 +1,11 @@
|
||||
{"file": ".trellis/tasks/09-19-application-testing/prd.md", "reason": "当前应用测试需求、边界与验收标准"}
|
||||
{"file": ".trellis/tasks/09-19-application-testing/design.md", "reason": "已收口的编译器与运行器技术设计"}
|
||||
{"file": ".trellis/tasks/09-19-application-testing/implement.md", "reason": "分阶段实施与最小验证清单"}
|
||||
{"file": ".trellis/spec/backend/compiler-contracts.md", "reason": "保持语言、检查器、HIR 与原生后端单一路径合同"}
|
||||
{"file": ".trellis/spec/backend/directory-structure.md", "reason": "约束源码目录、crate 与测试文件归属"}
|
||||
{"file": ".trellis/spec/backend/error-handling.md", "reason": "约束源码诊断、运行失败与错误传播"}
|
||||
{"file": ".trellis/spec/backend/quality-guidelines.md", "reason": "约束复用、复杂度与回归测试质量"}
|
||||
{"file": ".trellis/spec/backend/toolchain-and-library.md", "reason": "约束 Rust 工具链、依赖与定向命令"}
|
||||
{"file": ".trellis/spec/backend/database-guidelines.md", "reason": "约束 Model、SQLite 隔离与 PostgreSQL 边界"}
|
||||
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "避免测试路径形成平行 parser/checker/runtime"}
|
||||
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "核对 CLI 到 checker/native/runtime 的完整数据流"}
|
||||
@@ -0,0 +1,123 @@
|
||||
# Dever 应用测试体系实施清单
|
||||
|
||||
## Start Gate
|
||||
|
||||
- [x] 用户在最终规划摘要之后明确批准实施。
|
||||
- [x] 运行 `python3 ./.trellis/scripts/task.py start 09-19-application-testing`,确认任务进入 `in_progress`。
|
||||
- [x] 重新加载 `trellis-before-dev`,核对当前 PRD、设计、执行清单和 backend specs。
|
||||
- [x] 开工前记录实际 git/worktree 状态,只处理本任务拥有的文件,不清理用户已有改动。
|
||||
|
||||
## Phase 1: Test Source Layout And Static Contract
|
||||
|
||||
- [x] 在 `source.rs` 增加编译器赋予的 Test 来源、显示/逻辑路径和组合项目加载;保持现有生产 `SourceMap::load` 合同。
|
||||
- [x] 在 layout/parser/checker 中加入严格的 `<component>/<domain>/<topic>` Test 布局,不增加源码关键字或第二套 parser。
|
||||
- [x] 在检查结果中建立按身份排序的 `TestCase`,强制同名 ordinary 零输入零输出入口和文件内私有辅助声明。
|
||||
- [x] 扩展 role-aware function/type resolution:同领域 App+Domain、跨领域 App、禁止 Model CRUD/Adapter/Port/API/其他测试。
|
||||
- [x] 把来源/逻辑路径加入 check cache identity,防止 Test 与普通 strict source 误复用。
|
||||
- [x] 增加路径、入口、重复身份和可见性的定向正反测试。
|
||||
|
||||
## Phase 2: Assertions And Native Entry
|
||||
|
||||
- [x] 复用 `Intrinsic` HIR 形状加入仅 Test owner 可构造的 `assert`/`assert_eq`,保持普通同名业务函数语义不变。
|
||||
- [x] `assert` 强制 Bool;`assert_eq` 强制相同 comparable 类型并保持左右各求值一次、源顺序不变。
|
||||
- [x] 在 native emitter 生成源码定位的 assertion failure,复用现有 `Render` 输出 actual/expected。
|
||||
- [x] 增加从 checked `TestCase` 编译私有入口的 native API,不放宽生产 `Program::entry` 的公开性要求。
|
||||
- [x] 验证标量、record、choice、nullable、List、Map、MapEntry、Model ID,以及不可比较/类型不匹配/生产源码调用断言的拒绝。
|
||||
|
||||
## Phase 3: CLI Runner And Isolation
|
||||
|
||||
- [x] 在 CLI 增加 `test <project-root>`,复用现有 module 检查、warning 和 API baseline 路径。
|
||||
- [x] 建立单一 `test_runner` owner,负责排序、单套件 compile、逐用例独立进程、输出捕获、状态汇总和临时目录生命周期;不创建通用 runner/framework 层。
|
||||
- [x] 无测试时输出 `0 tests` 成功;源码检查失败时零执行;单用例失败后继续后续用例。
|
||||
- [x] 对无数据库 effect 的用例使用 base profile,不读写 setting。
|
||||
- [x] 对有数据库 effect 的用例生成临时 SQLite setting,复用正式 Settings 校验、Model schema/Seed/transaction/shutdown。
|
||||
- [x] 每用例使用独立临时项目和数据库;确认项目 `config/setting.json`、`data/` 与环境变量不参与数据库选择。
|
||||
- [x] 失败时分区展示捕获的 stdout/stderr,runner 状态行不与测试输出混合。
|
||||
- [x] 覆盖编译失败、启动失败错误映射、断言失败、未捕获业务失败、runtime fault 和清理。
|
||||
|
||||
## Phase 4: Formatter And Maintained CMS
|
||||
|
||||
- [x] 调整 CLI formatter,使 module/test 两个可选源码集合先完整诊断和 prepare,再统一 commit;任一失败时零改写。
|
||||
- [x] 为 CMS Dever 项目增加 `test/<component>/<domain>/<topic>.dever` 业务用例。
|
||||
- [x] 为 CMS Markdown 项目增加相同路径、入口和合同的 `.dever.md` 用例,遵守现有 Markdown 文档协议。
|
||||
- [x] 删除 CMS CLI 回归中被正式应用测试替代的临时 probe 源码注入,仅保留仍验证 build/package 的独立职责。
|
||||
- [x] 更新 `LANGUAGE.md`、`MARKDOWN-SYNTAX.md`、根 README 和 examples README 的正式测试合同与命令。
|
||||
|
||||
## Phase 5: Refactor And Verification
|
||||
|
||||
- [x] 检查 SourceMap 双根加载、test metadata、assert lowering、runner 报告和 temp ownership 是否存在重复 owner 或平行逻辑。
|
||||
- [x] 搜索并删除调试输出、过渡 API、无用 helper、死分支、临时测试文件和过期 CMS probe。
|
||||
- [x] 确认生产 source set、API snapshot、main entry、native cache、runtime profile 与无测试项目行为不变。
|
||||
- [x] 检查磁盘空间后再运行会链接原生程序的定向测试;不运行 workspace 全量测试、真实 PostgreSQL 或性能压测。
|
||||
|
||||
## Verification Record
|
||||
|
||||
- `cargo check --offline -p dever-core --features reference -p dever-cli -p dever-tests --test application_testing`:通过。
|
||||
- `application_testing`:8/8 通过;包含 plain/Markdown 发现、可见性、类型、`result(call)`、复合断言、源码位置及含未引用 Model 的 base profile。
|
||||
- `formatter`:12/12 通过;module/test 统一 prepare 和只读失败零改写通过;两套 CMS `fmt --check` 通过。
|
||||
- `source_architecture`:8/8 通过;`markdown_source`:20/20 通过。
|
||||
- `source_visibility`:10/10 通过;`contract_execution`:9/9 通过;`api_routes`(`api` feature):7/7 通过。
|
||||
- `cms_project_checks_runs_and_builds_from_an_isolated_copy`:通过;Dever/Markdown 各连续测试两次,部署 setting 被替换为非法 JSON 时测试仍为 2/2,通过后 check/run/build/打包程序仍通过,且无效 test 源码不进入生产命令。
|
||||
- CLI 零测试、失败继续/输出分区、成功/失败临时目录清理:分别通过;`RUSTC=/bin/false` 定向故障注入确认两个编译失败均汇总且零临时残留。
|
||||
- 未捕获业务失败和动态数值 fault 的既有原生定向用例:通过。
|
||||
- `git diff --check` 和变更文件尾随空白扫描:通过;CLI 默认依赖仅启用 `dever-core` default feature,不含 reference。
|
||||
- `rustfmt`、`clippy` 组件未安装,未运行;未运行 workspace 全量测试、真实 PostgreSQL、性能/内存压测或持久服务。
|
||||
|
||||
## Phase 6: Test Suite Compilation Performance
|
||||
|
||||
- [x] 在 native owner 中生成一个包含全部 checked TestCase specialization 的测试套件,不增加解释器或动态注册表。
|
||||
- [x] 复用 native 构建缓存并使用测试专用快速编译参数;生产 `run/build` 优化参数保持不变。
|
||||
- [x] CLI 只编译一次套件,按稳定用例编号逐个启动独立进程并保留失败继续、输出捕获和临时目录清理。
|
||||
- [x] 混合数据库/非数据库套件仅在数据库分支加载临时 SQLite 配置并初始化 Model。
|
||||
- [x] 增加多入口分派、一次编译、缓存命中、配置隔离和失败继续的定向回归。
|
||||
- [x] 记录 CMS 冷/暖执行耗时和峰值 RSS;不把本机数据外推为生产性能。
|
||||
|
||||
### Phase 6 Verification Record
|
||||
|
||||
- `cargo check --offline -p dever-core -p dever-cli -p dever-tests --test application_testing --test native_cache`:通过。
|
||||
- `application_testing`:8/8 通过;一个 native suite artifact 按两个稳定编号执行成功/失败入口,并确认第二次编译命中 native cache。
|
||||
- `native_cache`:6/6 通过,另有 1 个既有显式 ignored;runtime 内容变化、工具链/参数变化、并发发布、损坏产物拒绝、原始快照与元数据摘要备忘均通过。
|
||||
- CLI 定向回归:混合数据库/非数据库套件 3/3 通过;失败后继续与 stdout/stderr 捕获通过;零测试不调用编译并成功;套件编译失败时 2/2 用例统一标记失败并清理临时目录。
|
||||
- 普通单入口 native hello 定向用例通过,确认 `run/build` 共用的生产发射路径未被套件抽取破坏。
|
||||
- CMS Dever 实际 `deverc test`:2/2 通过。稳定 runtime 下首次单套件链接为 21.37 秒、峰值 346068 KiB;随后内容缓存命中为 1.79 秒、峰值 54132 KiB。优化前同项目为 119.40 秒、594772 KiB。
|
||||
- 在 Cargo 测试环境的绝对 `RUSTC` 与普通 PATH `rustc` 之间切换会触发 Cargo 重建 native runtime;一次 75.56 秒、594976 KiB 的观测包含该重建,不作为测试套件链接基线。
|
||||
- runtime 摘要目录只保留当前元数据身份;`/tmp` 无 `dever-application-test-*`、`dever-build-*` 或 `dever-cms-project-*` 残留,CMS 示例无数据库或测试可执行文件残留。
|
||||
- `git diff --check` 与变更文件尾随空白扫描通过。`rustfmt`、`clippy` 组件仍未安装;未运行 workspace 全量测试、真实 PostgreSQL、64/128 MiB/cgroup 压测或持久服务。
|
||||
|
||||
## Focused Validation
|
||||
|
||||
允许且计划执行的最小检查:
|
||||
|
||||
```bash
|
||||
cargo check --offline -p dever-core -p dever-cli
|
||||
cargo test --offline -p dever-tests --test application_testing
|
||||
cargo test --offline -p dever-tests --test source_architecture
|
||||
cargo test --offline -p dever-tests --test markdown_source
|
||||
cargo test --offline -p dever-cli --test cms_project
|
||||
git diff --check
|
||||
```
|
||||
|
||||
如 `rustfmt`/`clippy` 组件可用,再执行:
|
||||
|
||||
```bash
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --offline -p dever-core -p dever-runtime -p dever-cli -p dever-tests --test application_testing -- -D warnings
|
||||
```
|
||||
|
||||
不运行:
|
||||
|
||||
- workspace 全量测试;
|
||||
- `cargo build` 或应用全量发布构建;
|
||||
- 真实 PostgreSQL;
|
||||
- 64/128 MiB、cgroup 或吞吐压测;
|
||||
- 持久 HTTP/网络服务。
|
||||
|
||||
## High-risk Files And Review Points
|
||||
|
||||
- `crates/dever-core/src/source.rs`、`check.rs`、`check/layout.rs`、`check/symbols.rs`:来源身份和可见性不能泄漏到生产。
|
||||
- `crates/dever-core/src/check/expressions.rs`、`intrinsic.rs`、`native.rs`、`native/intrinsics.rs`、`native/records.rs`:断言必须静态类型化、单次求值并保留源码位置。
|
||||
- `crates/dever-core/src/native/build.rs`、`model.rs`:私有测试入口不能放宽生产 entry,SQLite profile 必须来自测试生成配置。
|
||||
- `crates/dever-cli/src/main.rs`、`format.rs`、新增 runner:临时目录、输出捕获、失败继续和格式化原子性。
|
||||
- `examples/cms/{dever,md}`:两套测试合同必须同步,不给生产 App 增加测试专用能力。
|
||||
|
||||
任何实现如果需要 Test 关键字、共享动态值、生产公开测试函数、环境变量数据库配置或真实 PostgreSQL,均视为超出当前设计,回到 planning 复核。
|
||||
@@ -0,0 +1,101 @@
|
||||
# Dever 应用测试体系
|
||||
|
||||
## Goal
|
||||
|
||||
让 Dever 应用可以用 Dever 源码编写和运行确定性的领域回归测试,使 AI 修改 App、Domain 或 Model 后能够验证业务合同,而不依赖 Rust 宿主测试、HTTP 接口或正式数据库。
|
||||
|
||||
测试是可选能力。没有测试的项目保持现有开发和运行方式;测试能力不得引入第二套解释器、生产可见测试入口或环境变量配置。
|
||||
|
||||
## Background
|
||||
|
||||
- 当前 CLI 只有 `check`、`api`、`fmt`、`run` 和 `build`,应用自身没有测试命令。
|
||||
- 编译器仓库已有 Rust 回归测试,但它们验证编译器实现,不能表达具体 Dever 应用的业务合同。
|
||||
- 生产源码从 `module/` 加载,`main.main` 是 `run/build` 的唯一启动入口;App 是跨领域能力边界,Domain、Model、Port、Adapter 和 API 是领域实现边界。
|
||||
- parser、checker、HIR、原生后端、源码位置、错误传播、Model 迁移和 Seed 已有单一正式路径,应由测试复用。
|
||||
- ORM 的真实 PostgreSQL 验收已经由独立编译器测试覆盖,不属于首版应用测试命令。
|
||||
|
||||
## Requirements
|
||||
|
||||
### Source And Discovery
|
||||
|
||||
- 应用测试固定放在项目根 `test/`,与 `module/` 生产源码分离。
|
||||
- 测试路径必须是 `test/<component>/<domain>/<topic>.dever` 或 `test/<component>/<domain>/<topic>.dever.md`,不支持更深层级。
|
||||
- 每个测试文件由文件名选择同名普通函数作为唯一入口。例如 `test/user/account/register.dever` 执行其中的 `register() ()`。
|
||||
- 测试入口必须是零输入、零输出的 ordinary 函数。缺少同名函数、同名入口签名错误或重复测试身份必须在执行前产生源码诊断。
|
||||
- 测试文件中的其他函数和类型只属于该文件,不自动执行,也不能被其他测试文件或生产源码调用。
|
||||
- 不增加 `test` 关键字、`test_` 前缀、每文件 `main()`、package/expose 声明或顶层可执行语句。
|
||||
- `dever test <project-root>` 同时加载生产源码和测试源码,并在运行任何用例前完成全部语法、名称、类型、分句、effect、失败义务和架构检查。
|
||||
- 发现顺序按测试身份稳定排序,不依赖文件系统遍历顺序。
|
||||
- 项目没有 `test/` 或目录为空时,合法项目执行 `dever test` 输出 `0 tests` 并成功退出。
|
||||
- `check/api/run/build` 不加载测试源码。测试声明不进入生产二进制、API 快照或 HTTP 路由。
|
||||
- `fmt` 作为源码工具应同时格式化 `module/` 和存在的 `test/`,保持两者统一的 `.dever`/`.dever.md` 格式合同;任一源码失败时不得部分改写。
|
||||
|
||||
### Architecture And Visibility
|
||||
|
||||
- 测试文件是所属 `<component>/<domain>` 的测试友元,可以调用同领域 App 和 Domain。
|
||||
- 跨领域调用只能经过目标 App。
|
||||
- 测试可以读取 App 返回的 Model 值和同领域类型,但不能直接调用 Model CRUD、Adapter、Port 或 API。
|
||||
- 生产 App、Domain 和其他测试文件都不能反向调用测试函数或访问测试私有类型。
|
||||
- 测试不能声明 Model、HTTP 路由或生产 App 能力。
|
||||
- 测试文件必须表达一个内聚业务场景;不建立 `common`、`shared`、`utils`、`helper`、全局 fixture 等测试垃圾桶。
|
||||
|
||||
### Assertions And Failures
|
||||
|
||||
- 首版只提供测试源码专用的 `assert(condition)` 和 `assert_eq(actual, expected)`。
|
||||
- `assert` 只接受 `Bool`。`assert_eq` 要求两侧是相同且可比较的类型,不做数值提升、字符串求值或运行时反射。
|
||||
- `assert_eq` 复用现有比较与渲染合同,支持标量、record、choice、可空值、List、Map、MapEntry 和 Model ID;失败报告测试身份、源码位置、实际值和期望值。
|
||||
- 不增加 `assert_true`、`assert_false`、`assert_not_equal`、`assert_null`、异常断言或自定义 matcher。
|
||||
- 业务失败继续使用现有 `result(call)` 和 choice 分句验证;不增加 `try/catch/throws`。
|
||||
- 测试成功表示入口正常完成;断言失败、未捕获业务失败或程序 fault 表示该用例失败。
|
||||
- 一个用例失败后继续运行其余独立用例,最终汇总通过、失败和未运行数量,并以非零状态结束。
|
||||
|
||||
### Isolation And Lifecycle
|
||||
|
||||
- 首版没有 `before/after`、全局 setup、共享 fixture、用例排序依赖或测试间共享状态。
|
||||
- 每个测试文件在独立原生进程和独立临时项目目录中运行。
|
||||
- 不触达 Model 的测试不初始化数据库。
|
||||
- 触达 Model 的测试由运行器生成每用例独立的临时 SQLite 配置,走正式配置解析、迁移、Seed、事务、连接池和 shutdown 路径。
|
||||
- 测试运行器不读取、复制或连接项目正式数据库 URL;不使用环境变量或命令行数据库覆盖。
|
||||
- 临时数据库、数据目录、配置、可执行文件和运行产物在成功或失败后由运行器清理,不写入项目 `data/`。
|
||||
- 首版不使用外层事务回滚代替隔离,因为这会改变被测 App 的真实提交和回滚语义。
|
||||
- 测试按稳定顺序串行运行;并行、随机顺序、重试和自动超时不属于首版。
|
||||
|
||||
### CLI And Reporting
|
||||
|
||||
- 新增正式命令 `dever test <project-root>`;当前开发二进制对应 `deverc test <project-root>`。
|
||||
- 默认输出简洁的人类可读状态和最终汇总。测试进程 stdout/stderr 被捕获,不得冒充 runner 状态;失败时按独立区块展示。
|
||||
- 源码诊断、原生编译失败、进程启动失败、断言失败、业务失败和 runtime fault 保留各自可辨认的信息。
|
||||
- 测试复用与 `run/build` 相同的原生后端、runtime feature 裁剪、源码位置和构建缓存,不链接 reference evaluator。
|
||||
- 首版不增加测试名过滤、机器报告格式或新的数据库配置结构。
|
||||
|
||||
### Maintained Example
|
||||
|
||||
- CMS 的 Dever 与 Markdown 项目各自增加等价测试,验证账户规范化、Model 写入/Seed 和文章发布等现有 App 合同。
|
||||
- 两套测试使用相同路径、入口名称和断言,只因源码容器格式不同而不同。
|
||||
- 旧 `examples/old/` 不迁移为应用测试模板。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `.dever` 与 `.dever.md` 测试均能按 `<component>/<domain>/<topic>` 稳定发现,文件名选择同名零输入、零输出函数。
|
||||
- [x] 非法路径、缺失/错误入口、重复身份、测试跨域越权、直接 Model/API 调用和生产源码反向访问测试均在执行前被拒绝并定位源码。
|
||||
- [x] `assert` 和 `assert_eq` 有正反类型检查;相等失败包含测试身份、原源码位置、实际值和期望值。
|
||||
- [x] 测试可以通过 `result(call)` 验证业务失败;未捕获业务失败、断言失败和 runtime fault 均使当前用例失败而不阻止后续用例。
|
||||
- [x] 无 `test/` 的合法项目执行 `dever test` 报告 `0 tests` 并成功;非法生产源码仍被拒绝。
|
||||
- [x] 纯非数据库套件使用 base runtime;混合套件的非数据库用例不读取配置或初始化数据库;使用 Model 的两个测试各自执行迁移和 Seed,写入互不影响,顺序互换结果不变。
|
||||
- [x] 测试不读取正式 `setting.json` 的数据库 URL,不连接 PostgreSQL,不在项目 `data/` 或源码目录留下临时文件。
|
||||
- [x] 测试进程成功和失败后均清理临时配置、SQLite 文件、可执行文件和 native 临时目录。
|
||||
- [x] `check/api/run/build` 的源码集合、API 快照、CMS 运行结果和生产产物不因存在 `test/` 而变化。
|
||||
- [x] `fmt` 对 module/test 的变更采用同一次准备后提交;任一格式诊断或准备失败时两处源码均保持原样。
|
||||
- [x] CMS Dever/Markdown 测试覆盖相同业务合同并由 `dever test` 通过。
|
||||
- [x] 同一项目连续运行两次时测试顺序和汇总一致,生成测试产物不链接 reference evaluator。
|
||||
- [x] 一次 `dever test` 只生成和编译一个测试套件程序;每个用例仍在独立子进程和临时项目中运行。
|
||||
- [x] 测试套件复用 native 内容寻址缓存;未改变有效生成代码时再次运行不调用 rustc 编译测试程序。
|
||||
- [x] 混合数据库/非数据库用例时,只有数据库用例进程读取运行器生成的 SQLite 配置并初始化 Model;非数据库用例不读取配置或初始化数据库。
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- 真实 PostgreSQL 应用测试配置、schema 管理和外部数据库清理;继续由现有 `postgres_orm` 专项验收负责。
|
||||
- 自动 `before/after`、共享 fixture 文件、mock/stub、Port/Adapter 替身和依赖注入协议。
|
||||
- 测试筛选、watch、并行、随机顺序、重试、自动超时、覆盖率、快照、属性测试和模糊测试。
|
||||
- 浏览器/UI、HTTP 录制回放、外部服务沙箱和机器可读报告。
|
||||
- API 新设计、本体、智能体、package registry、跨平台发行和编译器自举。
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "application-testing",
|
||||
"name": "application-testing",
|
||||
"title": "Dever 应用测试体系",
|
||||
"description": "为 Dever 应用建立根 test 目录、dever test 命令、类型安全断言、隔离 fixture 与可重复执行合同",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P1",
|
||||
"creator": "Antigravity",
|
||||
"assignee": "Antigravity",
|
||||
"createdAt": "2026-09-19",
|
||||
"completedAt": null,
|
||||
"branch": "main",
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "09-04-dever-language-mvp",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {
|
||||
"execution_mode": "inline"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user