use common::binary;
use core::sync::atomic::{AtomicU64, Ordering};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{self, Command, Output};
mod command_behavior;
mod comment_lexicons;
mod csharp;
mod forbidden_characters;
mod json_output;
mod mod001_module_headers;
mod mod001_module_size;
mod python;
mod rust;
mod text001_paragraph_size;
mod text002_line_length;
mod text003_sentence_length;
mod text004_header_opener;
mod text006_verbose_synonyms;
mod text007_passive_narration;
mod text008_list_density;
#[path = "../common/mod.rs"]
mod common;
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
#[test]
fn all_reports_remaining_doc_gaps() {
let dir = temp_dir();
fs::create_dir_all(&dir).unwrap();
let file = dir.join("gap.rs");
fs::write(&file, "pub fn undocumented() {}\n").unwrap();
let output = run_command(&[], &file);
assert!(
!output.status.success(),
"all should fail on remaining doc gaps"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("DOC001"),
"all should report doc gaps, got:\n{stderr}"
);
let _ = fs::remove_dir_all(&dir);
}
fn assert_has_diagnostic(stderr: &str, code: &str, item_name: Option<&str>) {
assert!(
stderr.contains(code),
"stderr should contain {code}, got:\n{stderr}"
);
if let Some(name) = item_name {
assert!(
stderr.contains(name),
"stderr should mention `{name}`, got:\n{stderr}"
);
}
}
#[test]
fn check_nonexistent_path_fails() {
let nonexistent = std::env::temp_dir().join(format!(
"rust-llm-tidy-lint-missing-{}-{}.rs",
std::process::id(),
TEST_COUNTER.fetch_add(1, Ordering::Relaxed)
));
let output = run_command(&["--include", "lints"], &nonexistent);
assert!(
!output.status.success(),
"non-existent path should exit non-zero"
);
}
#[test]
fn check_recursive_directory() {
let dir = temp_dir();
let sub = dir.join("sub");
fs::create_dir_all(&sub).unwrap();
fs::copy(rust_fixture_dir().join("clean.rs"), dir.join("clean.rs")).unwrap();
fs::write(sub.join("dirty.rs"), "pub fn dirty() {}\n").unwrap();
let output = run_command(&["--include", "lints"], &dir);
assert!(
!output.status.success(),
"directory with missing docs should fail"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("DOC001") && stderr.contains("dirty.rs"),
"should flag the nested undocumented file, got:\n{stderr}"
);
let _ = fs::remove_dir_all(&dir);
}
fn defaults_fixture_dir() -> PathBuf {
fixture_dir().join("defaults")
}
#[test]
fn dry_run_should_fail_when_lint_clean_source_needs_reordering() {
let path = rust_fixture_dir().join("clean.rs");
let output = run_command(&["--dry-run"], &path);
assert!(
!output.status.success(),
"dry-run must fail for proposed reordering: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(String::from_utf8_lossy(&output.stderr).contains("success[REORDER]"));
}
fn fix_fixture_dir() -> PathBuf {
manifest_dir().join("tests").join("fixtures").join("fix")
}
fn oversized_paragraph_md() -> String {
let lines: String = (0..10)
.map(|i| format!("sentence number {i} carries some filler text\n"))
.collect();
format!("# Title\n\n{lines}\nTrailer.\n")
}
fn reorder_fixture_dir() -> PathBuf {
manifest_dir()
.join("tests")
.join("fixtures")
.join("reorder")
}
fn run_lexicon_fixture(name: &str) -> (String, i32) {
let path = fixture_dir().join(name);
let output = run_command(&["--include", "lints"], &path);
(
String::from_utf8_lossy(&output.stderr).to_string(),
output.status.code().unwrap_or(-1),
)
}
fn run_python_fixture(name: &str) -> (String, i32) {
let path = python_fixture_dir().join(name);
let output = run_command(&["--include", "lints"], &path);
(
String::from_utf8_lossy(&output.stderr).to_string(),
output.status.code().unwrap_or(-1),
)
}
fn run_qualified_path_source(source: &str, extension: &str) -> (String, i32) {
let directory = tempfile::tempdir().unwrap();
let config = directory.path().join(".rust-llm-tidy.yml");
let path = directory.path().join(format!("example.{extension}"));
fs::write(&config, "{}\n").unwrap();
fs::write(&path, source).unwrap();
let output = Command::new(binary())
.arg("--config")
.arg(config)
.args(["--include", "MOD003"])
.arg(&path)
.output()
.unwrap_or_else(|e| panic!("failed to spawn rust-llm-tidy on {}: {e}", path.display()));
(
String::from_utf8_lossy(&output.stderr).to_string(),
output.status.code().unwrap_or(-1),
)
}
fn temp_md(content: &str) -> PathBuf {
let path = temp_file("md");
fs::write(&path, content).unwrap();
path
}
fn temp_named_file(rel: &str, content: &str) -> PathBuf {
let path = temp_dir().join(rel);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, content).unwrap();
path
}
fn text007_marker_and_passive_md() -> String {
"This no longer panics.\nErrors are returned by the scanner.\n".to_string()
}
fn python_fixture_dir() -> PathBuf {
fixture_dir().join("python")
}
fn run_command(args: &[&str], path: &Path) -> Output {
let mut cmd = Command::new(binary());
cmd.args(["--no-config", "--all-lines"])
.args(args)
.arg(path);
cmd.output()
.unwrap_or_else(|e| panic!("failed to spawn rust-llm-tidy on {}: {e}", path.display()))
}
fn rust_fixture_dir() -> PathBuf {
fixture_dir().join("rust")
}
fn temp_dir() -> PathBuf {
let seq = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = process::id();
std::env::temp_dir().join(format!("rust-llm-tidy-lint-dir-{}-{}", pid, seq))
}
fn temp_file(ext: &str) -> PathBuf {
let seq = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = process::id();
std::env::temp_dir().join(format!("rust-llm-tidy-all-{}-{}.{}", pid, seq, ext))
}
fn fixture_dir() -> PathBuf {
manifest_dir().join("tests").join("fixtures").join("doc")
}
fn manifest_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}