Skip to main content

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        #[cfg(feature = "streaming")]
85        ConditionGroup::StreamPattern { .. } => {
86            // Stream patterns don't have simple single conditions to flatten
87            // Skip for now
88        }
89    }
90    out
91}
92
93/// 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
94pub fn generate_test_facts_for_rule(rule: &crate::engine::rule::Rule) -> Vec<crate::Facts> {
95    use crate::types::Value;
96    let conds = flatten_conditions(&rule.conditions);
97    let mut test_facts = Vec::new();
98
99    // Sinh facts cho từng điều kiện riêng lẻ
100    for cond in &conds {
101        let facts = crate::Facts::new();
102        let field = cond.field.clone();
103        match &cond.value {
104            Value::Integer(i) => {
105                facts.set(&field, Value::Integer(*i));
106                test_facts.push(facts.clone());
107                facts.set(&field, Value::Integer(i + 1));
108                test_facts.push(facts.clone());
109            }
110            Value::Boolean(b) => {
111                facts.set(&field, Value::Boolean(*b));
112                test_facts.push(facts.clone());
113                facts.set(&field, Value::Boolean(!b));
114                test_facts.push(facts.clone());
115            }
116            Value::String(s) => {
117                facts.set(&field, Value::String(s.clone()));
118                test_facts.push(facts.clone());
119                facts.set(&field, Value::String("other_value".to_string()));
120                test_facts.push(facts.clone());
121            }
122            _ => {}
123        }
124    }
125
126    // Sinh facts kết hợp nhiều điều kiện (all true)
127    if !conds.is_empty() {
128        let facts = crate::Facts::new();
129        for cond in &conds {
130            let field = cond.field.clone();
131            match &cond.value {
132                Value::Integer(i) => facts.set(&field, Value::Integer(*i)),
133                Value::Boolean(b) => facts.set(&field, Value::Boolean(*b)),
134                Value::String(s) => facts.set(&field, Value::String(s.clone())),
135                _ => {}
136            }
137        }
138        test_facts.push(facts);
139    }
140
141    test_facts
142}