Skip to main content

the_code_graph_cli/commands/
eval.rs

1use domain::error::{CodeGraphError, Result};
2use eval::report::SuiteResult;
3use eval::{Suite, SuiteConfig};
4use std::io::Write;
5
6use super::EvalArgs;
7use crate::output::{Displayable, OutputFormat};
8
9fn suite_from_str(s: &str) -> Result<Suite> {
10    match s {
11        "search" => Ok(Suite::Search),
12        "impact" => Ok(Suite::Impact),
13        "all" => Ok(Suite::All),
14        _ => Err(CodeGraphError::Other(format!(
15            "Unknown suite '{}'. Valid: search, impact, all",
16            s
17        ))),
18    }
19}
20
21impl Displayable for SuiteResult {
22    fn fmt_compact(&self, w: &mut dyn Write) -> std::io::Result<()> {
23        self.fmt_compact(w)
24    }
25    fn fmt_table(&self, w: &mut dyn Write) -> std::io::Result<()> {
26        self.fmt_table(w)
27    }
28    fn fmt_json(&self, w: &mut dyn Write) -> std::io::Result<()> {
29        self.fmt_json(w)
30    }
31}
32
33pub fn run_eval(args: &EvalArgs, output_format: OutputFormat) -> Result<()> {
34    let suite = suite_from_str(&args.suite)?;
35    let suites_dir = find_suites_dir()?;
36
37    let config = SuiteConfig {
38        suite,
39        no_cache: args.no_cache,
40        suites_dir,
41        search_limit: 20,
42    };
43
44    let result = eval::run_suite(&config)?;
45    crate::output::print(&result, output_format);
46
47    if !result.all_passed() {
48        return Err(CodeGraphError::Other(
49            "Quality targets not met — see results above".into(),
50        ));
51    }
52    Ok(())
53}
54
55fn find_suites_dir() -> Result<std::path::PathBuf> {
56    let cwd_suites = std::path::PathBuf::from("eval/suites");
57    if cwd_suites.is_dir() {
58        return Ok(cwd_suites);
59    }
60    if let Ok(exe) = std::env::current_exe() {
61        let exe_suites = exe.parent().unwrap_or(exe.as_ref()).join("eval/suites");
62        if exe_suites.is_dir() {
63            return Ok(exe_suites);
64        }
65    }
66    Err(CodeGraphError::Other(
67        "eval/suites/ directory not found — run from project root".into(),
68    ))
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use eval::report::{ImpactSuiteResult, SearchSuiteResult, SuiteResult};
75
76    #[test]
77    fn suite_from_string_search() {
78        let suite = suite_from_str("search");
79        assert!(suite.is_ok());
80        assert!(matches!(suite.unwrap(), Suite::Search));
81    }
82
83    #[test]
84    fn suite_from_string_impact() {
85        let suite = suite_from_str("impact");
86        assert!(suite.is_ok());
87        assert!(matches!(suite.unwrap(), Suite::Impact));
88    }
89
90    #[test]
91    fn suite_from_string_all() {
92        let suite = suite_from_str("all");
93        assert!(suite.is_ok());
94        assert!(matches!(suite.unwrap(), Suite::All));
95    }
96
97    #[test]
98    fn suite_from_string_invalid() {
99        let suite = suite_from_str("unknown");
100        assert!(suite.is_err());
101        let msg = format!("{}", suite.unwrap_err());
102        assert!(msg.contains("Unknown suite 'unknown'"));
103    }
104
105    fn sample_suite_result() -> SuiteResult {
106        SuiteResult {
107            search: Some(SearchSuiteResult {
108                repos: 3,
109                queries: 30,
110                mrr: 0.65,
111                precision_at_5: 0.72,
112                precision_at_10: 0.60,
113                mrr_target: 0.30,
114                mrr_passed: true,
115                per_category: vec![],
116            }),
117            impact: Some(ImpactSuiteResult {
118                repos: 3,
119                scenarios: 15,
120                precision: 0.55,
121                recall: 0.45,
122                f1: 0.50,
123                precision_target: 0.40,
124                precision_passed: true,
125            }),
126        }
127    }
128
129    #[test]
130    fn displayable_compact_output() {
131        let result = sample_suite_result();
132        let mut buf = Vec::new();
133        Displayable::fmt_compact(&result, &mut buf).unwrap();
134        let output = String::from_utf8(buf).unwrap();
135        assert!(output.contains("Search Suite"));
136        assert!(output.contains("MRR"));
137        assert!(output.contains("Impact Suite"));
138        assert!(output.contains("Precision"));
139    }
140
141    #[test]
142    fn displayable_json_output() {
143        let result = sample_suite_result();
144        let mut buf = Vec::new();
145        Displayable::fmt_json(&result, &mut buf).unwrap();
146        let output = String::from_utf8(buf).unwrap();
147        let parsed: serde_json::Value = serde_json::from_str(output.trim()).unwrap();
148        assert!(parsed.get("search").is_some());
149        assert!(parsed.get("impact").is_some());
150    }
151}