use std::fs;
use tempfile::TempDir;
use crate::common::run_rsconstruct_with_env;
#[test]
fn analyzer_disabled_via_enabled_false() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[analyzer.markdown]
enabled = false
"#,
)
.unwrap();
fs::write(project_path.join("doc.md"), "# hi\n").unwrap();
let output = run_rsconstruct_with_env(
project_path,
&["analyzers", "used"],
&[("NO_COLOR", "1")],
);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.contains("markdown"),
"Disabled analyzer should not appear in `analyzers used`: {}",
stdout
);
}
#[test]
fn analyzer_enabled_true_is_active() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[analyzer.markdown]
enabled = true
"#,
)
.unwrap();
fs::write(project_path.join("doc.md"), "# hi\n").unwrap();
let output = run_rsconstruct_with_env(
project_path,
&["analyzers", "used"],
&[("NO_COLOR", "1")],
);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("markdown"),
"Enabled analyzer should appear in `analyzers used`: {}",
stdout
);
}
#[test]
fn analyzer_unknown_type_is_config_error() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[analyzer.not_a_real_analyzer]
"#,
)
.unwrap();
let output = run_rsconstruct_with_env(
project_path,
&["toml", "check"],
&[("NO_COLOR", "1")],
);
assert!(!output.status.success(), "Config with unknown analyzer must fail validation");
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
combined.contains("not_a_real_analyzer") && combined.contains("unknown analyzer"),
"Error should name the unknown analyzer: {}", combined
);
}
#[test]
fn analyzer_unknown_field_is_config_error() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[analyzer.markdown]
enabeld = false
"#,
)
.unwrap();
let output = run_rsconstruct_with_env(
project_path,
&["toml", "check"],
&[("NO_COLOR", "1")],
);
assert!(!output.status.success(), "Config with unknown analyzer field must fail validation");
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
combined.contains("enabeld") && combined.contains("unknown field"),
"Error should name the typo field: {}", combined
);
assert!(
combined.contains("enabled"),
"Error should list valid fields to help fix the typo: {}", combined
);
}
#[test]
fn analyzer_enabled_defaults_to_true() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[analyzer.markdown]
"#,
)
.unwrap();
fs::write(project_path.join("doc.md"), "# hi\n").unwrap();
let output = run_rsconstruct_with_env(
project_path,
&["analyzers", "used"],
&[("NO_COLOR", "1")],
);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("markdown"),
"Analyzer with no `enabled` field should default to active: {}",
stdout
);
}
#[test]
fn analyzer_deps_cache_reports_hits_on_unchanged_files() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[analyzer.markdown]
"#,
)
.unwrap();
for i in 1..=3 {
fs::write(project_path.join(format!("doc{i}.md")), format!("# Doc {i}\n\n")).unwrap();
fs::write(project_path.join(format!("pic{i}.png")), []).unwrap();
}
let out1 = run_rsconstruct_with_env(project_path, &["status"], &[("NO_COLOR", "1")]);
assert!(out1.status.success(), "first status failed: {}", String::from_utf8_lossy(&out1.stderr));
let out2 = run_rsconstruct_with_env(project_path, &["status"], &[("NO_COLOR", "1")]);
assert!(out2.status.success(), "second status failed: {}", String::from_utf8_lossy(&out2.stderr));
let combined = format!("{}{}", String::from_utf8_lossy(&out2.stdout), String::from_utf8_lossy(&out2.stderr));
assert!(
combined.contains("[deps] 3 files to check")
&& combined.contains("[deps] summary: 0 rescanned (3 cache hits:"),
"unchanged files should all hit the cache: {}", combined
);
}
#[test]
fn analyzer_deps_cache_rescans_changed_file() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[analyzer.markdown]
"#,
)
.unwrap();
for i in 1..=3 {
fs::write(project_path.join(format!("doc{i}.md")), format!("# Doc {i}\n\n")).unwrap();
fs::write(project_path.join(format!("pic{i}.png")), []).unwrap();
}
let _ = run_rsconstruct_with_env(project_path, &["status"], &[("NO_COLOR", "1")]);
std::thread::sleep(std::time::Duration::from_millis(1100));
fs::write(
project_path.join("doc1.md"),
"# Doc 1 (modified)\n\n",
)
.unwrap();
let out = run_rsconstruct_with_env(project_path, &["status"], &[("NO_COLOR", "1")]);
assert!(out.status.success());
let combined = format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
assert!(
combined.contains("[deps] 3 files to check")
&& combined.contains("[deps] summary: 1 rescanned (2 cache hits:"),
"modified file should trigger exactly one rescan: {}", combined
);
}
#[test]
fn analyzer_dedupes_shared_source_across_processors() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_path = temp_dir.path();
fs::write(
project_path.join("rsconstruct.toml"),
r#"[processor.markdown2html]
src_dirs = ["."]
[processor.zspell]
src_dirs = ["."]
[processor.markdownlint]
src_dirs = ["."]
[analyzer.markdown]
"#,
)
.unwrap();
fs::write(project_path.join("doc.md"), "# hi\n\n").unwrap();
fs::write(project_path.join("pic.png"), []).unwrap();
let _ = run_rsconstruct_with_env(project_path, &["status"], &[("NO_COLOR", "1")]);
let out = run_rsconstruct_with_env(project_path, &["status"], &[("NO_COLOR", "1")]);
assert!(out.status.success(), "second status failed: {}", String::from_utf8_lossy(&out.stderr));
let combined = format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
assert!(
combined.contains("[deps] 1 files to check (consumed by 3 products)"),
"display should show unique-source count with per-product fan-out: {}", combined
);
assert!(
combined.contains("[deps] summary: 0 rescanned (1 cache hits:"),
"shared source must be looked up in the cache exactly once, \
not once per consuming product: {}", combined
);
}