Full wasm support with `wasm-bindgen-futures` (#445)

This PR applies the necessary patches to correctly pass (all but two)
tests when compiling for wasm32-unknown-unknown.

2 tests have been disabled for wasm at the moment: test
messages::search_messages and test messages::send_message_attachment.
These two tests currently rely on `std::fs` to grab a file to
send/search for using chorus. This is not possible on wasm32-unknown,
because this target does not have a Filesystem, and does not emulate on
either. These two tests should be patched, but this should be good
enough for now.
This commit is contained in:
Flori 2023-11-22 16:09:43 +01:00 committed by GitHub
commit b7d4ff2f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 205 additions and 36 deletions

View File

@ -45,7 +45,7 @@ jobs:
cargo build --verbose --all-features
cargo test --verbose --all-features
fi
macos:
wasm-safari:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
@ -73,5 +73,59 @@ jobs:
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force
SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
wasm-gecko:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache-dependency-path: server/package-lock.json
- name: Prepare and start Spacebar server
run: |
npm install
npm run setup
npm run start &
working-directory: ./server
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
prefix-key: "macos"
- name: Run WASM tests with Safari, Firefox, Chrome
run: |
rustup target add wasm32-unknown-unknown
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
wasm-chrome:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache-dependency-path: server/package-lock.json
- name: Prepare and start Spacebar server
run: |
npm install
npm run setup
npm run start &
working-directory: ./server
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
prefix-key: "macos"
- name: Run WASM tests with Safari, Firefox, Chrome
run: |
rustup target add wasm32-unknown-unknown
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"

1
Cargo.lock generated
View File

@ -223,6 +223,7 @@ dependencies = [
"tokio-tungstenite",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"ws_stream_wasm",
]

View File

@ -68,7 +68,7 @@ hostname = "0.3.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.11", features = ["js"] }
ws_stream_wasm = "0.7.4"
wasm-bindgen-futures = "0.4.38"
[dev-dependencies]
lazy_static = "1.4.0"

View File

@ -98,7 +98,7 @@ dbg!(&user.object.read().unwrap().username);
## Supported Platforms
All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aarch64/x86_64)) are supported.
We are currently working on adding full support for `wasm32-unknown-unknown`. This will allow you to use Chorus in
`wasm32-unknown-unknown` is a supported compilation target on versions `>0.11.0`. This allows you to use Chorus in
your browser, or in any other environment that supports WebAssembly.
We recommend checking out the examples directory, as well as the documentation for more information.
@ -107,6 +107,25 @@ We recommend checking out the examples directory, as well as the documentation f
Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0.
## Development Setup
Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version`
in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target.
You can do this by running `rustup target add wasm32-unknown-unknown`.
### Testing
In general, the tests will require you to run a local instance of the Spacebar server. You can find instructions on how
to do that [here](https://docs.spacebar.chat/setup/server/). You can find a pre-configured version of the server
[here](https://github.com/bitfl0wer/server). It is recommended to use the pre-configured version, as certain things
like "proxy connection checking" are already disabled on this version, which otherwise might break tests.
### wasm
To test for wasm, you will need to `cargo install wasm-pack`. You can then run
`wasm-pack test --<chrome/firefox/safari> --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features`
to run the tests for wasm.
## Versioning
This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html).

View File

@ -2,6 +2,7 @@ use std::time::Duration;
use futures_util::{SinkExt, StreamExt};
use log::*;
#[cfg(not(target_arch = "wasm32"))]
use tokio::task;
use self::event::Events;
@ -74,9 +75,14 @@ impl Gateway {
};
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello
#[cfg(not(target_arch = "wasm32"))]
task::spawn(async move {
gateway.gateway_listen_task().await;
});
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(async move {
gateway.gateway_listen_task().await;
});
Ok(GatewayHandle {
url: websocket_url.clone(),

View File

@ -4,7 +4,8 @@ use std::time::{self, Duration, Instant};
use tokio::sync::mpsc::{Receiver, Sender};
use safina_timer::sleep_until;
use tokio::task::{self, JoinHandle};
#[cfg(not(target_arch = "wasm32"))]
use tokio::task;
use super::*;
use crate::types;
@ -20,8 +21,6 @@ pub(super) struct HeartbeatHandler {
pub heartbeat_interval: Duration,
/// The send channel for the heartbeat thread
pub send: Sender<HeartbeatThreadCommunication>,
/// The handle of the thread
handle: JoinHandle<()>,
}
impl HeartbeatHandler {
@ -33,14 +32,18 @@ impl HeartbeatHandler {
let (send, receive) = tokio::sync::mpsc::channel(32);
let kill_receive = kill_rc.resubscribe();
let handle: JoinHandle<()> = task::spawn(async move {
#[cfg(not(target_arch = "wasm32"))]
task::spawn(async move {
Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await;
});
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(async move {
Self::heartbeat_task(websocket_tx, heartbeat_interval, receive, kill_receive).await;
});
Self {
heartbeat_interval,
send,
handle,
}
}

View File

@ -1,8 +1,14 @@
use chorus::types::RegisterSchema;
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
mod common;
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_registration() {
let bundle = common::setup().await;
let reg = RegisterSchema {

View File

@ -4,8 +4,15 @@ use chorus::types::{
};
mod common;
// PRETTYFYME: Move common wasm setup to common.rs
#[tokio::test]
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn get_channel() {
let mut bundle = common::setup().await;
let bundle_channel = bundle.channel.read().unwrap().clone();
@ -18,7 +25,8 @@ async fn get_channel() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn delete_channel() {
let mut bundle = common::setup().await;
let channel_guard = bundle.channel.write().unwrap().clone();
@ -27,7 +35,8 @@ async fn delete_channel() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn modify_channel() {
const CHANNEL_NAME: &str = "beepboop";
let mut bundle = common::setup().await;
@ -85,7 +94,8 @@ async fn modify_channel() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn get_channel_messages() {
let mut bundle = common::setup().await;
let channel_id: Snowflake = bundle.channel.read().unwrap().id;
@ -141,7 +151,8 @@ async fn get_channel_messages() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn create_dm() {
let mut bundle = common::setup().await;
let other_user = bundle.create_user("integrationtestuser2").await;

View File

@ -4,8 +4,14 @@ use std::sync::{Arc, RwLock};
use chorus::gateway::*;
use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
/// Tests establishing a connection (hello and heartbeats) on the local gateway;
async fn test_gateway_establish() {
let bundle = common::setup().await;
@ -14,7 +20,8 @@ async fn test_gateway_establish() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
/// Tests establishing a connection and authenticating
async fn test_gateway_authenticate() {
let bundle = common::setup().await;
@ -28,7 +35,8 @@ async fn test_gateway_authenticate() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_self_updating_structs() {
let mut bundle = common::setup().await;
let received_channel = bundle
@ -61,7 +69,8 @@ async fn test_self_updating_structs() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_recursive_self_updating_structs() {
// Setup
let mut bundle = common::setup().await;

View File

@ -3,8 +3,14 @@ use chorus::types::{
};
mod common;
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn guild_creation_deletion() {
let mut bundle = common::setup().await;
@ -26,7 +32,8 @@ async fn guild_creation_deletion() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn get_channels() {
let mut bundle = common::setup().await;
let guild = bundle.guild.read().unwrap().clone();
@ -34,7 +41,8 @@ async fn get_channels() {
common::teardown(bundle).await;
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn guild_create_ban() {
// TODO: When routes exist to check if user x is on guild y, add this as an assertion to check
// if Spacebar actually bans the user.
@ -71,7 +79,8 @@ async fn guild_create_ban() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn modify_guild() {
let mut bundle = common::setup().await;
let schema = GuildModifySchema {
@ -86,7 +95,8 @@ async fn modify_guild() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn guild_remove_member() {
let mut bundle = common::setup().await;
let channel = bundle.channel.read().unwrap().clone();

View File

@ -1,6 +1,12 @@
mod common;
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn generate_general_configuration_schema() {
let bundle = common::setup().await;
bundle

View File

@ -1,6 +1,13 @@
mod common;
use chorus::types::CreateChannelInviteSchema;
#[tokio::test]
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn create_accept_invite() {
let mut bundle = common::setup().await;
let channel = bundle.channel.read().unwrap().clone();

View File

@ -1,8 +1,14 @@
use chorus::{errors::ChorusResult, types::GuildMember};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
mod common;
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn add_remove_role() -> ChorusResult<()> {
let mut bundle = common::setup().await;
let guild = bundle.guild.read().unwrap().id;

View File

@ -2,10 +2,16 @@ use std::fs::File;
use std::io::{BufReader, Read};
use chorus::types::{self, Guild, Message, MessageSearchQuery};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
mod common;
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn send_message() {
let mut bundle = common::setup().await;
let message = types::MessageSendSchema {
@ -17,8 +23,12 @@ async fn send_message() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn send_message_attachment() {
/// FIXME: Fix this test for wasm32. wasm32-unknown-unknown does not have a filesystem and therefore cannot read files.
#[cfg(target_arch = "wasm32")]
return;
let f = File::open("./README.md").unwrap();
let mut reader = BufReader::new(f);
let mut buffer = Vec::new();
@ -54,8 +64,12 @@ async fn send_message_attachment() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn search_messages() {
/// FIXME: Fix this test for wasm32. wasm32-unknown-unknown does not have a filesystem and therefore cannot read files.
#[cfg(target_arch = "wasm32")]
return;
let f = File::open("./README.md").unwrap();
let mut reader = BufReader::new(f);
let mut buffer = Vec::new();
@ -100,7 +114,8 @@ async fn search_messages() {
assert_eq!(query_result.get(0).unwrap().id, message.id);
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_stickies() {
let mut bundle = common::setup().await;
let message = types::MessageSendSchema {

View File

@ -1,8 +1,14 @@
use chorus::types::{self, Relationship, RelationshipType};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
mod common;
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_get_mutual_relationships() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
@ -23,7 +29,8 @@ async fn test_get_mutual_relationships() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_get_relationships() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
@ -46,7 +53,8 @@ async fn test_get_relationships() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_modify_relationship_friends() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
@ -97,7 +105,8 @@ async fn test_modify_relationship_friends() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_modify_relationship_block() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;

View File

@ -1,8 +1,14 @@
use chorus::types::{self, RoleCreateModifySchema, RoleObject};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
mod common;
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn create_and_get_roles() {
let mut bundle = common::setup().await;
let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS;
@ -31,7 +37,8 @@ async fn create_and_get_roles() {
common::teardown(bundle).await
}
#[tokio::test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn get_and_delete_role() {
let mut bundle = common::setup().await;
let guild_id = bundle.guild.read().unwrap().id;