#![allow(clippy::expect_used, clippy::unwrap_used)]
#![allow(unsafe_code)]
use std::time::Instant;
use ferridriver_bdd::js::{JsBddSession, bundle_steps};
fn step_source(marker: u128) -> String {
format!(
"// unique:{marker}\n\
function slugify(s) {{ return String(s).toLowerCase().replace(/\\s+/g, '-'); }}\n\
defineParameterType({{ name: 'color', regexp: /red|green|blue/, transformer: (s) => s.toUpperCase() }});\n\
Before(function () {{ this.count = 0; }});\n\
Given('I start with {{int}}', function (n) {{ this.count = n; }});\n\
Given('I pick a {{color}} item', function (c) {{ this.color = c; }});\n\
When('I add {{int}}', function (n) {{ this.count += n; }});\n\
When('I name it {{string}}', function (s) {{ this.slug = slugify(s); }});\n\
Then('the total is {{int}}', function (n) {{ if (this.count !== n) throw new Error('bad'); }});\n\
Then('the slug is {{string}}', function (s) {{ if (this.slug !== s) throw new Error('bad'); }});\n"
)
}
fn now_ns() -> u128 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
}
async fn time_bundle(globs: &[String], dir: &std::path::Path) -> f64 {
let t = Instant::now();
let _ = bundle_steps(globs, dir).await.expect("bundle");
t.elapsed().as_secs_f64() * 1e3
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "perf microbench; run with --ignored --nocapture"]
async fn js_plugin_load_bench() {
let src = tempfile::tempdir().expect("tempdir");
let dir = src.path().to_path_buf();
let cache = tempfile::tempdir().expect("cache tempdir");
unsafe { std::env::set_var("FERRIDRIVER_CACHE_DIR", cache.path()) };
unsafe { std::env::remove_var("FERRIDRIVER_NO_BYTECODE_CACHE") };
std::fs::write(dir.join("steps.ts"), step_source(now_ns())).expect("write");
let globs = vec!["steps.ts".to_string()];
let cold_ms = time_bundle(&globs, &dir).await;
let warm_ms = time_bundle(&globs, &dir).await; let warm2_ms = time_bundle(&globs, &dir).await;
std::fs::write(dir.join("steps.ts"), step_source(now_ns())).expect("rewrite");
let after_edit_ms = time_bundle(&globs, &dir).await;
let warm_after_edit_ms = time_bundle(&globs, &dir).await;
unsafe { std::env::set_var("FERRIDRIVER_NO_BYTECODE_CACHE", "1") };
let off_a_ms = time_bundle(&globs, &dir).await;
let off_b_ms = time_bundle(&globs, &dir).await;
unsafe { std::env::remove_var("FERRIDRIVER_NO_BYTECODE_CACHE") };
let bundle = bundle_steps(&globs, &dir).await.expect("bundle for session");
let _ = JsBddSession::load(bundle.clone(), &dir, serde_json::Value::Null)
.await
.expect("warm session");
let n = 50u32;
let sess = Instant::now();
for _ in 0..n {
let _ = JsBddSession::load(bundle.clone(), &dir, serde_json::Value::Null)
.await
.expect("session load");
}
let per_session_ms = (sess.elapsed().as_secs_f64() * 1e3) / f64::from(n);
println!("\n=== js plugin LOAD bench (1 step file, 7 steps) ===");
println!("cold bundle (rolldown+bytecode+store): {cold_ms:8.2} ms");
println!(
"disk-warm (validate+load bytecode) : {warm_ms:8.3} ms [{:.0}x faster]",
cold_ms / warm_ms
);
println!("disk-warm (again) : {warm2_ms:8.3} ms");
println!("after edit (must miss -> rebundle) : {after_edit_ms:8.2} ms [freshness ok]");
println!("disk-warm (post-edit content) : {warm_after_edit_ms:8.3} ms");
println!("cache OFF (a) : {off_a_ms:8.2} ms");
println!("cache OFF (b) : {off_b_ms:8.2} ms");
println!("per-session load (VM+eval+reg) : {per_session_ms:8.3} ms");
println!("===================================================\n");
assert!(warm_ms < cold_ms, "disk-warm must beat cold");
}