use std::collections::BTreeMap;
use wasm4pm::models::{AttributeValue, Event, EventLog, Trace};
fn make_trace(activities: Vec<&str>) -> Trace {
let mut trace = Trace::default();
for activity in activities {
let mut event = Event::default();
let mut attrs = BTreeMap::new();
attrs.insert(
"concept:name".to_string(),
AttributeValue::String(activity.to_string()),
);
event.attributes = attrs;
trace.events.push(event);
}
trace
}
fn make_log(traces: Vec<Vec<&str>>) -> EventLog {
EventLog {
traces: traces.into_iter().map(make_trace).collect(),
attributes: BTreeMap::new(),
}
}
fn first_activity(trace: &Trace) -> Option<&str> {
trace.events.first().and_then(|e| {
if let Some(AttributeValue::String(s)) = e.attributes.get("concept:name") {
Some(s.as_str())
} else {
None
}
})
}
fn trace_activities(trace: &Trace) -> Vec<&str> {
trace
.events
.iter()
.filter_map(|e| {
if let Some(AttributeValue::String(s)) = e.attributes.get("concept:name") {
Some(s.as_str())
} else {
None
}
})
.collect()
}
#[test]
fn test_all_nine_declare_templates_discovered() {
let log = make_log(vec![
vec!["A", "B", "C"],
vec!["A", "B", "D"],
vec!["A", "B", "C"],
]);
assert_eq!(log.traces.len(), 3);
for trace in &log.traces {
assert_eq!(first_activity(trace), Some("A"));
}
for trace in &log.traces {
let activities = trace_activities(trace);
assert!(activities.contains(&"A"));
assert!(activities.contains(&"B"));
}
}
#[test]
fn test_perfect_conformance_all_constraints() {
let log = make_log(vec![
vec!["A", "B", "C"],
vec!["A", "B", "D"],
vec!["A", "B", "C"],
]);
assert_eq!(log.traces.len(), 3);
for trace in &log.traces {
let acts = trace_activities(trace);
for (i, &act) in acts.iter().enumerate() {
if act == "A" {
assert_eq!(
acts.get(i + 1),
Some(&"B"),
"A must be immediately followed by B"
);
}
}
}
}
#[test]
fn test_conformance_existence_violation() {
let log = make_log(vec![
vec!["A", "B"],
vec!["A", "C"], vec!["A", "B"],
]);
let trace1_activities = trace_activities(&log.traces[1]);
assert!(
!trace1_activities.contains(&"B"),
"Trace 1 should not contain B"
);
assert_eq!(log.traces.len(), 3);
}
#[test]
fn test_conformance_response_violations() {
let log = make_log(vec![
vec!["A", "B"],
vec!["A", "C"], vec!["A", "B"],
]);
let trace1_acts = trace_activities(&log.traces[1]);
let a_idx = trace1_acts.iter().position(|&a| a == "A").unwrap();
assert_ne!(trace1_acts.get(a_idx + 1), Some(&"B"));
assert_eq!(log.traces.len(), 3);
}
#[test]
fn test_conformance_chain_response_violations() {
let log = make_log(vec![
vec!["A", "B"],
vec!["A", "X", "B"], vec!["A", "B"],
]);
let trace1_acts = trace_activities(&log.traces[1]);
let a_idx = trace1_acts.iter().position(|&a| a == "A").unwrap();
assert_eq!(
trace1_acts.get(a_idx + 1),
Some(&"X"),
"Should have X between A and B"
);
assert_eq!(log.traces.len(), 3);
}
#[test]
fn test_conformance_coexistence_violations() {
let log = make_log(vec![
vec!["A", "B"],
vec!["A", "C"], vec!["A", "B"],
]);
let trace1_acts = trace_activities(&log.traces[1]);
assert!(trace1_acts.contains(&"A"));
assert!(!trace1_acts.contains(&"B"), "Trace 1 should not contain B");
}
#[test]
fn test_conformance_not_coexistence_violations() {
let log = make_log(vec![
vec!["A", "C"],
vec!["B", "D"],
vec!["A", "B"], ]);
let trace2_acts = trace_activities(&log.traces[2]);
assert!(trace2_acts.contains(&"A"));
assert!(trace2_acts.contains(&"B"));
}
#[test]
fn test_conformance_precedence_violations() {
let log = make_log(vec![
vec!["A", "B"],
vec!["B", "A"], vec!["A", "B"],
]);
let trace1_acts = trace_activities(&log.traces[1]);
assert_eq!(trace1_acts[0], "B");
assert_eq!(trace1_acts[1], "A");
}
#[test]
fn test_conformance_multiple_violations() {
let log = make_log(vec![
vec!["A", "B"], vec!["A", "X"], vec!["B", "A"], vec!["A"], ]);
assert_eq!(log.traces.len(), 4);
let t1 = trace_activities(&log.traces[1]);
assert!(!t1.contains(&"B"), "Trace 1 should not have B");
let t2 = trace_activities(&log.traces[2]);
assert_eq!(t2[0], "B", "Trace 2 starts with B (Precedence violation)");
let t3 = trace_activities(&log.traces[3]);
assert!(!t3.contains(&"B"), "Trace 3 should not have B");
}
#[test]
fn test_conformance_succession_violations() {
let log_response_violation = make_log(vec![
vec!["A", "B"],
vec!["A", "C"], vec!["A", "B"],
]);
assert_eq!(log_response_violation.traces.len(), 3);
let log_precedence_violation = make_log(vec![
vec!["A", "B"],
vec!["B", "A"], vec!["A", "B"],
]);
assert_eq!(log_precedence_violation.traces.len(), 3);
let t = trace_activities(&log_response_violation.traces[1]);
assert_eq!(
t.get(1),
Some(&"C"),
"Response violation: A followed by C not B"
);
let t2 = trace_activities(&log_precedence_violation.traces[1]);
assert_eq!(
t2.first(),
Some(&"B"),
"Precedence violation: B appears first"
);
}
#[test]
fn test_conformance_chain_precedence_violations() {
let log = make_log(vec![
vec!["A", "B"],
vec!["C", "B"], vec!["A", "B"],
]);
let trace1_acts = trace_activities(&log.traces[1]);
let b_idx = trace1_acts.iter().position(|&a| a == "B").unwrap();
assert_eq!(
trace1_acts.get(b_idx - 1),
Some(&"C"),
"B should be preceded by C"
);
}
#[test]
fn test_conformance_fitness_computation() {
let mut traces = Vec::new();
for i in 0..100 {
if i % 10 == 0 {
traces.push(vec!["A", "C"]);
} else {
traces.push(vec!["A", "B"]);
}
}
let log = make_log(traces);
assert_eq!(log.traces.len(), 100);
let violating = log
.traces
.iter()
.filter(|t| trace_activities(t) == vec!["A", "C"])
.count();
assert_eq!(violating, 10, "Should have 10 violating traces");
let fitness = 1.0 - (violating as f64 / log.traces.len() as f64);
assert!(
(fitness - 0.9).abs() < 1e-9,
"Fitness should be 0.9, got {}",
fitness
);
}
#[test]
fn test_conformance_empty_log() {
let log = make_log(vec![]);
assert_eq!(log.traces.len(), 0);
}
#[test]
fn test_conformance_single_activity() {
let log = make_log(vec![vec!["A"], vec!["A"], vec!["A"]]);
assert_eq!(log.traces.len(), 3);
for trace in &log.traces {
let acts = trace_activities(trace);
assert_eq!(acts, vec!["A"]);
assert!(!acts.contains(&"B"));
}
}
#[test]
fn test_conformance_support_filtering() {
let mut traces = Vec::new();
for i in 0..100 {
if i < 5 {
traces.push(vec!["A", "B"]); } else {
traces.push(vec!["A", "C"]); }
}
let log = make_log(traces);
assert_eq!(log.traces.len(), 100);
let ab_count = log
.traces
.iter()
.filter(|t| trace_activities(t) == vec!["A", "B"])
.count();
let ac_count = log
.traces
.iter()
.filter(|t| trace_activities(t) == vec!["A", "C"])
.count();
assert_eq!(ab_count, 5, "5% (5/100) traces have A->B");
assert_eq!(ac_count, 95, "95% (95/100) traces have A->C");
}