From 3a7d9b57b5a0aa1d54935936ea6d8212aa8e22c6 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sun, 27 Nov 2022 09:00:20 -0500 Subject: [PATCH] veilid-tools work --- Cargo.lock | 96 +-- .../src/tests/common/test_host_interface.rs | 2 +- veilid-core/tests/web.rs | 49 -- veilid-tools/Cargo.toml | 95 +-- veilid-tools/src/lib.rs | 4 + veilid-tools/src/must_join_handle.rs | 2 +- veilid-tools/src/split_url.rs | 2 +- veilid-tools/src/tests/.gitignore | 4 + veilid-tools/src/tests/android/.gitignore | 16 + .../src/tests/android/.idea/.gitignore | 3 + veilid-tools/src/tests/android/.idea/.name | 1 + .../src/tests/android/.idea/compiler.xml | 6 + .../src/tests/android/.idea/gradle.xml | 21 + .../tests/android/.idea/jarRepositories.xml | 25 + veilid-tools/src/tests/android/.idea/misc.xml | 9 + veilid-tools/src/tests/android/.idea/vcs.xml | 6 + veilid-tools/src/tests/android/.project | 28 + .../org.eclipse.buildship.core.prefs | 13 + veilid-tools/src/tests/android/adb+.sh | 20 + veilid-tools/src/tests/android/app/.classpath | 6 + veilid-tools/src/tests/android/app/.gitignore | 4 + veilid-tools/src/tests/android/app/.project | 34 ++ .../org.eclipse.buildship.core.prefs | 2 + .../src/tests/android/app/CMakeLists.txt | 3 + .../src/tests/android/app/build.gradle | 87 +++ .../src/tests/android/app/cpplink.cpp | 0 .../src/tests/android/app/proguard-rules.pro | 21 + .../android/app/src/main/AndroidManifest.xml | 25 + .../MainActivity.java | 37 ++ .../drawable-v24/ic_launcher_foreground.xml | 30 + .../res/drawable/ic_launcher_background.xml | 170 ++++++ .../app/src/main/res/layout/activity_main.xml | 18 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes .../app/src/main/res/values-night/themes.xml | 16 + .../app/src/main/res/values/colors.xml | 10 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/themes.xml | 16 + veilid-tools/src/tests/android/build.gradle | 28 + .../src/tests/android/gradle.properties | 19 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + veilid-tools/src/tests/android/gradlew | 172 ++++++ veilid-tools/src/tests/android/gradlew.bat | 84 +++ .../tests/android/install_on_all_devices.sh | 2 + .../tests/android/remove_from_all_devices.sh | 3 + .../src/tests/android/settings.gradle | 2 + veilid-tools/src/tests/common/mod.rs | 2 + .../src/tests/common/test_async_tag_lock.rs | 158 +++++ .../src/tests/common/test_host_interface.rs | 554 ++++++++++++++++++ veilid-tools/src/tests/files/cert.pem | 88 +++ veilid-tools/src/tests/files/key.pem | 27 + veilid-tools/src/tests/ios/.gitignore | 91 +++ .../ios/veilidtools-tests/veilid-tools.c | 8 + .../ios/veilidtools-tests/veilid-tools.h | 13 + .../veilidtools-tests-Bridging-Header.h | 5 + .../project.pbxproj | 413 +++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/veilidcore-tests.xcscheme | 78 +++ .../veilidtools-tests/AppDelegate.swift | 37 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 98 ++++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 56 ++ .../Base.lproj/Main.storyboard | 70 +++ .../veilidtools-tests/Info.plist | 66 +++ .../veilidtools-tests/SceneDelegate.swift | 53 ++ .../veilidtools-tests/ViewController.swift | 19 + veilid-tools/src/tests/mod.rs | 3 + veilid-tools/src/tests/native/mod.rs | 152 +++++ .../tests/native/test_async_peek_stream.rs | 352 +++++++++++ veilid-tools/tests/web.rs | 87 +++ 83 files changed, 3440 insertions(+), 232 deletions(-) create mode 100644 veilid-tools/src/tests/.gitignore create mode 100644 veilid-tools/src/tests/android/.gitignore create mode 100644 veilid-tools/src/tests/android/.idea/.gitignore create mode 100644 veilid-tools/src/tests/android/.idea/.name create mode 100644 veilid-tools/src/tests/android/.idea/compiler.xml create mode 100644 veilid-tools/src/tests/android/.idea/gradle.xml create mode 100644 veilid-tools/src/tests/android/.idea/jarRepositories.xml create mode 100644 veilid-tools/src/tests/android/.idea/misc.xml create mode 100644 veilid-tools/src/tests/android/.idea/vcs.xml create mode 100644 veilid-tools/src/tests/android/.project create mode 100644 veilid-tools/src/tests/android/.settings/org.eclipse.buildship.core.prefs create mode 100755 veilid-tools/src/tests/android/adb+.sh create mode 100644 veilid-tools/src/tests/android/app/.classpath create mode 100644 veilid-tools/src/tests/android/app/.gitignore create mode 100644 veilid-tools/src/tests/android/app/.project create mode 100644 veilid-tools/src/tests/android/app/.settings/org.eclipse.buildship.core.prefs create mode 100644 veilid-tools/src/tests/android/app/CMakeLists.txt create mode 100644 veilid-tools/src/tests/android/app/build.gradle create mode 100644 veilid-tools/src/tests/android/app/cpplink.cpp create mode 100644 veilid-tools/src/tests/android/app/proguard-rules.pro create mode 100644 veilid-tools/src/tests/android/app/src/main/AndroidManifest.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/java/com/veilid/veilid-core/veilid-core_android_tests/MainActivity.java create mode 100644 veilid-tools/src/tests/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/layout/activity_main.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 veilid-tools/src/tests/android/app/src/main/res/values-night/themes.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/values/colors.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/values/strings.xml create mode 100644 veilid-tools/src/tests/android/app/src/main/res/values/themes.xml create mode 100644 veilid-tools/src/tests/android/build.gradle create mode 100644 veilid-tools/src/tests/android/gradle.properties create mode 100644 veilid-tools/src/tests/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 veilid-tools/src/tests/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 veilid-tools/src/tests/android/gradlew create mode 100644 veilid-tools/src/tests/android/gradlew.bat create mode 100755 veilid-tools/src/tests/android/install_on_all_devices.sh create mode 100755 veilid-tools/src/tests/android/remove_from_all_devices.sh create mode 100644 veilid-tools/src/tests/android/settings.gradle create mode 100644 veilid-tools/src/tests/common/mod.rs create mode 100644 veilid-tools/src/tests/common/test_async_tag_lock.rs create mode 100644 veilid-tools/src/tests/common/test_host_interface.rs create mode 100644 veilid-tools/src/tests/files/cert.pem create mode 100644 veilid-tools/src/tests/files/key.pem create mode 100644 veilid-tools/src/tests/ios/.gitignore create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.c create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.h create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests-Bridging-Header.h create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.pbxproj create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/xcshareddata/xcschemes/veilidcore-tests.xcscheme create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/AppDelegate.swift create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/Contents.json create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/LaunchScreen.storyboard create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/Main.storyboard create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Info.plist create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/SceneDelegate.swift create mode 100644 veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/ViewController.swift create mode 100644 veilid-tools/src/tests/mod.rs create mode 100644 veilid-tools/src/tests/native/mod.rs create mode 100644 veilid-tools/src/tests/native/test_async_peek_stream.rs create mode 100644 veilid-tools/tests/web.rs diff --git a/Cargo.lock b/Cargo.lock index 8690c6fa..39b78d46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,18 +334,6 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" -[[package]] -name = "async-tls" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e7fbc0843fc5ad3d5ca889c5b2bea9130984d34cd0e62db57ab70c2529a8e3" -dependencies = [ - "futures", - "rustls 0.18.1", - "webpki 0.21.4", - "webpki-roots 0.20.0", -] - [[package]] name = "async-tls" version = "0.11.0" @@ -354,7 +342,7 @@ checksum = "2f23d769dbf1838d5df5156e7b1ad404f4c463d1ac2c6aeb6cd943630f8a8400" dependencies = [ "futures-core", "futures-io", - "rustls 0.19.1", + "rustls", "webpki 0.21.4", "webpki-roots 0.21.1", ] @@ -376,7 +364,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5c45a0dd44b7e6533ac4e7acc38ead1a3b39885f5bbb738140d30ea528abc7c" dependencies = [ - "async-tls 0.9.0", "futures-io", "futures-util", "log", @@ -390,7 +377,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b750efd83b7e716a015eed5ebb583cda83c52d9b24a8f0125e5c48c3313c9f8" dependencies = [ - "async-tls 0.11.0", + "async-tls", "futures-io", "futures-util", "log", @@ -4309,19 +4296,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustls" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" -dependencies = [ - "base64 0.12.3", - "log", - "ring", - "sct", - "webpki 0.21.4", -] - [[package]] name = "rustls" version = "0.19.1" @@ -5588,7 +5562,7 @@ dependencies = [ "async-lock", "async-std", "async-std-resolver", - "async-tls 0.11.0", + "async-tls", "async-tungstenite 0.18.0", "async_executors", "backtrace", @@ -5641,7 +5615,7 @@ dependencies = [ "rtnetlink", "rusqlite", "rust-fsm", - "rustls 0.19.1", + "rustls", "rustls-pemfile", "secrecy", "send_wrapper 0.6.0", @@ -5763,47 +5737,15 @@ dependencies = [ name = "veilid-tools" version = "0.1.0" dependencies = [ - "async-io", "async-lock", "async-std", - "async-std-resolver", - "async-tls 0.11.0", - "async-tungstenite 0.8.0", "async_executors", - "backtrace", - "blake3", - "bugsalot", - "bytecheck", "cfg-if 1.0.0", - "chacha20 0.8.2", - "chacha20poly1305", - "chrono", - "config", - "console_error_panic_hook", - "curve25519-dalek-ng", - "data-encoding", - "digest 0.9.0", - "directories", - "ed25519-dalek", - "enumset", "eyre", - "flume", "futures-util", - "generic-array", - "getrandom 0.2.8", - "hashlink 0.8.1", - "hex", - "ifstructs", - "igd", "jni", "jni-sys", "js-sys", - "json", - "keyring-manager", - "keyvaluedb", - "keyvaluedb-sqlite", - "keyvaluedb-web", - "lazy_static", "libc", "log", "maplit", @@ -5811,45 +5753,24 @@ dependencies = [ "ndk-glue", "nix 0.22.3", "once_cell", - "owning_ref", "owo-colors", "parking_lot 0.11.2", "rand 0.7.3", - "rkyv", - "rtnetlink", - "rusqlite", - "rustls 0.19.1", - "rustls-pemfile", - "secrecy", "send_wrapper 0.6.0", - "serde", - "serde-big-array", - "serde_json", "serial_test", "simplelog 0.9.0", - "socket2", "static_assertions", "stop-token", "thiserror", "tokio 1.22.0", - "tokio-stream", "tokio-util", "tracing", "tracing-android", - "trust-dns-resolver", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", "wasm-logger", - "web-sys", - "webpki 0.21.4", - "webpki-roots 0.21.1", "wee_alloc", - "winapi 0.3.9", - "windows", - "windows-permissions", - "ws_stream_wasm", - "x25519-dalek-ng", ] [[package]] @@ -6060,15 +5981,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" -dependencies = [ - "webpki 0.21.4", -] - [[package]] name = "webpki-roots" version = "0.21.1" diff --git a/veilid-core/src/tests/common/test_host_interface.rs b/veilid-core/src/tests/common/test_host_interface.rs index cc0f750e..322f5d5e 100644 --- a/veilid-core/src/tests/common/test_host_interface.rs +++ b/veilid-core/src/tests/common/test_host_interface.rs @@ -567,7 +567,7 @@ pub async fn test_all() { test_get_random_u32().await; test_sleep().await; #[cfg(not(target_arch = "wasm32"))] - test_network_interfaces().await; + test_network_interfaces().await; XXX KEEP THIS IN NATIVE TESTS test_must_join_single_future().await; test_eventual().await; test_eventual_value().await; diff --git a/veilid-core/tests/web.rs b/veilid-core/tests/web.rs index a5ac14b7..c4df57b1 100644 --- a/veilid-core/tests/web.rs +++ b/veilid-core/tests/web.rs @@ -23,13 +23,6 @@ pub fn setup() -> () { }); } -#[wasm_bindgen_test] -async fn run_test_dht_key() { - setup(); - - test_dht_key::test_all().await; -} - #[wasm_bindgen_test] async fn run_test_host_interface() { setup(); @@ -37,48 +30,6 @@ async fn run_test_host_interface() { test_host_interface::test_all().await; } -#[wasm_bindgen_test] -async fn run_test_veilid_core() { - setup(); - - test_veilid_core::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_config() { - setup(); - - test_veilid_config::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_connection_table() { - setup(); - - test_connection_table::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_table_store() { - setup(); - - test_table_store::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_crypto() { - setup(); - - test_crypto::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_envelope_receipt() { - setup(); - - test_envelope_receipt::test_all().await; -} - #[wasm_bindgen_test] async fn run_test_async_tag_lock() { setup(); diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index 2e9aefc2..9d306626 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -9,14 +9,15 @@ license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)" crate-type = ["rlib"] [features] -default = [ "rt-tokio" ] -rt-async-std = [ "async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket" ] -rt-tokio = [ "tokio", "tokio-util", "tokio-stream", "trust-dns-resolver/tokio-runtime", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", "rtnetlink?/tokio_socket" ] +default = [ "rt-tokio", "tracing" ] +rt-async-std = [ "async-std", "async_executors/async_std", ] +rt-tokio = [ "tokio", "tokio-util", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", ] android_tests = [] ios_tests = [ "simplelog" ] tracking = [] tracing = [ "dep:tracing" ] +log = [ "dep:log" ] [dependencies] tracing = { version = "^0", features = ["log", "attributes"], optional = true } @@ -25,42 +26,12 @@ eyre = "^0" static_assertions = "^1" cfg-if = "^1" thiserror = "^1" -hex = "^0" -generic-array = "^0" -secrecy = "^0" -chacha20poly1305 = "^0" -chacha20 = "^0" -hashlink = { path = "../external/hashlink", features = ["serde_impl"] } -serde = { version = "^1", features = ["derive" ] } -serde_json = { version = "^1" } -serde-big-array = "^0" futures-util = { version = "^0", default_features = false, features = ["alloc"] } parking_lot = "^0" -lazy_static = "^1" -directories = "^4" once_cell = "^1" -json = "^0" -owning_ref = "^0" -flume = { version = "^0", features = ["async"] } -enumset = { version= "^1", features = ["serde"] } -backtrace = { version = "^0" } owo-colors = "^3" stop-token = { version = "^0", default-features = false } -ed25519-dalek = { version = "^1", default_features = false, features = ["alloc", "u64_backend"] } -x25519-dalek = { package = "x25519-dalek-ng", version = "^1", default_features = false, features = ["u64_backend"] } -curve25519-dalek = { package = "curve25519-dalek-ng", version = "^4", default_features = false, features = ["alloc", "u64_backend"] } -# ed25519-dalek needs rand 0.7 until it updates itself rand = "0.7" -# curve25519-dalek-ng is stuck on digest 0.9.0 -blake3 = { version = "1.1.0", default_features = false } -digest = "0.9.0" -rtnetlink = { version = "^0", default-features = false, optional = true } -async-std-resolver = { version = "^0", optional = true } -trust-dns-resolver = { version = "^0", optional = true } -keyvaluedb = { path = "../external/keyvaluedb/keyvaluedb" } -#rkyv = { version = "^0", default_features = false, features = ["std", "alloc", "strict", "size_32", "validation"] } -rkyv = { git = "https://github.com/rkyv/rkyv.git", rev = "57e2a8d", default_features = false, features = ["std", "alloc", "strict", "size_32", "validation"] } -bytecheck = "^0" # Dependencies for native builds only # Linux, Windows, Mac, iOS, Android @@ -68,25 +39,9 @@ bytecheck = "^0" async-std = { version = "^1", features = ["unstable"], optional = true} tokio = { version = "^1", features = ["full"], optional = true} tokio-util = { version = "^0", features = ["compat"], optional = true} -tokio-stream = { version = "^0", features = ["net"], optional = true} -async-io = { version = "^1" } -async-tungstenite = { version = "^0", features = ["async-tls"] } maplit = "^1" -config = { version = "^0", features = ["yaml"] } -keyring-manager = { path = "../external/keyring-manager" } -async-tls = "^0.11" -igd = { path = "../external/rust-igd" } -webpki = "^0" -webpki-roots = "^0" -rustls = "^0.19" -rustls-pemfile = "^0.2" futures-util = { version = "^0", default-features = false, features = ["async-await", "sink", "std", "io"] } -keyvaluedb-sqlite = { path = "../external/keyvaluedb/keyvaluedb-sqlite" } -data-encoding = { version = "^2" } -socket2 = "^0" -bugsalot = "^0" -chrono = "^0" libc = "^0" nix = "^0" @@ -95,30 +50,10 @@ nix = "^0" wasm-bindgen = "^0" js-sys = "^0" wasm-bindgen-futures = "^0" -keyvaluedb-web = { path = "../external/keyvaluedb/keyvaluedb-web" } -data-encoding = { version = "^2", default_features = false, features = ["alloc"] } -getrandom = { version = "^0.2", features = ["js"] } -ws_stream_wasm = "^0" async_executors = { version = "^0", default-features = false, features = [ "bindgen", "timer" ]} async-lock = "^2" send_wrapper = { version = "^0.6", features = ["futures"] } -wasm-logger = "^0" -# Configuration for WASM32 'web-sys' crate -[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] -version = "^0" -features = [ - 'Document', - 'HtmlDocument', - # 'Element', - # 'HtmlElement', - # 'Node', - 'IdbFactory', - 'IdbOpenDbRequest', - 'Storage', - 'Location', - 'Window', -] # Dependencies for Android [target.'cfg(target_os = "android")'.dependencies] @@ -128,30 +63,15 @@ ndk = { version = "^0", features = ["trace"] } ndk-glue = { version = "^0", features = ["logger"] } tracing-android = { version = "^0" } -# Dependenices for all Unix (Linux, Android, MacOS, iOS) -[target.'cfg(unix)'.dependencies] -ifstructs = "^0" - -# Dependencies for Linux or Android -[target.'cfg(any(target_os = "android",target_os = "linux"))'.dependencies] -rtnetlink = { version = "^0", default-features = false } - # Dependencies for Windows -[target.'cfg(target_os = "windows")'.dependencies] -winapi = { version = "^0.3.9", features = [ "iptypes", "iphlpapi" ] } -windows = { version = "^0", features = [ "Win32_NetworkManagement_Dns", "Win32_Foundation", "alloc" ]} -windows-permissions = "^0" +# [target.'cfg(target_os = "windows")'.dependencies] +# windows = { version = "^0", features = [ "Win32_NetworkManagement_Dns", "Win32_Foundation", "alloc" ]} +# windows-permissions = "^0" # Dependencies for iOS [target.'cfg(target_os = "ios")'.dependencies] simplelog = { version = "^0", optional = true } -# Rusqlite configuration to ensure platforms that don't come with sqlite get it bundled -# Except WASM which doesn't use sqlite -[target.'cfg(all(not(target_os = "ios"),not(target_os = "android"),not(target_arch = "wasm32")))'.dependencies.rusqlite] -version = "^0" -features = ["bundled"] - ### DEV DEPENDENCIES [dev-dependencies] @@ -162,7 +82,6 @@ simplelog = { version = "^0", features=["test"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "^0" -console_error_panic_hook = "^0" wee_alloc = "^0" wasm-logger = "^0" diff --git a/veilid-tools/src/lib.rs b/veilid-tools/src/lib.rs index d5f6ac70..0838c00f 100644 --- a/veilid-tools/src/lib.rs +++ b/veilid-tools/src/lib.rs @@ -74,6 +74,7 @@ pub use std::net::{ pub use std::ops::{Fn, FnMut, FnOnce}; pub use std::pin::Pin; pub use std::rc::Rc; +pub use std::str::FromStr; pub use std::string::{String, ToString}; pub use std::sync::atomic::{AtomicBool, Ordering}; pub use std::sync::{Arc, Weak}; @@ -132,3 +133,6 @@ pub use timestamp::*; pub use tools::*; #[cfg(target_arch = "wasm32")] pub use wasm::*; + +// Tests must be public for wasm-pack tests +pub mod tests; diff --git a/veilid-tools/src/must_join_handle.rs b/veilid-tools/src/must_join_handle.rs index a9de40b7..ff7231f1 100644 --- a/veilid-tools/src/must_join_handle.rs +++ b/veilid-tools/src/must_join_handle.rs @@ -77,7 +77,7 @@ impl Future for MustJoinHandle { } } } - }else if #[cfg(target_arch = "wasm32")] { + } else if #[cfg(target_arch = "wasm32")] { Poll::Ready(t) } else { compile_error!("needs executor implementation") diff --git a/veilid-tools/src/split_url.rs b/veilid-tools/src/split_url.rs index 718d4946..9cfd8c94 100644 --- a/veilid-tools/src/split_url.rs +++ b/veilid-tools/src/split_url.rs @@ -9,7 +9,7 @@ use super::*; -use core::str::FromStr; +use std::str::FromStr; fn is_alphanum(c: u8) -> bool { matches!(c, diff --git a/veilid-tools/src/tests/.gitignore b/veilid-tools/src/tests/.gitignore new file mode 100644 index 00000000..e8c47856 --- /dev/null +++ b/veilid-tools/src/tests/.gitignore @@ -0,0 +1,4 @@ +# exclude everything +tmp/* +# exception to the rule +!tmp/.gitkeep diff --git a/veilid-tools/src/tests/android/.gitignore b/veilid-tools/src/tests/android/.gitignore new file mode 100644 index 00000000..82e4fd4b --- /dev/null +++ b/veilid-tools/src/tests/android/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +/.idea/deploymentTargetDropDown.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/veilid-tools/src/tests/android/.idea/.gitignore b/veilid-tools/src/tests/android/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/veilid-tools/src/tests/android/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/veilid-tools/src/tests/android/.idea/.name b/veilid-tools/src/tests/android/.idea/.name new file mode 100644 index 00000000..cde590be --- /dev/null +++ b/veilid-tools/src/tests/android/.idea/.name @@ -0,0 +1 @@ +Veilid Tools Tests \ No newline at end of file diff --git a/veilid-tools/src/tests/android/.idea/compiler.xml b/veilid-tools/src/tests/android/.idea/compiler.xml new file mode 100644 index 00000000..fb7f4a8a --- /dev/null +++ b/veilid-tools/src/tests/android/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/.idea/gradle.xml b/veilid-tools/src/tests/android/.idea/gradle.xml new file mode 100644 index 00000000..4989a70a --- /dev/null +++ b/veilid-tools/src/tests/android/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/.idea/jarRepositories.xml b/veilid-tools/src/tests/android/.idea/jarRepositories.xml new file mode 100644 index 00000000..a5f05cd8 --- /dev/null +++ b/veilid-tools/src/tests/android/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/.idea/misc.xml b/veilid-tools/src/tests/android/.idea/misc.xml new file mode 100644 index 00000000..ef61796f --- /dev/null +++ b/veilid-tools/src/tests/android/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/.idea/vcs.xml b/veilid-tools/src/tests/android/.idea/vcs.xml new file mode 100644 index 00000000..4fce1d86 --- /dev/null +++ b/veilid-tools/src/tests/android/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/.project b/veilid-tools/src/tests/android/.project new file mode 100644 index 00000000..cf0850ea --- /dev/null +++ b/veilid-tools/src/tests/android/.project @@ -0,0 +1,28 @@ + + + Veilid Tools Tests + Project android created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + + + 0 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/veilid-tools/src/tests/android/.settings/org.eclipse.buildship.core.prefs b/veilid-tools/src/tests/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 00000000..094110d3 --- /dev/null +++ b/veilid-tools/src/tests/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/opt/android-studio/jre +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/veilid-tools/src/tests/android/adb+.sh b/veilid-tools/src/tests/android/adb+.sh new file mode 100755 index 00000000..5aa7fbbf --- /dev/null +++ b/veilid-tools/src/tests/android/adb+.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Script adb+ +# Usage +# You can run any command adb provide on all your current devices +# ./adb+ is the equivalent of ./adb -s +# +# Examples +# ./adb+ version +# ./adb+ install apidemo.apk +# ./adb+ uninstall com.example.android.apis + +adb devices | while read line +do + if [ ! "$line" = "" ] && [ `echo $line | awk '{print $2}'` = "device" ] + then + device=`echo $line | awk '{print $1}'` + echo "$device $@ ..." + adb -s $device $@ + fi +done diff --git a/veilid-tools/src/tests/android/app/.classpath b/veilid-tools/src/tests/android/app/.classpath new file mode 100644 index 00000000..4a04201c --- /dev/null +++ b/veilid-tools/src/tests/android/app/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/veilid-tools/src/tests/android/app/.gitignore b/veilid-tools/src/tests/android/app/.gitignore new file mode 100644 index 00000000..23c35ec2 --- /dev/null +++ b/veilid-tools/src/tests/android/app/.gitignore @@ -0,0 +1,4 @@ +/build +/.cxx + + diff --git a/veilid-tools/src/tests/android/app/.project b/veilid-tools/src/tests/android/app/.project new file mode 100644 index 00000000..c2e60fbb --- /dev/null +++ b/veilid-tools/src/tests/android/app/.project @@ -0,0 +1,34 @@ + + + VeilidTools Tests-app + Project VeilidTools Tests-app created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1635633714053 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/veilid-tools/src/tests/android/app/.settings/org.eclipse.buildship.core.prefs b/veilid-tools/src/tests/android/app/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 00000000..b1886adb --- /dev/null +++ b/veilid-tools/src/tests/android/app/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=.. +eclipse.preferences.version=1 diff --git a/veilid-tools/src/tests/android/app/CMakeLists.txt b/veilid-tools/src/tests/android/app/CMakeLists.txt new file mode 100644 index 00000000..57518a5d --- /dev/null +++ b/veilid-tools/src/tests/android/app/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.1) +project(cpplink CXX) +add_library(cpplink cpplink.cpp) \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/build.gradle b/veilid-tools/src/tests/android/app/build.gradle new file mode 100644 index 00000000..21c6c24a --- /dev/null +++ b/veilid-tools/src/tests/android/app/build.gradle @@ -0,0 +1,87 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.veilid.veilidtools.veilidtools_android_tests" + minSdkVersion 24 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + + // Required to copy libc++_shared.so + externalNativeBuild { + cmake { + arguments "-DANDROID_STL=c++_shared" + targets "cpplink" + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + ndkVersion '22.0.7026061' + + // Required to copy libc++_shared.so + externalNativeBuild { + cmake { + path file('CMakeLists.txt') + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.security:security-crypto:1.1.0-alpha03' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} + +apply plugin: 'org.mozilla.rust-android-gradle.rust-android' + +cargo { + module = "../../../../../veilid-tools" + libname = "veilid_tools" + targets = ["arm", "arm64", "x86", "x86_64"] + targetDirectory = "../../../../../target" + prebuiltToolchains = true + profile = gradle.startParameter.taskNames.any{it.toLowerCase().contains("debug")} ? "debug" : "release" + pythonCommand = "python3" + features { + defaultAnd("android_tests", "rt-tokio") + } +} + +afterEvaluate { + // The `cargoBuild` task isn't available until after evaluation. + android.applicationVariants.all { variant -> + def productFlavor = "" + variant.productFlavors.each { + productFlavor += "${it.name.capitalize()}" + } + def buildType = "${variant.buildType.name.capitalize()}" + tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"]) + } +} + diff --git a/veilid-tools/src/tests/android/app/cpplink.cpp b/veilid-tools/src/tests/android/app/cpplink.cpp new file mode 100644 index 00000000..e69de29b diff --git a/veilid-tools/src/tests/android/app/proguard-rules.pro b/veilid-tools/src/tests/android/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/veilid-tools/src/tests/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/AndroidManifest.xml b/veilid-tools/src/tests/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9c9b4431 --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/java/com/veilid/veilid-core/veilid-core_android_tests/MainActivity.java b/veilid-tools/src/tests/android/app/src/main/java/com/veilid/veilid-core/veilid-core_android_tests/MainActivity.java new file mode 100644 index 00000000..2b73f488 --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/java/com/veilid/veilid-core/veilid-core_android_tests/MainActivity.java @@ -0,0 +1,37 @@ +package com.veilid.veilidtools.veilidtools_android_tests; + +import androidx.appcompat.app.AppCompatActivity; +import android.content.Context; +import android.os.Bundle; + +public class MainActivity extends AppCompatActivity { + + static { + System.loadLibrary("veilid_tools"); + } + + private static native void run_tests(Context context); + + private Thread testThread; + + class TestThread extends Thread { + private Context context; + + TestThread(Context context) { + this.context = context; + } + + public void run() { + run_tests(this.context); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + this.testThread = new TestThread(this); + this.testThread.start(); + } +} diff --git a/veilid-tools/src/tests/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/veilid-tools/src/tests/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/res/drawable/ic_launcher_background.xml b/veilid-tools/src/tests/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/android/app/src/main/res/layout/activity_main.xml b/veilid-tools/src/tests/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4fc24441 --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/veilid-tools/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a571e60098c92c2baca8a5df62f2929cbff01b52 GIT binary patch literal 3593 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 P00000NkvXXu0mjfP=x?Y literal 0 HcmV?d00001 diff --git a/veilid-tools/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/veilid-tools/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..61da551c5594a1f9d26193983d2cd69189014603 GIT binary patch literal 5339 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 literal 0 HcmV?d00001 diff --git a/veilid-tools/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/veilid-tools/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b216f2d313cc673d8b8c4da591c174ebed52795c GIT binary patch literal 11873 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 literal 0 HcmV?d00001 diff --git a/veilid-tools/src/tests/android/app/src/main/res/values-night/themes.xml b/veilid-tools/src/tests/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..5ee570a8 --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/res/values/colors.xml b/veilid-tools/src/tests/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/res/values/strings.xml b/veilid-tools/src/tests/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..ca58c272 --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + VeilidTools Tests + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/app/src/main/res/values/themes.xml b/veilid-tools/src/tests/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..914f69d4 --- /dev/null +++ b/veilid-tools/src/tests/android/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/build.gradle b/veilid-tools/src/tests/android/build.gradle new file mode 100644 index 00000000..96496236 --- /dev/null +++ b/veilid-tools/src/tests/android/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath "com.android.tools.build:gradle:4.1.2" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +plugins { + id "org.mozilla.rust-android-gradle.rust-android" version "0.9.0" +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/veilid-tools/src/tests/android/gradle.properties b/veilid-tools/src/tests/android/gradle.properties new file mode 100644 index 00000000..52f5917c --- /dev/null +++ b/veilid-tools/src/tests/android/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/veilid-tools/src/tests/android/gradle/wrapper/gradle-wrapper.jar b/veilid-tools/src/tests/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/veilid-tools/src/tests/android/gradle/wrapper/gradle-wrapper.properties b/veilid-tools/src/tests/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..3a56e3d2 --- /dev/null +++ b/veilid-tools/src/tests/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jun 21 14:26:26 PDT 2021 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/veilid-tools/src/tests/android/gradlew b/veilid-tools/src/tests/android/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/veilid-tools/src/tests/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/veilid-tools/src/tests/android/gradlew.bat b/veilid-tools/src/tests/android/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/veilid-tools/src/tests/android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/veilid-tools/src/tests/android/install_on_all_devices.sh b/veilid-tools/src/tests/android/install_on_all_devices.sh new file mode 100755 index 00000000..f96d53b1 --- /dev/null +++ b/veilid-tools/src/tests/android/install_on_all_devices.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./gradlew installDebug diff --git a/veilid-tools/src/tests/android/remove_from_all_devices.sh b/veilid-tools/src/tests/android/remove_from_all_devices.sh new file mode 100755 index 00000000..eea0ef7e --- /dev/null +++ b/veilid-tools/src/tests/android/remove_from_all_devices.sh @@ -0,0 +1,3 @@ +#!/bin/bash +./adb+.sh uninstall com.veilid.veilidtools.veilidtools_android_tests + diff --git a/veilid-tools/src/tests/android/settings.gradle b/veilid-tools/src/tests/android/settings.gradle new file mode 100644 index 00000000..ff5d71fe --- /dev/null +++ b/veilid-tools/src/tests/android/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name = "Veilid Tools Tests" \ No newline at end of file diff --git a/veilid-tools/src/tests/common/mod.rs b/veilid-tools/src/tests/common/mod.rs new file mode 100644 index 00000000..06433eae --- /dev/null +++ b/veilid-tools/src/tests/common/mod.rs @@ -0,0 +1,2 @@ +pub mod test_async_tag_lock; +pub mod test_host_interface; diff --git a/veilid-tools/src/tests/common/test_async_tag_lock.rs b/veilid-tools/src/tests/common/test_async_tag_lock.rs new file mode 100644 index 00000000..c1b2ecc4 --- /dev/null +++ b/veilid-tools/src/tests/common/test_async_tag_lock.rs @@ -0,0 +1,158 @@ +use crate::*; + +pub async fn test_simple_no_contention() { + info!("test_simple_no_contention"); + + let table = AsyncTagLockTable::new(); + + let a1 = SocketAddr::new("1.2.3.4".parse().unwrap(), 1234); + let a2 = SocketAddr::new("6.9.6.9".parse().unwrap(), 6969); + + { + let g1 = table.lock_tag(a1).await; + let g2 = table.lock_tag(a2).await; + drop(g2); + drop(g1); + } + + { + let g1 = table.lock_tag(a1).await; + let g2 = table.lock_tag(a2).await; + drop(g1); + drop(g2); + } + + assert_eq!(table.len(), 0); +} + +pub async fn test_simple_single_contention() { + info!("test_simple_single_contention"); + + let table = AsyncTagLockTable::new(); + + let a1 = SocketAddr::new("1.2.3.4".parse().unwrap(), 1234); + + let g1 = table.lock_tag(a1).await; + + info!("locked"); + let t1 = spawn(async move { + // move the guard into the task + let _g1_take = g1; + // hold the guard for a bit + info!("waiting"); + sleep(1000).await; + // release the guard + info!("released"); + }); + + // wait to lock again, will contend until spawned task exits + let _g1_b = table.lock_tag(a1).await; + info!("locked"); + + // Ensure task is joined + t1.await; + + assert_eq!(table.len(), 1); +} + +pub async fn test_simple_double_contention() { + info!("test_simple_double_contention"); + + let table = AsyncTagLockTable::new(); + + let a1 = SocketAddr::new("1.2.3.4".parse().unwrap(), 1234); + let a2 = SocketAddr::new("6.9.6.9".parse().unwrap(), 6969); + + let g1 = table.lock_tag(a1).await; + let g2 = table.lock_tag(a2).await; + + info!("locked"); + let t1 = spawn(async move { + // move the guard into the tas + let _g1_take = g1; + // hold the guard for a bit + info!("waiting"); + sleep(1000).await; + // release the guard + info!("released"); + }); + let t2 = spawn(async move { + // move the guard into the task + let _g2_take = g2; + // hold the guard for a bit + info!("waiting"); + sleep(500).await; + // release the guard + info!("released"); + }); + + // wait to lock again, will contend until spawned task exits + let _g1_b = table.lock_tag(a1).await; + // wait to lock again, should complete immediately + let _g2_b = table.lock_tag(a2).await; + + info!("locked"); + + // Ensure tasks are joined + t1.await; + t2.await; + + assert_eq!(table.len(), 2); +} + +pub async fn test_parallel_single_contention() { + info!("test_parallel_single_contention"); + + let table = AsyncTagLockTable::new(); + + let a1 = SocketAddr::new("1.2.3.4".parse().unwrap(), 1234); + + let table1 = table.clone(); + let t1 = spawn(async move { + // lock the tag + let _g = table1.lock_tag(a1).await; + info!("locked t1"); + // hold the guard for a bit + info!("waiting t1"); + sleep(500).await; + // release the guard + info!("released t1"); + }); + + let table2 = table.clone(); + let t2 = spawn(async move { + // lock the tag + let _g = table2.lock_tag(a1).await; + info!("locked t2"); + // hold the guard for a bit + info!("waiting t2"); + sleep(500).await; + // release the guard + info!("released t2"); + }); + + let table3 = table.clone(); + let t3 = spawn(async move { + // lock the tag + let _g = table3.lock_tag(a1).await; + info!("locked t3"); + // hold the guard for a bit + info!("waiting t3"); + sleep(500).await; + // release the guard + info!("released t3"); + }); + + // Ensure tasks are joined + t1.await; + t2.await; + t3.await; + + assert_eq!(table.len(), 0); +} + +pub async fn test_all() { + test_simple_no_contention().await; + test_simple_single_contention().await; + test_parallel_single_contention().await; +} diff --git a/veilid-tools/src/tests/common/test_host_interface.rs b/veilid-tools/src/tests/common/test_host_interface.rs new file mode 100644 index 00000000..2d73c399 --- /dev/null +++ b/veilid-tools/src/tests/common/test_host_interface.rs @@ -0,0 +1,554 @@ +use crate::*; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use js_sys::*; + } else { + use std::time::{Duration, SystemTime}; + } +} + +pub async fn test_log() { + info!("testing log"); +} + +pub async fn test_get_timestamp() { + info!("testing get_timestamp"); + let t1 = get_timestamp(); + let t2 = get_timestamp(); + assert!(t2 >= t1); +} + +pub async fn test_eventual() { + info!("testing Eventual"); + { + let e1 = Eventual::new(); + let i1 = e1.instance_clone(1u32); + let i2 = e1.instance_clone(2u32); + let i3 = e1.instance_clone(3u32); + drop(i3); + let i4 = e1.instance_clone(4u32); + drop(i2); + + let jh = spawn(async move { + sleep(1000).await; + e1.resolve(); + }); + + assert_eq!(i1.await, 1u32); + assert_eq!(i4.await, 4u32); + + jh.await; + } + { + let e1 = Eventual::new(); + let i1 = e1.instance_clone(1u32); + let i2 = e1.instance_clone(2u32); + let i3 = e1.instance_clone(3u32); + let i4 = e1.instance_clone(4u32); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + let i5 = e1.instance_clone(5u32); + let i6 = e1.instance_clone(6u32); + assert_eq!(i1.await, 1u32); + assert_eq!(i5.await, 5u32); + assert_eq!(i6.await, 6u32); + }); + sleep(1000).await; + let resolved = e1_c1.resolve(); + drop(i2); + drop(i3); + assert_eq!(i4.await, 4u32); + resolved.await; + jh.await; + } + { + let e1 = Eventual::new(); + let i1 = e1.instance_clone(1u32); + let i2 = e1.instance_clone(2u32); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + assert_eq!(i1.await, 1u32); + assert_eq!(i2.await, 2u32); + }); + sleep(1000).await; + e1_c1.resolve().await; + + jh.await; + + e1_c1.reset(); + // + let j1 = e1.instance_clone(1u32); + let j2 = e1.instance_clone(2u32); + let jh = spawn(async move { + assert_eq!(j1.await, 1u32); + assert_eq!(j2.await, 2u32); + }); + sleep(1000).await; + e1_c1.resolve().await; + + jh.await; + + e1_c1.reset(); + } +} + +pub async fn test_eventual_value() { + info!("testing Eventual Value"); + { + let e1 = EventualValue::::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + drop(i3); + let i4 = e1.instance(); + drop(i2); + + let e1_c1 = e1.clone(); + let jh = spawn(async move { + sleep(1000).await; + e1_c1.resolve(3u32); + }); + + i1.await; + i4.await; + jh.await; + assert_eq!(e1.take_value(), Some(3u32)); + } + { + let e1 = EventualValue::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + let i4 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + let i5 = e1.instance(); + let i6 = e1.instance(); + i1.await; + i5.await; + i6.await; + }); + sleep(1000).await; + let resolved = e1_c1.resolve(4u16); + drop(i2); + drop(i3); + i4.await; + resolved.await; + jh.await; + assert_eq!(e1_c1.take_value(), Some(4u16)); + } + { + let e1 = EventualValue::new(); + assert_eq!(e1.take_value(), None); + let i1 = e1.instance(); + let i2 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + i1.await; + i2.await; + }); + sleep(1000).await; + e1_c1.resolve(5u32).await; + jh.await; + assert_eq!(e1_c1.take_value(), Some(5u32)); + e1_c1.reset(); + assert_eq!(e1_c1.take_value(), None); + // + let j1 = e1.instance(); + let j2 = e1.instance(); + let jh = spawn(async move { + j1.await; + j2.await; + }); + sleep(1000).await; + e1_c1.resolve(6u32).await; + jh.await; + assert_eq!(e1_c1.take_value(), Some(6u32)); + e1_c1.reset(); + assert_eq!(e1_c1.take_value(), None); + } +} + +pub async fn test_eventual_value_clone() { + info!("testing Eventual Value Clone"); + { + let e1 = EventualValueClone::::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + drop(i3); + let i4 = e1.instance(); + drop(i2); + + let jh = spawn(async move { + sleep(1000).await; + e1.resolve(3u32); + }); + + assert_eq!(i1.await, 3); + assert_eq!(i4.await, 3); + + jh.await; + } + + { + let e1 = EventualValueClone::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + let i4 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + let i5 = e1.instance(); + let i6 = e1.instance(); + assert_eq!(i1.await, 4); + assert_eq!(i5.await, 4); + assert_eq!(i6.await, 4); + }); + sleep(1000).await; + let resolved = e1_c1.resolve(4u16); + drop(i2); + drop(i3); + assert_eq!(i4.await, 4); + resolved.await; + jh.await; + } + + { + let e1 = EventualValueClone::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + assert_eq!(i1.await, 5); + assert_eq!(i2.await, 5); + }); + sleep(1000).await; + e1_c1.resolve(5u32).await; + jh.await; + e1_c1.reset(); + // + let j1 = e1.instance(); + let j2 = e1.instance(); + let jh = spawn(async move { + assert_eq!(j1.await, 6); + assert_eq!(j2.await, 6); + }); + sleep(1000).await; + e1_c1.resolve(6u32).await; + jh.await; + e1_c1.reset(); + } +} +pub async fn test_interval() { + info!("testing interval"); + + let tick: Arc> = Arc::new(Mutex::new(0u32)); + let stopper = interval(1000, move || { + let tick = tick.clone(); + async move { + let mut tick = tick.lock(); + trace!("tick {}", tick); + *tick += 1; + } + }); + + sleep(5500).await; + + stopper.await; +} + +pub async fn test_timeout() { + info!("testing timeout"); + + let tick: Arc> = Arc::new(Mutex::new(0u32)); + let tick_1 = tick.clone(); + assert!( + timeout(2500, async move { + let mut tick = tick_1.lock(); + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + }) + .await + .is_err(), + "should have timed out" + ); + + let ticks = *tick.lock(); + assert!(ticks <= 2); +} + +pub async fn test_sleep() { + info!("testing sleep"); + cfg_if! { + if #[cfg(target_arch = "wasm32")] { + + let t1 = Date::now(); + intf::sleep(1000).await; + let t2 = Date::now(); + assert!((t2-t1) >= 1000.0); + + } else { + + let sys_time = SystemTime::now(); + let one_sec = Duration::from_secs(1); + + sleep(1000).await; + assert!(sys_time.elapsed().unwrap() >= one_sec); + } + } +} + +macro_rules! assert_split_url { + ($url:expr, $scheme:expr, $host:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new($scheme, None, $host, None, None)) + ); + }; + ($url:expr, $scheme:expr, $host:expr, $port:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new($scheme, None, $host, $port, None)) + ); + }; + ($url:expr, $scheme:expr, $host:expr, $port:expr, $path:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new( + $scheme, + None, + $host, + $port, + Some(SplitUrlPath::new( + $path, + Option::::None, + Option::::None + )) + )) + ); + }; + ($url:expr, $scheme:expr, $host:expr, $port:expr, $path:expr, $frag:expr, $query:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new( + $scheme, + None, + $host, + $port, + Some(SplitUrlPath::new($path, $frag, $query)) + )) + ); + }; +} + +macro_rules! assert_split_url_parse { + ($url:expr) => { + let url = $url; + let su1 = SplitUrl::from_str(url).expect("should parse"); + assert_eq!(su1.to_string(), url); + }; +} + +fn host>(s: S) -> SplitUrlHost { + SplitUrlHost::Hostname(s.as_ref().to_owned()) +} + +fn ip>(s: S) -> SplitUrlHost { + SplitUrlHost::IpAddr(IpAddr::from_str(s.as_ref()).unwrap()) +} + +pub async fn test_split_url() { + info!("testing split_url"); + + assert_split_url!("http://foo", "http", host("foo")); + assert_split_url!("http://foo:1234", "http", host("foo"), Some(1234)); + assert_split_url!("http://foo:1234/", "http", host("foo"), Some(1234), ""); + assert_split_url!( + "http://foo:1234/asdf/qwer", + "http", + host("foo"), + Some(1234), + "asdf/qwer" + ); + assert_split_url!("http://foo/", "http", host("foo"), None, ""); + assert_split_url!("http://11.2.3.144/", "http", ip("11.2.3.144"), None, ""); + assert_split_url!("http://[1111::2222]/", "http", ip("1111::2222"), None, ""); + assert_split_url!( + "http://[1111::2222]:123/", + "http", + ip("1111::2222"), + Some(123), + "" + ); + + assert_split_url!( + "http://foo/asdf/qwer", + "http", + host("foo"), + None, + "asdf/qwer" + ); + assert_split_url!( + "http://foo/asdf/qwer#3", + "http", + host("foo"), + None, + "asdf/qwer", + Some("3"), + Option::::None + ); + assert_split_url!( + "http://foo/asdf/qwer?xxx", + "http", + host("foo"), + None, + "asdf/qwer", + Option::::None, + Some("xxx") + ); + assert_split_url!( + "http://foo/asdf/qwer#yyy?xxx", + "http", + host("foo"), + None, + "asdf/qwer", + Some("yyy"), + Some("xxx") + ); + assert_err!(SplitUrl::from_str("://asdf")); + assert_err!(SplitUrl::from_str("")); + assert_err!(SplitUrl::from_str("::")); + assert_err!(SplitUrl::from_str("://:")); + assert_err!(SplitUrl::from_str("a://:")); + assert_err!(SplitUrl::from_str("a://:1243")); + assert_err!(SplitUrl::from_str("a://:65536")); + assert_err!(SplitUrl::from_str("a://:-16")); + assert_err!(SplitUrl::from_str("a:///")); + assert_err!(SplitUrl::from_str("a:///qwer:")); + assert_err!(SplitUrl::from_str("a:///qwer://")); + assert_err!(SplitUrl::from_str("a://qwer://")); + assert_err!(SplitUrl::from_str("a://[1111::2222]:/")); + assert_err!(SplitUrl::from_str("a://[1111::2222]:")); + + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord#qux?zuz"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord#qux"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord?zuz"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord/"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234//"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234"); + assert_split_url_parse!("sch://foo:bar@[1111::2222]:1234"); + assert_split_url_parse!("sch://foo:bar@[::]:1234"); + assert_split_url_parse!("sch://foo:bar@1.2.3.4:1234"); + assert_split_url_parse!("sch://@baz.com:1234"); + assert_split_url_parse!("sch://baz.com/asdf/asdf"); + assert_split_url_parse!("sch://baz.com/"); + assert_split_url_parse!("s://s"); +} + +pub async fn test_get_random_u64() { + info!("testing random number generator for u64"); + let t1 = get_timestamp(); + let count = 10000; + for _ in 0..count { + let _ = get_random_u64(); + } + let t2 = get_timestamp(); + let tdiff = ((t2 - t1) as f64) / 1000000.0f64; + info!( + "running network interface test with {} iterations took {} seconds", + count, tdiff + ); +} + +pub async fn test_get_random_u32() { + info!("testing random number generator for u32"); + let t1 = get_timestamp(); + let count = 10000; + for _ in 0..count { + let _ = get_random_u32(); + } + let t2 = get_timestamp(); + let tdiff = ((t2 - t1) as f64) / 1000000.0f64; + info!( + "running network interface test with {} iterations took {} seconds", + count, tdiff + ); +} + +pub async fn test_must_join_single_future() { + info!("testing must join single future"); + let sf = MustJoinSingleFuture::::new(); + assert_eq!(sf.check().await, Ok(None)); + assert_eq!( + sf.single_spawn(async { + sleep(2000).await; + 69 + }) + .await, + Ok((None, true)) + ); + assert_eq!(sf.check().await, Ok(None)); + assert_eq!(sf.single_spawn(async { panic!() }).await, Ok((None, false))); + assert_eq!(sf.join().await, Ok(Some(69))); + assert_eq!( + sf.single_spawn(async { + sleep(1000).await; + 37 + }) + .await, + Ok((None, true)) + ); + sleep(2000).await; + assert_eq!( + sf.single_spawn(async { + sleep(1000).await; + 27 + }) + .await, + Ok((Some(37), true)) + ); + sleep(2000).await; + assert_eq!(sf.join().await, Ok(Some(27))); + assert_eq!(sf.check().await, Ok(None)); +} + +pub async fn test_tools() { + info!("testing retry_falloff_log"); + let mut last_us = 0u64; + for x in 0..1024 { + let cur_us = x as u64 * 1000000u64; + if retry_falloff_log(last_us, cur_us, 10_000_000u64, 6_000_000_000u64, 2.0f64) { + info!(" retry at {} secs", timestamp_to_secs(cur_us)); + last_us = cur_us; + } + } +} + +pub async fn test_all() { + test_log().await; + test_get_timestamp().await; + test_tools().await; + test_split_url().await; + test_get_random_u64().await; + test_get_random_u32().await; + test_sleep().await; + #[cfg(not(target_arch = "wasm32"))] + test_must_join_single_future().await; + test_eventual().await; + test_eventual_value().await; + test_eventual_value_clone().await; + test_interval().await; + test_timeout().await; +} diff --git a/veilid-tools/src/tests/files/cert.pem b/veilid-tools/src/tests/files/cert.pem new file mode 100644 index 00000000..b86846ed --- /dev/null +++ b/veilid-tools/src/tests/files/cert.pem @@ -0,0 +1,88 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 12:ce:63:bd:90:f5:ab:de:6d:7f:d7:3e:f3:e6:bb + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Veilid Test CA + Validity + Not Before: Nov 22 13:52:16 2021 GMT + Not After : Feb 25 13:52:16 2024 GMT + Subject: CN=Veilid Test Certificate + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:cb:2e:7a:47:81:be:6f:6b:53:37:51:c1:50:68: + 5a:44:3d:ba:b9:9b:78:40:84:35:d4:0e:e8:41:a6: + 0e:0a:b9:34:ae:97:a3:37:3e:81:ed:6c:0f:f8:8a: + 8b:0b:1a:ed:06:97:57:6d:49:a5:ec:b4:c4:d8:6d: + d2:57:c3:87:89:99:ee:b0:d7:c5:82:a1:dc:d5:98: + b3:ef:10:da:c0:5c:38:a2:bb:15:3e:0e:5e:bc:a0: + cd:a1:f0:07:67:bb:57:3f:89:cc:72:4f:bb:c0:a7: + ed:ad:15:07:61:c2:b4:21:73:39:00:9b:8f:aa:04: + 1b:c4:9d:d4:00:44:87:b1:79:b4:e1:4e:01:3c:ee: + a4:bb:f9:ad:5d:88:41:03:b4:bf:df:bf:71:24:ee: + 0b:69:59:55:dd:43:d1:91:04:de:98:9c:54:f2:ee: + 63:78:fe:76:19:bf:e6:5d:d6:58:81:3c:1b:02:3d: + 5d:cc:70:4a:c1:84:06:f6:1a:db:16:b0:e0:30:b0: + 3a:85:41:48:a1:88:c5:38:04:7b:03:c4:86:f0:da: + 1a:ff:bc:d1:ac:7f:cd:0c:e8:5a:42:5e:43:7f:0d: + 61:5d:41:67:0f:b8:07:47:21:93:44:b2:ab:fa:d8: + 69:bb:b9:6d:a1:56:6d:23:54:aa:49:67:e7:57:c6: + e9:c7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 70:ED:B0:96:71:33:43:16:EF:32:FF:69:11:C9:F0:02:3F:6C:81:88 + X509v3 Authority Key Identifier: + keyid:5D:7F:8D:AF:1A:56:D3:F4:CA:3D:D3:6D:EF:50:11:F7:64:99:6F:02 + DirName:/CN=Veilid Test CA + serial:22:7A:2E:68:7C:DF:7B:81:85:1A:50:98:16:62:22:D0:0B:D6:1C:4A + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + DNS:Veilid Test Certificate + Signature Algorithm: sha256WithRSAEncryption + b8:fc:ac:62:d6:95:af:09:db:24:7d:82:2c:02:e1:d0:7b:f5: + 69:03:a4:42:55:c6:0d:2a:f1:9d:0e:c4:9b:78:40:7d:0d:7d: + ec:66:f6:c4:6d:06:d0:5b:58:de:ba:e6:67:ea:af:41:a3:87: + b4:37:8b:a8:1f:51:ae:70:e0:0d:f5:51:0a:7a:b3:b3:1d:d1: + 77:92:63:35:ae:50:9e:04:3d:04:6e:f1:60:c8:e3:8f:1f:75: + 47:05:27:a0:ff:c5:1b:30:68:b2:f9:5b:e6:f2:81:0f:9b:f2: + e8:8c:9d:b6:57:b2:c1:29:e7:d0:d0:88:b3:ba:8e:78:2e:ef: + ce:03:a3:12:fa:b4:e9:4e:1f:de:1a:cb:77:72:6b:71:98:02: + 37:d2:b4:02:f0:2c:08:67:ca:75:0d:af:81:bf:f8:57:f8:d9: + 4a:93:4f:db:3c:e1:af:3e:ab:9c:fe:87:f0:3a:01:21:6a:5c: + 99:83:e3:03:47:98:15:23:24:b3:ee:29:27:f4:f1:34:c1:e4: + f8:39:5a:92:da:c7:08:dc:71:87:1c:ff:67:e7:ef:24:bc:34: + e3:4e:e0:16:12:84:60:d4:7f:a2:c0:5b:85:a9:c5:ef:78:0b: + c3:64:cb:b4:05:eb:51:e5:c1:0f:60:da:5c:98:08:bf:5d:b9: + 1d:33:a7:26 +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIPEs5jvZD1q95tf9c+8+a7MA0GCSqGSIb3DQEBCwUAMBkx +FzAVBgNVBAMMDlZlaWxpZCBUZXN0IENBMB4XDTIxMTEyMjEzNTIxNloXDTI0MDIy +NTEzNTIxNlowIjEgMB4GA1UEAwwXVmVpbGlkIFRlc3QgQ2VydGlmaWNhdGUwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLLnpHgb5va1M3UcFQaFpEPbq5 +m3hAhDXUDuhBpg4KuTSul6M3PoHtbA/4iosLGu0Gl1dtSaXstMTYbdJXw4eJme6w +18WCodzVmLPvENrAXDiiuxU+Dl68oM2h8Adnu1c/icxyT7vAp+2tFQdhwrQhczkA +m4+qBBvEndQARIexebThTgE87qS7+a1diEEDtL/fv3Ek7gtpWVXdQ9GRBN6YnFTy +7mN4/nYZv+Zd1liBPBsCPV3McErBhAb2GtsWsOAwsDqFQUihiMU4BHsDxIbw2hr/ +vNGsf80M6FpCXkN/DWFdQWcPuAdHIZNEsqv62Gm7uW2hVm0jVKpJZ+dXxunHAgMB +AAGjgckwgcYwCQYDVR0TBAIwADAdBgNVHQ4EFgQUcO2wlnEzQxbvMv9pEcnwAj9s +gYgwVAYDVR0jBE0wS4AUXX+NrxpW0/TKPdNt71AR92SZbwKhHaQbMBkxFzAVBgNV +BAMMDlZlaWxpZCBUZXN0IENBghQiei5ofN97gYUaUJgWYiLQC9YcSjATBgNVHSUE +DDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwIgYDVR0RBBswGYIXVmVpbGlkIFRl +c3QgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBALj8rGLWla8J2yR9giwC +4dB79WkDpEJVxg0q8Z0OxJt4QH0Nfexm9sRtBtBbWN665mfqr0Gjh7Q3i6gfUa5w +4A31UQp6s7Md0XeSYzWuUJ4EPQRu8WDI448fdUcFJ6D/xRswaLL5W+bygQ+b8uiM +nbZXssEp59DQiLO6jngu784DoxL6tOlOH94ay3dya3GYAjfStALwLAhnynUNr4G/ ++Ff42UqTT9s84a8+q5z+h/A6ASFqXJmD4wNHmBUjJLPuKSf08TTB5Pg5WpLaxwjc +cYcc/2fn7yS8NONO4BYShGDUf6LAW4Wpxe94C8Nky7QF61HlwQ9g2lyYCL9duR0z +pyY= +-----END CERTIFICATE----- diff --git a/veilid-tools/src/tests/files/key.pem b/veilid-tools/src/tests/files/key.pem new file mode 100644 index 00000000..38968f47 --- /dev/null +++ b/veilid-tools/src/tests/files/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyy56R4G+b2tTN1HBUGhaRD26uZt4QIQ11A7oQaYOCrk0rpej +Nz6B7WwP+IqLCxrtBpdXbUml7LTE2G3SV8OHiZnusNfFgqHc1Ziz7xDawFw4orsV +Pg5evKDNofAHZ7tXP4nMck+7wKftrRUHYcK0IXM5AJuPqgQbxJ3UAESHsXm04U4B +PO6ku/mtXYhBA7S/379xJO4LaVlV3UPRkQTemJxU8u5jeP52Gb/mXdZYgTwbAj1d +zHBKwYQG9hrbFrDgMLA6hUFIoYjFOAR7A8SG8Noa/7zRrH/NDOhaQl5Dfw1hXUFn +D7gHRyGTRLKr+thpu7ltoVZtI1SqSWfnV8bpxwIDAQABAoIBAQCae5MjbUWC56JU +7EdEQKNpQVoIp2mt/BgFTPRQfdYtVxX0LX0+krss7r3R5lzDq8xN96HUiWur5uHI +APAuJI+YEr8GHHii0zjZ+onMmg8ItNWm/QGwtjJXzxeqKZsnxqwWtkoJHBCP8d5n +fBapwOU+jaHokV6RESCfxLSdI33cdGcOgDAn4/lvcXZ4Pq0qbitFuZwBPpobHbp4 +Mo94K7oh4KCt3FDMfZshkSF0wlquRIeUsI2uZUofybDa/j1RgEsqBZIrHqM6xXV1 +/r13+mMZC4otE0qhBV9jTYffaxooOnae8/Ve0FgaPWpNm7AD6p7l4a3csIkcggMS +xx7cntR5AoGBAOvPgDDLJ7h+AgY7jAd13/eZ75NtuEWbSq4c4Kwwy1K17L+3isX/ +RRkQ5qGTNsT6j1dddfwzX6Rhsi+JywczEdJdWgGNpFCIa8Ly2+47YLDpv0ZIISIZ +V0Ngg6dyuuQo7gFlLz9Dhe/If32/93gEW6HZOjn+GmQu53ZSDdHvukpjAoGBANyT +0GZzaP2ulV3Pk+9nJhC6eK2BZWF4ODXHsAgNUEyWZ4qDM71oDxyFbaWS2IDg7jz7 +T2wVffRFDcx8oNXWbhWbejSBGHWI8HRk0Ki83K0r7Cj8Uhy53rQjGOsdLf3K9T9h +GGVcwMHlBGIvswqTnJKysvgcoh1Ir+6RqbfCmG5NAoGAaVa8UQ+vor7HcLlRCFQj +xJvDZfxxgMaqSbUkuEbjzQLvy4TWPTSXTWc7X5o/sSasub5KYmsgonHyA0Juq7yo +jWyeNGttp3wJh4CttnJX8y+3/lFiW7UuQi7vIPIjgqC2EXF99ajYQBE0wpvqlHZ9 +6IL9e8KDT5WUWEq3WbzZXzkCgYB/0Md6FnZISdoTui0nFMZh+yvinpB4ookv4L6I +a+6T8rOc99oLbzkSdd7LiwQZ6j0i6R1krC+IVFtimvU39EFmE+oEcqoRsYBkcebX +YFkfn8wBE/Ug4DPEfnH6C7aS0gC68TCJy+2GbYbUvn8pKdAY0aQTUcQ+49fOjmmi +KgjaIQKBgQDoT0af/7a7LnY9dkbkz624HmNVyMPOa4/STrdxyy3NRhq/dysRW+At +x30nvCWpv0Z5BAyaUCrRWPGFxhv3/Z7qb4fx5uUbC3Jc04I5D6fwYqrQofGS8TMK +Lrg83o5Ag++pllu1IeWiGQPRbn7VZ+O6pISgpRpYBexXGyLJ6wtcAw== +-----END RSA PRIVATE KEY----- diff --git a/veilid-tools/src/tests/ios/.gitignore b/veilid-tools/src/tests/ios/.gitignore new file mode 100644 index 00000000..438326e0 --- /dev/null +++ b/veilid-tools/src/tests/ios/.gitignore @@ -0,0 +1,91 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.c b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.c new file mode 100644 index 00000000..5d59813e --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.c @@ -0,0 +1,8 @@ +// +// veilidtools.c +// veilidtools-tests +// +// Created by JSmith on 7/6/21. +// + +#include "veilid-tools.h" diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.h b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.h new file mode 100644 index 00000000..8f11707a --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.h @@ -0,0 +1,13 @@ +// +// veilid-tools.h +// veilid-tools-tests +// +// Created by JSmith on 7/6/21. +// + +#ifndef veilid_tools_h +#define veilid_tools_h + +void run_veilid_tools_tests(void); + +#endif /* veilid-tools_h */ diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests-Bridging-Header.h b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests-Bridging-Header.h new file mode 100644 index 00000000..3344ec39 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#include "veilid-tools.h" diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.pbxproj b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.pbxproj new file mode 100644 index 00000000..b47f1821 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.pbxproj @@ -0,0 +1,413 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 4317C6BD2694A676009C717F /* veilid-tools.c in Sources */ = {isa = PBXBuildFile; fileRef = 4317C6BC2694A676009C717F /* veilid-tools.c */; }; + 43C436B0268904AC002D11C5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C436AF268904AC002D11C5 /* AppDelegate.swift */; }; + 43C436B2268904AC002D11C5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C436B1268904AC002D11C5 /* SceneDelegate.swift */; }; + 43C436B4268904AC002D11C5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C436B3268904AC002D11C5 /* ViewController.swift */; }; + 43C436B7268904AC002D11C5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43C436B5268904AC002D11C5 /* Main.storyboard */; }; + 43C436B9268904AD002D11C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43C436B8268904AD002D11C5 /* Assets.xcassets */; }; + 43C436BC268904AD002D11C5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43C436BA268904AD002D11C5 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 4317C6BA2694A675009C717F /* veilidtools-tests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "veilidtools-tests-Bridging-Header.h"; sourceTree = ""; }; + 4317C6BB2694A676009C717F /* veilid-tools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "veilid-tools.h"; sourceTree = ""; }; + 4317C6BC2694A676009C717F /* veilid-tools.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "veilid-tools.c"; sourceTree = ""; }; + 43C436AC268904AC002D11C5 /* veilidtools-tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "veilidtools-tests.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 43C436AF268904AC002D11C5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 43C436B1268904AC002D11C5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 43C436B3268904AC002D11C5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 43C436B6268904AC002D11C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 43C436B8268904AD002D11C5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 43C436BB268904AD002D11C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 43C436BD268904AD002D11C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 43C436A9268904AC002D11C5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4317C6B7269490DA009C717F /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 43C436A3268904AC002D11C5 = { + isa = PBXGroup; + children = ( + 4317C6BB2694A676009C717F /* veilid-tools.h */, + 4317C6BC2694A676009C717F /* veilid-tools.c */, + 43C436AE268904AC002D11C5 /* veilidtools-tests */, + 43C436AD268904AC002D11C5 /* Products */, + 4317C6B7269490DA009C717F /* Frameworks */, + 4317C6BA2694A675009C717F /* veilidtools-tests-Bridging-Header.h */, + ); + sourceTree = ""; + }; + 43C436AD268904AC002D11C5 /* Products */ = { + isa = PBXGroup; + children = ( + 43C436AC268904AC002D11C5 /* veilidtools-tests.app */, + ); + name = Products; + sourceTree = ""; + }; + 43C436AE268904AC002D11C5 /* veilidtools-tests */ = { + isa = PBXGroup; + children = ( + 43C436AF268904AC002D11C5 /* AppDelegate.swift */, + 43C436B1268904AC002D11C5 /* SceneDelegate.swift */, + 43C436B3268904AC002D11C5 /* ViewController.swift */, + 43C436B5268904AC002D11C5 /* Main.storyboard */, + 43C436B8268904AD002D11C5 /* Assets.xcassets */, + 43C436BA268904AD002D11C5 /* LaunchScreen.storyboard */, + 43C436BD268904AD002D11C5 /* Info.plist */, + ); + path = "veilidtools-tests"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 43C436AB268904AC002D11C5 /* veilidtools-tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43C436C0268904AD002D11C5 /* Build configuration list for PBXNativeTarget "veilidtools-tests" */; + buildPhases = ( + 43C436C326893020002D11C5 /* Cargo Build */, + 43C436A8268904AC002D11C5 /* Sources */, + 43C436A9268904AC002D11C5 /* Frameworks */, + 43C436AA268904AC002D11C5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "veilidtools-tests"; + productName = "veilidtools-tests"; + productReference = 43C436AC268904AC002D11C5 /* veilidtools-tests.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 43C436A4268904AC002D11C5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1250; + LastUpgradeCheck = 1250; + TargetAttributes = { + 43C436AB268904AC002D11C5 = { + CreatedOnToolsVersion = 12.5.1; + LastSwiftMigration = 1250; + }; + }; + }; + buildConfigurationList = 43C436A7268904AC002D11C5 /* Build configuration list for PBXProject "veilidtools-tests" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 43C436A3268904AC002D11C5; + productRefGroup = 43C436AD268904AC002D11C5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 43C436AB268904AC002D11C5 /* veilidtools-tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 43C436AA268904AC002D11C5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C436BC268904AD002D11C5 /* LaunchScreen.storyboard in Resources */, + 43C436B9268904AD002D11C5 /* Assets.xcassets in Resources */, + 43C436B7268904AC002D11C5 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 43C436C326893020002D11C5 /* Cargo Build */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Cargo Build"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "../../../../ios_build.sh --features ios_tests\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 43C436A8268904AC002D11C5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C436B4268904AC002D11C5 /* ViewController.swift in Sources */, + 43C436B0268904AC002D11C5 /* AppDelegate.swift in Sources */, + 43C436B2268904AC002D11C5 /* SceneDelegate.swift in Sources */, + 4317C6BD2694A676009C717F /* veilid-tools.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 43C436B5268904AC002D11C5 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 43C436B6268904AC002D11C5 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 43C436BA268904AD002D11C5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 43C436BB268904AD002D11C5 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 43C436BE268904AD002D11C5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = arm64; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 43C436BF268904AD002D11C5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = arm64; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 43C436C1268904AD002D11C5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ZJPQSFX5MW; + INFOPLIST_FILE = "veilidtools-tests/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ""; + "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "-L../../../../../target/aarch64-apple-ios/debug", + "-lveilid_tools", + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( + "-L../../../../../target/x86_64-apple-ios/debug", + "-lveilid_tools", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.veilid.veilidtools-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "veilidtools-tests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 43C436C2268904AD002D11C5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ZJPQSFX5MW; + INFOPLIST_FILE = "veilidtools-tests/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ""; + "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "-L../../../../../target/aarch64-apple-ios/release", + "-lveilid_tools", + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( + "-L../../../../../target/x86_64-apple-ios/release", + "-lveilid_tools", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.veilid.veilidtools-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "veilidtools-tests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 43C436A7268904AC002D11C5 /* Build configuration list for PBXProject "veilidtools-tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43C436BE268904AD002D11C5 /* Debug */, + 43C436BF268904AD002D11C5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 43C436C0268904AD002D11C5 /* Build configuration list for PBXNativeTarget "veilidtools-tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43C436C1268904AD002D11C5 /* Debug */, + 43C436C2268904AD002D11C5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 43C436A4268904AC002D11C5 /* Project object */; +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/xcshareddata/xcschemes/veilidcore-tests.xcscheme b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/xcshareddata/xcschemes/veilidcore-tests.xcscheme new file mode 100644 index 00000000..f1525db5 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/xcshareddata/xcschemes/veilidcore-tests.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/AppDelegate.swift b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/AppDelegate.swift new file mode 100644 index 00000000..7b8b8e2b --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// veilidtools-tests +// +// Created by JSmith on 6/27/21. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window : UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + @available(iOS 13.0, *) + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + @available(iOS 13.0, *) + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AccentColor.colorset/Contents.json b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AppIcon.appiconset/Contents.json b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/Contents.json b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/LaunchScreen.storyboard b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..3fd36928 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/Main.storyboard b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/Main.storyboard new file mode 100644 index 00000000..678f8974 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/Main.storyboard @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Info.plist b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Info.plist new file mode 100644 index 00000000..5b531f7b --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/SceneDelegate.swift b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/SceneDelegate.swift new file mode 100644 index 00000000..5ea6baef --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// veilidtools-tests +// +// Created by JSmith on 6/27/21. +// + +import UIKit + +@available(iOS 13.0, *) +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/ViewController.swift b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/ViewController.swift new file mode 100644 index 00000000..922d5462 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// veilidtools-tests +// +// Created by JSmith on 6/27/21. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + run_veilid_tools_tests() + } + + +} + diff --git a/veilid-tools/src/tests/mod.rs b/veilid-tools/src/tests/mod.rs new file mode 100644 index 00000000..0b6b6216 --- /dev/null +++ b/veilid-tools/src/tests/mod.rs @@ -0,0 +1,3 @@ +pub mod common; +#[cfg(not(target_arch = "wasm32"))] +mod native; diff --git a/veilid-tools/src/tests/native/mod.rs b/veilid-tools/src/tests/native/mod.rs new file mode 100644 index 00000000..9ab6f14d --- /dev/null +++ b/veilid-tools/src/tests/native/mod.rs @@ -0,0 +1,152 @@ +//! Test suite for Native +#![cfg(not(target_arch = "wasm32"))] + +mod test_async_peek_stream; + +use crate::tests::common::*; +use crate::*; + +#[cfg(all(target_os = "android", feature = "android_tests"))] +use jni::{objects::JClass, objects::JObject, JNIEnv}; + +#[cfg(all(target_os = "android", feature = "android_tests"))] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn Java_com_veilid_veilidtools_veilidtools_1android_1tests_MainActivity_run_1tests( + env: JNIEnv, + _class: JClass, + ctx: JObject, +) { + crate::intf::utils::android::veilid_tools_setup_android( + env, + ctx, + "veilid_tools", + crate::veilid_config::VeilidConfigLogLevel::Trace, + ); + run_all_tests(); +} + +#[cfg(all(target_os = "ios", feature = "ios_tests"))] +#[no_mangle] +pub extern "C" fn run_veilid_tools_tests() { + let log_path: std::path::PathBuf = [ + std::env::var("HOME").unwrap().as_str(), + "Documents", + "veilid-tools.log", + ] + .iter() + .collect(); + crate::intf::utils::ios_test_setup::veilid_tools_setup( + "veilid-tools", + Some(Level::Trace), + Some((Level::Trace, log_path.as_path())), + ); + run_all_tests(); +} + +/////////////////////////////////////////////////////////////////////////// + +#[allow(dead_code)] +pub fn run_all_tests() { + info!("TEST: exec_test_host_interface"); + exec_test_host_interface(); + info!("TEST: exec_test_async_peek_stream"); + exec_test_async_peek_stream(); + info!("TEST: exec_test_async_tag_lock"); + exec_test_async_tag_lock(); + + info!("Finished unit tests"); +} + +#[cfg(feature = "rt-tokio")] +fn block_on, T>(f: F) -> T { + let rt = tokio::runtime::Runtime::new().unwrap(); + let local = tokio::task::LocalSet::new(); + local.block_on(&rt, f) +} +#[cfg(feature = "rt-async-std")] +fn block_on, T>(f: F) -> T { + async_std::task::block_on(f) +} + +fn exec_test_host_interface() { + block_on(async { + test_host_interface::test_all().await; + }); +} +fn exec_test_async_peek_stream() { + block_on(async { + test_async_peek_stream::test_all().await; + }) +} +fn exec_test_async_tag_lock() { + block_on(async { + test_async_tag_lock::test_all().await; + }) +} +/////////////////////////////////////////////////////////////////////////// +cfg_if! { + if #[cfg(test)] { + + static DEFAULT_LOG_IGNORE_LIST: [&str; 21] = [ + "mio", + "h2", + "hyper", + "tower", + "tonic", + "tokio", + "runtime", + "tokio_util", + "want", + "serial_test", + "async_std", + "async_io", + "polling", + "rustls", + "async_tungstenite", + "tungstenite", + "netlink_proto", + "netlink_sys", + "trust_dns_resolver", + "trust_dns_proto", + "attohttpc", + ]; + + use serial_test::serial; + use simplelog::*; + use std::sync::Once; + + static SETUP_ONCE: Once = Once::new(); + + pub fn setup() { + SETUP_ONCE.call_once(|| { + let mut cb = ConfigBuilder::new(); + for ig in DEFAULT_LOG_IGNORE_LIST { + cb.add_filter_ignore_str(ig); + } + TestLogger::init(LevelFilter::Trace, cb.build()).unwrap(); + }); + } + + #[test] + #[serial] + fn run_test_host_interface() { + setup(); + exec_test_host_interface(); + } + + #[test] + #[serial] + fn run_test_async_peek_stream() { + setup(); + exec_test_async_peek_stream(); + } + + #[test] + #[serial] + fn run_test_async_tag_lock() { + setup(); + exec_test_async_tag_lock(); + } + } +} diff --git a/veilid-tools/src/tests/native/test_async_peek_stream.rs b/veilid-tools/src/tests/native/test_async_peek_stream.rs new file mode 100644 index 00000000..0797a4f8 --- /dev/null +++ b/veilid-tools/src/tests/native/test_async_peek_stream.rs @@ -0,0 +1,352 @@ +use crate::*; + +cfg_if! { + if #[cfg(feature="rt-async-std")] { + use async_std::net::{TcpListener, TcpStream}; + use async_std::prelude::FutureExt; + use async_std::task::sleep; + } else if #[cfg(feature="rt-tokio")] { + use tokio::net::{TcpListener, TcpStream}; + use tokio::time::sleep; + use tokio_util::compat::*; + } +} + +use futures_util::{AsyncReadExt, AsyncWriteExt}; +use std::io; + +static MESSAGE: &[u8; 62] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +async fn make_tcp_loopback() -> Result<(TcpStream, TcpStream), io::Error> { + let listener = TcpListener::bind("127.0.0.1:0").await?; + let local_addr = listener.local_addr()?; + + let accept_future = async { + let (accepted_stream, peer_address) = listener.accept().await?; + trace!("connection from {}", peer_address); + accepted_stream.set_nodelay(true)?; + Result::::Ok(accepted_stream) + }; + let connect_future = async { + sleep(Duration::from_secs(1)).await; + let connected_stream = TcpStream::connect(local_addr).await?; + connected_stream.set_nodelay(true)?; + Result::::Ok(connected_stream) + }; + + cfg_if! { + if #[cfg(feature="rt-async-std")] { + accept_future.try_join(connect_future).await + } else if #[cfg(feature="rt-tokio")] { + tokio::try_join!(accept_future, connect_future) + } + } +} + +async fn make_async_peek_stream_loopback() -> (AsyncPeekStream, AsyncPeekStream) { + let (acc, conn) = make_tcp_loopback().await.unwrap(); + #[cfg(feature = "rt-tokio")] + let acc = acc.compat(); + #[cfg(feature = "rt-tokio")] + let conn = conn.compat(); + + let aps_a = AsyncPeekStream::new(acc); + let aps_c = AsyncPeekStream::new(conn); + + (aps_a, aps_c) +} + +#[cfg(feature = "rt-tokio")] +async fn make_stream_loopback() -> (Compat, Compat) { + let (a, c) = make_tcp_loopback().await.unwrap(); + (a.compat(), c.compat()) +} +#[cfg(feature = "rt-async-std")] +async fn make_stream_loopback() -> (TcpStream, TcpStream) { + make_tcp_loopback().await.unwrap() +} + +pub async fn test_nothing() { + info!("test_nothing"); + let (mut a, mut c) = make_stream_loopback().await; + let outbuf = MESSAGE.to_vec(); + + a.write_all(&outbuf).await.unwrap(); + + let mut inbuf: Vec = Vec::new(); + inbuf.resize(outbuf.len(), 0u8); + c.read_exact(&mut inbuf).await.unwrap(); + + assert_eq!(inbuf, outbuf); +} + +pub async fn test_no_peek() { + info!("test_no_peek"); + let (mut a, mut c) = make_async_peek_stream_loopback().await; + + let outbuf = MESSAGE.to_vec(); + + a.write_all(&outbuf).await.unwrap(); + + let mut inbuf: Vec = Vec::new(); + inbuf.resize(outbuf.len(), 0u8); + c.read_exact(&mut inbuf).await.unwrap(); + + assert_eq!(inbuf, outbuf); +} + +pub async fn test_peek_all_read() { + info!("test_peek_all_read"); + + let (mut a, mut c) = make_async_peek_stream_loopback().await; + // write everything + let outbuf = MESSAGE.to_vec(); + a.write_all(&outbuf).await.unwrap(); + + // peek everything + let mut peekbuf1: Vec = Vec::new(); + peekbuf1.resize(outbuf.len(), 0u8); + let peeksize1 = c.peek(&mut peekbuf1).await.unwrap(); + + assert_eq!(peeksize1, peekbuf1.len()); + // read everything + let mut inbuf: Vec = Vec::new(); + inbuf.resize(outbuf.len(), 0u8); + c.read_exact(&mut inbuf).await.unwrap(); + + assert_eq!(inbuf, outbuf); + assert_eq!(peekbuf1, outbuf); +} + +pub async fn test_peek_some_read() { + info!("test_peek_some_read"); + + let (mut a, mut c) = make_async_peek_stream_loopback().await; + + // write everything + let outbuf = MESSAGE.to_vec(); + a.write_all(&outbuf).await.unwrap(); + + // peek partially + let mut peekbuf1: Vec = Vec::new(); + peekbuf1.resize(outbuf.len() / 2, 0u8); + let peeksize1 = c.peek(&mut peekbuf1).await.unwrap(); + assert_eq!(peeksize1, peekbuf1.len()); + // read everything + let mut inbuf: Vec = Vec::new(); + inbuf.resize(outbuf.len(), 0u8); + c.read_exact(&mut inbuf).await.unwrap(); + + assert_eq!(inbuf, outbuf); + assert_eq!(peekbuf1, outbuf[0..peeksize1].to_vec()); +} + +pub async fn test_peek_some_peek_some_read() { + info!("test_peek_some_peek_some_read"); + + let (mut a, mut c) = make_async_peek_stream_loopback().await; + + // write everything + let outbuf = MESSAGE.to_vec(); + a.write_all(&outbuf).await.unwrap(); + + // peek partially + let mut peekbuf1: Vec = Vec::new(); + peekbuf1.resize(outbuf.len() / 4, 0u8); + let peeksize1 = c.peek(&mut peekbuf1).await.unwrap(); + assert_eq!(peeksize1, peekbuf1.len()); + + // peek partially + let mut peekbuf2: Vec = Vec::new(); + peekbuf2.resize(peeksize1 + 1, 0u8); + let peeksize2 = c.peek(&mut peekbuf2).await.unwrap(); + assert_eq!(peeksize2, peekbuf2.len()); + + // read everything + let mut inbuf: Vec = Vec::new(); + inbuf.resize(outbuf.len(), 0u8); + c.read_exact(&mut inbuf).await.unwrap(); + + assert_eq!(inbuf, outbuf); + assert_eq!(peekbuf1, outbuf[0..peeksize1].to_vec()); + assert_eq!(peekbuf2, outbuf[0..peeksize2].to_vec()); +} + +pub async fn test_peek_some_read_peek_some_read() { + info!("test_peek_some_read_peek_some_read"); + + let (mut a, mut c) = make_async_peek_stream_loopback().await; + + // write everything + let outbuf = MESSAGE.to_vec(); + a.write_all(&outbuf).await.unwrap(); + + // peek partially + let mut peekbuf1: Vec = Vec::new(); + peekbuf1.resize(outbuf.len() / 4, 0u8); + let peeksize1 = c.peek(&mut peekbuf1).await.unwrap(); + assert_eq!(peeksize1, peekbuf1.len()); + + // read partially + let mut inbuf1: Vec = Vec::new(); + inbuf1.resize(peeksize1 - 1, 0u8); + c.read_exact(&mut inbuf1).await.unwrap(); + + // peek partially + let mut peekbuf2: Vec = Vec::new(); + peekbuf2.resize(2, 0u8); + let peeksize2 = c.peek(&mut peekbuf2).await.unwrap(); + assert_eq!(peeksize2, peekbuf2.len()); + + // read partially + let mut inbuf2: Vec = Vec::new(); + inbuf2.resize(2, 0u8); + c.read_exact(&mut inbuf2).await.unwrap(); + + assert_eq!(peekbuf1, outbuf[0..peeksize1].to_vec()); + assert_eq!(inbuf1, outbuf[0..peeksize1 - 1].to_vec()); + assert_eq!(peekbuf2, outbuf[peeksize1 - 1..peeksize1 + 1].to_vec()); + assert_eq!(inbuf2, peekbuf2); +} + +pub async fn test_peek_some_read_peek_all_read() { + info!("test_peek_some_read_peek_all_read"); + + let (mut a, mut c) = make_async_peek_stream_loopback().await; + + // write everything + let outbuf = MESSAGE.to_vec(); + a.write_all(&outbuf).await.unwrap(); + + // peek partially + let mut peekbuf1: Vec = Vec::new(); + peekbuf1.resize(outbuf.len() / 4, 0u8); + let peeksize1 = c.peek(&mut peekbuf1).await.unwrap(); + assert_eq!(peeksize1, peekbuf1.len()); + + // read partially + let mut inbuf1: Vec = Vec::new(); + inbuf1.resize(peeksize1 + 1, 0u8); + c.read_exact(&mut inbuf1).await.unwrap(); + + // peek past end + let mut peekbuf2: Vec = Vec::new(); + peekbuf2.resize(outbuf.len(), 0u8); + let peeksize2 = c.peek(&mut peekbuf2).await.unwrap(); + assert_eq!(peeksize2, outbuf.len() - (peeksize1 + 1)); + + // read remaining + let mut inbuf2: Vec = Vec::new(); + inbuf2.resize(peeksize2, 0u8); + c.read_exact(&mut inbuf2).await.unwrap(); + + assert_eq!(peekbuf1, outbuf[0..peeksize1].to_vec()); + assert_eq!(inbuf1, outbuf[0..peeksize1 + 1].to_vec()); + assert_eq!( + peekbuf2[0..peeksize2].to_vec(), + outbuf[peeksize1 + 1..outbuf.len()].to_vec() + ); + assert_eq!(inbuf2, peekbuf2[0..peeksize2].to_vec()); +} + +pub async fn test_peek_some_read_peek_some_read_all_read() { + info!("test_peek_some_read_peek_some_read_peek_all_read"); + + let (mut a, mut c) = make_async_peek_stream_loopback().await; + + // write everything + let outbuf = MESSAGE.to_vec(); + a.write_all(&outbuf).await.unwrap(); + + // peek partially + let mut peekbuf1: Vec = Vec::new(); + peekbuf1.resize(outbuf.len() / 4, 0u8); + let peeksize1 = c.peek(&mut peekbuf1).await.unwrap(); + assert_eq!(peeksize1, peekbuf1.len()); + + // read partially + let mut inbuf1: Vec = Vec::new(); + inbuf1.resize(peeksize1 - 1, 0u8); + c.read_exact(&mut inbuf1).await.unwrap(); + + // peek partially + let mut peekbuf2: Vec = Vec::new(); + peekbuf2.resize(2, 0u8); + let peeksize2 = c.peek(&mut peekbuf2).await.unwrap(); + assert_eq!(peeksize2, peekbuf2.len()); + // read partially + let mut inbuf2: Vec = Vec::new(); + inbuf2.resize(1, 0u8); + c.read_exact(&mut inbuf2).await.unwrap(); + + // read remaining + let mut inbuf3: Vec = Vec::new(); + inbuf3.resize(outbuf.len() - peeksize1, 0u8); + c.read_exact(&mut inbuf3).await.unwrap(); + + assert_eq!(peekbuf1, outbuf[0..peeksize1].to_vec()); + assert_eq!(inbuf1, outbuf[0..peeksize1 - 1].to_vec()); + assert_eq!( + peekbuf2[0..peeksize2].to_vec(), + outbuf[peeksize1 - 1..peeksize1 + 1].to_vec() + ); + assert_eq!(inbuf2, peekbuf2[0..1].to_vec()); + assert_eq!(inbuf3, outbuf[peeksize1..outbuf.len()].to_vec()); +} + +pub async fn test_peek_exact_read_peek_exact_read_all_read() { + info!("test_peek_exact_read_peek_exact_read_all_read"); + + let (mut a, mut c) = make_async_peek_stream_loopback().await; + + // write everything + let outbuf = MESSAGE.to_vec(); + a.write_all(&outbuf).await.unwrap(); + + // peek partially + let mut peekbuf1: Vec = Vec::new(); + peekbuf1.resize(outbuf.len() / 4, 0u8); + let peeksize1 = c.peek_exact(&mut peekbuf1).await.unwrap(); + assert_eq!(peeksize1, peekbuf1.len()); + + // read partially + let mut inbuf1: Vec = Vec::new(); + inbuf1.resize(peeksize1 - 1, 0u8); + c.read_exact(&mut inbuf1).await.unwrap(); + + // peek partially + let mut peekbuf2: Vec = Vec::new(); + peekbuf2.resize(2, 0u8); + let peeksize2 = c.peek_exact(&mut peekbuf2).await.unwrap(); + assert_eq!(peeksize2, peekbuf2.len()); + // read partially + let mut inbuf2: Vec = Vec::new(); + inbuf2.resize(1, 0u8); + c.read_exact(&mut inbuf2).await.unwrap(); + + // read remaining + let mut inbuf3: Vec = Vec::new(); + inbuf3.resize(outbuf.len() - peeksize1, 0u8); + c.read_exact(&mut inbuf3).await.unwrap(); + + assert_eq!(peekbuf1, outbuf[0..peeksize1].to_vec()); + assert_eq!(inbuf1, outbuf[0..peeksize1 - 1].to_vec()); + assert_eq!( + peekbuf2[0..peeksize2].to_vec(), + outbuf[peeksize1 - 1..peeksize1 + 1].to_vec() + ); + assert_eq!(inbuf2, peekbuf2[0..1].to_vec()); + assert_eq!(inbuf3, outbuf[peeksize1..outbuf.len()].to_vec()); +} + +pub async fn test_all() { + test_nothing().await; + test_no_peek().await; + test_peek_all_read().await; + test_peek_some_read().await; + test_peek_some_peek_some_read().await; + test_peek_some_read_peek_some_read().await; + test_peek_some_read_peek_all_read().await; + test_peek_some_read_peek_some_read_all_read().await; + test_peek_exact_read_peek_exact_read_all_read().await; +} diff --git a/veilid-tools/tests/web.rs b/veilid-tools/tests/web.rs new file mode 100644 index 00000000..7e7c3c5f --- /dev/null +++ b/veilid-tools/tests/web.rs @@ -0,0 +1,87 @@ +//! Test suite for the Web and headless browsers. +#![cfg(target_arch = "wasm32")] + +use veilid_tools::tests::common::*; +use veilid_tools::xx::*; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +extern crate wee_alloc; +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +static SETUP_ONCE: Once = Once::new(); +pub fn setup() -> () { + SETUP_ONCE.call_once(|| { + console_error_panic_hook::set_once(); + let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); + builder.set_report_logs_in_timings(false); + builder.set_max_level(Level::TRACE); + builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithConsoleColor); + tracing_wasm::set_as_global_default_with_config(builder.build()); + }); +} + +#[wasm_bindgen_test] +async fn run_test_dht_key() { + setup(); + + test_dht_key::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_host_interface() { + setup(); + + test_host_interface::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_veilid_tools() { + setup(); + + test_veilid_tools::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_config() { + setup(); + + test_veilid_config::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_connection_table() { + setup(); + + test_connection_table::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_table_store() { + setup(); + + test_table_store::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_crypto() { + setup(); + + test_crypto::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_envelope_receipt() { + setup(); + + test_envelope_receipt::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_async_tag_lock() { + setup(); + + test_async_tag_lock::test_all().await; +}