From 67d49057b2933ba38b027c6d77383eb7e5dc8ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3zsa=20Zolt=C3=A1n?= <67325669+rozsazoltan@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:47:39 +0200 Subject: [PATCH] feat: add nextest, rstest and insta test tooling --- .github/workflows/release.yml | 7 ++- .github/workflows/test.yml | 1 + Cargo.toml | 4 ++ README.md | 9 +++- crates/repository/Cargo.toml | 2 + crates/repository/src/project.rs | 10 ++++- crates/repository/src/quality.rs | 15 ++++++- crates/repository/src/schema.rs | 75 ++++++++++++++++++++++++------- crates/repository/src/workflow.rs | 9 ++++ hk.pkl | 2 +- mise.toml | 1 + 11 files changed, 112 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1efb360..fa6bb83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,6 +102,8 @@ jobs: - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@nextest + - name: Check formatting run: cargo fmt --all -- --check @@ -109,7 +111,10 @@ jobs: run: cargo clippy --workspace --all-targets -- -D warnings - name: Run tests - run: cargo test --workspace --all-targets + run: cargo nextest run --workspace + + - name: Run doctests + run: cargo test --workspace --doc build: name: Build Verzly for ${{ matrix.target }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8cc438a..c62e6f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,6 +41,7 @@ jobs: with: cache: true + - name: Repository policy run: cargo run -p repository -- check diff --git a/Cargo.toml b/Cargo.toml index 63ec6b2..f81d912 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,7 @@ members = [ edition = "2021" license = "AGPL-3.0-only" repository = "https://github.com/verzly/toolchain" + +[workspace.dependencies] +insta = "1.47.2" +rstest = "0.26.1" diff --git a/README.md b/README.md index ebb306a..c9eea64 100644 --- a/README.md +++ b/README.md @@ -902,9 +902,12 @@ Run workspace checks from the repository root: ```sh cargo fmt --all -- --check cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace --all-targets +cargo nextest run --workspace +cargo test --workspace --doc ``` +`cargo-nextest` is the default unit/integration test runner. It runs the existing Rust `#[test]` tests with better CI output and scheduling, while doctests stay on Cargo because nextest does not run doctests. Use `rstest` for parameterized/table-driven tests and `insta` for snapshot assertions when exact diagnostics or generated output should be reviewed. + Run selected commands from source: ```sh @@ -920,6 +923,7 @@ Keep the boundary between executable, tools, and actions clear: - `crates/verzly` is the unified executable and should mostly dispatch. - Each tool crate owns its CLI contract, command logic, tests, and reusable `run_from` entrypoint. +- Prefer `rstest` for case matrices and `insta` for stable snapshots of diagnostics, plans, and generated configuration. - Standalone binaries remain compatibility wrappers. - GitHub Actions should call the released `verzly` binary, not duplicate tool logic. - Repository standards should be enforced through `repository check`, not copied into ad hoc scripts. @@ -940,7 +944,8 @@ Before opening a PR, run: ```sh cargo fmt --all -- --check cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace --all-targets +cargo nextest run --workspace +cargo test --workspace --doc ``` Do not reintroduce: diff --git a/crates/repository/Cargo.toml b/crates/repository/Cargo.toml index 268cdfc..7ad13c8 100644 --- a/crates/repository/Cargo.toml +++ b/crates/repository/Cargo.toml @@ -23,7 +23,9 @@ version = "0.29" [dev-dependencies] assert_cmd = "2.0" +insta = { workspace = true } predicates = "3.1" +rstest = { workspace = true } tempfile = "3.10" [package] diff --git a/crates/repository/src/project.rs b/crates/repository/src/project.rs index 59123c6..9a7c623 100644 --- a/crates/repository/src/project.rs +++ b/crates/repository/src/project.rs @@ -356,7 +356,14 @@ impl ProjectProfile { &self.mise_tools, "rust", "stable", - "Rust files were detected; rustfmt, clippy, and cargo test need Rust stable", + "Rust files were detected; rustfmt, clippy, nextest, and doctests need Rust stable", + ); + push_missing_tool( + &mut recommendations, + &self.mise_tools, + "cargo:cargo-nextest", + "latest", + "Rust files were detected; cargo-nextest is the preferred unit and integration test runner", ); } @@ -1377,6 +1384,7 @@ package = "auto" .collect::>(); assert!(tools.contains("rust")); + assert!(tools.contains("cargo:cargo-nextest")); assert!(tools.contains("aube")); assert!(tools.contains("php")); assert!(tools.contains("composer")); diff --git a/crates/repository/src/quality.rs b/crates/repository/src/quality.rs index 4f30d27..7e96ce3 100644 --- a/crates/repository/src/quality.rs +++ b/crates/repository/src/quality.rs @@ -49,7 +49,7 @@ pub fn render_hk_config(profile: &ProjectProfile) -> String { }); quality_steps.push(Step { name: "test-rust".into(), - check: profile.command("cargo test --workspace --all-targets"), + check: profile.command("cargo nextest run --workspace && cargo test --workspace --doc"), fix: None, stage: vec![], depends: vec!["format-rust".into(), "lint-rust".into()], @@ -292,5 +292,18 @@ mod tests { assert!(config.contains("windows = \"cmd /d /s /c\"")); assert!(!config.contains(" }\n\n}\n\nlocal qualitySteps")); assert!(!config.contains(" }\n\n}\n\nlocal fullQualitySteps")); + + insta::assert_snapshot!(step_block(&config, "[\"test-rust\"]"), @r###"["test-rust"] { + shell = defaultShell + depends = List("format-rust", "lint-rust") + check = "cd \"workspace/app\" && cargo nextest run --workspace && cargo test --workspace --doc" + }"###); + } + + fn step_block(config: &str, marker: &str) -> String { + let start = config.find(marker).expect("step marker exists"); + let rest = &config[start..]; + let end = rest.find("\n }\n").expect("step terminator exists") + "\n }".len(); + rest[..end].to_string() } } diff --git a/crates/repository/src/schema.rs b/crates/repository/src/schema.rs index 6b3fd84..e564db5 100644 --- a/crates/repository/src/schema.rs +++ b/crates/repository/src/schema.rs @@ -708,6 +708,7 @@ fn type_name(value: &Value) -> &'static str { #[cfg(test)] mod tests { use super::*; + use rstest::rstest; use std::time::{SystemTime, UNIX_EPOCH}; #[test] @@ -756,36 +757,76 @@ frontend_install = "aube install" assert!(validate_datarose_schema(&path).unwrap().is_empty()); } - #[test] - fn reports_unknown_and_missing_keys() { - let path = temp_config( - "invalid", - r#"version = 1 + #[rstest] + #[case::unknown_quality_key( + r#"version = 1 [quality] langauges = ["rust"] +"#, + "quality.langauges is not a supported datarose.toml key" + )] + #[case::missing_release_target_name( + r#"version = 1 [release] enabled = true +source_repository = "verzly/toolchain" [[release.targets]] nam = "verzly" +"#, + "release.targets[0].nam is not a supported datarose.toml key\nrelease.targets[0].name is required" + )] + #[case::invalid_types_and_values( + r#"version = "1" + +[quality] +languages = ["rust", 42] +js_runner = "bower" + +[release] +enabled = "yes" +target_branch = 123 +source_repository = "verzly/toolchain" +targets = "verzly" +"#, + "datarose.toml.version must be an integer; found string\nquality.languages[1] must be a string; found integer\nquality.js_runner has unsupported value `bower`; expected one of aube, npm, pnpm, yarn, bun\nrelease.enabled must be a boolean; found string\nrelease.target_branch must be a string; found integer\nrelease.targets must be an array of tables; found string" + )] + fn reports_schema_issues(#[case] content: &str, #[case] expected: &str) { + let path = temp_config("invalid", content); + + let issues = validate_datarose_schema(&path).unwrap(); + + assert_eq!(issues.join("\n"), expected); + } + + #[test] + fn snapshots_complex_schema_diagnostics() { + let path = temp_config( + "snapshot", + r#"version = "1" + +[quality] +languages = ["rust", 42] +js_runner = "bower" + +[release] +enabled = "yes" +target_branch = 123 +source_repository = "verzly/toolchain" +targets = "verzly" "#, ); let issues = validate_datarose_schema(&path).unwrap(); - assert!(issues - .iter() - .any(|issue| issue.contains("quality.langauges"))); - assert!(issues - .iter() - .any(|issue| issue.contains("release.source_repository"))); - assert!(issues - .iter() - .any(|issue| issue.contains("release.targets[0].nam"))); - assert!(issues - .iter() - .any(|issue| issue.contains("release.targets[0].name"))); + + insta::assert_snapshot!(issues.join("\n"), @"datarose.toml.version must be an integer; found string +quality.languages[1] must be a string; found integer +quality.js_runner has unsupported value `bower`; expected one of aube, npm, pnpm, yarn, bun +release.enabled must be a boolean; found string +release.target_branch must be a string; found integer +release.targets must be an array of tables; found string"); } #[test] diff --git a/crates/repository/src/workflow.rs b/crates/repository/src/workflow.rs index 881c191..92b6574 100644 --- a/crates/repository/src/workflow.rs +++ b/crates/repository/src/workflow.rs @@ -59,6 +59,7 @@ jobs: - uses: jdx/mise-action@v4 with: cache: true + __REPOSITORY_POLICY_STEP__ - name: Quality gate run: mise exec -- hk check @@ -360,6 +361,14 @@ mod tests { .any(|file| file.content.contains("DISTRIBUTION_REPO_TOKEN"))); } + #[test] + fn test_workflow_runs_mise_backed_hk_quality_gate() { + let workflow = render_test_workflow(&profile_with_config(DataroseConfig::default())); + + assert!(workflow.contains("jdx/mise-action@v4")); + assert!(workflow.contains("mise exec -- hk check")); + } + #[test] fn skips_release_workflow_files_when_management_is_disabled() { let mut config = DataroseConfig::default(); diff --git a/hk.pkl b/hk.pkl index d4cafd3..e4e02f5 100644 --- a/hk.pkl +++ b/hk.pkl @@ -31,7 +31,7 @@ local qualitySteps = new Mapping { ["test-rust"] { shell = defaultShell depends = List("format-rust", "lint-rust") - check = "cargo test --workspace --all-targets" + check = "cargo nextest run --workspace && cargo test --workspace --doc" } } diff --git a/mise.toml b/mise.toml index a9bf4ba..63b96e3 100644 --- a/mise.toml +++ b/mise.toml @@ -2,3 +2,4 @@ hk = "latest" pkl = "latest" rust = "stable" +"cargo:cargo-nextest" = "latest"