wasm unit tests work and attachment manager fix

This commit is contained in:
Christien Rioux 2024-03-14 21:30:09 -04:00
parent ff28273a59
commit ee54358c27
11 changed files with 195 additions and 172 deletions

View File

@ -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,

View File

@ -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(),

View File

@ -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(

View File

@ -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,

View File

@ -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()
}

View File

@ -101,6 +101,7 @@ impl VeilidRoutingContext {
// --------------------------------
// Instance methods
// --------------------------------
fn getRoutingContext(&self) -> APIResult<RoutingContext> {
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<ValueSubkeyRangeSet>,
expiration: Option<String>,
count: Option<u32>,
) -> APIResult<String> {
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<ValueSubkeyRangeSet>,
) -> APIResult<bool> {
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?;

View File

@ -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);
});
});
});
});

View File

@ -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;
})();

View File

@ -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<T>(asyncPromise: Promise<T>, timeLimit: number) => {
let timeoutHandle: ReturnType<typeof setTimeout>;
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);
}
}

View File

@ -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);

View File

@ -142,7 +142,7 @@ describe('veilidCrypto', () => {
}).not.toThrow();
});
describe('contants', () => {
describe('constants', () => {
it('CRYPTO_KEY_LENGTH', () => {
expect(typeof veilidCrypto.CRYPTO_KEY_LENGTH).toBe('number');
});