#![allow(clippy::expect_used, clippy::unwrap_used)]
use std::sync::Arc;
use std::time::Instant;
use ferridriver_script::{
InMemoryVars, Outcome, PathSandbox, PluginBinding, RunContext, RunOptions, ScriptEngineConfig, Session,
compile_and_extract_plugins,
};
const FILES: &[(&str, &str)] = &[
(
"login.js",
"defineTool({ name: 'box.login', description: 'login', \
inputSchema: { type: 'object' }, allow: { commands: { resolveUser: 'true' } }, \
exposeAsTool: true, async handler({ args }) { return { ok: true, user: args && args.user }; } });",
),
(
"core.js",
"const V = 'yes';\n\
defineTool({ name: 'box.noop', description: 'noop', exposeAsTool: true, async handler() { return null; } });\n\
defineTool({ name: 'box.setFeatureFlip', description: 'ff', exposeAsTool: true, \
async handler({ args }) { \
const flags = Array.isArray(args.flag) ? args.flag : [args.flag]; \
const cookies = flags.map((f) => ({ name: 'ff_' + f, value: V, domain: '.box.com', path: '/' })); \
return { flags, value: V, cookies: JSON.parse(JSON.stringify(cookies)) }; } });",
),
(
"ui.js",
"defineTool({ name: 'box.click', description: 'click', exposeAsTool: true, async handler() { return 1; } });\n\
defineTool({ name: 'box.type', description: 'type', exposeAsTool: true, async handler() { return 2; } });",
),
(
"sign.js",
"defineTool({ name: 'box.sign', description: 'sign', exposeAsTool: true, async handler() { return 'signed'; } });",
),
];
struct Compiled {
bytecode: Arc<[u8]>,
}
fn bindings(compiled: &[Compiled]) -> Vec<PluginBinding> {
compiled
.iter()
.map(|c| PluginBinding {
bytecode: c.bytecode.clone(),
})
.collect()
}
const ITERS: u32 = 200;
#[tokio::test(flavor = "multi_thread")]
#[ignore = "perf microbench; run with --ignored --nocapture"]
async fn plugin_path_bench() {
let src_tmp = tempfile::tempdir().expect("tempdir");
let paths: Vec<_> = FILES
.iter()
.map(|(file, src)| {
let p = src_tmp.path().join(file);
std::fs::write(&p, src).expect("write plugin");
p
})
.collect();
let cold = Instant::now();
let (cp, failures) = compile_and_extract_plugins(&paths).await;
let cold_ms = cold.elapsed().as_secs_f64() * 1e3;
assert!(failures.is_empty(), "compile failures: {failures:?}");
assert_eq!(cp.len(), FILES.len(), "all files must compile");
let warm_cache = Instant::now();
let (cp2, _) = compile_and_extract_plugins(&paths).await;
let warm_cache_ms = warm_cache.elapsed().as_secs_f64() * 1e3;
assert_eq!(cp2.len(), FILES.len(), "cache hit must return all files");
let compiled: Vec<Compiled> = cp.into_iter().map(|c| Compiled { bytecode: c.bytecode }).collect();
let tmp = tempfile::tempdir().expect("tempdir");
let mk_ctx = || RunContext {
vars: Arc::new(InMemoryVars::new()),
sandbox: Arc::new(PathSandbox::new(tmp.path()).expect("sandbox")),
artifacts: None,
page: None,
browser_context: None,
request: None,
browser: None,
plugins: bindings(&compiled),
trusted_modules: false,
host: ferridriver_script::ExtensionHost::Script,
caps: ferridriver_script::ScriptCaps::default(),
};
let n_sessions = 50;
let sess_t = Instant::now();
for _ in 0..n_sessions {
let ctx = mk_ctx();
Session::create(ScriptEngineConfig::default(), &ctx)
.await
.expect("session create");
}
let per_session_ms = (sess_t.elapsed().as_secs_f64() * 1e3) / f64::from(n_sessions);
let ctx = mk_ctx();
let session = Session::create(ScriptEngineConfig::default(), &ctx)
.await
.expect("session create");
let warm = session
.execute(
"return await plugins['box.noop']({});",
&[],
RunOptions::default(),
&ctx,
)
.await;
assert!(matches!(warm.result.outcome, Outcome::Ok { .. }), "noop must succeed");
let noop_t = Instant::now();
for _ in 0..ITERS {
let r = session
.execute(
"return await plugins['box.noop']({});",
&[],
RunOptions::default(),
&ctx,
)
.await;
assert!(matches!(r.result.outcome, Outcome::Ok { .. }));
}
let noop_us = (noop_t.elapsed().as_secs_f64() * 1e6) / f64::from(ITERS);
let ff_src = "return await plugins['box.setFeatureFlip']({ flag: ['vega','nova','orion'] });";
let ff_t = Instant::now();
for _ in 0..ITERS {
let r = session.execute(ff_src, &[], RunOptions::default(), &ctx).await;
assert!(matches!(r.result.outcome, Outcome::Ok { .. }));
}
let ff_us = (ff_t.elapsed().as_secs_f64() * 1e6) / f64::from(ITERS);
println!("\n=== plugin path bench ({} files, {ITERS} iters) ===", FILES.len());
println!("cold start (bundle+compile all): {cold_ms:8.2} ms");
println!("warm start (content-hash cache): {warm_cache_ms:8.3} ms");
println!("per-session install : {per_session_ms:8.3} ms");
println!("per-call no-op dispatch : {noop_us:8.2} us");
println!("per-call setFeatureFlip-class : {ff_us:8.2} us");
println!("================================================\n");
}