|
| 1 | +# `twenty-first` wasm32 integration |
| 2 | + |
| 3 | +This document provides a summary of changes that were required to get |
| 4 | +the twenty-first crate to build and run tests for wasm32 target. |
| 5 | + |
| 6 | + |
| 7 | +## 1\. Summary of `Cargo.toml` Changes |
| 8 | + |
| 9 | +To support both native and `wasm32` environments, the `Cargo.toml` file was restructured to use target-specific dependency sections. This allows us to have different configurations and dependencies for each compilation target. |
| 10 | + |
| 11 | +### General Dependencies for Wasm |
| 12 | + |
| 13 | +A new section, `[target.'cfg(target_arch = "wasm32")'.dependencies]`, was added to define dependencies that are only needed when compiling for WebAssembly: |
| 14 | + |
| 15 | +- `getrandom = { ..., features = ["js"] }`: Provides an entropy source required by the `rand` crate by linking to JavaScript's Web Crypto API. |
| 16 | +- `wasm-bindgen`: The core library enabling communication between Rust and JavaScript. |
| 17 | + |
| 18 | +```toml |
| 19 | +[target.'cfg(target_arch = "wasm32")'.dependencies] |
| 20 | +getrandom = { version = "0.3", features = ["wasm_js"] } |
| 21 | +wasm-bindgen = "=0.2.100" |
| 22 | +``` |
| 23 | + |
| 24 | +### Target-Specific Development Dependencies |
| 25 | + |
| 26 | +The `[dev-dependencies]` have been split into two target-specific sections to handle the different needs of testing in native vs. Wasm environments. |
| 27 | + |
| 28 | +- When targeting `wasm32`, we use `[target.'cfg(target_arch = "wasm32")'.dev-dependencies]`: |
| 29 | + |
| 30 | + - `criterion` and `proptest` are included with `default-features = false` to ensure they are `no_std`-compatible. |
| 31 | + - `wasm-bindgen-test` is included to provide the test runner for the Wasm environment. |
| 32 | + |
| 33 | +- For all other targets (i.e., native builds), we use `[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]`: |
| 34 | + |
| 35 | + - This section contains the standard versions of `criterion` and `proptest` with all their default features enabled for native benchmarking and testing. |
| 36 | + |
| 37 | +## 2\. Rationale for Forked `proptest-arbitrary-interop` |
| 38 | + |
| 39 | +The `twenty-first` crate uses property-based testing via the `proptest` library. To generate arbitrary instances of external types (like `num_bigint::BigUint`), we rely on the `proptest-arbitrary-interop` crate. |
| 40 | + |
| 41 | +The official version of this crate on `crates.io` lacks `wasm32` support. To enable our property tests to run in a WebAssembly environment, we use a forked version of the crate. This fork specifically adds conditional dependencies for the `wasm32` target. |
| 42 | + |
| 43 | +As shown in the fork's `Cargo.toml`, when compiling for `wasm32` it correctly pulls in `proptest` with its `std` feature enabled and adds `getrandom` with the `js` feature, which is necessary for the tests to compile and run successfully. |
| 44 | + |
| 45 | +```toml |
| 46 | +# In the forked proptest-arbitrary-interop/Cargo.toml |
| 47 | +[target.'cfg(target_arch = "wasm32")'.dependencies] |
| 48 | +proptest = { version = "1.2.0", default-features = false, features = ["std"] } |
| 49 | +# convince getrandom to build for wasm target. |
| 50 | +# note that there is also a flag in .cargo/config.toml |
| 51 | +getrandom = { version = "0.2", features = ["js"] } |
| 52 | +``` |
| 53 | + |
| 54 | +The dependency is specified in our `Cargo.toml` to point directly to the git repository containing the fork: |
| 55 | + |
| 56 | +```toml |
| 57 | +# In twenty-first/Cargo.toml |
| 58 | +[dev-dependencies] |
| 59 | +proptest-arbitrary-interop = { git = "https://github.com/dan-da/proptest-arbitrary-interop" } |
| 60 | +``` |
| 61 | + |
| 62 | +### PR Raised to `proptest-arbitrary-interop` |
| 63 | + |
| 64 | +[PR #3](https://github.com/graydon/proptest-arbitrary-interop/pull/3) has been raised to the [proptest-arbitrary-interop repo |
| 65 | +](https://github.com/graydon/proptest-arbitrary-interop). So if/when it is included in a new release on crates.io |
| 66 | +twenty-first can again use the official version. |
| 67 | + |
| 68 | +## 3\. Code Changes for Wasm Testing |
| 69 | + |
| 70 | +The WebAssembly environment uses its own test harness (`wasm-pack test`) which ignores `#[test]` and looks for tests marked with `#[wasm_bindgen_test]` instead. |
| 71 | +Yet our test suite should run in both the regular `cargo test` harness and the `wasm-pack test` harness. |
| 72 | + |
| 73 | +## Dual Test Harness Compatibility |
| 74 | + |
| 75 | +The core change is to conditionally add the `#[wasm_bindgen_test]` attribute to our existing tests, which already have the `#[test]` or `#[proptest]` attributes. This is accomplished using the `#[cfg_attr]` attribute, which applies another attribute only when a certain configuration is met. |
| 76 | + |
| 77 | +This pattern allows a single test function to be recognized by both test harnesses: |
| 78 | + |
| 79 | +1. The native harness sees `#[test]` (or `#[proptest]`) and runs the test. |
| 80 | +2. The Wasm harness sees `#[wasm_bindgen_test]` and runs the test. |
| 81 | +3. Each harness ignores the attribute it doesn't recognize. |
| 82 | + |
| 83 | +Here is the specific pattern used throughout the codebase: |
| 84 | + |
| 85 | +```rust |
| 86 | +// This 'use' statement is only included when compiling Wasm tests. |
| 87 | +#[cfg(all(test, target_arch = "wasm32"))] |
| 88 | +use wasm_bindgen_test::wasm_bindgen_test; |
| 89 | + |
| 90 | +// The #[wasm_bindgen_test] attribute is conditionally added only when building |
| 91 | +// for the wasm32 target. This prevents compilation errors on native builds. |
| 92 | +#[cfg_attr(all(test, target_arch = "wasm32"), wasm_bindgen_test)] |
| 93 | +// The standard #[test] attribute is always present for the native test runner. |
| 94 | +# [test] |
| 95 | +fn my_dual_target_test() { |
| 96 | + // ... test logic ... |
| 97 | +} |
| 98 | + |
| 99 | +// The same pattern applies to proptests: |
| 100 | +# [cfg_attr(all(test, target_arch = "wasm32"), wasm_bindgen_test)] |
| 101 | +# [proptest] |
| 102 | +fn my_dual_target_proptest() { |
| 103 | + // ... test logic ... |
| 104 | +} |
| 105 | + |
| 106 | +By using `#[cfg_attr(...)]` and a conditional `use` statement, we make our test suite compatible with the Wasm environment without breaking the existing native test workflow. |
0 commit comments