rust_rule_engine/engine/
coverage.rs1#![allow(deprecated)]
5
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Default)]
9pub struct RuleCoverage {
10 pub rule_hits: HashMap<String, usize>,
12 pub rule_facts: HashMap<String, HashSet<String>>,
14 pub tested_facts: HashSet<String>,
16}
17
18impl RuleCoverage {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 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 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
63fn 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 }
84 }
85 out
86}
87
88pub 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 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 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}