1use clap::Args;
4use std::path::PathBuf;
5
6use skm_core::SkillRegistry;
7use skm_learn::{TestSuite, TriggerTestHarness};
8use skm_select::{CascadeSelector, TriggerStrategy};
9
10#[derive(Args)]
11pub struct TestArgs {
12 suite: PathBuf,
14
15 #[arg(short, long, default_value = ".")]
17 skills: Vec<PathBuf>,
18
19 #[arg(short, long)]
21 verbose: bool,
22
23 #[arg(long, default_value = "text")]
25 format: String,
26}
27
28pub async fn test(args: TestArgs) -> anyhow::Result<()> {
29 let suite = TestSuite::from_file(&args.suite)?;
31 println!("Loaded test suite: {} ({} cases)", suite.name, suite.cases.len());
32
33 let registry = SkillRegistry::new(&args.skills).await?;
35 let trigger = TriggerStrategy::from_registry(®istry).await?;
36 let selector = CascadeSelector::builder().with_triggers(trigger).build();
37
38 let harness = TriggerTestHarness::new();
40 let report = harness.run(&suite, &selector, ®istry).await?;
41
42 match args.format.as_str() {
43 "json" => {
44 let output = serde_json::json!({
45 "suite": report.suite_name,
46 "total": report.total,
47 "passed": report.passed,
48 "accuracy": report.accuracy(),
49 "avg_latency_ms": report.avg_latency_ms,
50 "results": report.results.iter().map(|r| {
51 serde_json::json!({
52 "name": r.name,
53 "passed": r.passed,
54 "selected": r.selected.as_ref().map(|s| s.as_str()),
55 "score": r.score,
56 "latency_ms": r.latency_ms
57 })
58 }).collect::<Vec<_>>()
59 });
60 println!("{}", serde_json::to_string_pretty(&output)?);
61 }
62 _ => {
63 if args.verbose {
64 println!("\nResults:");
65 for r in &report.results {
66 let status = if r.passed { "✓" } else { "✗" };
67 let selected = r
68 .selected
69 .as_ref()
70 .map(|s| s.as_str())
71 .unwrap_or("(none)");
72 println!(
73 " {} {} -> {} ({}ms)",
74 status, r.name, selected, r.latency_ms
75 );
76 }
77 println!();
78 }
79
80 println!("Test Results: {}/{} passed ({:.1}%)",
82 report.passed,
83 report.total,
84 report.accuracy() * 100.0
85 );
86 println!("Average latency: {:.1}ms", report.avg_latency_ms);
87
88 if !report.per_skill.is_empty() {
90 println!("\nPer-skill metrics:");
91 for (skill, metrics) in &report.per_skill {
92 println!(
93 " {}: P={:.1}% R={:.1}% F1={:.1}%",
94 skill,
95 metrics.precision() * 100.0,
96 metrics.recall() * 100.0,
97 metrics.f1() * 100.0
98 );
99 }
100 }
101 }
102 }
103
104 if report.passed < report.total {
106 std::process::exit(1);
107 }
108
109 Ok(())
110}