From ee54358c275dc5748aac3e0aa3a936daa3831710 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 14 Mar 2024 21:30:09 -0400 Subject: [PATCH] wasm unit tests work and attachment manager fix --- veilid-core/src/attachment_manager.rs | 10 ++ .../src/routing_table/routing_table_inner.rs | 5 + veilid-core/src/storage_manager/mod.rs | 2 + veilid-core/src/veilid_api/types/safety.rs | 12 +- veilid-wasm/src/veilid_client_js.rs | 2 +- veilid-wasm/src/veilid_routing_context_js.rs | 19 ++- .../tests/src/VeilidRoutingContext.test.ts | 90 +++++++++--- veilid-wasm/tests/src/utils/veilid-config.ts | 134 ++---------------- veilid-wasm/tests/src/utils/wait-utils.ts | 58 ++++++++ veilid-wasm/tests/src/veilidClient.test.ts | 33 +++-- veilid-wasm/tests/src/veilidCrypto.test.ts | 2 +- 11 files changed, 195 insertions(+), 172 deletions(-) diff --git a/veilid-core/src/attachment_manager.rs b/veilid-core/src/attachment_manager.rs index 177c517a..aebb6749 100644 --- a/veilid-core/src/attachment_manager.rs +++ b/veilid-core/src/attachment_manager.rs @@ -179,7 +179,14 @@ impl AttachmentManager { fn update_attaching_detaching_state(&self, state: AttachmentState) { let update_callback = { let mut inner = self.inner.lock(); + + // Clear routing table health so when we start measuring it we start from scratch + inner.last_routing_table_health = None; + + // Set attachment state directly inner.last_attachment_state = state; + + // Set timestamps if state == AttachmentState::Attaching { inner.attach_ts = Some(get_aligned_timestamp()); } else if state == AttachmentState::Detached { @@ -189,9 +196,12 @@ impl AttachmentManager { } else { unreachable!("don't use this for attached states, use update_attachment()"); } + + // Get callback inner.update_callback.clone() }; + // Send update if let Some(update_callback) = update_callback { update_callback(VeilidUpdate::Attachment(Box::new(VeilidStateAttachment { state, diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index 1af08ccf..80c6ad1d 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -888,11 +888,16 @@ impl RoutingTableInner { } } + // Public internet routing domain is ready for app use, + // when we have proper dialinfo/networkclass let public_internet_ready = !matches!( self.get_network_class(RoutingDomain::PublicInternet) .unwrap_or_default(), NetworkClass::Invalid ); + + // Local internet routing domain is ready for app use + // when we have proper dialinfo/networkclass let local_network_ready = !matches!( self.get_network_class(RoutingDomain::LocalNetwork) .unwrap_or_default(), diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index dbb3ddb4..09f4af44 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -531,6 +531,8 @@ impl StorageManager { // Drop the lock for network access drop(inner); + log_stor!(debug "Writing subkey to the network: {}:{} len={}", key, subkey, signed_value_data.value_data().data().len() ); + // Use the safety selection we opened the record with let result = self .outbound_set_value( diff --git a/veilid-core/src/veilid_api/types/safety.rs b/veilid-core/src/veilid_api/types/safety.rs index 26ef4305..caa0b6f6 100644 --- a/veilid-core/src/veilid_api/types/safety.rs +++ b/veilid-core/src/veilid_api/types/safety.rs @@ -4,7 +4,11 @@ use super::*; #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, )] -#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi, namespace))] +#[cfg_attr( + target_arch = "wasm32", + derive(Tsify), + tsify(from_wasm_abi, into_wasm_abi, namespace) +)] pub enum Sequencing { NoPreference = 0, PreferOrdered = 1, @@ -21,7 +25,11 @@ impl Default for Sequencing { #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, )] -#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi, namespace))] +#[cfg_attr( + target_arch = "wasm32", + derive(Tsify), + tsify(from_wasm_abi, into_wasm_abi, namespace) +)] pub enum Stability { LowLatency = 0, Reliable = 1, diff --git a/veilid-wasm/src/veilid_client_js.rs b/veilid-wasm/src/veilid_client_js.rs index dcf4f275..8f061e14 100644 --- a/veilid-wasm/src/veilid_client_js.rs +++ b/veilid-wasm/src/veilid_client_js.rs @@ -222,7 +222,7 @@ impl VeilidClient { veilid_core::veilid_version_string() } - /// Return the default veilid configuration in json string format. + /// Return the default veilid configuration, in string format pub fn defaultConfig() -> String { veilid_core::default_veilid_config() } diff --git a/veilid-wasm/src/veilid_routing_context_js.rs b/veilid-wasm/src/veilid_routing_context_js.rs index 95495f76..77841c3c 100644 --- a/veilid-wasm/src/veilid_routing_context_js.rs +++ b/veilid-wasm/src/veilid_routing_context_js.rs @@ -101,6 +101,7 @@ impl VeilidRoutingContext { // -------------------------------- // Instance methods // -------------------------------- + fn getRoutingContext(&self) -> APIResult { APIResult::Ok(self.inner_routing_context.clone()) } @@ -323,13 +324,18 @@ impl VeilidRoutingContext { pub async fn watchDhtValues( &self, key: String, - subkeys: ValueSubkeyRangeSet, - expiration: String, - count: u32, + subkeys: Option, + expiration: Option, + count: Option, ) -> APIResult { let key = TypedKey::from_str(&key)?; - let expiration = - veilid_core::Timestamp::from_str(&expiration).map_err(VeilidAPIError::generic)?; + let subkeys = subkeys.unwrap_or_default(); + let expiration = if let Some(expiration) = expiration { + veilid_core::Timestamp::from_str(&expiration).map_err(VeilidAPIError::generic)? + } else { + veilid_core::Timestamp::default() + }; + let count = count.unwrap_or(u32::MAX); let routing_context = self.getRoutingContext()?; let res = routing_context @@ -349,9 +355,10 @@ impl VeilidRoutingContext { pub async fn cancelDhtWatch( &self, key: String, - subkeys: ValueSubkeyRangeSet, + subkeys: Option, ) -> APIResult { let key = TypedKey::from_str(&key)?; + let subkeys = subkeys.unwrap_or_default(); let routing_context = self.getRoutingContext()?; let res = routing_context.cancel_dht_watch(key, subkeys).await?; diff --git a/veilid-wasm/tests/src/VeilidRoutingContext.test.ts b/veilid-wasm/tests/src/VeilidRoutingContext.test.ts index 8d332798..07918144 100644 --- a/veilid-wasm/tests/src/VeilidRoutingContext.test.ts +++ b/veilid-wasm/tests/src/VeilidRoutingContext.test.ts @@ -12,7 +12,7 @@ import { veilidCrypto, } from 'veilid-wasm'; import { textEncoder, textDecoder } from './utils/marshalling-utils'; -import { waitForMs } from './utils/wait-utils'; +import { asyncCallWithTimeout, waitForPublicAttachment } from './utils/wait-utils'; describe('VeilidRoutingContext', () => { before('veilid startup', async () => { @@ -23,10 +23,12 @@ describe('VeilidRoutingContext', () => { // } }, JSON.stringify(veilidCoreStartupConfig)); await veilidClient.attach(); - await waitForMs(2000); + await asyncCallWithTimeout(waitForPublicAttachment(), 10000); + //console.log("---Started Up---"); }); after('veilid shutdown', async () => { + //console.log("---Shutting Down---"); await veilidClient.detach(); await veilidClient.shutdownCore(); }); @@ -35,22 +37,16 @@ describe('VeilidRoutingContext', () => { it('should create using .create()', async () => { const routingContext = VeilidRoutingContext.create(); expect(routingContext instanceof VeilidRoutingContext).toBe(true); - - routingContext.free(); }); it('should create using new', async () => { const routingContext = new VeilidRoutingContext(); expect(routingContext instanceof VeilidRoutingContext).toBe(true); - - routingContext.free(); }); it('should create with default safety', async () => { const routingContext = VeilidRoutingContext.create().withDefaultSafety(); expect(routingContext instanceof VeilidRoutingContext).toBe(true); - - routingContext.free(); }); it('should create with safety', async () => { @@ -62,16 +58,12 @@ describe('VeilidRoutingContext', () => { }, }); expect(routingContext instanceof VeilidRoutingContext).toBe(true); - - routingContext.free(); }); it('should create with sequencing', async () => { const routingContext = VeilidRoutingContext.create().withSequencing('EnsureOrdered'); expect(routingContext instanceof VeilidRoutingContext).toBe(true); - - routingContext.free(); }); }); @@ -79,19 +71,16 @@ describe('VeilidRoutingContext', () => { let routingContext: VeilidRoutingContext; before('create routing context', () => { - routingContext = VeilidRoutingContext.create() - .withSequencing('EnsureOrdered'); + routingContext = VeilidRoutingContext.create().withSafety({ + Unsafe: 'EnsureOrdered', + }); }); - after('free routing context', () => { - routingContext.free(); - }); - - describe('DHT kitchen sink', async () => { + describe('DHT kitchen sink', () => { let dhtRecord: DHTRecordDescriptor; const data = '🚀 This example DHT data with unicode a Ā 𐀀 文 🚀'; - before('create dht record', async () => { + beforeEach('create dht record', async () => { const bestKind = veilidCrypto.bestCryptoKind(); dhtRecord = await routingContext.createDhtRecord( { @@ -107,7 +96,7 @@ describe('VeilidRoutingContext', () => { expect(dhtRecord.schema).toEqual({ kind: 'DFLT', o_cnt: 1 }); }); - after('free dht record', async () => { + afterEach('free dht record', async () => { await routingContext.deleteDhtRecord(dhtRecord.key); }); @@ -121,6 +110,14 @@ describe('VeilidRoutingContext', () => { }); it('should get value with force refresh', async () => { + + const setValueRes = await routingContext.setDhtValue( + dhtRecord.key, + 0, + textEncoder.encode(data) + ); + expect(setValueRes).toBeUndefined(); + const getValueRes = await routingContext.getDhtValue( dhtRecord.key, 0, @@ -192,6 +189,57 @@ describe('VeilidRoutingContext', () => { ); expect(setValueRes).toBeUndefined(); }); + + it('should watch value and cancel watch', async () => { + const setValueRes = await routingContext.setDhtValue( + dhtRecord.key, + 0, + textEncoder.encode(data) + ); + expect(setValueRes).toBeUndefined(); + + // With typical values + const watchValueRes = await routingContext.watchDhtValues( + dhtRecord.key, + [[0, 0]], + "0", + 0xFFFFFFFF, + ); + expect(watchValueRes).toBeDefined(); + expect(watchValueRes).not.toEqual(""); + expect(watchValueRes).not.toEqual("0"); + + const cancelValueRes = await routingContext.cancelDhtWatch( + dhtRecord.key, + [], + ) + + expect(cancelValueRes).toEqual(false); + + }); + + it('should watch value and cancel watch with default values', async () => { + const setValueRes = await routingContext.setDhtValue( + dhtRecord.key, + 0, + textEncoder.encode(data) + ); + expect(setValueRes).toBeUndefined(); + + // Again with default values + const watchValueRes = await routingContext.watchDhtValues( + dhtRecord.key, + ); + expect(watchValueRes).toBeDefined(); + expect(watchValueRes).not.toEqual(""); + expect(watchValueRes).not.toEqual("0"); + + const cancelValueRes = await routingContext.cancelDhtWatch( + dhtRecord.key, + ) + expect(cancelValueRes).toEqual(false); + }); + }); }); }); diff --git a/veilid-wasm/tests/src/utils/veilid-config.ts b/veilid-wasm/tests/src/utils/veilid-config.ts index 6251c42c..a4d7e666 100644 --- a/veilid-wasm/tests/src/utils/veilid-config.ts +++ b/veilid-wasm/tests/src/utils/veilid-config.ts @@ -1,4 +1,5 @@ import type { VeilidWASMConfig } from 'veilid-wasm'; +import { veilidClient } from 'veilid-wasm'; export const veilidCoreInitConfig: VeilidWASMConfig = { logging: { @@ -17,129 +18,10 @@ export const veilidCoreInitConfig: VeilidWASMConfig = { }, }; -export const 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_allowlist_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, - public_watch_limit: 32, - member_watch_limit: 8, - max_watch_expiration_ms: 600000, - }, - 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', - }, - }, - }, -}; +export var veilidCoreStartupConfig = (() => { + var defaultConfig = JSON.parse(veilidClient.defaultConfig()); + defaultConfig.program_name = 'veilid-wasm-test'; + defaultConfig.network.routing_table.bootstrap = ['ws://bootstrap.dev.veilid.net:5150/ws']; + defaultConfig.network.network_key_password = 'dev'; + return defaultConfig; +})(); diff --git a/veilid-wasm/tests/src/utils/wait-utils.ts b/veilid-wasm/tests/src/utils/wait-utils.ts index f32b3969..4b69bd09 100644 --- a/veilid-wasm/tests/src/utils/wait-utils.ts +++ b/veilid-wasm/tests/src/utils/wait-utils.ts @@ -1,3 +1,61 @@ +import { veilidClient } from 'veilid-wasm'; + export const waitForMs = (milliseconds: number) => { return new Promise((resolve) => setTimeout(resolve, milliseconds)); }; + +export const asyncCallWithTimeout = async(asyncPromise: Promise, timeLimit: number) => { + let timeoutHandle: ReturnType; + + const timeoutPromise = new Promise((_resolve, reject) => { + timeoutHandle = setTimeout( + () => reject(new Error('Async call timeout limit reached')), + timeLimit + ); + }); + + return Promise.race([asyncPromise, timeoutPromise]).then(result => { + clearTimeout(timeoutHandle); + return result; + }) +} + +export const waitForPublicAttachment = async () => { + while (true) { + let state = await veilidClient.getState(); + if (state.attachment.public_internet_ready) { + var attached = false + switch (state.attachment.state) { + case "Detached": + case "Detaching": + case "Attaching": + break; + default: + attached = true; + break; + } + if (attached) { + break; + } + } + await waitForMs(1000); + } +} + +export const waitForDetached = async () => { + while (true) { + let state = await veilidClient.getState(); + var detached = false + switch (state.attachment.state) { + case "Detached": + detached = true; + break; + default: + break; + } + if (detached) { + break; + } + await waitForMs(1000); + } +} diff --git a/veilid-wasm/tests/src/veilidClient.test.ts b/veilid-wasm/tests/src/veilidClient.test.ts index d6bb64dc..61c9204f 100644 --- a/veilid-wasm/tests/src/veilidClient.test.ts +++ b/veilid-wasm/tests/src/veilidClient.test.ts @@ -6,29 +6,29 @@ import { } from './utils/veilid-config'; import { VeilidState, veilidClient } from 'veilid-wasm'; -import { waitForMs } from './utils/wait-utils'; +import { asyncCallWithTimeout, waitForPublicAttachment } from './utils/wait-utils'; -describe('veilidClient', () => { - before('veilid startup', async () => { +describe('veilidClient', function () { + before('veilid startup', async function () { veilidClient.initializeCore(veilidCoreInitConfig); - await veilidClient.startupCore((_update) => { + await veilidClient.startupCore(function (_update) { // if (_update.kind === 'Log') { // console.log(_update.message); // } }, JSON.stringify(veilidCoreStartupConfig)); }); - after('veilid shutdown', async () => { + after('veilid shutdown', async function () { await veilidClient.shutdownCore(); }); - it('should print version', async () => { + it('should print version', async function () { const version = veilidClient.versionString(); expect(typeof version).toBe('string'); expect(version.length).toBeGreaterThan(0); }); - it('should get config string', async () => { + it('should get config string', async function () { const defaultConfig = veilidClient.defaultConfig(); expect(typeof defaultConfig).toBe('string'); expect(defaultConfig.length).toBeGreaterThan(0); @@ -41,29 +41,32 @@ describe('veilidClient', () => { expect(defaultConfigStr).toEqual(defaultConfigStr2); }); - it('should attach and detach', async () => { + it('should attach and detach', async function () { await veilidClient.attach(); - await waitForMs(2000); + await asyncCallWithTimeout(waitForPublicAttachment(), 10000); await veilidClient.detach(); }); - describe('kitchen sink', () => { - before('attach', async () => { + describe('kitchen sink', function () { + before('attach', async function () { await veilidClient.attach(); - await waitForMs(2000); + await waitForPublicAttachment(); + + }); + after('detach', async function () { + await veilidClient.detach(); }); - after('detach', () => veilidClient.detach()); let state: VeilidState; - it('should get state', async () => { + it('should get state', async function () { state = await veilidClient.getState(); expect(state.attachment).toBeDefined(); expect(state.config.config).toBeDefined(); expect(state.network).toBeDefined(); }); - it('should call debug command', async () => { + it('should call debug command', async function () { const response = await veilidClient.debug('txtrecord'); expect(response).toBeDefined(); expect(response.length).toBeGreaterThan(0); diff --git a/veilid-wasm/tests/src/veilidCrypto.test.ts b/veilid-wasm/tests/src/veilidCrypto.test.ts index da2a0190..c325e052 100644 --- a/veilid-wasm/tests/src/veilidCrypto.test.ts +++ b/veilid-wasm/tests/src/veilidCrypto.test.ts @@ -142,7 +142,7 @@ describe('veilidCrypto', () => { }).not.toThrow(); }); - describe('contants', () => { + describe('constants', () => { it('CRYPTO_KEY_LENGTH', () => { expect(typeof veilidCrypto.CRYPTO_KEY_LENGTH).toBe('number'); });