the_code_graph_cli/commands/
eval.rs1use 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}