prx 0.5.9

Praxis — agent-native Unix tools. Single binary replacing grep, cat, find, sed, diff for AI coding agents.
use super::{Diagnostic, ParsedResult, define_regex};

define_regex!(
    TEST_SUMMARY_RE,
    r"test result: (ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored"
);
define_regex!(TOTAL_RE, r"^TOTAL\s+\d+\s+\d+\s+([\d.]+%)");
define_regex!(FILE_COV_RE, r"^\s*(\S+\.rs)\s+\d+\s+\d+\s+([\d.]+%)");

pub fn parse(output: &str) -> ParsedResult {
    let mut passed = 0;
    let mut failed = 0;
    let mut skipped = 0;
    let mut total_coverage = String::new();
    let mut low_coverage = Vec::new();

    for line in output.lines() {
        if let Some(caps) = TEST_SUMMARY_RE.captures(line) {
            passed = caps[2].parse().unwrap_or(0);
            failed = caps[3].parse().unwrap_or(0);
            skipped = caps[4].parse().unwrap_or(0);
        }

        if let Some(caps) = TOTAL_RE.captures(line) {
            total_coverage = caps[1].to_string();
        }

        if let Some(caps) = FILE_COV_RE.captures(line) {
            let file = &caps[1];
            let pct_str = &caps[2];
            let pct: f32 = pct_str.trim_end_matches('%').parse().unwrap_or(100.0);
            if pct < 80.0 {
                low_coverage.push(Diagnostic {
                    name: file.to_string(),
                    location: None,
                    message: format!("{pct_str} line coverage"),
                });
            }
        }
    }

    let summary = if total_coverage.is_empty() {
        format!("{passed} passed, {failed} failed")
    } else {
        format!("{total_coverage} coverage, {passed} passed, {failed} failed")
    };

    ParsedResult::new(summary, passed, failed, skipped, low_coverage)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_coverage_summary() {
        let output = "\
running 10 tests
test foo ... ok
test bar ... ok

test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Filename                   Regions    Missed Regions     Cover   Functions  Missed Functions  Executed       Lines      Missed Lines     Cover    Branches   Missed Branches     Cover
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
src/auth.rs                     50                 5    90.00%          10                 0   100.00%         200                20    90.00%           0                 0         -
src/handler.rs                  30                15    50.00%           5                 2    60.00%         100                50    50.00%           0                 0         -
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL                           80                20    75.00%          15                 2    86.67%         300                70    76.67%           0                 0         -
";
        let result = parse(output);
        assert_eq!(result.passed, 10);
        assert_eq!(result.failed, 0);
        assert!(result.summary.contains("75.00%"));
        assert!(result.summary.contains("coverage"));
        assert_eq!(result.failures.len(), 1);
        assert_eq!(result.failures[0].name, "src/handler.rs");
        assert!(result.failures[0].message.contains("50.00%"));
    }

    #[test]
    fn parse_all_high_coverage() {
        let output = "\
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

TOTAL                          100                 5    95.00%          20                 0   100.00%         500                25    95.00%           0                 0         -
";
        let result = parse(output);
        assert_eq!(result.passed, 5);
        assert!(result.summary.contains("95.00%"));
        assert!(result.failures.is_empty());
    }

    #[test]
    fn parse_no_coverage_table() {
        let output = "test result: ok. 3 passed; 0 failed; 0 ignored\n";
        let result = parse(output);
        assert_eq!(result.passed, 3);
        assert!(!result.summary.contains("coverage"));
    }
}