smb_server_test_dashboard/
csv.rs1use std::path::Path;
8
9pub struct TestRow {
11 pub suite: String,
12 pub name: String,
13 pub category: String,
14 pub status: String,
15 pub duration_ms: u64,
16 pub timestamp: String,
17 pub commit: String,
18 pub note: String,
19}
20
21pub struct Summary {
23 pub total: usize,
24 pub passed: usize,
25 pub failed: usize,
26 pub skipped: usize,
27 pub unknown: usize,
28}
29
30pub fn load(path: &Path) -> Vec<TestRow> {
33 let Ok(text) = std::fs::read_to_string(path) else {
34 return Vec::new();
35 };
36 text.lines()
37 .skip(1) .filter(|line| !line.trim().is_empty())
39 .filter_map(parse_row)
40 .collect()
41}
42
43#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn parse_row(line: &str) -> Option<TestRow> {
45 let fields = split_csv_line(line);
46 if fields.len() < 4 {
47 return None;
48 }
49 Some(TestRow {
50 suite: fields[0].clone(),
51 name: fields[1].clone(),
52 category: fields[2].clone(),
53 status: normalize_status(&fields[3]),
54 duration_ms: fields.get(4).and_then(|f| f.parse().ok()).unwrap_or(0),
55 timestamp: fields.get(5).cloned().unwrap_or_default(),
56 commit: fields.get(6).cloned().unwrap_or_default(),
57 note: fields.get(7).cloned().unwrap_or_default(),
58 })
59}
60
61fn normalize_status(raw: &str) -> String {
62 match raw.trim().to_ascii_lowercase().as_str() {
63 "pass" | "passed" | "ok" => "pass",
64 "fail" | "failed" | "error" => "fail",
65 "skip" | "skipped" | "notexecuted" | "ignored" => "skip",
66 _ => "unknown",
67 }
68 .to_string()
69}
70
71fn split_csv_line(line: &str) -> Vec<String> {
73 let mut fields = Vec::new();
74 let mut field = String::new();
75 let mut in_quotes = false;
76 let mut chars = line.chars().peekable();
77 while let Some(c) = chars.next() {
78 match c {
79 '"' if in_quotes && chars.peek() == Some(&'"') => {
80 field.push('"');
81 chars.next();
82 }
83 '"' => in_quotes = !in_quotes,
84 ',' if !in_quotes => fields.push(std::mem::take(&mut field)),
85 _ => field.push(c),
86 }
87 }
88 fields.push(field);
89 fields
90}
91
92pub fn summarize(rows: &[TestRow]) -> Summary {
94 let mut summary =
95 Summary { total: rows.len(), passed: 0, failed: 0, skipped: 0, unknown: 0 };
96 for row in rows {
97 match row.status.as_str() {
98 "pass" => summary.passed += 1,
99 "fail" => summary.failed += 1,
100 "skip" => summary.skipped += 1,
101 _ => summary.unknown += 1,
102 }
103 }
104 summary
105}