use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
const VIOLATION: &str = "# Heading with two spaces\n";
fn project_reachable_through_a_symlink() -> (TempDir, PathBuf, PathBuf) {
let temp = TempDir::new().unwrap();
let root = fs::canonicalize(temp.path()).unwrap();
let real = root.join("real");
let link = root.join("link");
fs::create_dir_all(real.join("sub")).unwrap();
fs::write(real.join("sub/note.md"), VIOLATION).unwrap();
fs::write(real.join("other.md"), VIOLATION).unwrap();
std::os::unix::fs::symlink(&real, &link).unwrap();
(temp, real, link)
}
fn check_with_config(real: &Path, config_body: &str) -> String {
fs::write(real.join(".rumdl.toml"), config_body).unwrap();
let home = real.parent().unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_rumdl"))
.args(["check", "--no-cache", "."])
.current_dir(real)
.env("HOME", home)
.env("USERPROFILE", home)
.env("XDG_CONFIG_HOME", home.join(".config"))
.output()
.expect("failed to execute rumdl");
format!(
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
}
#[test]
fn per_file_ignores_silences_a_file_its_pattern_named_through_a_symlink() {
let (_temp, real, link) = project_reachable_through_a_symlink();
let output = check_with_config(
&real,
&format!(
"[per-file-ignores]\n'{}/sub/**' = [\"MD019\", \"MD064\"]\n",
link.display()
),
);
assert!(
!output.contains("sub/note.md"),
"a pattern naming `sub` through the symlink should silence its rules. Output:\n{output}"
);
assert!(
output.contains("other.md"),
"control: a file outside the pattern must still be reported. Output:\n{output}"
);
}
#[test]
fn per_file_ignores_control_reports_the_file_for_an_unrelated_absolute_pattern() {
let (_temp, real, _link) = project_reachable_through_a_symlink();
let output = check_with_config(
&real,
"[per-file-ignores]\n'/nowhere/at/all/sub/**' = [\"MD019\", \"MD064\"]\n",
);
assert!(
output.contains("sub/note.md"),
"control: an unrelated absolute pattern must leave the rules active. Output:\n{output}"
);
}
#[test]
fn exclude_drops_a_directory_its_pattern_named_through_a_symlink() {
let (_temp, real, link) = project_reachable_through_a_symlink();
let output = check_with_config(&real, &format!("[global]\nexclude = ['{}/sub']\n", link.display()));
assert!(
!output.contains("sub/note.md"),
"a pattern naming `sub` through the symlink should exclude it. Output:\n{output}"
);
assert!(
output.contains("other.md"),
"control: a file outside the pattern must still be linted. Output:\n{output}"
);
}
#[test]
fn exclude_control_lints_the_directory_for_an_unrelated_absolute_pattern() {
let (_temp, real, _link) = project_reachable_through_a_symlink();
let output = check_with_config(&real, "[global]\nexclude = ['/nowhere/at/all/sub']\n");
assert!(
output.contains("sub/note.md"),
"control: an unrelated absolute pattern must leave the file linted. Output:\n{output}"
);
}
#[test]
fn include_selects_a_directory_its_pattern_named_through_a_symlink() {
let (_temp, real, link) = project_reachable_through_a_symlink();
let output = check_with_config(&real, &format!("[global]\ninclude = ['{}/sub/**']\n", link.display()));
assert!(
output.contains("sub/note.md"),
"a pattern naming `sub` through the symlink should select its files. Output:\n{output}"
);
assert!(
!output.contains("other.md"),
"control: include must still restrict discovery to matching files. Output:\n{output}"
);
}
#[test]
fn include_control_selects_nothing_for_an_unrelated_absolute_pattern() {
let (_temp, real, _link) = project_reachable_through_a_symlink();
let output = check_with_config(&real, "[global]\ninclude = ['/nowhere/at/all/sub/**']\n");
assert!(
!output.contains("note.md") && !output.contains("other.md"),
"control: an unrelated absolute include must select no file. Output:\n{output}"
);
}
#[test]
fn a_brace_alternation_reaches_through_the_symlink_for_per_file_ignores() {
let (_temp, real, link) = project_reachable_through_a_symlink();
let output = check_with_config(
&real,
&format!(
"[per-file-ignores]\n'{{/nowhere/at/all,{}}}/sub/**' = [\"MD019\", \"MD064\"]\n",
link.display()
),
);
assert!(
!output.contains("sub/note.md"),
"an alternation whose second branch names the symlink should silence its rules. Output:\n{output}"
);
assert!(
output.contains("other.md"),
"control: a file outside the pattern must still be reported. Output:\n{output}"
);
}