Skip to main content

mobench_sdk/
web.rs

1//! JSON bridge for browser-hosted WebAssembly benchmark runners.
2//!
3//! The bridge deliberately accepts and returns strings so browser automation
4//! can use the same [`crate::BenchSpec`] and [`crate::RunnerReport`] JSON
5//! contract as the native runners without depending on JavaScript object
6//! conversion details.
7
8use crate::{BenchError, BenchSpec};
9use thiserror::Error;
10
11/// Errors produced by the browser JSON bridge.
12#[derive(Debug, Error)]
13pub enum BrowserRunnerError {
14    /// The input was not a valid serialized [`BenchSpec`].
15    #[error("failed to parse BenchSpec JSON: {0}")]
16    InvalidSpec(serde_json::Error),
17    /// Benchmark lookup or execution failed.
18    #[error("failed to run benchmark: {0}")]
19    Benchmark(#[from] BenchError),
20    /// The resulting [`crate::RunnerReport`] could not be serialized.
21    #[error("failed to serialize RunnerReport JSON: {0}")]
22    SerializeReport(serde_json::Error),
23}
24
25/// Runs a registered benchmark from a serialized [`BenchSpec`].
26///
27/// On success, the returned string is a serialized [`crate::RunnerReport`].
28/// Browser bundles can expose this as `window.mobench.run(spec)` and pass the
29/// result directly back through WebDriver.
30pub fn run_benchmark_json(spec_json: &str) -> Result<String, BrowserRunnerError> {
31    let spec =
32        serde_json::from_str::<BenchSpec>(spec_json).map_err(BrowserRunnerError::InvalidSpec)?;
33    let report = crate::run_benchmark(spec)?;
34    serde_json::to_string(&report).map_err(BrowserRunnerError::SerializeReport)
35}
36
37/// `wasm-bindgen` export used by generated browser bundles.
38///
39/// JavaScript name: `runBenchmarkJson`.
40#[cfg(target_arch = "wasm32")]
41#[wasm_bindgen::prelude::wasm_bindgen(js_name = runBenchmarkJson)]
42pub fn run_benchmark_json_wasm(spec_json: &str) -> Result<String, wasm_bindgen::JsValue> {
43    run_benchmark_json(spec_json)
44        .map_err(|error| wasm_bindgen::JsValue::from_str(&error.to_string()))
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use crate::timing::{BenchReport, TimingError, run_closure};
51
52    fn run_test_benchmark(spec: BenchSpec) -> Result<BenchReport, TimingError> {
53        run_closure(spec, || {
54            std::hint::black_box(21_u64.saturating_mul(2));
55            Ok(())
56        })
57    }
58
59    inventory::submit! {
60        crate::registry::BenchFunction {
61            name: "mobench_sdk::web::tests::browser_json_contract",
62            runner: run_test_benchmark,
63        }
64    }
65
66    #[test]
67    fn returns_runner_report_json() {
68        let result =
69            run_benchmark_json(r#"{"name":"browser_json_contract","iterations":2,"warmup":1}"#)
70                .expect("browser benchmark should run");
71        let report: crate::RunnerReport =
72            serde_json::from_str(&result).expect("valid RunnerReport JSON");
73
74        assert_eq!(report.spec.name, "browser_json_contract");
75        assert_eq!(report.samples.len(), 2);
76    }
77
78    #[test]
79    fn rejects_invalid_spec_json() {
80        let error = run_benchmark_json("{not json}").expect_err("invalid JSON should fail");
81
82        assert!(matches!(error, BrowserRunnerError::InvalidSpec(_)));
83        assert!(error.to_string().contains("failed to parse BenchSpec JSON"));
84    }
85
86    #[test]
87    fn reports_unknown_benchmark() {
88        let error = run_benchmark_json(
89            r#"{"name":"definitely_missing_browser_benchmark","iterations":1,"warmup":0}"#,
90        )
91        .expect_err("unknown function should fail");
92
93        assert!(matches!(
94            error,
95            BrowserRunnerError::Benchmark(BenchError::UnknownFunction(_, _))
96        ));
97    }
98}