Skip to main content

quanttide_devops/stage/
test.rs

1/// 测试结果汇总。
2#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
3pub struct TestSummary {
4    pub total: u32,
5    pub passed: u32,
6    pub failed: u32,
7    pub skipped: u32,
8}
9
10/// 覆盖率数据。
11#[derive(Debug, Default, Clone, PartialEq)]
12pub struct Coverage {
13    pub percentage: f64,
14    pub threshold: f64,
15}
16
17impl Coverage {
18    pub fn met(&self) -> bool {
19        self.percentage >= self.threshold
20    }
21}
22
23/// 质量审计结果。
24#[derive(Debug, Default, Clone, PartialEq)]
25pub struct AuditReport {
26    pub total_tests: usize,
27    pub total_pub_fns: usize,
28    pub pure_pub_fns: usize,
29    pub tested_pub_fns: usize,
30    pub error_variants: usize,
31    pub tested_variants: usize,
32    pub uncovered_fns: Vec<(String, String)>,
33    pub uncovered_variants: Vec<String>,
34    pub coverage_pct: f64,
35    pub coverage_threshold: f64,
36    pub gates_met: bool,
37}
38
39/// I/O 函数模式:这些名称的函数不要求单元测试(集成测试覆盖即可)。
40///
41/// 注意:纯函数(parse_/determine_/build_/fmt_/map_/normalize_/apply_/extract_/is_)
42/// 不应在此列表。
43const IO_FN_PATTERNS: &[&str] = &[
44    "status", "status_to", "run", "run_direct", "run_scoped",
45    "sync", "sync_all", "sync_to_parent", "sync_all_to_parent",
46    "publish", "scan", "scan_offline",
47    "push_submodule", "push_parent", "fetch_submodule", "rebase_submodule",
48    "check_command", "check_syntax", "check_ci", "check_deps",
49    "clear_cache", "save_test_summary",
50    "ensure_", "delete_", "create_",
51    "git_output", "llm_decide", "llm_changelog", "edit_llm",
52    "detect_version", "detect_single_scope", "detect_project_type",
53    "resolve_roadmap_path",
54    "print_status", "print_status_to", "print_scope_audit",
55    "collect_git_log", "collect_tags_with_scope", "collect_test_summary_from_run",
56    "load_contract_scopes", "load_scopes_map",
57    "apply_rule_fixes",
58    "collect_rs_files", "collect_test_fns", "collect_pub_fns", "collect_error_variants",
59];
60
61/// 判断函数名是否为 I/O 型(无需单元测试,集成测试覆盖即可)。
62pub fn is_io_fn(name: &str) -> bool {
63    IO_FN_PATTERNS.iter().any(|p| {
64        if p.ends_with('_') {
65            name.starts_with(p)
66        } else {
67            name == *p
68        }
69    })
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    // ── TestSummary ──────────────────────────────────────────────
77
78    #[test]
79    fn test_test_summary_default() {
80        let s = TestSummary::default();
81        assert_eq!(s.total, 0);
82    }
83
84    #[test]
85    fn test_test_summary_serde_roundtrip() {
86        let s = TestSummary { total: 10, passed: 8, failed: 1, skipped: 1 };
87        let json = serde_json::to_string(&s).unwrap();
88        let back: TestSummary = serde_json::from_str(&json).unwrap();
89        assert_eq!(back, s);
90    }
91
92    #[test]
93    fn test_test_summary_serde_all_zero() {
94        let s = TestSummary { total: 0, passed: 0, failed: 0, skipped: 0 };
95        let json = serde_json::to_string(&s).unwrap();
96        let back: TestSummary = serde_json::from_str(&json).unwrap();
97        assert_eq!(back, s);
98    }
99
100    // ── Coverage ─────────────────────────────────────────────────
101
102    #[test]
103    fn test_coverage_met() {
104        let c = Coverage { percentage: 90.0, threshold: 80.0 };
105        assert!(c.met());
106    }
107
108    #[test]
109    fn test_coverage_not_met() {
110        let c = Coverage { percentage: 70.0, threshold: 80.0 };
111        assert!(!c.met());
112    }
113
114    #[test]
115    fn test_coverage_met_exact() {
116        let c = Coverage { percentage: 80.0, threshold: 80.0 };
117        assert!(c.met());
118    }
119
120    #[test]
121    fn test_coverage_met_zero_threshold() {
122        let c = Coverage { percentage: 0.0, threshold: 0.0 };
123        assert!(c.met());
124    }
125
126    #[test]
127    fn test_coverage_default() {
128        let c = Coverage::default();
129        assert_eq!(c.percentage, 0.0);
130        assert_eq!(c.threshold, 0.0);
131    }
132
133    // ── AuditReport ──────────────────────────────────────────────
134
135    #[test]
136    fn test_audit_report_default() {
137        let r = AuditReport::default();
138        assert_eq!(r.total_tests, 0);
139        assert!(!r.gates_met);
140    }
141
142    #[test]
143    fn test_audit_report_gates_met() {
144        let r = AuditReport {
145            total_tests: 10,
146            total_pub_fns: 5,
147            pure_pub_fns: 5,
148            tested_pub_fns: 3,
149            error_variants: 2,
150            tested_variants: 2,
151            uncovered_fns: vec![],
152            uncovered_variants: vec![],
153            coverage_pct: 100.0,
154            coverage_threshold: 80.0,
155            gates_met: true,
156        };
157        assert!(r.gates_met);
158    }
159
160    // ── is_io_fn ──────────────────────────────────────────────────
161
162    #[test]
163    fn test_is_io_fn_status() {
164        assert!(is_io_fn("status"));
165        assert!(is_io_fn("publish"));
166        assert!(is_io_fn("clear_cache"));
167    }
168
169    #[test]
170    fn test_is_io_fn_prefix() {
171        assert!(is_io_fn("ensure_changelog"));
172        assert!(is_io_fn("delete_tag"));
173        assert!(is_io_fn("create_release"));
174    }
175
176    #[test]
177    fn test_is_io_fn_pure() {
178        assert!(!is_io_fn("parse_version"));
179        assert!(!is_io_fn("normalize_version"));
180        assert!(!is_io_fn("build_version"));
181    }
182}