pub mod config;
pub mod report;
pub mod rule;
pub mod rules;
pub use config::ComplyConfig;
#[allow(unused_imports)]
pub use config::ProjectOverride;
pub use report::{ComplyReport, ComplyReportFormat};
pub use rule::StackComplianceRule;
#[allow(unused_imports)]
pub use rule::{FixResult, RuleResult};
use crate::stack::PAIML_CRATES;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct StackComplyEngine {
config: ComplyConfig,
rules: Vec<Box<dyn StackComplianceRule>>,
discovered_projects: Vec<ProjectInfo>,
}
#[derive(Debug, Clone)]
pub struct ProjectInfo {
pub name: String,
pub path: PathBuf,
pub is_paiml_crate: bool,
}
impl StackComplyEngine {
pub fn new(config: ComplyConfig) -> Self {
let mut engine = Self { config, rules: Vec::new(), discovered_projects: Vec::new() };
engine.register_rule(Box::new(rules::MakefileRule::new()));
engine.register_rule(Box::new(rules::CargoTomlRule::new()));
engine.register_rule(Box::new(rules::CiWorkflowRule::new()));
engine.register_rule(Box::new(rules::DuplicationRule::new()));
engine
}
pub fn default_for_workspace(workspace: &Path) -> Self {
Self::new(ComplyConfig::default_for_workspace(workspace))
}
pub fn register_rule(&mut self, rule: Box<dyn StackComplianceRule>) {
self.rules.push(rule);
}
pub fn discover_projects(&mut self, workspace: &Path) -> anyhow::Result<&[ProjectInfo]> {
self.discovered_projects.clear();
for entry in
walkdir::WalkDir::new(workspace).max_depth(2).into_iter().filter_map(|e| e.ok())
{
let path = entry.path();
if path.file_name() == Some(std::ffi::OsStr::new("Cargo.toml")) {
if let Some(project) = self.parse_project(path)? {
self.discovered_projects.push(project);
}
}
}
Ok(&self.discovered_projects)
}
fn parse_project(&self, cargo_toml: &Path) -> anyhow::Result<Option<ProjectInfo>> {
let content = std::fs::read_to_string(cargo_toml)?;
let toml: toml::Value = toml::from_str(&content)?;
let name = toml
.get("package")
.and_then(|p| p.get("name"))
.and_then(|n| n.as_str())
.map(String::from);
match name {
Some(name) => {
let path = cargo_toml.parent().unwrap_or(Path::new(".")).to_path_buf();
let is_paiml_crate = PAIML_CRATES.contains(&name.as_str());
Ok(Some(ProjectInfo { name, path, is_paiml_crate }))
}
None => Ok(None),
}
}
pub fn check_all(&self) -> ComplyReport {
let mut report = ComplyReport::new();
for project in &self.discovered_projects {
if !project.is_paiml_crate && !self.config.include_external {
continue;
}
for rule in &self.rules {
if !self.is_rule_enabled(rule.id()) {
continue;
}
if self.has_rule_exemption(&project.name, rule.id()) {
report.add_exemption(&project.name, rule.id());
continue;
}
match rule.check(&project.path) {
Ok(result) => {
report.add_result(&project.name, rule.id(), result);
}
Err(e) => {
report.add_error(&project.name, rule.id(), e.to_string());
}
}
}
}
report.finalize();
report
}
pub fn check_rule(&self, rule_id: &str) -> ComplyReport {
let mut report = ComplyReport::new();
let rule = match self.rules.iter().find(|r| r.id() == rule_id) {
Some(r) => r,
None => {
report.add_global_error(format!("Unknown rule: {}", rule_id));
return report;
}
};
for project in &self.discovered_projects {
if !project.is_paiml_crate && !self.config.include_external {
continue;
}
if self.has_rule_exemption(&project.name, rule_id) {
report.add_exemption(&project.name, rule_id);
continue;
}
match rule.check(&project.path) {
Ok(result) => {
report.add_result(&project.name, rule_id, result);
}
Err(e) => {
report.add_error(&project.name, rule_id, e.to_string());
}
}
}
report.finalize();
report
}
pub fn fix_all(&self, dry_run: bool) -> ComplyReport {
let mut report = ComplyReport::new();
for project in &self.discovered_projects {
if !project.is_paiml_crate && !self.config.include_external {
continue;
}
for rule in &self.rules {
if !self.is_rule_enabled(rule.id()) || !rule.can_fix() {
continue;
}
if self.has_rule_exemption(&project.name, rule.id()) {
continue;
}
let check_result = match rule.check(&project.path) {
Ok(r) => r,
Err(e) => {
report.add_error(&project.name, rule.id(), e.to_string());
continue;
}
};
if check_result.passed {
report.add_result(&project.name, rule.id(), check_result);
continue;
}
if dry_run {
report.add_dry_run_fix(&project.name, rule.id(), &check_result.violations);
} else {
match rule.fix(&project.path) {
Ok(fix_result) => {
report.add_fix_result(&project.name, rule.id(), fix_result);
}
Err(e) => {
report.add_error(&project.name, rule.id(), e.to_string());
}
}
}
}
}
report.finalize();
report
}
fn is_rule_enabled(&self, rule_id: &str) -> bool {
if self.config.enabled_rules.is_empty() {
!self.config.disabled_rules.contains(&rule_id.to_string())
} else {
self.config.enabled_rules.contains(&rule_id.to_string())
}
}
fn has_rule_exemption(&self, project_name: &str, rule_id: &str) -> bool {
self.config
.project_overrides
.get(project_name)
.map(|o| o.exempt_rules.contains(&rule_id.to_string()))
.unwrap_or(false)
}
pub fn available_rules(&self) -> Vec<(&str, &str)> {
self.rules.iter().map(|r| (r.id(), r.description())).collect()
}
pub fn projects(&self) -> &[ProjectInfo] {
&self.discovered_projects
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_comply_engine_creation() {
let config = ComplyConfig::default();
let engine = StackComplyEngine::new(config);
assert!(!engine.rules.is_empty());
}
#[test]
fn test_available_rules() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let rules = engine.available_rules();
assert!(rules.iter().any(|(id, _)| *id == "makefile-targets"));
assert!(rules.iter().any(|(id, _)| *id == "cargo-toml-consistency"));
assert!(rules.iter().any(|(id, _)| *id == "ci-workflow-parity"));
assert!(rules.iter().any(|(id, _)| *id == "code-duplication"));
}
#[test]
fn test_rule_enabled_check() {
let mut config = ComplyConfig::default();
config.disabled_rules.push("makefile-targets".to_string());
let engine = StackComplyEngine::new(config);
assert!(!engine.is_rule_enabled("makefile-targets"));
assert!(engine.is_rule_enabled("cargo-toml-consistency"));
}
#[test]
fn test_project_exemption() {
let mut config = ComplyConfig::default();
let mut override_config = ProjectOverride::default();
override_config.exempt_rules.push("makefile-targets".to_string());
config.project_overrides.insert("test-project".to_string(), override_config);
let engine = StackComplyEngine::new(config);
assert!(engine.has_rule_exemption("test-project", "makefile-targets"));
assert!(!engine.has_rule_exemption("test-project", "cargo-toml-consistency"));
assert!(!engine.has_rule_exemption("other-project", "makefile-targets"));
}
#[test]
fn test_default_for_workspace() {
let engine = StackComplyEngine::default_for_workspace(Path::new("."));
assert!(!engine.rules.is_empty());
}
#[test]
fn test_projects_empty_initially() {
let engine = StackComplyEngine::new(ComplyConfig::default());
assert!(engine.projects().is_empty());
}
#[test]
fn test_enabled_rules_explicit() {
let mut config = ComplyConfig::default();
config.enabled_rules.push("makefile-targets".to_string());
let engine = StackComplyEngine::new(config);
assert!(engine.is_rule_enabled("makefile-targets"));
assert!(!engine.is_rule_enabled("cargo-toml-consistency"));
}
#[test]
fn test_project_info_fields() {
let info = ProjectInfo {
name: "test-project".to_string(),
path: PathBuf::from("/path/to/project"),
is_paiml_crate: true,
};
assert_eq!(info.name, "test-project");
assert_eq!(info.path, PathBuf::from("/path/to/project"));
assert!(info.is_paiml_crate);
}
#[test]
fn test_check_rule_unknown() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let report = engine.check_rule("nonexistent-rule");
assert!(!report.errors.is_empty());
}
#[test]
fn test_check_all_empty_projects() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let report = engine.check_all();
assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_fix_all_empty_projects() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let report = engine.fix_all(true);
assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_fix_all_dry_run() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let report = engine.fix_all(true); assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_fix_all_actual_run() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let report = engine.fix_all(false); assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_register_custom_rule() {
use crate::comply::rules::MakefileRule;
let mut engine = StackComplyEngine::new(ComplyConfig::default());
let initial_count = engine.rules.len();
engine.register_rule(Box::new(MakefileRule::new()));
assert_eq!(engine.rules.len(), initial_count + 1);
}
#[test]
fn test_project_info_clone() {
let info = ProjectInfo {
name: "test-project".to_string(),
path: PathBuf::from("/path/to/project"),
is_paiml_crate: true,
};
let cloned = info.clone();
assert_eq!(cloned.name, info.name);
assert_eq!(cloned.path, info.path);
assert_eq!(cloned.is_paiml_crate, info.is_paiml_crate);
}
#[test]
fn test_project_info_non_paiml() {
let info = ProjectInfo {
name: "third-party-lib".to_string(),
path: PathBuf::from("/external/lib"),
is_paiml_crate: false,
};
assert!(!info.is_paiml_crate);
}
#[test]
fn test_discover_projects_in_tempdir() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("my-project");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
let cargo_toml = r#"
[package]
name = "my-project"
version = "0.1.0"
"#;
std::fs::write(project_dir.join("Cargo.toml"), cargo_toml).expect("fs write failed");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
let projects = engine.discover_projects(tempdir.path()).expect("unexpected failure");
assert_eq!(projects.len(), 1);
assert_eq!(projects[0].name, "my-project");
assert!(!projects[0].is_paiml_crate);
}
#[test]
fn test_discover_projects_paiml_crate() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
let cargo_toml = r#"
[package]
name = "trueno"
version = "0.1.0"
"#;
std::fs::write(project_dir.join("Cargo.toml"), cargo_toml).expect("fs write failed");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
let projects = engine.discover_projects(tempdir.path()).expect("unexpected failure");
assert_eq!(projects.len(), 1);
assert!(projects[0].is_paiml_crate);
}
#[test]
fn test_discover_projects_multiple() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
for name in &["proj-a", "proj-b"] {
let project_dir = tempdir.path().join(name);
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
let cargo_toml = format!(
r#"
[package]
name = "{}"
version = "0.1.0"
"#,
name
);
std::fs::write(project_dir.join("Cargo.toml"), cargo_toml).expect("fs write failed");
}
let mut engine = StackComplyEngine::new(ComplyConfig::default());
let projects = engine.discover_projects(tempdir.path()).expect("unexpected failure");
assert_eq!(projects.len(), 2);
}
#[test]
fn test_discover_projects_no_name() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("unnamed");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
let cargo_toml = r#"
[package]
version = "0.1.0"
"#;
std::fs::write(project_dir.join("Cargo.toml"), cargo_toml).expect("fs write failed");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
let projects = engine.discover_projects(tempdir.path()).expect("unexpected failure");
assert_eq!(projects.len(), 0);
}
#[test]
fn test_discover_projects_clears_cache() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("proj");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"proj\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
assert_eq!(engine.projects().len(), 1);
let empty_dir = tempfile::tempdir().expect("tempdir creation failed");
engine.discover_projects(empty_dir.path()).expect("unexpected failure");
assert_eq!(engine.projects().len(), 0);
}
#[test]
fn test_check_all_with_include_external() {
let config = ComplyConfig { include_external: true, ..Default::default() };
let engine = StackComplyEngine::new(config);
let report = engine.check_all();
assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_check_rule_with_discovered_projects() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_rule("makefile-targets");
assert!(!report.results.is_empty() || !report.errors.is_empty());
}
#[test]
fn test_available_rules_descriptions() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let rules = engine.available_rules();
for (id, desc) in &rules {
assert!(!id.is_empty());
assert!(!desc.is_empty());
}
}
#[test]
fn test_engine_debug() {
let engine = StackComplyEngine::new(ComplyConfig::default());
let debug_str = format!("{:?}", engine);
assert!(debug_str.contains("StackComplyEngine"));
}
#[test]
fn test_project_info_debug() {
let info = ProjectInfo {
name: "test".to_string(),
path: PathBuf::from("/test"),
is_paiml_crate: false,
};
let debug_str = format!("{:?}", info);
assert!(debug_str.contains("ProjectInfo"));
}
#[test]
fn test_check_all_with_paiml_project_no_makefile() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_all();
assert_eq!(report.summary.total_projects, 1);
assert!(report.summary.total_checks > 0);
}
#[test]
fn test_check_all_skips_non_paiml_without_include_external() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("some-lib");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"some-lib\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let config = ComplyConfig::default(); let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_all();
assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_check_all_includes_non_paiml_with_include_external() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("external-lib");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"external-lib\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let config = ComplyConfig { include_external: true, ..Default::default() };
let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_all();
assert_eq!(report.summary.total_projects, 1);
}
#[test]
fn test_check_all_with_disabled_rule() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut config = ComplyConfig::default();
config.disabled_rules.push("makefile-targets".to_string());
let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_all();
let trueno_results = report.results.get("trueno");
if let Some(results) = trueno_results {
assert!(!results.contains_key("makefile-targets"));
}
}
#[test]
fn test_check_all_with_exemption() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut config = ComplyConfig::default();
let mut override_cfg = ProjectOverride::default();
override_cfg.exempt_rules.push("makefile-targets".to_string());
config.project_overrides.insert("trueno".to_string(), override_cfg);
let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_all();
assert!(report
.exemptions
.iter()
.any(|e| e.project == "trueno" && e.rule == "makefile-targets"));
}
#[test]
fn test_fix_all_dry_run_with_paiml_project() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.fix_all(true); assert_eq!(report.summary.total_projects, 1);
}
#[test]
fn test_fix_all_actual_with_paiml_project() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.fix_all(false); assert_eq!(report.summary.total_projects, 1);
}
#[test]
fn test_fix_all_skips_non_paiml() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("external");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"external\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.fix_all(false);
assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_fix_all_skips_disabled_rules() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut config = ComplyConfig::default();
config.disabled_rules.push("makefile-targets".to_string());
config.disabled_rules.push("cargo-toml-consistency".to_string());
config.disabled_rules.push("ci-workflow-parity".to_string());
config.disabled_rules.push("code-duplication".to_string());
let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.fix_all(false);
assert_eq!(report.summary.total_checks, 0);
}
#[test]
fn test_fix_all_with_exemption() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut config = ComplyConfig::default();
let mut override_cfg = ProjectOverride::default();
override_cfg.exempt_rules.push("makefile-targets".to_string());
config.project_overrides.insert("trueno".to_string(), override_cfg);
let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.fix_all(false);
assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_fix_all_passing_project_with_makefile() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let makefile = r#"test-fast:
cargo nextest run --lib
test:
cargo nextest run
lint:
cargo clippy
fmt:
cargo fmt
coverage:
cargo llvm-cov
"#;
std::fs::write(project_dir.join("Makefile"), makefile).expect("fs write failed");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.fix_all(false);
assert_eq!(report.summary.total_projects, 1);
}
#[test]
fn test_check_rule_skips_non_paiml() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("external");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"external\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_rule("makefile-targets");
assert_eq!(report.summary.total_projects, 0);
}
#[test]
fn test_check_rule_with_exemption() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut config = ComplyConfig::default();
let mut override_cfg = ProjectOverride::default();
override_cfg.exempt_rules.push("makefile-targets".to_string());
config.project_overrides.insert("trueno".to_string(), override_cfg);
let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_rule("makefile-targets");
assert!(report
.exemptions
.iter()
.any(|e| e.project == "trueno" && e.rule == "makefile-targets"));
}
#[test]
fn test_check_rule_includes_non_paiml_with_external() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("ext-proj");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"ext-proj\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let config = ComplyConfig { include_external: true, ..Default::default() };
let mut engine = StackComplyEngine::new(config);
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_rule("makefile-targets");
assert!(report.summary.total_projects > 0 || !report.results.is_empty());
}
#[test]
fn test_parse_project_workspace_toml() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("workspace");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
let cargo_toml = r#"
[workspace]
members = ["crate-a", "crate-b"]
"#;
std::fs::write(project_dir.join("Cargo.toml"), cargo_toml).expect("fs write failed");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
let projects = engine.discover_projects(tempdir.path()).expect("unexpected failure");
assert_eq!(projects.len(), 0);
}
#[test]
fn test_check_all_reports_rule_errors() {
let tempdir = tempfile::tempdir().expect("tempdir creation failed");
let project_dir = tempdir.path().join("trueno");
std::fs::create_dir_all(&project_dir).expect("mkdir failed");
std::fs::write(
project_dir.join("Cargo.toml"),
"[package]\nname = \"trueno\"\nversion = \"0.1.0\"",
)
.expect("unexpected failure");
let mut engine = StackComplyEngine::new(ComplyConfig::default());
engine.discover_projects(tempdir.path()).expect("unexpected failure");
let report = engine.check_all();
assert_eq!(report.summary.total_projects, 1);
assert!(report.summary.total_checks > 0);
}
}