Files
el/engrams/el-arch/src/tests.rs
T
Will Anderson a42429012e rename crates/ to engrams/; add el-compiler el package with bootstrap artifact
- crates/ → engrams/ (Rust engrams live here)
- el-compiler/ added: el self-hosting compiler as an el package
  - src/{compiler,lexer,parser,codegen}.el
  - bootstrap/el-compiler.elc (114KB, Rust-compiled seed)
- el.toml Cargo.toml workspace paths updated
- neuron-rs cross-repo path deps fixed (were pointing to products/ instead of foundation/)
2026-04-29 03:27:32 -05:00

449 lines
17 KiB
Rust

//! Comprehensive tests for the el-arch architectural checker.
use crate::{ArchChecker, ArchDiagnostic, Severity};
// ── Test helpers ──────────────────────────────────────────────────────────────
fn check(src: &str) -> Vec<ArchDiagnostic> {
let tokens = el_lexer::tokenize(src).expect("lex failed");
let prog = el_parser::parse(tokens, src.to_string()).expect("parse failed");
ArchChecker::new().check(&prog)
}
fn errors(src: &str) -> Vec<ArchDiagnostic> {
check(src).into_iter().filter(|d| d.severity == Severity::Error).collect()
}
fn warnings(src: &str) -> Vec<ArchDiagnostic> {
check(src).into_iter().filter(|d| d.severity == Severity::Warning).collect()
}
fn has_rule(diags: &[ArchDiagnostic], rule: &str) -> bool {
diags.iter().any(|d| d.rule == rule)
}
// ── 1. VBD-001: @accessor calling @manager → error ──────────────────────────
#[test]
fn test_accessor_calls_manager_error() {
let src = r#"
@manager fn orchestrate(x: String) -> String { return x }
@accessor fn fetch_user(id: String) -> String { return orchestrate(id) }
"#;
let errs = errors(src);
assert!(!errs.is_empty(), "expected error when @accessor calls @manager");
assert!(has_rule(&errs, "VBD-001"), "expected VBD-001 rule");
}
// ── 2. @accessor calling @accessor → no VBD-001 error ────────────────────────
#[test]
fn test_accessor_calls_accessor_no_error() {
let src = r#"
@accessor fn get_name(id: String) -> String { return id }
@accessor fn get_user(id: String) -> String { return get_name(id) }
"#;
let errs = errors(src);
let vbd001: Vec<_> = errs.iter().filter(|d| d.rule == "VBD-001").collect();
assert!(vbd001.is_empty(), "accessor->accessor should not trigger VBD-001");
}
// ── 3. @experience calling @experience → VBD-002 error ───────────────────────
#[test]
fn test_experience_calls_experience_error() {
let src = r#"
@experience fn checkout(cart: String) -> Result<String, String> { return cart }
@experience fn payment(amount: String) -> Result<String, String> { return checkout(amount) }
"#;
let errs = errors(src);
assert!(!errs.is_empty(), "expected error when @experience calls @experience");
assert!(has_rule(&errs, "VBD-002"), "expected VBD-002 rule");
}
// ── 4. @experience calling non-experience → no VBD-002 error ─────────────────
#[test]
fn test_experience_calls_non_experience_no_error() {
let src = r#"
@accessor fn load_cart(id: String) -> String { return id }
@experience fn checkout(cart: String) -> Result<String, String> { return load_cart(cart) }
"#;
let errs = errors(src);
let vbd002: Vec<_> = errs.iter().filter(|d| d.rule == "VBD-002").collect();
assert!(vbd002.is_empty(), "experience->non-experience should not trigger VBD-002");
}
// ── 5. @public function with activate inside → SEC-001 error ─────────────────
#[test]
fn test_public_fn_with_activate_error() {
let src = r#"
@public fn list_users() -> String {
let users: String = activate User where "all users"
return users
}
"#;
let errs = errors(src);
assert!(!errs.is_empty(), "expected error for @public fn with activate");
assert!(has_rule(&errs, "SEC-001"), "expected SEC-001 rule");
}
// ── 6. @public function without activate → no SEC-001 error ──────────────────
#[test]
fn test_public_fn_without_activate_no_error() {
let src = r#"
@public fn greet(name: String) -> String { return name }
"#;
let errs = errors(src);
let sec001: Vec<_> = errs.iter().filter(|d| d.rule == "SEC-001").collect();
assert!(sec001.is_empty(), "@public without activate should not trigger SEC-001");
}
// ── 7. N+1: activate inside a for loop → GRAPH-001 warning ───────────────────
#[test]
fn test_activate_in_for_loop_n1_warning() {
let src = r#"
fn process_ids(ids: String) -> String {
for id in ids {
let u: String = activate User where "user by id"
}
return "done"
}
"#;
let warns = warnings(src);
assert!(!warns.is_empty(), "expected N+1 warning for activate inside loop");
assert!(has_rule(&warns, "GRAPH-001"), "expected GRAPH-001 rule");
}
// ── 8. activate NOT in loop → no GRAPH-001 warning ───────────────────────────
#[test]
fn test_activate_not_in_loop_no_n1_warning() {
let src = r#"
fn fetch_users() -> String {
let users: String = activate User where "recent users"
return users
}
"#;
let warns = warnings(src);
let graph001: Vec<_> = warns.iter().filter(|d| d.rule == "GRAPH-001").collect();
assert!(graph001.is_empty(), "activate outside loop should not trigger GRAPH-001");
}
// ── 9. Duplicate activate same type → GRAPH-002 warning ──────────────────────
#[test]
fn test_duplicate_activate_same_type_warning() {
let src = r#"
fn inefficient_fn(x: String) -> String {
let a: String = activate User where "active users"
let b: String = activate User where "recent users"
return a
}
"#;
let warns = warnings(src);
assert!(!warns.is_empty(), "expected warning for duplicate activate on same type");
assert!(has_rule(&warns, "GRAPH-002"), "expected GRAPH-002 rule");
}
// ── 10. Different activate types → no GRAPH-002 warning ──────────────────────
#[test]
fn test_different_activate_types_no_duplicate_warning() {
let src = r#"
fn fetch_all(x: String) -> String {
let users: String = activate User where "users"
let orders: String = activate Order where "orders"
return users
}
"#;
let warns = warnings(src);
let graph002: Vec<_> = warns.iter().filter(|d| d.rule == "GRAPH-002").collect();
assert!(graph002.is_empty(), "different activate types should not trigger GRAPH-002");
}
// ── 11. @swarm_agent calling @swarm_agent → SWARM-001 error ──────────────────
#[test]
fn test_swarm_agent_calls_swarm_agent_error() {
let src = r#"
@swarm_agent fn worker_b(x: String) -> String { return x }
@swarm_agent fn worker_a(x: String) -> String { return worker_b(x) }
"#;
let errs = errors(src);
assert!(!errs.is_empty(), "expected error when @swarm_agent calls @swarm_agent");
assert!(has_rule(&errs, "SWARM-001"), "expected SWARM-001 rule");
}
// ── 12. @swarm_agent in isolation → no SWARM-001 error ───────────────────────
#[test]
fn test_swarm_agent_isolation_no_error() {
let src = r#"
fn utility(x: String) -> String { return x }
@swarm_agent fn worker(x: String) -> String { return utility(x) }
"#;
let errs = errors(src);
let swarm001: Vec<_> = errs.iter().filter(|d| d.rule == "SWARM-001").collect();
assert!(swarm001.is_empty(), "isolated @swarm_agent should not trigger SWARM-001");
}
// ── 13. Multiple rules fire on same function ──────────────────────────────────
#[test]
fn test_multiple_rules_fire_same_function() {
// @swarm_agent calling a swarm_agent AND accessing shared state
let src = r#"
@swarm_agent fn peer(x: String) -> String { return x }
fn get_shared_cache(x: String) -> String { return x }
@swarm_agent fn violator(x: String) -> String {
let a: String = peer(x)
let b: String = get_shared_cache(x)
return a
}
"#;
let errs = errors(src);
// Should fire both SWARM-001 (calls peer) and SWARM-003 (calls get_shared_cache)
assert!(errs.len() >= 2, "expected multiple errors: {:?}", errs.iter().map(|e| &e.rule).collect::<Vec<_>>());
}
// ── 14. ArchChecker::has_errors() → true when errors present ─────────────────
#[test]
fn test_has_errors_true_when_errors_present() {
let src = r#"
@manager fn do_manage(x: String) -> String { return x }
@accessor fn bad_fetch(x: String) -> String { return do_manage(x) }
"#;
let diags = check(src);
assert!(ArchChecker::has_errors(&diags), "has_errors should be true");
}
// ── 15. ArchChecker::has_errors() → false when only warnings ─────────────────
#[test]
fn test_has_errors_false_when_only_warnings() {
let src = r#"
@experience fn sign_up(email: String) -> String { return email }
"#;
// sign_up doesn't return Result so triggers VBD-003 warning
let diags = check(src);
let has_warn = diags.iter().any(|d| d.severity == Severity::Warning);
assert!(has_warn || diags.is_empty(), "expected either warnings or empty");
assert!(!ArchChecker::has_errors(&diags.iter().filter(|d| d.severity == Severity::Warning).cloned().collect::<Vec<_>>()), "has_errors should be false for warnings");
}
// ── 16. Clean function → empty diagnostics ────────────────────────────────────
#[test]
fn test_clean_function_no_diagnostics() {
let src = r#"
fn pure_add(a: String, b: String) -> String { return a }
"#;
let diags = check(src);
assert!(diags.is_empty(), "clean function should produce no diagnostics, got: {:?}", diags.iter().map(|d| &d.rule).collect::<Vec<_>>());
}
// ── 17. @engine function → runs without panic ─────────────────────────────────
#[test]
fn test_engine_function_no_panic() {
let src = r#"
@engine fn compute(data: String) -> String { return data }
"#;
// Just verify no panic — engine rules only produce warnings in some impls
let _diags = check(src);
}
// ── 18. sealed { } not in loop → no SEC-002 warning ──────────────────────────
#[test]
fn test_sealed_not_in_loop_no_warning() {
let src = r#"
fn encrypt_data(secret: String) -> String {
sealed { let key: String = secret }
return secret
}
"#;
let warns = warnings(src);
let sec002: Vec<_> = warns.iter().filter(|d| d.rule == "SEC-002").collect();
assert!(sec002.is_empty(), "sealed not in loop should not trigger SEC-002");
}
// ── 19. Rule names are unique ─────────────────────────────────────────────────
#[test]
fn test_rule_names_are_unique() {
let checker = ArchChecker::new();
let mut names = std::collections::HashSet::new();
for rule in checker.rules() {
let inserted = names.insert(rule.name().to_string());
assert!(inserted, "duplicate rule name: {}", rule.name());
}
}
// ── 20. All rules implement ArchRule (compile-time check) ─────────────────────
#[test]
fn test_all_rules_implement_arch_rule() {
use crate::rule::ArchRule;
use crate::rules::{
vbd::{AccessorMustNotCallManager, ExperienceMustNotCallExperience, ExperienceShouldReturnResult},
security::{PublicFnWithActivate, SealedInLoop, AuthnWithoutAuthz},
graph::{N1Detection, DuplicateActivateType},
swarm::{SwarmAgentIsolation, SwarmAgentNoSpawn, SwarmAgentNoSharedState},
};
fn assert_arch_rule<T: ArchRule>() {}
assert_arch_rule::<AccessorMustNotCallManager>();
assert_arch_rule::<ExperienceMustNotCallExperience>();
assert_arch_rule::<ExperienceShouldReturnResult>();
assert_arch_rule::<PublicFnWithActivate>();
assert_arch_rule::<SealedInLoop>();
assert_arch_rule::<AuthnWithoutAuthz>();
assert_arch_rule::<N1Detection>();
assert_arch_rule::<DuplicateActivateType>();
assert_arch_rule::<SwarmAgentIsolation>();
assert_arch_rule::<SwarmAgentNoSpawn>();
assert_arch_rule::<SwarmAgentNoSharedState>();
}
// ── 21. FnContext builds correctly for a decorated function ───────────────────
#[test]
fn test_fn_context_from_decorated_function() {
// Run checker and verify the location field is set to the function name
let src = r#"
@accessor fn fetch_item(id: String) -> String { return id }
"#;
let diags = check(src);
// No violations — but verify the checker doesn't panic and processes it
// (accessor with no calls should produce no errors)
let _: Vec<_> = diags;
}
// ── 22. @experience without Result return type → VBD-003 warning ─────────────
#[test]
fn test_experience_non_result_return_warning() {
let src = r#"
@experience fn show_profile(user: String) -> String { return user }
"#;
let warns = warnings(src);
assert!(!warns.is_empty(), "expected warning for @experience not returning Result");
assert!(has_rule(&warns, "VBD-003"), "expected VBD-003 rule");
}
// ── 23. @authenticate without @authorize on mutation → SEC-003 warning ────────
#[test]
fn test_authenticate_without_authorize_on_mutation_warning() {
let src = r#"
@authenticate fn create_account(email: String) -> String { return email }
"#;
let warns = warnings(src);
assert!(!warns.is_empty(), "expected SEC-003 warning");
assert!(has_rule(&warns, "SEC-003"), "expected SEC-003 rule");
}
// ── 24. Two @experience functions checked independently ───────────────────────
#[test]
fn test_two_experience_functions_checked_independently() {
let src = r#"
@experience fn sign_up(email: String) -> Result<String, String> { return email }
@experience fn log_in(token: String) -> String { return token }
"#;
let warns = warnings(src);
// sign_up returns Result — no VBD-003 for it
// log_in returns String — VBD-003 fires for it
let vbd003: Vec<_> = warns.iter().filter(|d| d.rule == "VBD-003").collect();
assert_eq!(vbd003.len(), 1, "only log_in should trigger VBD-003, got {:?}", vbd003.iter().map(|d| &d.location).collect::<Vec<_>>());
assert!(vbd003[0].location.as_deref() == Some("log_in"), "VBD-003 should point to log_in");
}
// ── 25. Mixed errors and warnings → has_errors() returns true ─────────────────
#[test]
fn test_mixed_errors_and_warnings_has_errors_true() {
let src = r#"
@manager fn manage_data(x: String) -> String { return x }
@accessor fn bad_read(x: String) -> String { return manage_data(x) }
@experience fn display(x: String) -> String { return x }
"#;
let diags = check(src);
assert!(ArchChecker::has_errors(&diags), "should have errors (VBD-001)");
let has_warn = diags.iter().any(|d| d.severity == Severity::Warning);
assert!(has_warn, "should also have warnings (VBD-003 for display)");
}
// ── 26. @swarm_agent calling spawn → SWARM-002 error ─────────────────────────
#[test]
fn test_swarm_agent_no_spawn() {
let src = r#"
fn spawn(agent: String) -> String { return agent }
@swarm_agent fn initiator(x: String) -> String { return spawn(x) }
"#;
let errs = errors(src);
assert!(!errs.is_empty(), "expected SWARM-002 error for calling spawn");
assert!(has_rule(&errs, "SWARM-002"), "expected SWARM-002 rule");
}
// ── 27. @swarm_agent accessing shared state → SWARM-003 error ────────────────
#[test]
fn test_swarm_agent_no_shared_state() {
let src = r#"
fn get_shared_counter(x: String) -> String { return x }
@swarm_agent fn agent(x: String) -> String { return get_shared_counter(x) }
"#;
let errs = errors(src);
assert!(!errs.is_empty(), "expected SWARM-003 error for shared state access");
assert!(has_rule(&errs, "SWARM-003"), "expected SWARM-003 rule");
}
// ── 28. sealed block inside for loop → SEC-002 warning ───────────────────────
#[test]
fn test_sealed_in_for_loop_warning() {
let src = r#"
fn encrypt_many(items: String) -> String {
for item in items {
sealed { let x: String = item }
}
return items
}
"#;
let warns = warnings(src);
assert!(!warns.is_empty(), "expected SEC-002 warning for sealed in loop");
assert!(has_rule(&warns, "SEC-002"), "expected SEC-002 rule");
}
// ── 29. @authenticate with @authorize → no SEC-003 warning ───────────────────
#[test]
fn test_authenticate_with_authorize_no_warning() {
let src = r#"
@authenticate @authorize fn create_post(content: String) -> String { return content }
"#;
let warns = warnings(src);
let sec003: Vec<_> = warns.iter().filter(|d| d.rule == "SEC-003").collect();
assert!(sec003.is_empty(), "@authenticate + @authorize should not trigger SEC-003");
}
// ── 30. @experience returning Result → no VBD-003 warning ────────────────────
#[test]
fn test_experience_returns_result_no_warning() {
let src = r#"
@experience fn register(email: String) -> Result<String, String> { return email }
"#;
let warns = warnings(src);
let vbd003: Vec<_> = warns.iter().filter(|d| d.rule == "VBD-003").collect();
assert!(vbd003.is_empty(), "@experience returning Result should not trigger VBD-003");
}