diff --git a/Cargo.lock b/Cargo.lock index cdc6fc79..1883fa09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5879,6 +5879,7 @@ dependencies = [ "gloo-utils 0.2.0", "js-sys", "lazy_static", + "parking_lot 0.12.1", "send_wrapper 0.6.0", "serde", "serde-wasm-bindgen", diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 06313daf..059cfd70 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -39,3 +39,4 @@ serde-wasm-bindgen = "0.5.0" [dev-dependencies] wasm-bindgen-test = "^0" +parking_lot = "0.12.1" diff --git a/veilid-wasm/tests/web.rs b/veilid-wasm/tests/web.rs index 8022f2bb..0c251687 100644 --- a/veilid-wasm/tests/web.rs +++ b/veilid-wasm/tests/web.rs @@ -1,182 +1,218 @@ //! Test suite for the Web and headless browsers. - -//XXXXXXXXXXXXXXX -//XXX DOES NOT WORK. +//! These tests only work with WASM_BINDGEN_USE_NO_MODULE=true env var, +//! as otherwise there's no way to access the generated wasm bindings from inside JS. #![cfg(target_arch = "wasm32")] extern crate alloc; extern crate wasm_bindgen_test; -use core::sync::atomic::AtomicBool; +use js_sys::*; +use parking_lot::Once; use veilid_wasm::*; +use wasm_bindgen::*; +use wasm_bindgen_futures::*; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); -static SETUP_ONCE: AtomicBool = AtomicBool::new(false); +static SETUP_ONCE: Once = Once::new(); pub fn setup() -> () { - if SETUP_ONCE - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - console_log("setup()"); + SETUP_ONCE.call_once(|| { + console_log!("setup()"); console_error_panic_hook::set_once(); - wasm_logger::init(wasm_logger::Config::new(Level::Trace)); init_callbacks(); - } + }) } -// xxx needs updating to new keys and veilid_api object + fn init_callbacks() { assert_eq!(js_sys::eval(r#" window.sleep = (milliseconds) => { return new Promise(resolve => setTimeout(resolve, milliseconds)) }; - window.stateChangeCallback = async (stateChange) => { console.log("State change: " + stateChange); }; - window.configCallback = (configKey) => { - switch(configKey) { - case "namespace": return ""; - case "capabilities.disable": return []; - case "tablestore.directory": return ""; - case "network.routing_table.node_id": return []; - case "network.routing_table.node_id_secret": return []; - case "network.routing_table.bootstrap": return []; - case "network.routing_table.limit_over_attached": return 64; - case "network.routing_table.limit_fully_attached": return 32; - case "network.routing_table.limit_attached_strong": return 16; - case "network.routing_table.limit_attached_good": return 8; - case "network.routing_table.limit_attached_weak": return 4; - case "network.rpc.concurrency": return 2; - case "network.rpc.queue_size": return 128; - case "network.rpc.max_timestamp_behind": return 10000000; - case "network.rpc.max_timestamp_ahead": return 10000000; - case "network.rpc.timeout": return 10000000; - case "network.rpc.max_route_hop_count": return 4; - case "network.rpc.default_route_hop_count": return 1; - case "network.dht.max_find_node_count": return 20; - case "network.dht.resolve_node_timeout": return 10000; - case "network.dht.resolve_node_count": return 1; - case "network.dht.resolve_node_fanout": return 4; - case "network.dht.get_value_timeout": return 10000; - case "network.dht.get_value_count": return 3; - case "network.dht.get_value_fanout": return 4; - case "network.dht.set_value_timeout": return 10000; - case "network.dht.set_value_count": return 5; - case "network.dht.set_value_fanout": return 4; - case "network.dht.min_peer_count": return 20; - case "network.dht.min_peer_refresh_time": return 2000000; - case "network.dht.validate_dial_info_receipt_time": return 5000000; - case "network.upnp": return false; - case "network.detect_address_changes": return true; - case "network.address_filter": return true; - case "network.restricted_nat_retries": return 3; - case "network.tls.certificate_path": return ""; - case "network.tls.private_key_path": return ""; - case "network.application.path": return "/app"; - case "network.application.https.enabled": return false; - case "network.application.https.listen_address": return ""; - case "network.application.http.enabled": return false; - case "network.application.http.listen_address": return ""; - case "network.protocol.udp.enabled": return false; - case "network.protocol.udp.socket_pool_size": return 0; - case "network.protocol.udp.listen_address": return ""; - case "network.protocol.udp.public_address": return ""; - case "network.protocol.tcp.connect": return false; - case "network.protocol.tcp.listen": return false; - case "network.protocol.tcp.max_connections": return 32; - case "network.protocol.tcp.listen_address": return ""; - case "network.protocol.tcp.public_address": return ""; - case "network.protocol.ws.connect": return true; - case "network.protocol.ws.listen": return false; - case "network.protocol.ws.max_connections": return 16; - case "network.protocol.ws.listen_address": return ""; - case "network.protocol.ws.path": return "/ws"; - case "network.protocol.ws.public_address": return ""; - case "network.protocol.wss.connect": return true; - case "network.protocol.wss.listen": return false; - case "network.protocol.wss.max_connections": return 16; - case "network.protocol.wss.listen_address": return ""; - case "network.protocol.wss.path": return "/ws"; - case "network.protocol.wss.public_address": return ""; - default: - console.log("config key '" + key +"' doesn't exist"); break; - } + window.stateChangeCallback = async (stateChange) => { + delete stateChange.peers; // makes logs less verbose + console.log("State change: ", JSON.stringify(stateChange, null, 2)); }; + window.veilidCoreInitConfig = { + logging: { + api: { + enabled: true, + level: 'Info', + }, + performance: { + enabled: false, + level: 'Info', + logs_in_timings: false, + logs_in_console: false, + }, + }, + }; + + window.veilidCoreStartupConfig = { + program_name: 'veilid-wasm-test', + namespace: '', + capabilities: { + disable: [], + }, + protected_store: { + allow_insecure_fallback: true, + always_use_insecure_storage: true, + directory: '', + delete: false, + device_encryption_key_password: 'some-user-secret-value', + // "new_device_encryption_key_password": "an-updated-user-secret-value" + }, + table_store: { + directory: '', + delete: false, + }, + block_store: { + directory: '', + delete: false, + }, + network: { + connection_initial_timeout_ms: 2000, + connection_inactivity_timeout_ms: 60000, + max_connections_per_ip4: 32, + max_connections_per_ip6_prefix: 32, + max_connections_per_ip6_prefix_size: 56, + max_connection_frequency_per_min: 128, + client_whitelist_timeout_ms: 300000, + reverse_connection_receipt_time_ms: 5000, + hole_punch_receipt_time_ms: 5000, + network_key_password: '', + disable_capabilites: [], + routing_table: { + node_id: [], + node_id_secret: [], + bootstrap: [ + 'ws://bootstrap.veilid.net:5150/ws', + ], + limit_over_attached: 64, + limit_fully_attached: 32, + limit_attached_strong: 16, + limit_attached_good: 8, + limit_attached_weak: 4, + }, + rpc: { + concurrency: 0, + queue_size: 1024, + max_timestamp_behind_ms: 10000, + max_timestamp_ahead_ms: 10000, + timeout_ms: 5000, + max_route_hop_count: 4, + default_route_hop_count: 1, + }, + dht: { + max_find_node_count: 20, + resolve_node_timeout_ms: 10000, + resolve_node_count: 1, + resolve_node_fanout: 4, + get_value_timeout_ms: 10000, + get_value_count: 3, + get_value_fanout: 4, + set_value_timeout_ms: 10000, + set_value_count: 5, + set_value_fanout: 4, + min_peer_count: 20, + min_peer_refresh_time_ms: 60000, + validate_dial_info_receipt_time_ms: 2000, + local_subkey_cache_size: 128, + local_max_subkey_cache_memory_mb: 256, + remote_subkey_cache_size: 1024, + remote_max_records: 65536, + remote_max_subkey_cache_memory_mb: 256, + remote_max_storage_space_mb: 0, + }, + upnp: true, + detect_address_changes: true, + restricted_nat_retries: 0, + tls: { + certificate_path: '', + private_key_path: '', + connection_initial_timeout_ms: 2000, + }, + application: { + https: { + enabled: false, + listen_address: ':5150', + path: 'app', + }, + http: { + enabled: false, + listen_address: ':5150', + path: 'app', + }, + }, + protocol: { + udp: { + enabled: false, + socket_pool_size: 0, + listen_address: '', + }, + tcp: { + connect: false, + listen: false, + max_connections: 32, + listen_address: '', + }, + ws: { + connect: true, + listen: true, + max_connections: 16, + listen_address: ':5150', + path: 'ws', + }, + wss: { + connect: true, + listen: false, + max_connections: 16, + listen_address: '', + path: 'ws', + }, + }, + }, + }; true "#).expect("failed to eval"), JsValue::TRUE); } -/////////////////////////////////////////////////////////////////////////////////////////////////// -/// - -#[wasm_bindgen_test] -fn test_construct() { - setup(); - - assert_eq!( - js_sys::eval( - r#" - let vc = new VeilidCore(); - true - "# - ) - .expect("failed to eval"), - JsValue::TRUE - ); -} - -#[wasm_bindgen_test(async)] -async fn test_startup_shutdown() { - setup(); - - assert_eq!( - JsFuture::from( - js_sys::eval( - r#" - (async function() { - let vc = new VeilidCore(); - await vc.startup(window.stateChangeCallback, window.configCallback); - await vc.shutdown(); - return true; - })().then(v => { - console.log("finished: " + v); - return v; - }); - "# - ) - .expect("failed to eval") +/// Helper for converting an eval Promise result into a JsValue +async fn eval_promise(source: &str) -> JsValue { + JsFuture::from( + eval(source) + .expect("Failed to eval") .dyn_into::() - .unwrap() - ) - .await, - Ok(JsValue::TRUE) - ); + .unwrap(), + ) + .await + .unwrap() } -#[wasm_bindgen_test(async)] -async fn test_attach_detach() { +// ---------------------------------------------------------------- + +// TODO: now that veilidClient uses a single instance of VeilidAPI, +// subsequent tests fail because veilidCore has already been initialized. +#[wasm_bindgen_test()] +async fn test_kitchen_sink() { setup(); - assert_eq!( - JsFuture::from( - js_sys::eval( - r#" - (async function() { - let vc = new VeilidCore(); - await vc.startup(window.stateChangeCallback, window.configCallback); - await vc.attach(); - await window.sleep(1000); - await vc.detach(); - await vc.shutdown(); - return true; - })().then(v => { - console.log("finished: " + v); - return v; - }); - "# - ) - .expect("failed to eval") - .dyn_into::() - .unwrap() - ) - .await, - Ok(JsValue::TRUE) - ); + let res = eval_promise( + r#" + (async function () { + const { veilidClient } = wasm_bindgen; // only accessible in no_module mode. + veilidClient.initializeCore(window.veilidCoreInitConfig); + await veilidClient.startupCore(window.stateChangeCallback, JSON.stringify(window.veilidCoreStartupConfig)); + + console.log(veilidClient.versionString()); + await veilidClient.attach(); + + await sleep(10000); + await veilidClient.detach(); + await veilidClient.shutdownCore(); + + return true; + })(); + "#, + ).await; + + assert_eq!(res, JsValue::TRUE); } diff --git a/veilid-wasm/wasm_test.sh b/veilid-wasm/wasm_test.sh new file mode 100755 index 00000000..27209505 --- /dev/null +++ b/veilid-wasm/wasm_test.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -eo pipefail + +WASM_BINDGEN_USE_NO_MODULE=true wasm-pack test --firefox "$@"