Skip to content

Commit b904d77

Browse files
hyperpolymathclaude
andcommitted
test: add 131 new tests across 20 modules (397→528 passing)
Comprehensive test expansion covering: - Provers: SPIN (11), CBMC (8), Alloy (7), NuSMV (6), UPPAAL (5), CaDiCaL (11), Agda (13), Alt-Ergo (9), E-Prover (7), SPASS (7) - Exchange: OpenTheory (5), Dedukti (7) - Verification: portfolio (5), mutation (5), certificates (5) - Core: dispatch (6), proof_encoding (7), groove (4), integration (4), proof_search (3), agent/router (8) Also fix nul-terminated string clippy warning in FFI. 528 tests passing, 0 failures, 0 clippy warnings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bd086d3 commit b904d77

22 files changed

Lines changed: 1666 additions & 1 deletion

src/rust/agent/router.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,145 @@ mod tests {
281281
assert!(stats.contains_key(&ProverKind::Z3));
282282
assert_eq!(stats[&ProverKind::Z3].successes, 10);
283283
}
284+
285+
#[test]
286+
fn test_prover_stats_default() {
287+
let stats = ProverStats::new();
288+
assert_eq!(stats.attempts, 0);
289+
assert_eq!(stats.successes, 0);
290+
assert_eq!(stats.failures, 0);
291+
assert_eq!(stats.total_time_ms, 0);
292+
}
293+
294+
#[test]
295+
fn test_prover_stats_success_rate_no_data() {
296+
let stats = ProverStats::new();
297+
assert_eq!(stats.success_rate(), 0.5); // Default 50% when no data
298+
}
299+
300+
#[test]
301+
fn test_prover_stats_success_rate_with_data() {
302+
let stats = ProverStats {
303+
attempts: 10,
304+
successes: 8,
305+
failures: 2,
306+
total_time_ms: 5000,
307+
};
308+
assert!((stats.success_rate() - 0.8).abs() < f64::EPSILON);
309+
}
310+
311+
#[test]
312+
fn test_prover_stats_success_rate_zero_attempts() {
313+
let stats = ProverStats {
314+
attempts: 0,
315+
successes: 0,
316+
failures: 0,
317+
total_time_ms: 0,
318+
};
319+
assert_eq!(stats.success_rate(), 0.5);
320+
}
321+
322+
#[test]
323+
fn test_router_select_arithmetic() {
324+
let router = ProverRouter::new();
325+
let goal = AgenticGoal {
326+
goal: Goal {
327+
id: "test".to_string(),
328+
target: Term::Var("A".to_string()),
329+
hypotheses: vec![],
330+
},
331+
priority: Priority::High,
332+
attempts: 0,
333+
max_attempts: 3,
334+
preferred_prover: None,
335+
aspects: vec!["arithmetic".to_string()],
336+
parent: None,
337+
};
338+
339+
assert_eq!(router.select(&goal), ProverKind::Z3);
340+
}
341+
342+
#[test]
343+
fn test_router_select_inductive() {
344+
let router = ProverRouter::new();
345+
let goal = AgenticGoal {
346+
goal: Goal {
347+
id: "test".to_string(),
348+
target: Term::Var("A".to_string()),
349+
hypotheses: vec![],
350+
},
351+
priority: Priority::High,
352+
attempts: 0,
353+
max_attempts: 3,
354+
preferred_prover: None,
355+
aspects: vec!["inductive".to_string()],
356+
parent: None,
357+
};
358+
359+
assert_eq!(router.select(&goal), ProverKind::Coq);
360+
}
361+
362+
#[test]
363+
fn test_router_select_type_theory() {
364+
let router = ProverRouter::new();
365+
let goal = AgenticGoal {
366+
goal: Goal {
367+
id: "test".to_string(),
368+
target: Term::Var("A".to_string()),
369+
hypotheses: vec![],
370+
},
371+
priority: Priority::High,
372+
attempts: 0,
373+
max_attempts: 3,
374+
preferred_prover: None,
375+
aspects: vec!["type_theory".to_string()],
376+
parent: None,
377+
};
378+
379+
assert_eq!(router.select(&goal), ProverKind::Lean);
380+
}
381+
382+
#[test]
383+
fn test_router_select_default() {
384+
let router = ProverRouter::new();
385+
let goal = AgenticGoal {
386+
goal: Goal {
387+
id: "test".to_string(),
388+
target: Term::Var("A".to_string()),
389+
hypotheses: vec![],
390+
},
391+
priority: Priority::Medium,
392+
attempts: 0,
393+
max_attempts: 3,
394+
preferred_prover: None,
395+
aspects: vec![],
396+
parent: None,
397+
};
398+
399+
assert_eq!(router.select(&goal), ProverKind::Metamath);
400+
}
401+
402+
#[tokio::test]
403+
async fn test_router_record_failure() {
404+
let router = ProverRouter::new();
405+
let goal = AgenticGoal {
406+
goal: Goal {
407+
id: "test".to_string(),
408+
target: Term::Var("A".to_string()),
409+
hypotheses: vec![],
410+
},
411+
priority: Priority::Medium,
412+
attempts: 0,
413+
max_attempts: 3,
414+
preferred_prover: None,
415+
aspects: vec!["logic".to_string()],
416+
parent: None,
417+
};
418+
419+
router.record_failure(&goal, ProverKind::Lean).await;
420+
421+
let stats = router.get_all_stats().await;
422+
assert_eq!(stats[&ProverKind::Lean].failures, 1);
423+
assert_eq!(stats[&ProverKind::Lean].attempts, 1);
424+
}
284425
}

src/rust/dispatch.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,4 +473,69 @@ mod tests {
473473
let json = serde_json::to_string(&result).unwrap();
474474
assert!(json.contains("Level4"));
475475
}
476+
477+
#[test]
478+
fn test_dispatch_result_deserialization() {
479+
let json = r#"{"verified":false,"trust_level":"Level1","provers_used":["Z3"],"proof_time_ms":100,"goals_remaining":1,"axiom_report":null,"certificate_hash":null,"message":"Failed","cross_checked":false}"#;
480+
let result: DispatchResult = serde_json::from_str(json).unwrap();
481+
assert!(!result.verified);
482+
assert_eq!(result.trust_level, TrustLevel::Level1);
483+
assert_eq!(result.goals_remaining, 1);
484+
}
485+
486+
#[test]
487+
fn test_prover_selection_agda() {
488+
let prover = ProverDispatcher::select_prover(
489+
"module MyModule where\ndata Nat : Set where",
490+
None,
491+
);
492+
assert_eq!(prover, ProverKind::Agda);
493+
}
494+
495+
#[test]
496+
fn test_prover_selection_isabelle() {
497+
let prover = ProverDispatcher::select_prover(
498+
"theory MyTheory\nimports Main",
499+
None,
500+
);
501+
assert_eq!(prover, ProverKind::Isabelle);
502+
}
503+
504+
#[test]
505+
fn test_prover_selection_cnf_tptp() {
506+
let prover = ProverDispatcher::select_prover("cnf(ax1, axiom, p(a)).", None);
507+
assert_eq!(prover, ProverKind::Vampire);
508+
}
509+
510+
#[test]
511+
fn test_dispatch_config_custom() {
512+
let config = DispatchConfig {
513+
cross_check: true,
514+
min_trust_level: TrustLevel::Level4,
515+
track_axioms: false,
516+
generate_certificates: true,
517+
timeout: 600,
518+
};
519+
assert!(config.cross_check);
520+
assert_eq!(config.min_trust_level, TrustLevel::Level4);
521+
assert!(!config.track_axioms);
522+
assert!(config.generate_certificates);
523+
assert_eq!(config.timeout, 600);
524+
}
525+
526+
#[test]
527+
fn test_prover_dispatcher_default() {
528+
let dispatcher = ProverDispatcher::default();
529+
assert!(!dispatcher.config.cross_check);
530+
}
531+
532+
#[test]
533+
fn test_prover_dispatcher_with_config() {
534+
let config = DispatchConfig {
535+
cross_check: true,
536+
..Default::default()
537+
};
538+
let dispatcher = ProverDispatcher::with_config(config);
539+
assert!(dispatcher.config.cross_check);
540+
}
476541
}

src/rust/exchange/dedukti.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,76 @@ mod tests {
360360
assert_eq!(DeduktiExporter::sanitize_name("my-theorem"), "my_theorem");
361361
assert_eq!(DeduktiExporter::sanitize_name("mod.thm"), "mod_thm");
362362
}
363+
364+
#[test]
365+
fn test_export_empty_state() {
366+
let state = ProofState::default();
367+
let module = DeduktiExporter::export(&state).unwrap();
368+
369+
assert_eq!(module.name, "echidna.export");
370+
assert_eq!(module.requires, vec!["echidna.prelude".to_string()]);
371+
assert!(module.declarations.is_empty());
372+
}
373+
374+
#[test]
375+
fn test_export_with_goals() {
376+
let mut state = ProofState::default();
377+
state.goals.push(crate::core::Goal {
378+
id: "my-goal".to_string(),
379+
target: Term::Const("Prop".to_string()),
380+
hypotheses: vec![],
381+
});
382+
383+
let module = DeduktiExporter::export(&state).unwrap();
384+
assert_eq!(module.declarations.len(), 1);
385+
}
386+
387+
#[test]
388+
fn test_render_rule_declaration() {
389+
let module = DeduktiModule {
390+
name: "test".to_string(),
391+
requires: vec![],
392+
declarations: vec![DeduktiDeclaration::Rule {
393+
variables: vec!["x".to_string()],
394+
lhs: "plus 0 x".to_string(),
395+
rhs: "x".to_string(),
396+
}],
397+
};
398+
399+
let rendered = DeduktiExporter::render(&module);
400+
assert!(rendered.contains("[x]"));
401+
assert!(rendered.contains("plus 0 x"));
402+
assert!(rendered.contains("x"));
403+
}
404+
405+
#[test]
406+
fn test_term_to_dedukti_const() {
407+
let term = Term::Const("Nat".to_string());
408+
let dk = DeduktiExporter::term_to_dedukti(&term);
409+
assert_eq!(dk, "Nat");
410+
}
411+
412+
#[test]
413+
fn test_term_to_dedukti_var() {
414+
let term = Term::Var("x".to_string());
415+
let dk = DeduktiExporter::term_to_dedukti(&term);
416+
assert_eq!(dk, "x");
417+
}
418+
419+
#[test]
420+
fn test_import_with_definition() {
421+
let module = DeduktiModule {
422+
name: "test".to_string(),
423+
requires: vec![],
424+
declarations: vec![DeduktiDeclaration::Definition {
425+
name: "id".to_string(),
426+
ty: "Nat -> Nat".to_string(),
427+
body: "(x => x)".to_string(),
428+
}],
429+
};
430+
431+
let state = DeduktiExporter::import(&module).unwrap();
432+
assert_eq!(state.context.theorems.len(), 1);
433+
assert!(state.context.theorems[0].proof.is_some());
434+
}
363435
}

src/rust/exchange/opentheory.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,60 @@ mod tests {
192192

193193
assert_eq!(reimported.context.theorems.len(), 1);
194194
}
195+
196+
#[test]
197+
fn test_export_empty_state() {
198+
let state = ProofState::default();
199+
let article = OpenTheoryExporter::export(&state).unwrap();
200+
201+
assert_eq!(article.name, "echidna-export");
202+
assert!(article.assumptions.is_empty());
203+
assert!(article.conclusions.is_empty());
204+
assert!(article.commands.contains(&"version 6".to_string()));
205+
}
206+
207+
#[test]
208+
fn test_export_with_goals() {
209+
let mut state = ProofState::default();
210+
state.goals.push(crate::core::Goal {
211+
id: "g1".to_string(),
212+
target: Term::Const("True".to_string()),
213+
hypotheses: vec![],
214+
});
215+
216+
let article = OpenTheoryExporter::export(&state).unwrap();
217+
assert_eq!(article.conclusions.len(), 1);
218+
}
219+
220+
#[test]
221+
fn test_import_empty_article() {
222+
let article = OpenTheoryArticle {
223+
name: "empty".to_string(),
224+
assumptions: vec![],
225+
conclusions: vec![],
226+
commands: vec![],
227+
};
228+
229+
let state = OpenTheoryExporter::import(&article).unwrap();
230+
assert!(state.context.theorems.is_empty());
231+
assert!(state.goals.is_empty());
232+
}
233+
234+
#[test]
235+
fn test_term_to_opentheory_pi() {
236+
let term = Term::Pi {
237+
param: "x".to_string(),
238+
param_type: Box::new(Term::Const("Nat".to_string())),
239+
body: Box::new(Term::Const("Bool".to_string())),
240+
};
241+
let ot = OpenTheoryExporter::term_to_opentheory(&term);
242+
assert!(!ot.is_empty());
243+
}
244+
245+
#[test]
246+
fn test_term_to_opentheory_const() {
247+
let term = Term::Const("True".to_string());
248+
let ot = OpenTheoryExporter::term_to_opentheory(&term);
249+
assert!(ot.contains("True"));
250+
}
195251
}

src/rust/ffi/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ pub extern "C" fn echidna_last_error() -> *const u8 {
10181018
/// Get ECHIDNA version string.
10191019
#[no_mangle]
10201020
pub extern "C" fn echidna_version() -> *const u8 {
1021-
b"1.6.0\0".as_ptr()
1021+
c"1.6.0".as_ptr().cast()
10221022
}
10231023

10241024
/// Map ProverKind to u8 for FFI (reverse of kind_from_u8)

src/rust/groove.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,33 @@ mod tests {
166166
fn groove_port_is_9000() {
167167
assert_eq!(GROOVE_PORT, 9000);
168168
}
169+
170+
#[test]
171+
fn manifest_consumes_octad_storage() {
172+
let m = manifest();
173+
let consumes = m["consumes"].as_array().unwrap();
174+
assert!(consumes.iter().any(|v| v == "octad-storage"));
175+
}
176+
177+
#[test]
178+
fn manifest_has_proof_verification_type() {
179+
let m = manifest();
180+
assert_eq!(
181+
m["capabilities"]["proof_verification"]["type"],
182+
"proof-verification"
183+
);
184+
}
185+
186+
#[test]
187+
fn manifest_health_endpoint() {
188+
let m = manifest();
189+
assert_eq!(m["health"], "/health");
190+
}
191+
192+
#[test]
193+
fn manifest_service_version_not_empty() {
194+
let m = manifest();
195+
let version = m["service_version"].as_str().unwrap();
196+
assert!(!version.is_empty());
197+
}
169198
}

0 commit comments

Comments
 (0)