Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,19 @@ jobs:

- uses: dtolnay/rust-toolchain@stable

- uses: taiki-e/install-action@nextest

- name: Check formatting
run: cargo fmt --all -- --check

- name: Run clippy
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 }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
with:
cache: true


- name: Repository policy
run: cargo run -p repository -- check

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions crates/repository/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
10 changes: 9 additions & 1 deletion crates/repository/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
}

Expand Down Expand Up @@ -1377,6 +1384,7 @@ package = "auto"
.collect::<BTreeSet<_>>();

assert!(tools.contains("rust"));
assert!(tools.contains("cargo:cargo-nextest"));
assert!(tools.contains("aube"));
assert!(tools.contains("php"));
assert!(tools.contains("composer"));
Expand Down
15 changes: 14 additions & 1 deletion crates/repository/src/quality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
Expand Down Expand Up @@ -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()
}
}
75 changes: 58 additions & 17 deletions crates/repository/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
9 changes: 9 additions & 0 deletions crates/repository/src/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
- uses: jdx/mise-action@v4
with:
cache: true

__REPOSITORY_POLICY_STEP__
- name: Quality gate
run: mise exec -- hk check
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion hk.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ local qualitySteps = new Mapping<String, Step> {
["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"
}
}

Expand Down
1 change: 1 addition & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
hk = "latest"
pkl = "latest"
rust = "stable"
"cargo:cargo-nextest" = "latest"