1use crate::task::EvalTask;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct EvalSuite {
6 pub(crate) id: String,
7 pub name: String,
8 pub tasks: Vec<EvalTask>,
9 pub attempts: u32,
10}
11
12impl EvalSuite {
13 pub fn validate(&self) -> anyhow::Result<()> {
18 anyhow::ensure!(self.attempts >= 1, "evaluation suite attempts must be at least one");
19 Ok(())
20 }
21}
22
23#[cfg(test)]
24mod tests {
25 use super::*;
26 use crate::task::EvalCategory;
27
28 #[test]
29 fn suite_round_trips_through_json() {
30 let suite = EvalSuite {
31 id: "s1".into(),
32 name: "demo".into(),
33 tasks: vec![crate::task::EvalTask {
34 id: "t1".into(),
35 name: "t1".into(),
36 category: EvalCategory::Capability,
37 prompt: "do it".into(),
38 verify_commands: vec!["cargo test".into()],
39 timeout_secs: Some(30),
40 }],
41 attempts: 3,
42 };
43 let json = serde_json::to_string(&suite).unwrap();
44 let back: EvalSuite = serde_json::from_str(&json).unwrap();
45 assert_eq!(back.id, "s1");
46 assert_eq!(back.attempts, 3);
47 assert_eq!(back.tasks.len(), 1);
48 assert_eq!(back.tasks[0].timeout_secs, Some(30));
49 }
50
51 #[test]
52 fn suite_rejects_zero_attempts_via_validation() {
53 let suite: EvalSuite = serde_json::from_str(r#"{"id":"s","name":"n","tasks":[],"attempts":0}"#).unwrap();
56 assert_eq!(suite.attempts, 0);
57 assert!(suite.validate().is_err());
58 let valid: EvalSuite = serde_json::from_str(r#"{"id":"s","name":"n","tasks":[],"attempts":1}"#).unwrap();
59 assert!(valid.validate().is_ok());
60 }
61
62 #[test]
63 fn preview_budget_blocked_replan_regression_suite_is_env_verified() {
64 let json = include_str!("../evals/preview-budget-blocked-replan.json");
65 let suite: EvalSuite = serde_json::from_str(json).unwrap();
66 assert_eq!(suite.id, "preview-budget-blocked-replan");
67 assert_eq!(suite.attempts, 3);
68 assert_eq!(suite.tasks.len(), 3);
69 for task in &suite.tasks {
70 assert_eq!(task.category, EvalCategory::Regression);
71 assert!(!task.prompt.is_empty());
72 assert!(!task.verify_commands.is_empty());
73 }
74 let ids: Vec<&str> = suite.tasks.iter().map(|task| task.id.as_str()).collect();
75 assert_eq!(
76 ids,
77 vec![
78 "preview-budget-no-retry",
79 "blocked-handoff-resume",
80 "replan-continuation"
81 ]
82 );
83 }
84}