use dashmap::{mapref::entry::Entry, DashMap, DashSet};
use std::{
path::PathBuf,
sync::{
mpsc::{sync_channel, SyncSender},
Mutex,
},
thread::JoinHandle,
time::Duration,
};
lazy_static::lazy_static! {
static ref COVERED_TRANSITIONS: DashMap<String, DashSet<CoveredTransition>>
= DashMap::new();
static ref COVERAGE_SENDER: SyncSender<(String, CoveredTransition)> =
spawn_save_coverage_at_end();
static ref THREAD_HANDLE: Mutex<Option<JoinHandle<()>>> = Mutex::new(None);
}
#[derive(Eq, PartialEq, Hash, Debug)]
struct CoveredTransition {
from_state: String,
to_state: String,
event: String,
}
pub fn add_coverage(machine_name: String, from_state: String, to_state: String, event: String) {
let ct = CoveredTransition {
from_state,
to_state,
event,
};
let _ = COVERAGE_SENDER.send((machine_name, ct));
}
fn spawn_save_coverage_at_end() -> SyncSender<(String, CoveredTransition)> {
let (tx, rx) = sync_channel(1000);
let handle = std::thread::spawn(move || {
while let Ok((machine_name, ct)) = rx.recv_timeout(Duration::from_secs(1)) {
match COVERED_TRANSITIONS.entry(machine_name) {
Entry::Occupied(o) => {
o.get().insert(ct);
}
Entry::Vacant(v) => {
v.insert({
let ds = DashSet::new();
ds.insert(ct);
ds
});
}
}
}
});
*THREAD_HANDLE.lock().unwrap() = Some(handle);
tx
}
#[cfg(test)]
mod machine_coverage_report {
use super::*;
use crate::machines::fail_workflow_state_machine::FailWorkflowMachine;
use crate::machines::{
activity_state_machine::ActivityMachine,
complete_workflow_state_machine::CompleteWorkflowMachine,
timer_state_machine::TimerMachine, workflow_task_state_machine::WorkflowTaskMachine,
};
use rustfsm::StateMachine;
use std::fs::File;
use std::io::Write;
#[test]
#[ignore]
fn reporter() {
#[allow(clippy::no_effect)] {
&*COVERAGE_SENDER;
}
THREAD_HANDLE
.lock()
.unwrap()
.take()
.unwrap()
.join()
.unwrap();
let mut activity = ActivityMachine::visualizer().to_owned();
let mut timer = TimerMachine::visualizer().to_owned();
let mut complete_wf = CompleteWorkflowMachine::visualizer().to_owned();
let mut wf_task = WorkflowTaskMachine::visualizer().to_owned();
let mut fail_wf = FailWorkflowMachine::visualizer().to_owned();
for item in COVERED_TRANSITIONS.iter() {
let (machine, coverage) = item.pair();
match machine.as_ref() {
m @ "ActivityMachine" => cover_transitions(m, &mut activity, coverage),
m @ "TimerMachine" => cover_transitions(m, &mut timer, coverage),
m @ "CompleteWorkflowMachine" => cover_transitions(m, &mut complete_wf, coverage),
m @ "WorkflowTaskMachine" => cover_transitions(m, &mut wf_task, coverage),
m @ "FailWorkflowMachine" => cover_transitions(m, &mut fail_wf, coverage),
m => panic!("Unknown machine {}", m),
}
}
}
fn cover_transitions(machine: &str, viz: &mut String, cov: &DashSet<CoveredTransition>) {
for trans in cov.iter() {
let find_line = format!(
"{} --> {}: {}",
trans.from_state, trans.to_state, trans.event
);
if let Some(start) = viz.find(&find_line) {
let new_line = format!(
"{} -[#blue]-> {}: {}",
trans.from_state, trans.to_state, trans.event
);
viz.replace_range(start..start + find_line.len(), &new_line);
}
}
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("machine_coverage");
std::fs::create_dir_all(&d).unwrap();
d.push(format!("{}_Coverage.puml", machine));
let mut file = File::create(d).unwrap();
file.write_all(viz.as_bytes()).unwrap();
}
}