Skip to content

Commit 6e9ab48

Browse files
hyperpolymathclaude
andcommitted
test: implement 6 agentic integration tests, fix TypedWasm match exhaustiveness
- Replace 6 todo!() stubs with real tests: proof memory storage/retrieval, prover router learning, goal decomposition, agent queue priority, memory failure tracking, router aspect scoring - Make ProverStats fields + success_rate() pub for test assertions - Make AgentCore::next_goal() pub for queue priority testing - Add TypedWasm arms to all ProverKind match statements in test helpers - Fix prover count assertion (30→dynamic) for ProverFactory smoke test Test results: 387 passed, 0 failed, 16 ignored (up from 352/22) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f6db76b commit 6e9ab48

5 files changed

Lines changed: 308 additions & 25 deletions

File tree

src/rust/agent/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ impl AgentCore {
199199
}
200200

201201
/// Get the next goal from the queue
202-
async fn next_goal(&self) -> Option<AgenticGoal> {
202+
pub async fn next_goal(&self) -> Option<AgenticGoal> {
203203
let mut queue = self.goal_queue.write().await;
204204
queue.pop_front()
205205
}

src/rust/agent/router.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,21 @@ use super::AgenticGoal;
1919
#[derive(Debug, Clone, Serialize, Deserialize)]
2020
pub struct ProverStats {
2121
/// Total attempts
22-
attempts: u32,
22+
pub attempts: u32,
2323

2424
/// Successful proofs
25-
successes: u32,
25+
pub successes: u32,
2626

2727
/// Failures
28-
failures: u32,
28+
pub failures: u32,
2929

3030
/// Total time (milliseconds)
31-
total_time_ms: u64,
31+
pub total_time_ms: u64,
3232
}
3333

3434
impl ProverStats {
35-
fn new() -> Self {
35+
/// Create new empty stats
36+
pub fn new() -> Self {
3637
ProverStats {
3738
attempts: 0,
3839
successes: 0,
@@ -42,7 +43,7 @@ impl ProverStats {
4243
}
4344

4445
/// Success rate (0.0 - 1.0)
45-
fn success_rate(&self) -> f64 {
46+
pub fn success_rate(&self) -> f64 {
4647
if self.attempts == 0 {
4748
0.5 // No data, assume 50%
4849
} else {

tests/agentic_integration.rs

Lines changed: 290 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,27 +116,199 @@ impl ProverBackend for MockProver {
116116
}
117117

118118
#[tokio::test]
119-
#[ignore = "Requires API updates to SqliteMemory"]
120119
async fn test_proof_memory_storage_and_retrieval() {
121-
todo!("API updates needed for SqliteMemory")
120+
let tmp = TempDir::new().unwrap();
121+
let memory = SqliteMemory::new(tmp.path().join("test.db"));
122+
memory.init().await.unwrap();
123+
124+
let goal = AgenticGoal {
125+
goal: Goal {
126+
id: "mem_test".to_string(),
127+
target: Term::Var("P".to_string()),
128+
hypotheses: vec![],
129+
},
130+
priority: Priority::Medium,
131+
attempts: 0,
132+
max_attempts: 3,
133+
preferred_prover: None,
134+
aspects: vec!["logic".to_string()],
135+
parent: None,
136+
};
137+
138+
let proof = echidna::core::ProofState {
139+
goals: vec![],
140+
context: Default::default(),
141+
proof_script: vec![],
142+
metadata: Default::default(),
143+
};
144+
145+
// Store a success
146+
memory
147+
.store_success(&goal, &proof, ProverKind::Coq, 250)
148+
.await
149+
.unwrap();
150+
151+
// Retrieve successes
152+
let successes = memory.get_successes().await.unwrap();
153+
assert_eq!(successes.len(), 1);
154+
assert_eq!(successes[0].goal_id, "mem_test");
155+
assert_eq!(successes[0].prover, ProverKind::Coq);
156+
assert_eq!(successes[0].time_ms, 250);
157+
assert_eq!(successes[0].aspects, vec!["logic".to_string()]);
158+
159+
// Check stats
160+
let stats = memory.stats().await.unwrap();
161+
assert_eq!(stats.total_proofs, 1);
162+
assert_eq!(stats.total_failures, 0);
163+
assert!((stats.success_rate - 1.0).abs() < f64::EPSILON);
164+
assert!((stats.avg_proof_time_ms - 250.0).abs() < f64::EPSILON);
122165
}
123166

124167
#[tokio::test]
125-
#[ignore = "Requires API updates to ProverRouter"]
126168
async fn test_prover_router_learning() {
127-
todo!("API updates needed for ProverRouter")
169+
let router = ProverRouter::new();
170+
171+
let goal = AgenticGoal {
172+
goal: Goal {
173+
id: "router_test".to_string(),
174+
target: Term::Var("X".to_string()),
175+
hypotheses: vec![],
176+
},
177+
priority: Priority::Medium,
178+
attempts: 0,
179+
max_attempts: 3,
180+
preferred_prover: None,
181+
aspects: vec!["arithmetic".to_string()],
182+
parent: None,
183+
};
184+
185+
// Record several Z3 successes for arithmetic
186+
for _ in 0..5 {
187+
router.record_success(&goal, ProverKind::Z3).await;
188+
}
189+
190+
// Record Coq failures for arithmetic
191+
for _ in 0..5 {
192+
router.record_failure(&goal, ProverKind::Coq).await;
193+
}
194+
195+
// Verify global stats reflect the history
196+
let stats = router.get_all_stats().await;
197+
let z3_stats = stats.get(&ProverKind::Z3).unwrap();
198+
assert_eq!(z3_stats.successes, 5);
199+
assert_eq!(z3_stats.failures, 0);
200+
assert!((z3_stats.success_rate() - 1.0).abs() < f64::EPSILON);
201+
202+
let coq_stats = stats.get(&ProverKind::Coq).unwrap();
203+
assert_eq!(coq_stats.successes, 0);
204+
assert_eq!(coq_stats.failures, 5);
205+
assert!((coq_stats.success_rate() - 0.0).abs() < f64::EPSILON);
128206
}
129207

130208
#[tokio::test]
131-
#[ignore = "Requires API updates to RulePlanner"]
132209
async fn test_goal_decomposition() {
133-
todo!("API updates needed for RulePlanner")
210+
let planner = RulePlanner::new();
211+
212+
// Create an implication goal: A → B
213+
let impl_goal = AgenticGoal {
214+
goal: Goal {
215+
id: "impl_test".to_string(),
216+
target: Term::Pi {
217+
param: "x".to_string(),
218+
param_type: Box::new(Term::Var("A".to_string())),
219+
body: Box::new(Term::Var("B".to_string())),
220+
},
221+
hypotheses: vec![],
222+
},
223+
priority: Priority::High,
224+
attempts: 0,
225+
max_attempts: 3,
226+
preferred_prover: None,
227+
aspects: vec!["logic".to_string()],
228+
parent: None,
229+
};
230+
231+
let sub_goals = planner.decompose(&impl_goal).await.unwrap();
232+
assert!(!sub_goals.is_empty(), "Implication should decompose into sub-goals");
233+
234+
// The conclusion sub-goal should target B
235+
let conclusion = &sub_goals[0];
236+
assert_eq!(conclusion.goal.id, "impl_test_conclusion");
237+
assert_eq!(conclusion.parent, Some("impl_test".to_string()));
238+
239+
// A non-decomposable goal should return empty
240+
let atomic_goal = AgenticGoal {
241+
goal: Goal {
242+
id: "atomic_test".to_string(),
243+
target: Term::Var("X".to_string()),
244+
hypotheses: vec![],
245+
},
246+
priority: Priority::Low,
247+
attempts: 0,
248+
max_attempts: 3,
249+
preferred_prover: None,
250+
aspects: vec![],
251+
parent: None,
252+
};
253+
254+
// Non-decomposable goals return themselves as-is (vec![goal.clone()])
255+
let no_sub = planner.decompose(&atomic_goal).await.unwrap();
256+
assert_eq!(no_sub.len(), 1, "Atomic goal returns itself unchanged");
257+
assert_eq!(no_sub[0].goal.id, "atomic_test");
134258
}
135259

136260
#[tokio::test]
137-
#[ignore = "Requires API updates to AgentCore"]
138261
async fn test_agent_queue_priority() {
139-
todo!("API updates needed for AgentCore")
262+
let prover: Box<dyn ProverBackend> = Box::new(MockProver::new(ProverKind::Z3, true));
263+
let tmp = TempDir::new().unwrap();
264+
let memory = SqliteMemory::new(tmp.path().join("queue_test.db"));
265+
memory.init().await.unwrap();
266+
let planner = RulePlanner::new();
267+
let router = ProverRouter::new();
268+
269+
let config = AgentConfig::default();
270+
let agent = AgentCore::new(
271+
Box::new(memory),
272+
Box::new(planner),
273+
router,
274+
vec![prover],
275+
config,
276+
);
277+
278+
// Add goals with different priorities
279+
let make_goal = |id: &str, priority: Priority| AgenticGoal {
280+
goal: Goal {
281+
id: id.to_string(),
282+
target: Term::Var("X".to_string()),
283+
hypotheses: vec![],
284+
},
285+
priority,
286+
attempts: 0,
287+
max_attempts: 3,
288+
preferred_prover: None,
289+
aspects: vec![],
290+
parent: None,
291+
};
292+
293+
agent.add_goal(make_goal("low", Priority::Low)).await.unwrap();
294+
agent.add_goal(make_goal("high", Priority::High)).await.unwrap();
295+
agent.add_goal(make_goal("critical", Priority::Critical)).await.unwrap();
296+
agent.add_goal(make_goal("medium", Priority::Medium)).await.unwrap();
297+
298+
// Critical should come first (highest priority at front)
299+
let first = agent.next_goal().await.expect("Should have a goal");
300+
assert_eq!(first.goal.id, "critical");
301+
302+
let second = agent.next_goal().await.expect("Should have a goal");
303+
assert_eq!(second.goal.id, "high");
304+
305+
let third = agent.next_goal().await.expect("Should have a goal");
306+
assert_eq!(third.goal.id, "medium");
307+
308+
let fourth = agent.next_goal().await.expect("Should have a goal");
309+
assert_eq!(fourth.goal.id, "low");
310+
311+
assert!(agent.next_goal().await.is_none(), "Queue should be empty");
140312
}
141313

142314
#[test]
@@ -200,15 +372,121 @@ fn test_explanation_generation() {
200372
}
201373

202374
#[tokio::test]
203-
#[ignore = "Requires API updates to SqliteMemory"]
204375
async fn test_memory_failure_tracking() {
205-
todo!("API updates needed for SqliteMemory")
376+
let tmp = TempDir::new().unwrap();
377+
let memory = SqliteMemory::new(tmp.path().join("failure_test.db"));
378+
memory.init().await.unwrap();
379+
380+
let goal1 = AgenticGoal {
381+
goal: Goal {
382+
id: "fail1".to_string(),
383+
target: Term::Var("P".to_string()),
384+
hypotheses: vec![],
385+
},
386+
priority: Priority::High,
387+
attempts: 1,
388+
max_attempts: 3,
389+
preferred_prover: Some(ProverKind::Coq),
390+
aspects: vec!["inductive".to_string()],
391+
parent: None,
392+
};
393+
394+
let goal2 = AgenticGoal {
395+
goal: Goal {
396+
id: "fail2".to_string(),
397+
target: Term::Var("Q".to_string()),
398+
hypotheses: vec![],
399+
},
400+
priority: Priority::Medium,
401+
attempts: 1,
402+
max_attempts: 3,
403+
preferred_prover: None,
404+
aspects: vec!["arithmetic".to_string()],
405+
parent: None,
406+
};
407+
408+
// Store failures
409+
memory.store_failure(&goal1, "timeout".to_string()).await.unwrap();
410+
memory.store_failure(&goal2, "unsupported tactic".to_string()).await.unwrap();
411+
412+
// Verify failures stored
413+
let failures = memory.get_failures().await.unwrap();
414+
assert_eq!(failures.len(), 2);
415+
assert_eq!(failures[0].goal_id, "fail1");
416+
assert_eq!(failures[0].reason, "timeout");
417+
assert_eq!(failures[0].prover, Some(ProverKind::Coq));
418+
assert_eq!(failures[1].goal_id, "fail2");
419+
assert_eq!(failures[1].reason, "unsupported tactic");
420+
421+
// Stats should show 0% success rate
422+
let stats = memory.stats().await.unwrap();
423+
assert_eq!(stats.total_proofs, 0);
424+
assert_eq!(stats.total_failures, 2);
425+
assert!((stats.success_rate - 0.0).abs() < f64::EPSILON);
206426
}
207427

208428
#[tokio::test]
209-
#[ignore = "Requires API updates to ProverRouter"]
210429
async fn test_router_aspect_scoring() {
211-
todo!("API updates needed for ProverRouter")
430+
let router = ProverRouter::new();
431+
432+
// Build history: Z3 is great at arithmetic, Coq is great at inductive
433+
let arith_goal = AgenticGoal {
434+
goal: Goal {
435+
id: "arith".to_string(),
436+
target: Term::Var("N".to_string()),
437+
hypotheses: vec![],
438+
},
439+
priority: Priority::Medium,
440+
attempts: 0,
441+
max_attempts: 3,
442+
preferred_prover: None,
443+
aspects: vec!["arithmetic".to_string()],
444+
parent: None,
445+
};
446+
447+
let inductive_goal = AgenticGoal {
448+
goal: Goal {
449+
id: "induct".to_string(),
450+
target: Term::Var("P".to_string()),
451+
hypotheses: vec![],
452+
},
453+
priority: Priority::Medium,
454+
attempts: 0,
455+
max_attempts: 3,
456+
preferred_prover: None,
457+
aspects: vec!["inductive".to_string()],
458+
parent: None,
459+
};
460+
461+
// Z3 succeeds on arithmetic
462+
for _ in 0..10 {
463+
router.record_success(&arith_goal, ProverKind::Z3).await;
464+
}
465+
466+
// Coq succeeds on inductive
467+
for _ in 0..10 {
468+
router.record_success(&inductive_goal, ProverKind::Coq).await;
469+
}
470+
471+
// Z3 fails on inductive
472+
for _ in 0..10 {
473+
router.record_failure(&inductive_goal, ProverKind::Z3).await;
474+
}
475+
476+
// Verify aspect-specific routing works
477+
let all_stats = router.get_all_stats().await;
478+
479+
// Z3 global: 10 successes (arithmetic) + 10 failures (inductive) = 20 attempts
480+
let z3 = all_stats.get(&ProverKind::Z3).unwrap();
481+
assert_eq!(z3.successes, 10);
482+
assert_eq!(z3.failures, 10);
483+
assert!((z3.success_rate() - 0.5).abs() < f64::EPSILON);
484+
485+
// Coq global: 10 successes (inductive), 0 failures = 100% success
486+
let coq = all_stats.get(&ProverKind::Coq).unwrap();
487+
assert_eq!(coq.successes, 10);
488+
assert_eq!(coq.failures, 0);
489+
assert!((coq.success_rate() - 1.0).abs() < f64::EPSILON);
212490
}
213491

214492
#[test]

0 commit comments

Comments
 (0)