use super::common::binary;
use super::temp_dir;
use std::fs;
use std::process::Command;
#[rstest::rstest]
#[case::clean("//! Module docs.\nfn example() {}\n")]
#[case::suppressed_changes(
"//! Module docs.\n/// See [A](https://example.invalid).\npub struct A;\n"
)]
fn checks_only_should_skip_post_process(#[case] source: &str) {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("lib.rs");
fs::write(&tmp, source).unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"post_process:\n - {}\n extensions: [\"rs\"]\n",
post_process_command(1)
),
)
.unwrap();
let output = Command::new(binary())
.args([
"--config",
cfg.to_str().unwrap(),
"--include",
"links",
"--checks-only",
])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
assert_eq!(output.status.code(), Some(0), "{output:?}");
assert_eq!(fs::read(&tmp).unwrap(), source.as_bytes());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!stderr.contains("post_process"), "{stderr:?}");
let _ = fs::remove_dir_all(&dir);
}
#[rstest::rstest]
#[case::no_changes("pub fn example() {}\n", 0)]
#[case::proposed_changes("/// See [A](https://example.invalid).\npub struct A;\n", 1)]
fn dry_run_should_skip_post_process(#[case] source: &str, #[case] exit: i32) {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("lib.rs");
fs::write(&tmp, source).unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"post_process:\n - {}\n extensions: [\"rs\"]\n",
post_process_command(1)
),
)
.unwrap();
let output = Command::new(binary())
.args([
"--config",
cfg.to_str().unwrap(),
"--include",
"links",
"--dry-run",
])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
assert_eq!(output.status.code(), Some(exit), "{output:?}");
assert_eq!(fs::read(&tmp).unwrap(), source.as_bytes());
assert!(!String::from_utf8_lossy(&output.stderr).contains("post_process"));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn excluded_file_not_post_processed() {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("lib.rs");
fs::write(&tmp, "pub fn example() {}\n").unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"exclude_files:\n - \"lib.rs\"\npost_process:\n - {}\n extensions: [\"rs\"]\n",
post_process_command(1)
),
)
.unwrap();
let output = Command::new(binary())
.args(["--config", cfg.to_str().unwrap(), "--include", "tables"])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
assert!(
output.status.success(),
"excluded file must not be post-processed: {}",
String::from_utf8_lossy(&output.stderr)
);
let _ = fs::remove_dir_all(&dir);
}
fn post_process_command(exit_code: u8) -> String {
if cfg!(windows) {
format!("command: \"cmd.exe\"\n args: [\"/C\", \"exit\", \"{exit_code}\"]")
} else {
let command = if exit_code == 0 { "true" } else { "false" };
format!("command: \"{command}\"")
}
}
#[test]
fn post_process_failure_exits_nonzero() {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("lib.rs");
fs::write(&tmp, "pub fn example() {}\n").unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"post_process:\n - {}\n extensions: [\"rs\"]\n",
post_process_command(1)
),
)
.unwrap();
let output = Command::new(binary())
.args(["--config", cfg.to_str().unwrap(), "--include", "tables"])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
assert!(
!output.status.success(),
"a failing post_process command must exit non-zero"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn post_process_not_run_on_check() {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("lib.rs");
fs::write(&tmp, "pub fn example() {}\n").unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"post_process:\n - {}\n extensions: [\"rs\"]\n",
post_process_command(1)
),
)
.unwrap();
let output = Command::new(binary())
.args(["--config", cfg.to_str().unwrap(), "--include", "lints"])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("post_process"),
"lints must not invoke post_process: {stderr:?}"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn post_process_not_run_on_md_with_only_rust_ops() {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("doc.md");
fs::write(&tmp, "# Guide\n\nBody text.\n").unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"post_process:\n - {}\n extensions: [\"md\"]\n",
post_process_command(1)
),
)
.unwrap();
let output = Command::new(binary())
.args(["--config", cfg.to_str().unwrap(), "--include", "reorder"])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
assert!(
output.status.success(),
"reorder never mutates .md, so post_process must not run on it: {}",
String::from_utf8_lossy(&output.stderr)
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn post_process_runs_on_matching_extension() {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("lib.rs");
fs::write(&tmp, "pub fn example() {}\n").unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"post_process:\n - {}\n extensions: [\"rs\"]\n",
post_process_command(0)
),
)
.unwrap();
let output = Command::new(binary())
.args(["--config", cfg.to_str().unwrap(), "--include", "tables"])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
assert!(
output.status.success(),
"successful post_process should succeed: {}",
String::from_utf8_lossy(&output.stderr)
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn post_process_skips_non_matching_extension() {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let tmp = dir.join("lib.rs");
fs::write(&tmp, "pub fn example() {}\n").unwrap();
let cfg = dir.join(".rust-llm-tidy.yml");
fs::write(
&cfg,
format!(
"post_process:\n - {}\n extensions: [\"md\"]\n",
post_process_command(1)
),
)
.unwrap();
let output = Command::new(binary())
.args(["--config", cfg.to_str().unwrap(), "--include", "tables"])
.arg(&tmp)
.output()
.expect("failed to spawn rust-llm-tidy");
assert!(
output.status.success(),
"post_process must NOT run on a .rs file when extensions=[md]: {}",
String::from_utf8_lossy(&output.stderr)
);
let _ = fs::remove_dir_all(&dir);
}