mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
#786: make the expression budget tests independent of machine load
fanning_out_reads_stop_at_a_budget expects the run-count error, but under heavy load the 5 s wall-clock budget could fire first (different error). Add a test-only per-thread time limit and lift it in that test. Wall-clock assertions in the other new tests are now only generous hang guards (>=10x), the fuzzer's hang deadline is 24x MAX_TIME and the perf bound is 10x looser. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
9da66d9063
commit
d7a03ef3a7
3 files changed
+31
-7
No files matched your search
@@ -328,11 +328,30 @@ fn evaluate_in_time(script: &Script, ctx: &mut Context) -> Option<JsResult<JsVal
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// Test hook: this thread's time budget (tests of the run-count budget lift it so that a
|
||||
/// loaded machine cannot reach the clock first).
|
||||
pub static TIME_LIMIT: std::cell::Cell<std::time::Duration> = const { std::cell::Cell::new(crate::MAX_TIME) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn max_time() -> std::time::Duration {
|
||||
#[cfg(test)]
|
||||
{
|
||||
TIME_LIMIT.with(|t| t.get())
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
crate::MAX_TIME
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the outermost evaluation on this thread has used up [`crate::MAX_TIME`].
|
||||
pub fn out_of_time() -> bool {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
STARTED.with(|s| s.get()).is_some_and(|t| t.elapsed() > crate::MAX_TIME)
|
||||
STARTED.with(|s| s.get()).is_some_and(|t| t.elapsed() > max_time())
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
|
||||
@@ -385,7 +385,8 @@ fn circular_references_terminate() {
|
||||
f.set_expr(f.b, OPA, "thisComp.layer('A').transform.opacity");
|
||||
let start = Instant::now();
|
||||
let r = eval_property(&f.p, f.cid, f.a, OPA, 0.0);
|
||||
assert!(start.elapsed().as_secs_f64() < 10.0);
|
||||
// Only a hang guard (the budgets stop it far sooner); generous for a loaded machine.
|
||||
assert!(start.elapsed().as_secs_f64() < 100.0, "{:?}", start.elapsed());
|
||||
// The cycle is caught when A needs itself again, and falls back to keyframed values.
|
||||
assert!(matches!(r, Ok(Value::Scalar(_))) || r.is_err(), "{r:?}");
|
||||
}
|
||||
@@ -399,7 +400,7 @@ fn an_expression_needing_its_own_value_fails_fast() {
|
||||
f.set_expr(f.a, ROT, "fromWorld(toWorld(anchorPoint))[0]");
|
||||
let start = Instant::now();
|
||||
let r = eval_property(&f.p, f.cid, f.a, ROT, 0.5);
|
||||
assert!(start.elapsed().as_secs_f64() < 5.0, "{:?}", start.elapsed());
|
||||
assert!(start.elapsed().as_secs_f64() < 100.0, "{:?}", start.elapsed());
|
||||
// The inner read falls back to the keyframed rotation, as in a standalone evaluation.
|
||||
assert_eq!(r, f.ev(f.a, ROT, "fromWorld(toWorld(anchorPoint))[0]", 0.5));
|
||||
}
|
||||
@@ -412,9 +413,13 @@ fn fanning_out_reads_stop_at_a_budget() {
|
||||
let text = |other: &str| format!("var r = thisComp.layer('{other}').transform.rotation; r.valueAtTime(time + 0.001) + r.valueAtTime(time + 0.002)");
|
||||
f.set_expr(f.a, ROT, &text("B"));
|
||||
f.set_expr(f.b, ROT, &text("A"));
|
||||
// Take the clock out of the race: on a loaded machine the time budget could otherwise stop
|
||||
// the evaluation before the run count does, with a different error.
|
||||
crate::runtime::TIME_LIMIT.with(|t| t.set(std::time::Duration::from_secs(3600)));
|
||||
let start = Instant::now();
|
||||
let r = eval_property(&f.p, f.cid, f.a, ROT, 0.5);
|
||||
assert!(start.elapsed().as_secs_f64() < 10.0, "{:?}", start.elapsed());
|
||||
crate::runtime::TIME_LIMIT.with(|t| t.set(crate::MAX_TIME));
|
||||
assert!(start.elapsed().as_secs_f64() < 300.0, "{:?}", start.elapsed());
|
||||
assert!(r.unwrap_err().contains("too many"));
|
||||
// The budget is per outermost evaluation: the next one starts afresh.
|
||||
assert_close!(f.num("time * 2", 1.0), 2.0);
|
||||
@@ -440,7 +445,7 @@ fn a_long_running_script_stops_at_the_time_budget() {
|
||||
let start = Instant::now();
|
||||
let e = f.err("function g() { for (var i = 0; i < 900000; i++) {} }\nfor (var k = 0; k < 900000; k++) g()");
|
||||
assert!(e.contains("too long"), "{e}");
|
||||
assert!(start.elapsed() < crate::MAX_TIME * 3, "{:?}", start.elapsed());
|
||||
assert!(start.elapsed() < crate::MAX_TIME * 12, "{:?}", start.elapsed());
|
||||
// The thread's runtime starts afresh.
|
||||
assert_close!(f.num("time * 2", 1.0), 2.0);
|
||||
}
|
||||
@@ -1075,7 +1080,7 @@ fn hundred_wiggles_are_fast() {
|
||||
let per_frame = start.elapsed().as_secs_f64() * 1000.0 / n as f64;
|
||||
println!("100 wiggle properties: {per_frame:.3} ms/frame (checksum {acc})");
|
||||
// Generous bound so unoptimised CI builds pass; release is far below.
|
||||
assert!(per_frame < 100.0, "{per_frame} ms/frame");
|
||||
assert!(per_frame < 1000.0, "{per_frame} ms/frame");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -326,7 +326,7 @@ fn fuzz_expressions_never_crash() {
|
||||
let budget = Duration::from_secs(env_u64("EFFECTCRAFT_FUZZ_SECS", 5));
|
||||
let verbose = env_u64("EFFECTCRAFT_FUZZ_VERBOSE", 0) != 0;
|
||||
// Every evaluation gives up after `MAX_TIME`, and an input is evaluated a handful of times.
|
||||
let deadline = crate::MAX_TIME * 12;
|
||||
let deadline = crate::MAX_TIME * 24;
|
||||
let mut worker = Worker::start().unwrap();
|
||||
// The unmutated seeds evaluate everywhere without crashing.
|
||||
for (i, s) in SEEDS.iter().enumerate() {
|
||||
|
||||
Reference in new issue
Block a user