Skip to content

Actually run doctests#14

Merged
drewcrawford merged 1 commit intomainfrom
doctests-2
Jan 15, 2026
Merged

Actually run doctests#14
drewcrawford merged 1 commit intomainfrom
doctests-2

Conversation

@drewcrawford
Copy link
Owner

@drewcrawford drewcrawford commented Jan 11, 2026

Summary

This adds support for doctests to the wasm-bindgen-test-runner.

But wait

Don't they work already? ...and they have for many years?

I certainly thought so, github issues suggest so, our docs say so, and you can try it at home:

cargo clean && CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER="$(pwd)/instrumented-runner.sh" cargo +nightly test --target=wasm32-unknown-unknown --doc -Zbuild-std="std,panic_abort"

running 1 test
test src/lib.rs - without_configure (line 17) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s

But they were, all of them, deceived, for another doctest was made...

test src/lib.rs - this_doctest_absolutely_panics (line 17) ... ok

... ok

It took me some quite some time to trace through the layers here in cargo, rustdoc, changes to doctest compilation in the 2024 edition, webassembly instructions, layers of configuration options that are difficult to trace, and the incorrect documentation I met along the way.

But here's the answer: cargo test --doc does this:

wasm-bindgen-test-runner path/to/this_doctest_panics.wasm

which is a wasm file with a main function that runs that one doctest.

And our runner doesn't run it, instead it helpfully chirps

no tests to run!

which no one ever sees, because it also exits with code 0, which tells the layers above it "this doctest passed, no need to print its output"

Instead we need to actually run doctests.

Solution overview

The key insight is that doctests are compiled differently from regular #[wasm_bindgen_test] tests:

  • Regular tests export functions prefixed with _wbgt that the test runner discovers and executes via the WasmBindgenTestContext infrastructure
  • Doctests export a single main function that should be called directly

The fix detects doctests by checking for a main export when no _wbgt* tests are found, then executes them appropriately based on the configured test mode.

Changes

  1. Doctest detection (wasm_bindgen_test_runner.rs): When no _wbgt* tests are found, check if the wasm has both a main export AND a _doctest_main* function symbol (generated by rustdoc). Requiring both prevents false positives from cfg'd-out test files that may have a main export but aren't actually doctests.
  2. Node.js execution (doctest.rs): New module that generates JavaScript to load the wasm module and call main(). Handles both CommonJS and ES module formats.
  3. Deno execution (doctest.rs): Support for running doctests in Deno using ES module imports via wasm.__wasm.main().
  4. Browser execution (server.rs): New spawn_doctest() function that serves a simple page which loads wasm and calls main(). Supports:
    - Browser main thread mode (run_in_browser)
    - Dedicated worker mode (run_in_dedicated_worker)
    - Shared worker mode (run_in_shared_worker)
    - Service worker mode (run_in_service_worker) - forced to no_modules for Firefox < 147 compatibility
  5. Fallback execution (doctest.rs): When wasm-bindgen CLI fails to process the wasm file (e.g., when the doctest imports wasm-bindgen types but doesn't actually use them at runtime), a fallback mode provides stub implementations for wasm-bindgen imports and executes the wasm directly. The wasm trap behavior indicates pass/fail.
  6. Test mode detection: Doctests respect the wasm_bindgen_test_configure! macro just like regular tests - the __wasm_bindgen_test_unstable custom section is read to determine if the doctest should run in Node.js, Deno, browser, or a worker.
  7. CI update: Added Deno to the test_native CI job.

Test plan

Tests are in crates/cli/tests/wasm-bindgen-test-runner/doctests.rs. They build doctests from source using cargo +nightly test --doc with --persist-doctests to capture the generated wasm files, then run wasm-bindgen-test-runner on them.

Test Mode Configuration
test_doctest_node Node.js (CommonJS) Default (no configure macro)
test_doctest_node_experimental Node.js (ES modules) wasm_bindgen_test_configure!(run_in_node_experimental)
test_doctest_browser Browser main thread wasm_bindgen_test_configure!(run_in_browser)
test_doctest_dedicated_worker Browser dedicated worker wasm_bindgen_test_configure!(run_in_dedicated_worker)
test_doctest_shared_worker Browser shared worker wasm_bindgen_test_configure!(run_in_shared_worker)
test_doctest_service_worker Browser service worker wasm_bindgen_test_configure!(run_in_service_worker)
test_doctest_deno Deno WASM_BINDGEN_USE_DENO=1 env var

Each test verifies:

  • Doctest is detected (running 1 doctest)
  • Console output is captured
  • Test passes (test result: ok)
  • Exit code is 0

notes

  • Service workers use no_modules mode: Firefox < 147 doesn't support ES module service workers (https://bugzilla.mozilla.org/show_bug.cgi?id=1360870). Since GitHub Actions runners currently have Firefox 146, service worker doctests are forced to use importScripts() (classic mode) instead of ES module imports.
  • Deno tests require Deno: The Deno test will skip gracefully if Deno is not installed.
  • Nightly required: All doctest tests require nightly Rust for --persist-doctests and will skip if nightly is unavailable.

@drewcrawford drewcrawford force-pushed the doctests-2 branch 6 times, most recently from 2142951 to 408ee46 Compare January 14, 2026 23:56
@drewcrawford drewcrawford merged commit 4ce169b into main Jan 15, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant