Add an xtask to help with running tests

During development I often need to run a bunch of tests. Instead of
having some unwieldy shell command, I have added this xtask to help with
running the testing commands.
This commit is contained in:
Eric Huss
2025-09-26 18:47:59 -07:00
parent b4c53b9e9c
commit 2c7d192b50
6 changed files with 162 additions and 6 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path=crates/xtask/Cargo.toml --"

View File

@@ -134,6 +134,21 @@ The main test harness is described in the [testsuite documentation](tests/testsu
- `cargo clippy --workspace --all-targets --no-deps -- -D warnings` — This makes sure that there are no clippy warnings. - `cargo clippy --workspace --all-targets --no-deps -- -D warnings` — This makes sure that there are no clippy warnings.
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --document-private-items --no-deps` — This verifies that there aren't any rustdoc warnings. - `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --document-private-items --no-deps` — This verifies that there aren't any rustdoc warnings.
- `cargo fmt --check` — Verifies that everything is formatted correctly. - `cargo fmt --check` — Verifies that everything is formatted correctly.
- `cargo +stable semver-checks` — Verifies that no SemVer breaking changes have been made. You must install [`cargo-semver-checks`](https://crates.io/crates/cargo-semver-checks) first.
To help simplify running all these commands, you can run the following cargo command:
```sh
cargo xtask test-all
```
It is useful to run all tests before submitting a PR. While developing I recommend to run some subset of that command based on what you are working on. There are individual arguments for each one. For example:
```sh
cargo xtask test-workspace clippy doc eslint fmt gui semver-checks
```
While developing, remove any of those arguments that are not relevant to what you are changing, or are really slow.
## Making a pull-request ## Making a pull-request
@@ -208,12 +223,9 @@ Instructions for mdBook maintainers to publish a new release:
1. Create a PR to update the version and update the CHANGELOG: 1. Create a PR to update the version and update the CHANGELOG:
1. Update the version in `Cargo.toml` 1. Update the version in `Cargo.toml`
2. Run `cargo test` to verify that everything is passing, and to update `Cargo.lock`. 2. Run `cargo xtask test-all` to verify that everything is passing, and to update `Cargo.lock`.
3. Double-check for any SemVer breaking changes. 3. Update `CHANGELOG.md` with any changes that users may be interested in.
Try [`cargo-semver-checks`](https://crates.io/crates/cargo-semver-checks), though beware that the current version of mdBook isn't properly adhering to SemVer due to the lack of `#[non_exhaustive]` and other issues. See https://github.com/rust-lang/mdBook/issues/1835. 4. Commit the changes, and open a PR.
4. Update `CHANGELOG.md` with any changes that users may be interested in.
5. Update `continuous-integration.md` to update the version number for the installation instructions.
6. Commit the changes, and open a PR.
2. After the PR has been merged, create a release in GitHub. This can either be done in the GitHub web UI, or on the command-line: 2. After the PR has been merged, create a release in GitHub. This can either be done in the GitHub web UI, or on the command-line:
```bash ```bash
MDBOOK_VERS="`cargo read-manifest | jq -r .version`" ; \ MDBOOK_VERS="`cargo read-manifest | jq -r .version`" ; \

4
Cargo.lock generated
View File

@@ -2514,6 +2514,10 @@ dependencies = [
"markup5ever 0.11.0", "markup5ever 0.11.0",
] ]
[[package]]
name = "xtask"
version = "0.0.0"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.26" version = "0.8.26"

12
crates/xtask/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "xtask"
publish = false
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[dependencies]
[lints]
workspace = true

4
crates/xtask/README.md Normal file
View File

@@ -0,0 +1,4 @@
# xtask
This is a CLI utility for running development commands for mdbook.
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for how to use this.

122
crates/xtask/src/main.rs Normal file
View File

@@ -0,0 +1,122 @@
//! Helper for local development.
use std::collections::BTreeMap;
use std::error::Error;
use std::process::Command;
use std::process::exit;
type Result<T> = std::result::Result<T, Box<dyn Error>>;
fn main() -> Result<()> {
macro_rules! commands {
($($name:literal => $func:ident),* $(,)?) => {
[$(($name, $func as fn() -> Result<()>)),*]
};
}
let cmds: BTreeMap<&'static str, fn() -> Result<()>> = commands! {
"test-all" => test_all,
"test-workspace" => test_workspace,
"clippy" => clippy,
"doc" => doc,
"fmt" => fmt,
"semver-checks" => semver_checks,
"eslint" => eslint,
"gui" => gui,
}
.into_iter()
.collect();
let keys = cmds.keys().copied().collect::<Vec<_>>().join(", ");
let mut args = std::env::args().skip(1).peekable();
if args.peek().is_none() {
eprintln!("error: specify a command (valid options: {keys})");
exit(1);
}
for arg in args {
if let Some(cmd_fn) = cmds.get(arg.as_str()) {
cmd_fn()?;
} else if matches!(arg.as_str(), "-h" | "--help") {
println!("valid options: {keys}");
exit(0)
} else {
eprintln!("error: unknown command `{arg}` (valid options: {keys}");
exit(1);
}
}
println!("all tests passed!");
Ok(())
}
fn test_all() -> Result<()> {
test_workspace()?;
clippy()?;
doc()?;
fmt()?;
semver_checks()?;
eslint()?;
gui()?;
Ok(())
}
fn cargo(args: &str, cb: &dyn Fn(&mut Command)) -> Result<()> {
println!("Running `cargo {args}`");
let mut cmd = Command::new("cargo");
cmd.args(args.split_whitespace());
cb(&mut cmd);
let status = cmd.status().expect("cargo should be installed");
if !status.success() {
return Err("command `cargo {args}` failed".into());
}
Ok(())
}
fn test_workspace() -> Result<()> {
cargo("test --workspace", &|_| {})?;
cargo("test --workspace --no-default-features", &|_| {})?;
Ok(())
}
fn clippy() -> Result<()> {
cargo(
"clippy --workspace --all-targets --no-deps -- -D warnings",
&|_| {},
)?;
Ok(())
}
fn doc() -> Result<()> {
cargo(
"doc --workspace --document-private-items --no-deps",
&|cmd| {
cmd.env("RUSTDOCFLAGS", "-D warnings");
},
)?;
Ok(())
}
fn fmt() -> Result<()> {
cargo("fmt --check", &|_| {})?;
Ok(())
}
fn semver_checks() -> Result<()> {
cargo("+stable semver-checks --workspace", &|_| {})?;
Ok(())
}
fn gui() -> Result<()> {
cargo("test --test gui", &|_| {})?;
Ok(())
}
fn eslint() -> Result<()> {
println!("Running `npm run lint`");
let status = Command::new("npm")
.args(["run", "lint"])
.status()
.expect("npm should be installed");
if !status.success() {
return Err("eslint failed".into());
}
Ok(())
}