rust_rule_engine/engine/
coverage.rs

1//! Module đo coverage cho rule engine
2//! Lưu lại thông tin rule đã được test, facts đã test, và sinh báo cáo coverage
3
4#![allow(deprecated)]
5
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Default)]
9pub struct RuleCoverage {
10    /// Tên rule -> số lần được kích hoạt
11    pub rule_hits: HashMap<String, usize>,
12    /// Tên rule -> facts đã test
13    pub rule_facts: HashMap<String, HashSet<String>>,
14    /// Facts đã test toàn bộ
15    pub tested_facts: HashSet<String>,
16}
17
18impl RuleCoverage {
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Ghi nhận rule được kích hoạt với facts
24    pub fn record_hit(&mut self, rule_name: &str, facts_id: &str) {
25        *self.rule_hits.entry(rule_name.to_string()).or_insert(0) += 1;
26        self.rule_facts
27            .entry(rule_name.to_string())
28            .or_default()
29            .insert(facts_id.to_string());
30        self.tested_facts.insert(facts_id.to_string());
31    }
32
33    /// Sinh báo cáo coverage, cảnh báo rule chưa được test
34    pub fn report(&self, all_rules: &[String]) -> String {
35        let mut report = String::new();
36        report.push_str("Rule Coverage Report\n====================\n");
37        let mut untested = Vec::new();
38        for rule in all_rules {
39            let hits = self.rule_hits.get(rule).copied().unwrap_or(0);
40            let facts = self.rule_facts.get(rule).map(|f| f.len()).unwrap_or(0);
41            report.push_str(&format!(
42                "Rule: {} | Hits: {} | Facts tested: {}\n",
43                rule, hits, facts
44            ));
45            if hits == 0 {
46                untested.push(rule.clone());
47            }
48        }
49        report.push_str(&format!(
50            "Total facts tested: {}\n",
51            self.tested_facts.len()
52        ));
53        if !untested.is_empty() {
54            report.push_str("\n⚠️ Cảnh báo: Các rule chưa được test:\n");
55            for rule in untested {
56                report.push_str(&format!("  - {}\n", rule));
57            }
58        }
59        report
60    }
61}
62
63/// Lấy tất cả điều kiện Single từ ConditionGroup
64fn flatten_conditions(
65    group: &crate::engine::rule::ConditionGroup,
66) -> Vec<crate::engine::rule::Condition> {
67    use crate::engine::rule::ConditionGroup;
68    let mut out = Vec::new();
69    match group {
70        ConditionGroup::Single(cond) => out.push(cond.clone()),
71        ConditionGroup::Compound { left, right, .. } => {
72            out.extend(flatten_conditions(left));
73            out.extend(flatten_conditions(right));
74        }
75        ConditionGroup::Not(inner)
76        | ConditionGroup::Exists(inner)
77        | ConditionGroup::Forall(inner) => {
78            out.extend(flatten_conditions(inner));
79        }
80        ConditionGroup::Accumulate { .. } => {
81            // Accumulate doesn't have simple single conditions to flatten
82            // Skip for now
83        }
84    }
85    out
86}
87
88/// Sinh facts mẫu cho một rule dựa trên nhiều kiểu dữ liệu và kết hợp nhiều điều kiện
89pub fn generate_test_facts_for_rule(rule: &crate::engine::rule::Rule) -> Vec<crate::Facts> {
90    use crate::types::Value;
91    let conds = flatten_conditions(&rule.conditions);
92    let mut test_facts = Vec::new();
93
94    // Sinh facts cho từng điều kiện riêng lẻ
95    for cond in &conds {
96        let facts = crate::Facts::new();
97        let field = cond.field.clone();
98        match &cond.value {
99            Value::Integer(i) => {
100                facts.set(&field, Value::Integer(*i));
101                test_facts.push(facts.clone());
102                facts.set(&field, Value::Integer(i + 1));
103                test_facts.push(facts.clone());
104            }
105            Value::Boolean(b) => {
106                facts.set(&field, Value::Boolean(*b));
107                test_facts.push(facts.clone());
108                facts.set(&field, Value::Boolean(!b));
109                test_facts.push(facts.clone());
110            }
111            Value::String(s) => {
112                facts.set(&field, Value::String(s.clone()));
113                test_facts.push(facts.clone());
114                facts.set(&field, Value::String("other_value".to_string()));
115                test_facts.push(facts.clone());
116            }
117            _ => {}
118        }
119    }
120
121    // Sinh facts kết hợp nhiều điều kiện (all true)
122    if !conds.is_empty() {
123        let facts = crate::Facts::new();
124        for cond in &conds {
125            let field = cond.field.clone();
126            match &cond.value {
127                Value::Integer(i) => facts.set(&field, Value::Integer(*i)),
128                Value::Boolean(b) => facts.set(&field, Value::Boolean(*b)),
129                Value::String(s) => facts.set(&field, Value::String(s.clone())),
130                _ => {}
131            }
132        }
133        test_facts.push(facts);
134    }
135
136    test_facts
137}