use lemma::{Engine, ResourceLimits, SourceType};
use std::fmt::Write;
use std::time::{Duration, Instant};
const DEEP_5000_MAX: Duration = Duration::from_secs(6);
const WIDE_5000_MAX: Duration = Duration::from_secs(21);
const UNLESS_5000_MAX: Duration = Duration::from_secs(4);
fn deep_source(n: usize) -> String {
let mut code = String::with_capacity(n * 32);
write!(code, "spec bench_deep\ndata x0: number\nrule r1: x0 + 1\n").unwrap();
for i in 2..=n {
writeln!(code, "rule r{i}: r{} + 1", i - 1).unwrap();
}
code
}
fn wide_source(n: usize) -> String {
let mut code = String::with_capacity(n * 80);
writeln!(code, "spec bench_wide").unwrap();
for i in 0..n {
writeln!(code, "data d{i}: number").unwrap();
}
for i in 0..n {
writeln!(code, "rule p{i}: d{i} * 2").unwrap();
}
writeln!(code, "rule s0: p0").unwrap();
for i in 1..n {
writeln!(code, "rule s{i}: s{} + p{i}", i - 1).unwrap();
}
code
}
fn unless_source(n: usize) -> String {
let mut code = String::with_capacity(n * 40);
write!(code, "spec bench_unless\ndata x: number\nrule r: 0\n").unwrap();
for i in 0..n {
writeln!(code, " unless x is {i} then {i}").unwrap();
}
code
}
fn load_limits() -> ResourceLimits {
ResourceLimits {
max_source_size_bytes: 100 * 1024 * 1024,
max_expression_count: 500_000,
max_normalized_expression_nodes: 500_000,
..ResourceLimits::default()
}
}
fn load_elapsed(code: String) -> Duration {
let mut engine = Engine::with_limits(load_limits());
let start = Instant::now();
engine
.load([(SourceType::Volatile, code)])
.unwrap_or_else(|errs| panic!("load must succeed: {errs:?}"));
start.elapsed()
}
#[test]
fn load_deep_5000_under_bound() {
let elapsed = load_elapsed(deep_source(5000));
assert!(
elapsed < DEEP_5000_MAX,
"deep N=5000 load {elapsed:?} exceeded bound {DEEP_5000_MAX:?} (10× measured 594ms)"
);
}
#[test]
fn load_wide_5000_under_bound() {
let elapsed = load_elapsed(wide_source(5000));
assert!(
elapsed < WIDE_5000_MAX,
"wide N=5000 load {elapsed:?} exceeded bound {WIDE_5000_MAX:?} (10× measured 2008ms)"
);
}
#[test]
fn load_unless_5000_under_bound() {
let elapsed = load_elapsed(unless_source(5000));
assert!(
elapsed < UNLESS_5000_MAX,
"unless N=5000 load {elapsed:?} exceeded bound {UNLESS_5000_MAX:?} (10× measured 317ms)"
);
}
#[test]
fn show_wide_200_needed_by_rules_alphabetical_transitive() {
const N: usize = 200;
let mut engine = Engine::with_limits(load_limits());
engine
.load([(SourceType::Volatile, wide_source(N))])
.expect("wide N=200 must load");
let show = engine
.show(None, "bench_wide", None)
.expect("show must succeed");
let mut expected_d0: Vec<String> = vec!["p0".to_string()];
expected_d0.extend((0..N).map(|i| format!("s{i}")));
expected_d0.sort();
assert_eq!(
show.data.get("d0").expect("d0").needed_by_rules,
expected_d0,
"d0 must list p0 and every s* in byte order"
);
let mut expected_d199 = vec!["p199".to_string(), "s199".to_string()];
expected_d199.sort();
assert_eq!(
show.data.get("d199").expect("d199").needed_by_rules,
expected_d199,
"d199 must list only p199 and s199"
);
}