From 078c4d95138d86237e409ea6db21b8ee2fd19fb0 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Tue, 30 Jul 2024 22:31:02 +0100 Subject: [PATCH] Tests --- Cargo.lock | 7 + leptos_workers/Cargo.toml | 7 +- leptos_workers/tests/assets/test_image.jpg | Bin 0 -> 1523 bytes leptos_workers/tests/worker.rs | 341 +++++++++++++++++++-- scripts/test.sh | 11 + 5 files changed, 335 insertions(+), 31 deletions(-) create mode 100644 leptos_workers/tests/assets/test_image.jpg create mode 100755 scripts/test.sh diff --git a/Cargo.lock b/Cargo.lock index d825eab..7acd45d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,6 +470,7 @@ dependencies = [ "gloo-timers 0.3.0", "js-sys", "leptos_workers_macro", + "paste", "pollster", "serde", "serde-wasm-bindgen 0.6.5", @@ -557,6 +558,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" diff --git a/leptos_workers/Cargo.toml b/leptos_workers/Cargo.toml index 7dd0cf6..53fa56b 100644 --- a/leptos_workers/Cargo.toml +++ b/leptos_workers/Cargo.toml @@ -59,16 +59,19 @@ features = [ "WorkerLocation", "WorkerOptions", "WorkerType", + "MessageChannel", "MessagePort", "WritableStream", "TransformStream", "ImageBitmap", "OffscreenCanvas", - "RtcDataChannel" + "RtcDataChannel", + "RtcPeerConnection", ] [dev-dependencies] gloo-timers = { version = "0.3", features = ["futures"]} trybuild = "1.0" pollster = "0.3" -wasm-bindgen-test = "0.3" \ No newline at end of file +wasm-bindgen-test = "0.3" +paste = "1.0" diff --git a/leptos_workers/tests/assets/test_image.jpg b/leptos_workers/tests/assets/test_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0655464557e475570059e126c5d01ad5cfe6897e GIT binary patch literal 1523 zcmbu7ZB&wH7{~ADfyYp^feD=0nKRU6l*9M3OdnJ{nmRFGdNI>{qiNYJwqB08M{;J6 ztrxXSUC1#))K*!@OhwIF+L%VfM92qk80Gee~2>iPs z7-xQPAzAt`gvb3OfXFL6Ik0_f-p<0)*J`_F%__=&%>Y=aRPz7&=CsGa7H3A1abyq# z`X<;;BB5Ps2#_=v#(6x?y=h42)CbbM?GxpmnY!eFIYL`OV!vmNm8!m2_Y^wlm*s^G zUJ%)FD)XAgB(%CYXz%Q?mi)`;plq>OwTpdOocQ!(YNwkbS=SP`9?BXkYsQ@lp)KxE zr+KOCz#SB^!^tk~l-rzbm;D*^p`NbH_t2(FHQRrQl;j2Z2A)oJ|MjEltz)j`-cyBX zZ0+!Q2HZR3<`^=P)PF%SHTr7o65it;XmhR7GW{ne6LY+tQ4&+i`bRgY|KM#??&eqC z3HW4*v0`6q%c<}kQS);G!jjogBo+a_wn%wwquiA%Us~n)#lsh0=@TZO<$X4aDNW$S z#N9gV8~2vt_G<@4CwdFigRLk9kZ2In1Ax7Tw_Gq&;U{`h^;|~X@m(IiG;75kHQIjr ziCfV*<9WMWBj1_^d%;o@FtEF!(jzX}0+A+XZ&_v$my~)B8Bq4i7>B(!(P` z>46RW$c95pHmFw}Ng>@C2DBSsJ4*C{s;{1z&vk8+%G@3|*BsryI=#Hw5zKgAVUO>B zv3zZC$*p>A@Ap3>R9B=ByN}lBlQg^#pFZ-F2Vo{Xk9vjN@2GtCb4m$Y2EV$$<(#oeW2nID7I&= zWJ-k1FO60wo!>@N_9g9t>1=|k<&iPwfPM(tQ{ShHRrHTU>1?ePl4xouHO4oALNfnT z9y}L0aV%B4YFUj{!+L&%04rU|1S)vEXQqmC#X0b&lV%kFB;@*UCCY(t;!5e6#hVN2 z?!yH+TcLZ1m8ubYQ2*W`xv%0|mc%TKTG~~|&rWU^j!b;I3Al5?rr=>{YsZ&G$^i=p zjWu6T*px9VTCj))87>X4o&8!p%dpBXY3MquKQ3{d5D(GoFB%j~6hIU}uEQsIAiqbS zt{F%wu*~a#2k{mVIWZz%*l&E=>wBWZ|NWmkn+zGon9Wgp<|VC-=Y_J>kQXNRxz9sT z3auLe7z-XNYT*HZM^KBK;JL?O5M*Dl8F|r4Zz|@EALbj&(48WZB%B&)`zlMaQbn&4xFh zJhd}ze1CUs@hU&Yg;p^{XNTL|k9ve!vMF|yJP$OPBVp9aS@xL5A)bSJ+%0NYPR~H! zGKj=W*^jMd56RrbZ4VMy8=Y#GNwU4bRk@->3J9=N$b#ikf=?Ma*GX=kzcg&~YhlB? z$}acJYye9DL18NgjvGbwW5dKD8}Yi>fJT&y;v$q*OM!`r}2 Oc!rUWu>c4%4gUpAHKZ#5 literal 0 HcmV?d00001 diff --git a/leptos_workers/tests/worker.rs b/leptos_workers/tests/worker.rs index dc14a3f..1154ade 100644 --- a/leptos_workers/tests/worker.rs +++ b/leptos_workers/tests/worker.rs @@ -1,8 +1,13 @@ #![allow(clippy::self_assignment)] +use std::collections::HashMap; + +use futures::future::join_all; use gloo_timers::future::TimeoutFuture; +use js_sys::Uint8Array; use leptos_workers_macro::worker; use serde::{Deserialize, Serialize}; +use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -239,46 +244,324 @@ fn should_panic_on_ssr() { pollster::block_on(async { future_worker(TestRequest(5)).await.unwrap() }); } +/// Seeded simple random byte generator fns. +fn seeded_rand_byte() -> impl FnMut() -> u8 { + let mut next = 0; + move || { + next = (next + 1) % 255; + next + } +} + #[derive(Clone, Serialize, Deserialize)] -struct TestTransferableMsg(#[serde(with = "leptos_workers::transferable")] Vec); +struct TransMsgArrWithCheck( + #[serde(with = "leptos_workers::transferable")] js_sys::Uint8Array, + Vec, +); + +impl TransMsgArrWithCheck { + pub fn check(&self) { + assert_eq!(self.0.length(), self.1.len() as u32); + for (i, byte) in self.1.iter().enumerate() { + assert_eq!(self.0.get_index(i as u32), *byte); + } + } +} -#[cfg(not(feature = "ssr"))] -#[worker(TestTransferableWorker)] -async fn worker_with_transferable_data(req: TestTransferableMsg) -> TestTransferableMsg { - test_transferable_vec(req.0.clone()); +#[worker] +async fn worker_contended(req: TransMsgArrWithCheck) -> TransMsgArrWithCheck { + req.check(); req } -// Verify the test data: 10 items, each arr with 10 elements, all ordered by index: -#[cfg(not(feature = "ssr"))] -fn test_transferable_vec(vec: Vec) { - assert_eq!(vec.len(), 10); - for (x, arr) in vec.into_iter().enumerate() { - assert_eq!(arr.length(), 10); - for y in 0..10 { - assert_eq!(arr.get_index(y as u32), (x * 10 + y) as u8); +#[wasm_bindgen_test] +async fn test_transferable_whilst_contended() { + // Will concurrently call the worker this many times with varying requests: + let num_calls = 3; + + let mut get_next_byte = seeded_rand_byte(); + let mut reqs = Vec::with_capacity(num_calls); + for _ in 0..num_calls { + let mut vec = Vec::with_capacity(10); + for _ in 0..10 { + vec.push(get_next_byte()); } + let js_arr = js_sys::Uint8Array::new_with_length(10); + js_arr.copy_from(&vec); + reqs.push(TransMsgArrWithCheck(js_arr, vec)); + } + // Process all via join simulating concurrency/contended serialization/deserialization: + for result in join_all(reqs.into_iter().map(worker_contended)).await { + result.unwrap().check(); } } -/// Test sending uint8 arrays to a worker and back via transfer. -/// The response uses the same transferable objects, checking re-use works too. -#[cfg(not(feature = "ssr"))] -#[wasm_bindgen_test] -async fn transferable_test() { - // Test data, 10 items, each arr with 10 elements, all ordered by index: - let mut vec = Vec::new(); - for x in 0..10 { +/// Standard worker test, passing an object containing transferables to a worker and back again. +macro_rules! transferable_test { + ($id:ident, $typ:ty, $create:block, $verify:expr) => { + paste::item! { + #[allow(non_camel_case_types)] + #[derive(Clone, Serialize, Deserialize)] + struct [< TransMsg_ $id >](#[serde(with = "leptos_workers::transferable")] $typ); + + #[worker] + async fn [< worker_fn_ $id >](req: [< TransMsg_ $id >]) -> [< TransMsg_ $id >] { + ($verify)(req.0.clone()); + req + } + + #[wasm_bindgen_test] + async fn [< test_transferable_ $id >]() { + let contents = $create; + ($verify)(contents.clone()); + let response_msg = [< worker_fn_ $id >]([< TransMsg_ $id >](contents)) + .await + .unwrap(); + ($verify)(response_msg.0); + } + } + }; +} + +transferable_test! { + complex, + HashMap>>>, + { + let mut get_next_byte = seeded_rand_byte(); + let mut map = HashMap::new(); + for x in 0..10 { + let mut vec = Vec::new(); + for _y in 0..10 { + let mut inner_map = HashMap::new(); + for z in 0..10 { + let uint8_array = js_sys::Uint8Array::new(&js_sys::ArrayBuffer::new(10)); + for w in 0..10 { + uint8_array.set_index(w as u32, get_next_byte()); + } + inner_map.insert(z.to_string(), Some(uint8_array)); + } + vec.push(inner_map); + } + map.insert(x.to_string(), vec); + } + map + }, + |map: HashMap>>>| { + let mut get_next_byte = seeded_rand_byte(); + + assert_eq!(map.len(), 10); + for x in 0..10 { + let vec = map.get(&x.to_string()).unwrap(); + assert_eq!(vec.len(), 10); + for inner_map in vec.iter() { + assert_eq!(inner_map.len(), 10); + for z in 0..10 { + let uint8_array = inner_map.get(&z.to_string()).unwrap().as_ref().unwrap(); + assert_eq!(uint8_array.length(), 10); + for w in 0..10 { + assert_eq!(uint8_array.get_index(w as u32), get_next_byte()); + } + } + } + } + } +} + +transferable_test! { + uint8_array, + js_sys::Uint8Array, + { let uint8_array = js_sys::Uint8Array::new(&js_sys::ArrayBuffer::new(10)); - for y in 0..10 { - uint8_array.set_index(y as u32, x * 10 + y); + for x in 0..10 { + uint8_array.set_index(x as u32, x); + } + uint8_array + }, + |arr: js_sys::Uint8Array| { + assert_eq!(arr.length(), 10); + for x in 0..10 { + assert_eq!(arr.get_index(x as u32), x); + } + } +} + +transferable_test! { + arb_js, + JsValue, + { + let js_val: JsValue = js_sys::ArrayBuffer::new(10).into(); + js_val + }, + |val: JsValue| { + let arr = val.dyn_into::().unwrap(); + assert_eq!(arr.byte_length(), 10); + } +} + +transferable_test! { + option_t_some, + Option, + { + Some(js_sys::ArrayBuffer::new(10)) + }, + |opt: Option| { + let arr = opt.unwrap(); + assert_eq!(arr.byte_length(), 10); + } +} + +transferable_test! { + option_t_none, + Option, + { + None + }, + |opt: Option| { + assert!(opt.is_none()); + } +} + +transferable_test! { + vec_of_t, + Vec, + { + // 10 items, each arr with 10 elements, all ordered by index: + let mut vec = Vec::new(); + for x in 0..10 { + let uint8_array = js_sys::Uint8Array::new(&js_sys::ArrayBuffer::new(10)); + for y in 0..10 { + uint8_array.set_index(y as u32, x * 10 + y); + } + vec.push(uint8_array); + } + vec + }, + |vec: Vec| { + assert_eq!(vec.len(), 10); + for (x, arr) in vec.into_iter().enumerate() { + assert_eq!(arr.length(), 10); + for y in 0..10 { + assert_eq!(arr.get_index(y as u32), (x * 10 + y) as u8); + } + } + } +} + +transferable_test! { + hashmap, + HashMap, + { + let mut map = HashMap::new(); + for x in 0..10 { + let uint8_array = js_sys::Uint8Array::new(&js_sys::ArrayBuffer::new(10)); + for y in 0..10 { + uint8_array.set_index(y as u32, x * 10 + y); + } + map.insert(x.to_string(), uint8_array); + } + map + }, + |map: HashMap| { + assert_eq!(map.len(), 10); + for (x, arr) in map.into_iter() { + assert_eq!(arr.length(), 10); + for y in 0..10 { + assert_eq!(arr.get_index(y as u32), (x.parse::().unwrap() * 10 + y)); + } } - vec.push(uint8_array); } +} - test_transferable_vec(vec.clone()); - let response_msg = worker_with_transferable_data(TestTransferableMsg(vec)) - .await - .unwrap(); - test_transferable_vec(response_msg.0); +// All the base-level types: +transferable_test! { + array_buffer, + js_sys::ArrayBuffer, + { + js_sys::ArrayBuffer::new(10) + }, + |arr: js_sys::ArrayBuffer| { + assert_eq!(arr.byte_length(), 10); + } } +transferable_test! { + message_port, + web_sys::MessagePort, + { + let channel = web_sys::MessageChannel::new().unwrap(); + channel.port1() + }, + |port: web_sys::MessagePort| { + assert!(port.is_instance_of::()); + } +} +transferable_test! { + readable_stream, + web_sys::ReadableStream, + { + web_sys::Blob::new().unwrap().stream() + }, + |stream: web_sys::ReadableStream| { + assert!(stream.is_instance_of::()); + } +} +transferable_test! { + writable_stream, + web_sys::WritableStream, + { + web_sys::WritableStream::new().unwrap() + }, + |stream: web_sys::WritableStream| { + assert!(stream.is_instance_of::()); + } +} +transferable_test! { + transform_stream, + web_sys::TransformStream, + { + web_sys::TransformStream::new().unwrap() + }, + |stream: web_sys::TransformStream| { + assert!(stream.is_instance_of::()); + } +} +transferable_test! { + image_bitmap, + web_sys::ImageBitmap, + { + let jpg_data = include_bytes!("./assets/test_image.jpg"); + let arr = Uint8Array::new_with_length(jpg_data.len() as u32); + arr.copy_from(jpg_data.as_slice()); + let parts = js_sys::Array::new(); + parts.push(&arr.buffer()); + let image = web_sys::Blob::new_with_u8_array_sequence_and_options(&parts, web_sys::BlobPropertyBag::new().type_("image/jpeg")).unwrap(); + wasm_bindgen_futures::JsFuture::from( + web_sys::window().unwrap().create_image_bitmap_with_blob(&image).unwrap() + ).await.unwrap().unchecked_into::() + }, + |bitmap: web_sys::ImageBitmap| { + assert!(bitmap.is_instance_of::()); + } +} +transferable_test! { + offscreen_canvas, + web_sys::OffscreenCanvas, + { + web_sys::OffscreenCanvas::new(100, 100).unwrap() + }, + |canvas: web_sys::OffscreenCanvas| { + assert!(canvas.is_instance_of::()); + } +} + +// Only actually supported by safari currently: +// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel#browser_compatibility +// transferable_test! { +// rtc_data_channel, +// web_sys::RtcDataChannel, +// { +// web_sys::RtcPeerConnection::new().unwrap().create_data_channel("test") +// }, +// |channel: web_sys::RtcDataChannel| { +// assert!(channel.is_instance_of::()); +// } +// } diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..71a3ebd --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Stop on error: +set -e + +cargo fmt --all +cargo fmt --all -- --check +cargo clippy --all-targets --all-features --workspace +cargo test --all-features --workspace +wasm-pack test --node leptos_workers +wasm-pack test --chrome leptos_workers