use std::fs;
use tempfile::TempDir;
#[test]
fn test_exclude_patterns_with_explicit_paths() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
let docs_dir = base_path.join("docs");
fs::create_dir(&docs_dir).unwrap();
let root_file = base_path.join("README.md");
let docs_file = docs_dir.join("guide.md");
fs::write(&root_file, "Some content without heading.\n").unwrap();
fs::write(&docs_file, "Some content without heading.\n").unwrap();
let pyproject_content = r#"
[tool.rumdl]
exclude = ["docs/*"]
"#;
fs::write(base_path.join("pyproject.toml"), pyproject_content).unwrap();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg(root_file.to_str().unwrap())
.arg(docs_file.to_str().unwrap())
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let combined = format!("{stdout}\n{stderr}");
assert!(
!stderr.contains("ignored because of exclude pattern"),
"Default run should not emit a per-file exclusion notice. stderr:\n{stderr}"
);
assert!(
!stdout.contains("docs/guide.md"),
"docs/guide.md should not be in linting results. stdout:\n{stdout}"
);
assert!(
combined.contains("README.md"),
"README.md should be processed. Output:\n{combined}"
);
let output_v = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg("--verbose")
.arg(root_file.to_str().unwrap())
.arg(docs_file.to_str().unwrap())
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stderr_v = String::from_utf8_lossy(&output_v.stderr);
assert!(
stderr_v.contains("docs/guide.md") && stderr_v.contains("ignored because of exclude pattern"),
"--verbose should surface the exclusion notice. stderr:\n{stderr_v}"
);
let output2 = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg(".")
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stdout2 = String::from_utf8_lossy(&output2.stdout);
let stderr2 = String::from_utf8_lossy(&output2.stderr);
let combined2 = format!("{stdout2}\n{stderr2}");
assert!(
!combined2.contains("docs/guide.md"),
"docs/guide.md should be excluded in discovery mode. Output:\n{combined2}"
);
}
#[test]
fn test_force_exclude_with_explicit_paths() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
let docs_dir = base_path.join("docs");
fs::create_dir(&docs_dir).unwrap();
let docs_file = docs_dir.join("guide.md");
fs::write(&docs_file, "# Guide\n\nSome content.\n").unwrap();
let pyproject_content = r#"
[tool.rumdl]
exclude = ["docs/*"]
force_exclude = true
"#;
fs::write(base_path.join("pyproject.toml"), pyproject_content).unwrap();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg(docs_file.to_str().unwrap())
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("ignored because of exclude pattern"),
"Default run should not emit a per-file exclusion notice. stderr: {stderr}"
);
assert!(
stdout.contains("No markdown files found") || stderr.contains("No markdown files found"),
"Should report no markdown files found when all are excluded. stdout: {stdout}, stderr: {stderr}"
);
}
#[test]
fn test_no_exclude_flag() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = TempDir::new()?;
let base_path = temp_dir.path();
let docs_dir = base_path.join("docs");
fs::create_dir(&docs_dir)?;
let docs_file = docs_dir.join("guide.md");
fs::write(&docs_file, "# Guide\n\nSome content.\n")?;
let pyproject_content = r#"
[tool.rumdl]
exclude = ["docs/*"]
"#;
fs::write(base_path.join("pyproject.toml"), pyproject_content)?;
let output = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg(docs_file.to_str().unwrap())
.arg("--no-exclude")
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("warning:") && !stderr.contains("ignored because of exclude pattern"),
"Should not show exclusion warning with --no-exclude. stderr: {stderr}"
);
assert!(
stdout.contains("Success") || stdout.contains("No issues found"),
"Should report linting success. stdout: {stdout}"
);
Ok(())
}
#[test]
fn test_cli_exclude_overrides_config() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
let docs_dir = base_path.join("docs");
let tests_dir = base_path.join("tests");
fs::create_dir(&docs_dir).unwrap();
fs::create_dir(&tests_dir).unwrap();
let docs_file = docs_dir.join("guide.md");
let tests_file = tests_dir.join("test.md");
fs::write(&docs_file, "# Guide\n\nSome content.\n").unwrap();
fs::write(&tests_file, "# Test\n\nSome content.\n").unwrap();
let pyproject_content = r#"
[tool.rumdl]
exclude = ["docs/*"]
"#;
fs::write(base_path.join("pyproject.toml"), pyproject_content).unwrap();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg("--exclude")
.arg("tests/*")
.arg(docs_file.to_str().unwrap())
.arg(tests_file.to_str().unwrap())
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("ignored because of exclude pattern"),
"Default run should not emit a per-file exclusion notice. stderr: {stderr}"
);
assert!(
!stdout.contains("tests/test.md"),
"tests/test.md should not be in linting results. stdout: {stdout}"
);
assert!(output.status.success() || output.status.code() == Some(1));
}
#[test]
fn test_excluded_file_notice_suppressed_by_default() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::write(base_path.join("CHANGELOG.md"), "Auto-generated, no heading.\n").unwrap();
fs::write(base_path.join("README.md"), "Some content without heading.\n").unwrap();
fs::write(
base_path.join("pyproject.toml"),
"[tool.rumdl]\nexclude = [\"CHANGELOG.md\"]\n",
)
.unwrap();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg("--no-cache")
.arg("CHANGELOG.md")
.arg("README.md")
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("ignored because of exclude pattern"),
"Excluded file must not emit a per-file notice by default. stderr:\n{stderr}"
);
let combined = format!("{stdout}\n{stderr}");
assert!(
combined.contains("README.md"),
"README.md should still be linted. Output:\n{combined}"
);
assert!(
!combined.contains("CHANGELOG.md"),
"CHANGELOG.md should be silently excluded. Output:\n{combined}"
);
}
#[test]
fn test_fmt_excluded_file_notice_verbose_only() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::write(base_path.join("CHANGELOG.md"), "# Changelog\n\nEntry.\n").unwrap();
fs::write(
base_path.join("pyproject.toml"),
"[tool.rumdl]\nexclude = [\"CHANGELOG.md\"]\n",
)
.unwrap();
let run = |extra: &[&str]| {
let mut cmd = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"));
cmd.arg("fmt").arg("--no-cache");
for a in extra {
cmd.arg(a);
}
cmd.arg("CHANGELOG.md")
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl")
};
let default_stderr = String::from_utf8_lossy(&run(&[]).stderr).into_owned();
assert!(
!default_stderr.contains("ignored because of exclude pattern"),
"fmt should not emit a per-file exclusion notice by default. stderr:\n{default_stderr}"
);
let verbose_stderr = String::from_utf8_lossy(&run(&["--verbose"]).stderr).into_owned();
assert!(
verbose_stderr.contains("CHANGELOG.md") && verbose_stderr.contains("ignored because of exclude pattern"),
"fmt --verbose should surface the exclusion notice. stderr:\n{verbose_stderr}"
);
}
#[test]
fn test_excluded_file_notice_absent_in_json_output() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::write(base_path.join("CHANGELOG.md"), "no heading\n").unwrap();
fs::write(
base_path.join("pyproject.toml"),
"[tool.rumdl]\nexclude = [\"CHANGELOG.md\"]\n",
)
.unwrap();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_rumdl"))
.arg("check")
.arg("--no-cache")
.arg("--output-format")
.arg("json")
.arg("CHANGELOG.md")
.current_dir(base_path)
.output()
.expect("Failed to execute rumdl");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("ignored because of exclude pattern"),
"JSON run should not emit a per-file exclusion notice on stderr. stderr:\n{stderr}"
);
}