#![cfg(feature = "parse")]
use std::time::{Duration, Instant};
use rumba_core::{expr::Expr, parser::parse_expr, simplify, varint::make_mask};
const SEMANTIC_TEST_COUNT: usize = 200;
const BIT_COUNT: u8 = 64;
const MASK: u64 = make_mask(BIT_COUNT);
enum Status {
Ok,
OkZ,
NG,
}
struct ExperimentResult {
elapsed: Duration,
status: Status,
}
struct Experiment {
filename: &'static str,
line_nb: usize,
mba: Expr,
gt: Expr,
}
impl Experiment {
fn new(filename: &'static str, line: &str, line_nb: usize) -> Self {
let line = line.trim();
let line: &str = line.trim();
let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
assert!(parts.len() == 2, "line {}: expected 2 columns", line_nb + 1);
Self {
filename,
line_nb: line_nb + 1,
mba: parse_expr(parts[0]).unwrap(),
gt: parse_expr(parts[1]).unwrap(),
}
}
fn run(&self) -> ExperimentResult {
let start = Instant::now();
let simplified_mba = simplify::simplify_mba(self.mba.clone(), BIT_COUNT);
let elapsed = start.elapsed();
if let Err((_, v1, v2)) = simplified_mba.sem_equal(&self.gt, MASK, SEMANTIC_TEST_COUNT) {
assert_eq!(
v1, v2,
"{}:{} semantic error mba: {}, gt: {}",
self.filename, self.line_nb, simplified_mba, self.gt
);
}
let simplified_gt = simplify::simplify_mba(self.gt.clone(), BIT_COUNT);
let mut status = Status::NG;
if simplified_mba == simplified_gt {
status = Status::Ok;
} else if simplify::simplify_mba(simplified_gt - simplified_mba.clone(), BIT_COUNT)
== Expr::zero()
{
status = Status::OkZ;
} else {
}
ExperimentResult { elapsed, status }
}
}
fn quartiles(samples: &[ExperimentResult]) -> (Duration, Duration, Duration, Duration, Duration) {
let mut execution_times: Vec<_> = samples.iter().map(|res| res.elapsed).collect();
execution_times.sort_unstable();
let n = execution_times.len();
let q0 = execution_times[0];
let q1 = execution_times[n / 4];
let q2 = execution_times[n / 2];
let q3 = execution_times[(3 * n) / 4];
let q4 = execution_times[n - 1];
(q0, q1, q2, q3, q4)
}
fn format_duration(dur: Duration) -> String {
let secs = dur.as_secs();
let nanos = dur.subsec_nanos();
if secs >= 1 {
let total = secs as f64 + (nanos as f64) / 1_000_000_000.0;
format!("{:.2}s", total)
} else if nanos >= 1_000_000 {
let total = nanos as f64 / 1_000_000.0;
format!("{:.2}ms", total)
} else if nanos >= 1_000 {
let total = nanos as f64 / 1_000.0;
format!("{:.2}µs", total)
} else {
format!("{}ns", nanos)
}
}
fn count_types(results: &Vec<ExperimentResult>) -> (usize, usize, usize) {
let mut oks = 0;
let mut okzs = 0;
let mut ngs = 0;
for res in results {
match res.status {
Status::Ok => oks += 1,
Status::OkZ => okzs += 1,
Status::NG => ngs += 1,
};
}
(oks, okzs, ngs)
}
fn run_csv_tests(filename: &'static str, csv: &str) -> Vec<ExperimentResult> {
let mut results: Vec<ExperimentResult> = vec![];
for (line_nb, line) in csv.lines().enumerate() {
if line.is_empty() {
continue;
}
let expe = Experiment::new(filename, line, line_nb);
results.push(expe.run());
}
let (q0, q1, q2, q3, q4) = quartiles(&results);
let (oks, okzs, ngs) = count_types(&results);
println!(
"RESULTS \"{}\" (count: {}):\n\t[{}, {}, {}, {}, {}]\n\tOK: {}\tOKZ: {}\tNG: {}\n",
filename,
results.len(),
format_duration(q0),
format_duration(q1),
format_duration(q2),
format_duration(q3),
format_duration(q4),
oks,
okzs,
ngs
);
results
}
macro_rules! run_on_dataset {
($filename: literal) => {
run_csv_tests($filename, include_str!(concat!("./datasets/", $filename)))
};
}
macro_rules! test_dataset {
($name:ident, $filename:literal $(, $attr:meta)?) => {
#[test]
$(#[$attr])?
fn $name() {
run_on_dataset!($filename);
}
};
}
#[cfg(test)]
mod datasets {
use super::*;
test_dataset!(loki_tiny, "loki_tiny.csv");
test_dataset!(mba_flatten, "mba_flatten.csv");
test_dataset!(mba_obf_linear, "mba_obf_linear.csv");
test_dataset!(mba_obf_nonlinear, "mba_obf_nonlinear.csv");
test_dataset!(neureduce, "neureduce.csv");
test_dataset!(qsynth_ea, "qsynth_ea.csv");
test_dataset!(syntia, "syntia.csv");
}