use std::{
collections::HashSet,
time::{Duration, Instant}
};
use pretty_assertions::{assert_eq, assert_ne};
use super::{
ast::{DEPTH, Nesting, nest_function},
recovery::SCALE
};
use crate::{
Compiler, Passes, compile, compile_unoptimized,
support::{
compile_valid, on_small_stack, on_small_stack_within, optimize,
read_compilation_test_cases
}
};
#[test]
fn test_compile_unoptimized()
{
let mut seen = HashSet::new();
for (index, (source, expected)) in read_compilation_test_cases(
include_str!("../../tests/test_compile_unoptimized.txt")
)
.iter()
.enumerate()
{
assert!(seen.insert(source), "duplicate test case: {}", source);
let actual = format!("{}", compile_valid(source));
assert_eq!(actual.trim(), *expected, "case {}: {}", index + 1, source);
}
}
#[test]
fn test_compile_optimizes_fully()
{
for (index, (source, _)) in read_compilation_test_cases(include_str!(
"../../tests/test_full_optimization.txt"
))
.iter()
.enumerate()
{
let expected = optimize(compile_valid(source), Passes::all());
let actual = compile(source).unwrap();
assert_eq!(actual, expected, "case {}: {}", index + 1, source);
}
}
#[test]
fn test_compile_folds_constants()
{
let source = "(2 + 3) * 1D6 + 4 * 2";
let optimized = compile(source).unwrap();
let unoptimized = compile_unoptimized(source).unwrap();
assert_ne!(optimized, unoptimized);
assert_eq!(optimized.instructions.len(), 5, "{}", optimized);
let rendition = optimized.to_string();
assert!(!rendition.contains("2 + 3"), "{}", rendition);
assert!(!rendition.contains("4 * 2"), "{}", rendition);
}
#[test]
#[ignore = "stress: run with just stress"]
fn test_compile_deep()
{
on_small_stack(|| {
for nesting in Nesting::ROTATION.into_iter().chain([Nesting::Mixed])
{
let function = Compiler::compile(&nest_function(nesting, DEPTH));
assert_eq!(function.externals, ["x"], "{:?}", nesting);
}
});
}
const OPTIMIZATION_DEPTH: usize = DEPTH / 10;
const OPTIMIZATION_TIMEOUT: Duration = Duration::from_secs(300);
#[test]
#[ignore = "stress: run with just stress"]
fn test_optimize_deep()
{
on_small_stack_within(OPTIMIZATION_TIMEOUT, || {
for nesting in Nesting::ROTATION.into_iter().chain([Nesting::Mixed])
{
let function =
Compiler::compile(&nest_function(nesting, OPTIMIZATION_DEPTH));
let function = optimize(function, Passes::all());
assert_eq!(function.externals, ["x"], "{:?}", nesting);
}
});
}
const WIDTH: usize = 2_000;
const RUNS: usize = 3;
const TIME_SCALE: u32 = 3 * SCALE as u32;
const TIME_FLOOR: Duration = Duration::from_micros(100);
type Member = fn(usize) -> String;
const FAMILIES: &[(&str, Member)] = &[
("negations of a constant", |n| format!("{}1", "-".repeat(n))),
("negations of a variable", |n| {
format!("{}{{x}}", "-".repeat(n))
}),
("nested ranges", |n| {
format!("{}1{}", "[1:".repeat(n), "]".repeat(n))
}),
("nested ranges of a variable", |n| {
format!("{}{{x}}{}", "[1:".repeat(n), "]".repeat(n))
}),
("nested powers", |n| format!("{}2", "2^".repeat(n))),
("nested groups", |n| {
format!("{}1{}", "(".repeat(n), ")".repeat(n))
}),
("nested dice counts", |n| {
format!("{}1{}", "(".repeat(n), ")D6".repeat(n))
}),
("sums of constants", |n| format!("1{}", " + 1".repeat(n))),
("sums of a variable", |n| {
format!("{{x}}{}", " + {x} - 1".repeat(n))
}),
("products of rolls", |n| {
format!("1D6{}", " * 1D6".repeat(n))
}),
("differences of divisions", |n| {
format!("{{x}}{}", " / 2 - {x} % 3".repeat(n))
}),
("stacked drop clauses", |n| {
format!("{}D6{}", n + 1, " drop lowest 1".repeat(n))
})
];
#[test]
#[ignore = "stress: run with just stress"]
fn test_compile_is_linear()
{
for (name, source) in FAMILIES
{
let sources = [WIDTH, SCALE * WIDTH].map(source);
let mut times = [Duration::MAX; 2];
for _ in 0..RUNS
{
for (i, source) in sources.iter().enumerate()
{
let start = Instant::now();
let result = compile(source);
times[i] = times[i].min(start.elapsed());
assert!(result.is_ok(), "{}: {:?}", name, result.err());
}
}
assert!(
times[1] <= TIME_SCALE * times[0].max(TIME_FLOOR),
"{} took {:?}, then {:?} for {} times the depth",
name,
times[0],
times[1],
SCALE
);
}
}