Skip to main content

the_code_graph_eval/
lib.rs

1pub mod adapters;
2pub mod dataset;
3pub mod metrics;
4pub mod report;
5pub mod runner;
6
7use domain::error::Result;
8use report::SuiteResult;
9
10/// Which evaluation suite to run.
11#[derive(Debug, Clone)]
12pub enum Suite {
13    Search,
14    Impact,
15    All,
16}
17
18/// Configuration for an eval run.
19#[derive(Debug, Clone)]
20pub struct SuiteConfig {
21    pub suite: Suite,
22    pub no_cache: bool,
23    pub suites_dir: std::path::PathBuf,
24    pub search_limit: usize,
25}
26
27/// Run the evaluation suite. Entry point called by CLI.
28pub fn run_suite(config: &SuiteConfig) -> Result<SuiteResult> {
29    let search_result = match config.suite {
30        Suite::Search | Suite::All => Some(runner::run_search_suite(config)?),
31        _ => None,
32    };
33    let impact_result = match config.suite {
34        Suite::Impact | Suite::All => Some(runner::run_impact_suite(config)?),
35        _ => None,
36    };
37    Ok(SuiteResult {
38        search: search_result,
39        impact: impact_result,
40    })
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use std::path::PathBuf;
47
48    /// Resolve the project-root `eval/suites/` directory from CARGO_MANIFEST_DIR.
49    fn suites_dir() -> PathBuf {
50        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
51        manifest_dir
52            .parent()
53            .unwrap()
54            .parent()
55            .unwrap()
56            .join("eval")
57            .join("suites")
58    }
59
60    #[test]
61    fn eval_crate_is_workspace_member() {
62        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
63        let root_cargo = manifest_dir
64            .parent()
65            .unwrap()
66            .parent()
67            .unwrap()
68            .join("Cargo.toml");
69        let content =
70            std::fs::read_to_string(&root_cargo).expect("root Cargo.toml should be readable");
71        let doc: toml::Value =
72            toml::from_str(&content).expect("root Cargo.toml should be valid TOML");
73        let members = doc["workspace"]["members"]
74            .as_array()
75            .expect("workspace.members should be an array");
76        assert_eq!(members.len(), 9, "workspace should have 9 members");
77        let has_eval = members.iter().any(|m| m.as_str() == Some("crates/eval"));
78        assert!(has_eval, "workspace members should include crates/eval");
79    }
80
81    #[test]
82    fn suite_config_search() {
83        let config = SuiteConfig {
84            suite: Suite::Search,
85            no_cache: false,
86            suites_dir: PathBuf::from("/tmp/suites"),
87            search_limit: 10,
88        };
89        assert!(matches!(config.suite, Suite::Search));
90        assert!(!config.no_cache);
91        assert_eq!(config.suites_dir, PathBuf::from("/tmp/suites"));
92        assert_eq!(config.search_limit, 10);
93    }
94
95    #[test]
96    fn suite_config_impact() {
97        let config = SuiteConfig {
98            suite: Suite::Impact,
99            no_cache: true,
100            suites_dir: PathBuf::from("/tmp/suites"),
101            search_limit: 5,
102        };
103        assert!(matches!(config.suite, Suite::Impact));
104        assert!(config.no_cache);
105        assert_eq!(config.suites_dir, PathBuf::from("/tmp/suites"));
106        assert_eq!(config.search_limit, 5);
107    }
108
109    #[test]
110    fn manifest_files_are_valid_json() {
111        let base = suites_dir();
112        let search_manifest = base.join("search").join("manifest.json");
113        let impact_manifest = base.join("impact").join("manifest.json");
114
115        let sm = dataset::parse_manifest(&search_manifest).expect("search manifest should parse");
116        assert!(!sm.repos.is_empty(), "search manifest should have repos");
117
118        let im = dataset::parse_manifest(&impact_manifest).expect("impact manifest should parse");
119        assert!(!im.repos.is_empty(), "impact manifest should have repos");
120    }
121
122    #[test]
123    fn search_query_files_are_valid_json() {
124        let dir = suites_dir().join("search").join("queries");
125        let entries: Vec<_> = std::fs::read_dir(&dir)
126            .expect("search queries dir should be readable")
127            .filter_map(|e| e.ok())
128            .filter(|e| e.path().extension().map_or(false, |ext| ext == "json"))
129            .collect();
130        assert!(
131            !entries.is_empty(),
132            "there should be at least one search query file"
133        );
134        for entry in entries {
135            let path = entry.path();
136            dataset::parse_search_queries(&path)
137                .unwrap_or_else(|e| panic!("{} should parse: {e}", path.display()));
138        }
139    }
140
141    #[test]
142    fn impact_query_files_are_valid_json() {
143        let dir = suites_dir().join("impact").join("queries");
144        let entries: Vec<_> = std::fs::read_dir(&dir)
145            .expect("impact queries dir should be readable")
146            .filter_map(|e| e.ok())
147            .filter(|e| e.path().extension().map_or(false, |ext| ext == "json"))
148            .collect();
149        assert!(
150            !entries.is_empty(),
151            "there should be at least one impact query file"
152        );
153        for entry in entries {
154            let path = entry.path();
155            dataset::parse_impact_queries(&path)
156                .unwrap_or_else(|e| panic!("{} should parse: {e}", path.display()));
157        }
158    }
159
160    #[test]
161    fn search_query_count_meets_minimum() {
162        let dir = suites_dir().join("search").join("queries");
163        let total: usize = std::fs::read_dir(&dir)
164            .expect("search queries dir should be readable")
165            .filter_map(|e| e.ok())
166            .filter(|e| e.path().extension().map_or(false, |ext| ext == "json"))
167            .map(|e| {
168                dataset::parse_search_queries(&e.path())
169                    .unwrap_or_else(|err| panic!("parse failed: {err}"))
170                    .len()
171            })
172            .sum();
173        assert!(total >= 50, "expected >= 50 search queries, found {total}");
174    }
175
176    #[test]
177    fn impact_scenario_count_meets_minimum() {
178        let dir = suites_dir().join("impact").join("queries");
179        let total: usize = std::fs::read_dir(&dir)
180            .expect("impact queries dir should be readable")
181            .filter_map(|e| e.ok())
182            .filter(|e| e.path().extension().map_or(false, |ext| ext == "json"))
183            .map(|e| {
184                dataset::parse_impact_queries(&e.path())
185                    .unwrap_or_else(|err| panic!("parse failed: {err}"))
186                    .len()
187            })
188            .sum();
189        assert!(
190            total >= 20,
191            "expected >= 20 impact scenarios, found {total}"
192        );
193    }
194}