use active_core::{parse, Engine, EvalError};
fn engine_with(budget: active_core::Budget, resilient: bool) -> Engine {
let mut engine = Engine::new();
engine.budget = budget;
engine.resilient = resilient;
engine
}
fn unlimited() -> active_core::Budget {
active_core::Budget {
max_nodes: 0,
max_depth: 0,
max_array_len: 0,
max_keys: 0,
max_wall_ms: 0,
max_value_size: 0,
max_total_value_size: 0,
}
}
fn eval_limited(src: &str, budget: active_core::Budget) -> EvalError {
let module = parse(src, "budgets").unwrap_or_else(|d| panic!("parse failed: {d:?}"));
let strict = engine_with(budget, false)
.evaluate(&module)
.expect_err("budget must trip strict");
let resilient = engine_with(budget, true)
.evaluate(&module)
.expect_err("budget must trip resilient");
assert_eq!(strict.code(), resilient.code(), "both modes agree");
strict
}
fn xcode(err: &EvalError) -> &str {
err.code()
}
#[test]
fn node_budget_trips_on_large_documents() {
let mut src = String::new();
for i in 0..20 {
src.push_str(&format!("k{i} = {i}\n"));
}
let budget = active_core::Budget {
max_nodes: 5,
..unlimited()
};
let err = eval_limited(&src, budget);
assert_eq!(xcode(&err), "X001");
}
#[test]
fn depth_budget_trips_with_span() {
let src = "a = { b = { c = { d = { e = { f = 1 } } } } }\n";
let budget = active_core::Budget {
max_depth: 3,
..unlimited()
};
let err = eval_limited(src, budget);
assert_eq!(xcode(&err), "X003");
assert!(err.span().is_some(), "depth trips carry a span");
}
#[test]
fn array_length_budget_trips_with_span() {
let src = "xs = [1, 2, 3, 4, 5]\n";
let budget = active_core::Budget {
max_array_len: 3,
..unlimited()
};
let err = eval_limited(src, budget);
assert_eq!(xcode(&err), "X004");
assert!(err.span().is_some(), "array trips carry a span");
}
#[test]
fn key_count_budget_trips() {
let mut src = String::new();
for i in 0..20 {
src.push_str(&format!("k{i} = {i}\n"));
}
let budget = active_core::Budget {
max_keys: 10,
..unlimited()
};
let err = eval_limited(&src, budget);
assert_eq!(xcode(&err), "X005");
}
#[test]
fn wall_clock_backstop_trips_on_slow_evaluations() {
let mut src = String::with_capacity(1 << 20);
for i in 0..50_000 {
src.push_str(&format!("k{i} = {i}\n"));
}
let budget = active_core::Budget {
max_wall_ms: 1,
..unlimited()
};
let module = parse(&src, "budgets").expect("must parse");
let err = engine_with(budget, true)
.evaluate(&module)
.expect_err("must trip");
assert_eq!(xcode(&err), "X002");
}
#[test]
fn zero_disables_every_limit() {
let mut src = String::new();
for i in 0..200 {
src.push_str(&format!("k{i} = {i}\n"));
}
src.push_str("xs = [");
for i in 0..200 {
src.push_str(&format!("{i},"));
}
src.push_str("]\n");
let module = parse(&src, "budgets").expect("must parse");
engine_with(unlimited(), false)
.evaluate(&module)
.expect("unlimited must pass");
}
#[test]
fn shipped_defaults_admit_ordinary_documents() {
let module = parse("port = 8080\nhost = \"h\"\n", "budgets").expect("must parse");
Engine::new().evaluate(&module).expect("defaults must pass");
let mut strict = Engine::new();
strict.resilient = false;
strict.evaluate(&module).expect("defaults must pass strict");
}
#[test]
fn reference_expansion_dies_before_materializing() {
let mut src = String::from("let a0 = \"x\"\n");
for i in 1..30 {
src.push_str(&format!("let a{i} = [a{}, a{}]\n", i - 1, i - 1));
}
src.push_str("top = a29\n");
let budget = active_core::Budget {
max_value_size: 1_000_000,
..unlimited()
};
let err = eval_limited(&src, budget);
assert_eq!(xcode(&err), "X006");
assert!(err.span().is_some(), "size trips carry a span");
assert!(err.to_string().contains("1000000"), "{err:?}");
}
#[test]
fn saturation_comparison_holds_at_max_limit() {
let mut src = String::from("let a0 = \"x\"\n");
for i in 1..70 {
src.push_str(&format!("let a{i} = [a{}, a{}]\n", i - 1, i - 1));
}
src.push_str("top = a69\n");
let budget = active_core::Budget {
max_value_size: u64::MAX,
..unlimited()
};
let err = eval_limited(&src, budget);
assert_eq!(xcode(&err), "X006");
}
#[test]
fn reference_chains_count_toward_depth() {
let mut src = String::from("let a0 = 1\n");
for i in 1..70 {
src.push_str(&format!("let a{i} = [a{}]\n", i - 1));
}
src.push_str("top = a69\n");
let budget = active_core::Budget {
max_depth: 64,
..unlimited()
};
let err = eval_limited(&src, budget);
assert_eq!(xcode(&err), "X003");
}
#[test]
fn dynamic_values_charge_actuals_per_clone() {
let prior = std::env::var("ACTIVE_TEST_BUDGET_BIG").ok();
std::env::set_var("ACTIVE_TEST_BUDGET_BIG", "z".repeat(10_000));
let mut src = String::from("let big = env.ACTIVE_TEST_BUDGET_BIG\n");
for i in 0..20 {
src.push_str(&format!("a{i} = big\n"));
}
let budget = active_core::Budget {
max_total_value_size: 50_000,
..unlimited()
};
let err = eval_limited(&src, budget);
match prior {
Some(prev) => std::env::set_var("ACTIVE_TEST_BUDGET_BIG", prev),
None => std::env::remove_var("ACTIVE_TEST_BUDGET_BIG"),
}
assert_eq!(xcode(&err), "X006");
}
#[test]
fn patch_composed_depth_is_checked() {
let src = "base = { a = 1 }\n@patch(\"base.b.c.d.e.f\", 1)\n";
let budget = active_core::Budget {
max_depth: 4,
..unlimited()
};
let err = eval_limited(src, budget);
assert_eq!(xcode(&err), "X003");
}
#[test]
fn wall_clock_deadline_never_panics_on_extremes() {
let budget = active_core::Budget {
max_wall_ms: u64::MAX,
..unlimited()
};
let _ = budget.wall_deadline();
let module = parse("x = 1\n", "budgets").expect("must parse");
engine_with(budget, true)
.evaluate(&module)
.expect("must pass");
}
#[test]
fn over_depth_spans_are_stable_across_runs() {
let src = "let a = { x = { y = { z = 1 } } }\nlet b = { x = { y = { z = 2 } } }\n";
let budget = active_core::Budget {
max_depth: 2,
..unlimited()
};
let run = || {
let module = parse(src, "budgets").expect("must parse");
engine_with(budget, false)
.evaluate(&module)
.expect_err("must trip")
.span()
.map(|s| (s.line, s.column))
};
assert_eq!(run(), run());
}
#[test]
fn cumulative_total_trips_on_many_aliases() {
let big = "x".repeat(1000);
let mut src = format!("let big = \"{big}\"\n");
for i in 0..200 {
src.push_str(&format!("a{i} = big\n"));
}
let budget = active_core::Budget {
max_total_value_size: 100_000,
..unlimited()
};
let err = eval_limited(&src, budget);
assert_eq!(xcode(&err), "X006");
assert!(err.to_string().contains("100000"), "{err:?}");
}
#[test]
fn patch_clones_charge_the_cumulative_total() {
let big = "y".repeat(10_000);
let mut src = format!("let big = \"{big}\"\nbase = {{}}\n");
for i in 0..3 {
src.push_str(&format!("@patch(\"base.k{i}\", big)\n"));
}
let budget = active_core::Budget {
max_total_value_size: 50_000,
..unlimited()
};
let err = eval_limited(&src, budget);
assert_eq!(xcode(&err), "X006");
}
#[test]
fn expired_deadline_takes_precedence_in_strict_validation() {
let mut src = String::from("schema = { xs = Array<Int(min = 0)> }\nxs = [");
for _ in 0..25_000 {
src.push_str("-1,");
}
src.push_str("]\n");
let budget = active_core::Budget {
max_wall_ms: 1,
..unlimited()
};
let module = parse(&src, "budgets").expect("must parse");
let err = engine_with(budget, false)
.evaluate(&module)
.expect_err("must fail");
assert_eq!(xcode(&err), "X002", "{err:?}");
}