matrix-sdk: Support compiling to wasm32-unknown-unknown

master
stoically 2020-05-08 16:12:21 +02:00
parent b6c0d4e3bb
commit ef6104bc53
36 changed files with 425 additions and 162 deletions

View File

@ -20,6 +20,28 @@ jobs:
after_success:
- bash <(curl -s https://codecov.io/bash)
- os: linux
name: wasm32-unknown-unknown
before_script:
- |
cargo install wasm-bindgen-cli
rustup target add wasm32-unknown-unknown
wget https://github.com/emscripten-core/emsdk/archive/master.zip
unzip master.zip
./emsdk-master/emsdk install latest
./emsdk-master/emsdk activate latest
script:
- |
source emsdk-master/emsdk_env.sh
cd matrix_sdk/examples/wasm_send_message
cargo build --target wasm32-unknown-unknown
cd -
cd matrix_sdk_base
cargo test --target wasm32-unknown-unknown --no-default-features --features encryption
before_script:
- rustup component add rustfmt

View File

@ -3,6 +3,10 @@ members = [
"matrix_sdk",
"matrix_sdk_base",
"matrix_sdk_test",
"matrix_sdk_test_macros",
"matrix_sdk_crypto",
"matrix_sdk_common",
]
[patch.crates-io]
olm-sys = { git = "https://gitlab.gnome.org/stoically/olm-sys", branch = "wasm-target" }

View File

@ -17,14 +17,14 @@ encryption = ["matrix-sdk-base/encryption"]
sqlite-cryptostore = ["matrix-sdk-base/sqlite-cryptostore"]
[dependencies]
futures = "0.3.4"
http = "0.2.1"
reqwest = "0.10.4"
serde_json = "1.0.52"
thiserror = "1.0.16"
tracing = "0.1.13"
url = "2.1.1"
uuid = { version = "0.8.1", features = ["v4"] }
futures-timer = "3.0.2"
matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" }
@ -38,11 +38,6 @@ version = "0.2.4"
default-features = false
features = ["std", "std-future"]
[dependencies.tokio]
version = "0.2.20"
default-features = false
features = ["time"]
[dev-dependencies]
async-trait = "0.1.30"
dirs = "2.0.2"
@ -54,3 +49,4 @@ tracing-subscriber = "0.2.5"
tempfile = "3.1.0"
mockito = "0.25.1"
lazy_static = "1.4.0"
futures = "0.3.4"

View File

@ -0,0 +1,7 @@
node_modules
pkg
target
Cargo.lock
*.swp
dist
package-lock.json

View File

@ -0,0 +1,21 @@
[package]
name = "wasm"
version = "0.1.0"
authors = ["stoically <stoically@protonmail.com>"]
edition = "2018"
# Config mostly pulled from: https://github.com/rustwasm/wasm-bindgen/blob/master/examples/fetch/Cargo.toml
[lib]
crate-type = ["cdylib"]
[dependencies]
matrix-sdk = { path = "../..", default-features = false, features = ["encryption"] }
url = "2.1.1"
wasm-bindgen = { version = "0.2.62", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4.12"
[workspace]
[patch.crates-io]
olm-sys = { git = "https://gitlab.gnome.org/stoically/olm-sys", branch = "wasm-target" }

View File

@ -0,0 +1,13 @@
## Example usage of matrix-rust-sdk from WASM
This requires [emscripten](https://github.com/emscripten-core/emscripten) to be installed and in PATH to compile successfully.
You can build the example locally with:
npm install
npm run serve
and then visiting http://localhost:8080 in a browser should run the example!
This example is loosely based off of [this example](https://github.com/seanmonstar/reqwest/tree/master/examples/wasm_github_fetch), an example usage of `fetch` from `wasm-bindgen`.

View File

@ -0,0 +1,7 @@
const rust = import('./pkg');
rust
.then(m => {
return m.run()
})
.catch(console.error);

View File

@ -0,0 +1,14 @@
{
"scripts": {
"build": "webpack",
"serve": "webpack-dev-server"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "1.0.1",
"text-encoding": "^0.7.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.29.4",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.1.0"
}
}

View File

@ -0,0 +1,37 @@
use matrix_sdk::{
events::room::message::{MessageEventContent, TextMessageEventContent},
identifiers::RoomId,
Client, ClientConfig,
};
use std::convert::TryFrom;
use url::Url;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub async fn run() -> Result<JsValue, JsValue> {
let homeserver_url = "http://localhost:8008";
let username = "user";
let password = "password";
let client_config = ClientConfig::new();
let homeserver_url = Url::parse(&homeserver_url).unwrap();
let client = Client::new_with_config(homeserver_url, None, client_config).unwrap();
client
.login(username, password, None, Some("rust-sdk"))
.await
.unwrap();
let room_id = RoomId::try_from("!KpLWMcXcHKDMfEYNqA:localhost").unwrap();
let content = MessageEventContent::Text(TextMessageEventContent {
body: "hello from wasm".to_string(),
format: None,
formatted_body: None,
relates_to: None,
});
client.room_send(&room_id, content, None).await.unwrap();
Ok(JsValue::NULL)
}

View File

@ -0,0 +1,26 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
devtool: 'source-map',
plugins: [
new HtmlWebpackPlugin(),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
// Have this example work in Edge which doesn't ship `TextEncoder` or
// `TextDecoder` at this time.
new webpack.ProvidePlugin({
TextDecoder: ['text-encoding', 'TextDecoder'],
TextEncoder: ['text-encoding', 'TextEncoder']
})
],
mode: 'development'
};

View File

@ -21,18 +21,18 @@ use std::result::Result as StdResult;
use std::sync::Arc;
use std::time::{Duration, Instant};
use uuid::Uuid;
use matrix_sdk_common::locks::RwLock;
use matrix_sdk_common::uuid::Uuid;
use futures::future::Future;
use tokio::sync::RwLock;
use tokio::time::delay_for as sleep;
use futures_timer::Delay as sleep;
use std::future::Future;
#[cfg(feature = "encryption")]
use tracing::{debug, warn};
use tracing::{info, instrument, trace};
use http::Method as HttpMethod;
use http::Response as HttpResponse;
use reqwest::header::{HeaderValue, InvalidHeaderValue};
use reqwest::header::{HeaderValue, InvalidHeaderValue, AUTHORIZATION};
use url::Url;
use crate::events::room::message::MessageEventContent;
@ -98,6 +98,7 @@ impl std::fmt::Debug for Client {
/// .state_store(Box::new(store));
/// ```
pub struct ClientConfig {
#[cfg(not(target_arch = "wasm32"))]
proxy: Option<reqwest::Proxy>,
user_agent: Option<HeaderValue>,
disable_ssl_verification: bool,
@ -106,9 +107,12 @@ pub struct ClientConfig {
impl std::fmt::Debug for ClientConfig {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> {
fmt.debug_struct("ClientConfig")
.field("proxy", &self.proxy)
.field("user_agent", &self.user_agent)
let mut res = fmt.debug_struct("ClientConfig");
#[cfg(not(target_arch = "wasm32"))]
let res = res.field("proxy", &self.proxy);
res.field("user_agent", &self.user_agent)
.field("disable_ssl_verification", &self.disable_ssl_verification)
.finish()
}
@ -137,6 +141,7 @@ impl ClientConfig {
/// .proxy("http://localhost:8080")
/// .unwrap();
/// ```
#[cfg(not(target_arch = "wasm32"))]
pub fn proxy(mut self, proxy: &str) -> Result<Self> {
self.proxy = Some(reqwest::Proxy::all(proxy)?);
Ok(self)
@ -262,27 +267,32 @@ impl Client {
let http_client = reqwest::Client::builder();
let http_client = if config.disable_ssl_verification {
http_client.danger_accept_invalid_certs(true)
} else {
http_client
#[cfg(not(target_arch = "wasm32"))]
let http_client = {
let http_client = if config.disable_ssl_verification {
http_client.danger_accept_invalid_certs(true)
} else {
http_client
};
let http_client = match config.proxy {
Some(p) => http_client.proxy(p),
None => http_client,
};
let mut headers = reqwest::header::HeaderMap::new();
let user_agent = match config.user_agent {
Some(a) => a,
None => HeaderValue::from_str(&format!("matrix-rust-sdk {}", VERSION)).unwrap(),
};
headers.insert(reqwest::header::USER_AGENT, user_agent);
http_client.default_headers(headers)
};
let http_client = match config.proxy {
Some(p) => http_client.proxy(p),
None => http_client,
};
let mut headers = reqwest::header::HeaderMap::new();
let user_agent = match config.user_agent {
Some(a) => a,
None => HeaderValue::from_str(&format!("matrix-rust-sdk {}", VERSION)).unwrap(),
};
headers.insert(reqwest::header::USER_AGENT, user_agent);
let http_client = http_client.default_headers(headers).build()?;
let http_client = http_client.build()?;
let base_client = if let Some(store) = config.state_store {
BaseClient::new_with_state_store(session, store)?
@ -317,21 +327,21 @@ impl Client {
/// Returns the joined rooms this client knows about.
///
/// A `HashMap` of room id to `matrix::models::Room`
pub fn joined_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<tokio::sync::RwLock<Room>>>>> {
pub fn joined_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.base_client.joined_rooms()
}
/// Returns the invited rooms this client knows about.
///
/// A `HashMap` of room id to `matrix::models::Room`
pub fn invited_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<tokio::sync::RwLock<Room>>>>> {
pub fn invited_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.base_client.invited_rooms()
}
/// Returns the left rooms this client knows about.
///
/// A `HashMap` of room id to `matrix::models::Room`
pub fn left_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<tokio::sync::RwLock<Room>>>>> {
pub fn left_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.base_client.left_rooms()
}
@ -837,7 +847,7 @@ impl Client {
let response = if let Ok(r) = response {
r
} else {
sleep(Duration::from_secs(1)).await;
sleep::new(Duration::from_secs(1)).await;
continue;
};
@ -871,7 +881,7 @@ impl Client {
// the sync timeout.
if let Some(t) = last_sync_time {
if now - t <= Duration::from_secs(1) {
sleep(Duration::from_secs(1)).await;
sleep::new(Duration::from_secs(1)).await;
}
}
@ -923,7 +933,8 @@ impl Client {
let session = self.base_client.session().read().await;
if let Some(session) = session.as_ref() {
request_builder.bearer_auth(&session.access_token)
let header_value = format!("Bearer {}", &session.access_token);
request_builder.header(AUTHORIZATION, header_value)
} else {
return Err(Error::AuthenticationRequired);
}
@ -980,7 +991,7 @@ impl Client {
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
/// # let mut client = Client::new(homeserver, None).unwrap();
/// # let room_id = RoomId::try_from("!test:localhost").unwrap();
/// use uuid::Uuid;
/// use matrix_sdk_common::uuid::Uuid;
///
/// let content = MessageEventContent::Text(TextMessageEventContent {
/// body: "Hello world".to_owned(),
@ -1203,7 +1214,7 @@ mod test {
use crate::identifiers::{EventId, RoomId, UserId};
use matrix_sdk_base::JsonStore;
use matrix_sdk_test::EventBuilder;
use matrix_sdk_test::{EventBuilder, EventsFile};
use mockito::{mock, Matcher};
use std::convert::TryFrom;
@ -1250,11 +1261,8 @@ mod test {
let client = Client::new(homeserver, Some(session)).unwrap();
let mut response = EventBuilder::default()
.add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember)
.add_room_event_from_file(
"../test_data/events/power_levels.json",
RoomEvent::RoomPowerLevels,
)
.add_room_event(EventsFile::Member, RoomEvent::RoomMember)
.add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels)
.build_sync_response();
client
@ -1651,7 +1659,7 @@ mod test {
#[tokio::test]
async fn room_message_send() {
use uuid::Uuid;
use matrix_sdk_common::uuid::Uuid;
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
let user = UserId::try_from("@example:localhost").unwrap();

View File

@ -26,8 +26,10 @@
//! destroyed.
#![deny(missing_docs)]
#[cfg(not(target_arch = "wasm32"))]
pub use matrix_sdk_base::JsonStore;
pub use matrix_sdk_base::{EventEmitter, Room, Session};
pub use matrix_sdk_base::{JsonStore, RoomState, StateStore};
pub use matrix_sdk_base::{RoomState, StateStore};
pub use matrix_sdk_common::*;
pub use reqwest::header::InvalidHeaderValue;

View File

@ -0,0 +1,3 @@
# required because of https://github.com/rustwasm/wasm-pack/issues/698
[target.wasm32-unknown-unknown]
runner = 'wasm-bindgen-test-runner'

View File

@ -17,7 +17,6 @@ encryption = ["matrix-sdk-crypto"]
sqlite-cryptostore = ["matrix-sdk-crypto/sqlite-cryptostore"]
[dependencies]
futures = "0.3.4"
async-trait = "0.1.30"
serde = "1.0.106"
serde_json = "1.0.52"
@ -28,15 +27,20 @@ matrix-sdk-crypto = { version = "0.1.0", path = "../matrix_sdk_crypto", optional
# Misc dependencies
thiserror = "1.0.16"
[dependencies.tokio]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "0.2.20"
default-features = false
features = ["sync", "fs"]
[dev-dependencies]
matrix-sdk-test = { version = "0.1.0", path = "../matrix_sdk_test" }
tokio = { version = "0.2.20", features = ["rt-threaded", "macros"] }
http = "0.2.1"
dirs = "2.0.2"
tracing-subscriber = "0.2.5"
tempfile = "3.1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "0.2.20", features = ["rt-threaded", "macros"] }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.12"

View File

@ -39,10 +39,10 @@ use crate::session::Session;
use crate::state::{ClientState, StateStore};
use crate::EventEmitter;
use std::ops::Deref;
#[cfg(feature = "encryption")]
use tokio::sync::Mutex;
use tokio::sync::RwLock;
use matrix_sdk_common::locks::Mutex;
use matrix_sdk_common::locks::RwLock;
use std::ops::Deref;
#[cfg(feature = "encryption")]
use crate::api::r0::keys::{
@ -297,7 +297,7 @@ impl BaseClient {
/// Returns the joined rooms this client knows about.
///
/// A `HashMap` of room id to `matrix::models::Room`
pub fn joined_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<tokio::sync::RwLock<Room>>>>> {
pub fn joined_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.joined_rooms.clone()
}
@ -331,7 +331,7 @@ impl BaseClient {
/// Returns the invited rooms this client knows about.
///
/// A `HashMap` of room id to `matrix::models::Room`
pub fn invited_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<tokio::sync::RwLock<Room>>>>> {
pub fn invited_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.invited_rooms.clone()
}
@ -365,7 +365,7 @@ impl BaseClient {
/// Returns the left rooms this client knows about.
///
/// A `HashMap` of room id to `matrix::models::Room`
pub fn left_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<tokio::sync::RwLock<Room>>>>> {
pub fn left_rooms(&self) -> Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> {
self.left_rooms.clone()
}

View File

@ -168,8 +168,13 @@ pub trait EventEmitter: Send + Sync {
#[cfg(test)]
mod test {
use super::*;
use matrix_sdk_common::locks::Mutex;
use matrix_sdk_test::{async_test, sync_response, SyncResponseFile};
use std::sync::Arc;
use tokio::sync::Mutex;
#[cfg(target_arch = "wasm32")]
pub use wasm_bindgen_test::*;
#[derive(Clone)]
pub struct EvEmitterTest(Arc<Mutex<Vec<String>>>);
@ -283,22 +288,10 @@ mod test {
}
}
use crate::api::r0::sync::sync_events::Response as SyncResponse;
use crate::identifiers::UserId;
use crate::{BaseClient, Session};
use http::Response;
use std::convert::TryFrom;
use std::fs::File;
use std::io::Read;
fn sync_response(file: &str) -> SyncResponse {
let mut file = File::open(file).unwrap();
let mut data = vec![];
file.read_to_end(&mut data).unwrap();
let response = Response::builder().body(data).unwrap();
SyncResponse::try_from(response).unwrap()
}
fn get_client() -> BaseClient {
let session = Session {
@ -309,7 +302,7 @@ mod test {
BaseClient::new(Some(session)).unwrap()
}
#[tokio::test]
#[async_test]
async fn event_emitter_joined() {
let vec = Arc::new(Mutex::new(Vec::new()));
let test_vec = Arc::clone(&vec);
@ -318,7 +311,7 @@ mod test {
let client = get_client();
client.add_event_emitter(emitter).await;
let mut response = sync_response("../test_data/sync.json");
let mut response = sync_response(SyncResponseFile::Default);
client.receive_sync_response(&mut response).await.unwrap();
let v = test_vec.lock().await;
@ -340,7 +333,7 @@ mod test {
)
}
#[tokio::test]
#[async_test]
async fn event_emitter_invite() {
let vec = Arc::new(Mutex::new(Vec::new()));
let test_vec = Arc::clone(&vec);
@ -349,7 +342,7 @@ mod test {
let client = get_client();
client.add_event_emitter(emitter).await;
let mut response = sync_response("../test_data/invite_sync.json");
let mut response = sync_response(SyncResponseFile::Invite);
client.receive_sync_response(&mut response).await.unwrap();
let v = test_vec.lock().await;
@ -359,7 +352,7 @@ mod test {
)
}
#[tokio::test]
#[async_test]
async fn event_emitter_leave() {
let vec = Arc::new(Mutex::new(Vec::new()));
let test_vec = Arc::clone(&vec);
@ -368,7 +361,7 @@ mod test {
let client = get_client();
client.add_event_emitter(emitter).await;
let mut response = sync_response("../test_data/leave_sync.json");
let mut response = sync_response(SyncResponseFile::Leave);
client.receive_sync_response(&mut response).await.unwrap();
let v = test_vec.lock().await;

View File

@ -41,4 +41,6 @@ pub use event_emitter::EventEmitter;
#[cfg(feature = "encryption")]
pub use matrix_sdk_crypto::{Device, TrustState};
pub use models::Room;
pub use state::{JsonStore, StateStore};
#[cfg(not(target_arch = "wasm32"))]
pub use state::JsonStore;
pub use state::StateStore;

View File

@ -143,6 +143,9 @@ mod test {
use std::collections::HashMap;
use std::convert::TryFrom;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
use crate::events::{collections::all::RoomEvent, EventJson};
use crate::identifiers::{RoomId, UserId};
use crate::Room;

View File

@ -521,27 +521,17 @@ impl Room {
#[cfg(test)]
mod test {
use super::*;
use crate::api::r0::sync::sync_events::Response as SyncResponse;
use crate::events::room::member::MembershipState;
use crate::identifiers::UserId;
use crate::{BaseClient, Session};
use matrix_sdk_test::EventBuilder;
use matrix_sdk_test::{async_test, sync_response, EventBuilder, EventsFile, SyncResponseFile};
use http::Response;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
use std::convert::TryFrom;
use std::fs::File;
use std::io::Read;
use std::ops::Deref;
fn sync_response(file: &str) -> SyncResponse {
let mut file = File::open(file).unwrap();
let mut data = vec![];
file.read_to_end(&mut data).unwrap();
let response = Response::builder().body(data).unwrap();
SyncResponse::try_from(response).unwrap()
}
fn get_client() -> BaseClient {
let session = Session {
access_token: "1234".to_owned(),
@ -555,11 +545,11 @@ mod test {
RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap()
}
#[tokio::test]
#[async_test]
async fn user_presence() {
let client = get_client();
let mut response = sync_response("../test_data/sync.json");
let mut response = sync_response(SyncResponseFile::Default);
client.receive_sync_response(&mut response).await.unwrap();
@ -579,18 +569,15 @@ mod test {
assert!(room.deref().power_levels.is_some())
}
#[tokio::test]
#[async_test]
async fn room_events() {
let client = get_client();
let room_id = get_room_id();
let user_id = UserId::try_from("@example:localhost").unwrap();
let mut response = EventBuilder::default()
.add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember)
.add_room_event_from_file(
"../test_data/events/power_levels.json",
RoomEvent::RoomPowerLevels,
)
.add_room_event(EventsFile::Member, RoomEvent::RoomMember)
.add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels)
.build_sync_response();
client.receive_sync_response(&mut response).await.unwrap();
@ -611,14 +598,14 @@ mod test {
);
}
#[tokio::test]
#[async_test]
async fn calculate_aliases() {
let client = get_client();
let room_id = get_room_id();
let mut response = EventBuilder::default()
.add_state_event_from_file("../test_data/events/aliases.json", StateEvent::RoomAliases)
.add_state_event(EventsFile::Aliases, StateEvent::RoomAliases)
.build_sync_response();
client.receive_sync_response(&mut response).await.unwrap();
@ -629,17 +616,14 @@ mod test {
assert_eq!("tutorial", room.display_name());
}
#[tokio::test]
#[async_test]
async fn calculate_alias() {
let client = get_client();
let room_id = get_room_id();
let mut response = EventBuilder::default()
.add_state_event_from_file(
"../test_data/events/alias.json",
StateEvent::RoomCanonicalAlias,
)
.add_state_event(EventsFile::Alias, StateEvent::RoomCanonicalAlias)
.build_sync_response();
client.receive_sync_response(&mut response).await.unwrap();
@ -650,14 +634,14 @@ mod test {
assert_eq!("tutorial", room.display_name());
}
#[tokio::test]
#[async_test]
async fn calculate_name() {
let client = get_client();
let room_id = get_room_id();
let mut response = EventBuilder::default()
.add_state_event_from_file("../test_data/events/name.json", StateEvent::RoomName)
.add_state_event(EventsFile::Name, StateEvent::RoomName)
.build_sync_response();
client.receive_sync_response(&mut response).await.unwrap();
@ -668,9 +652,9 @@ mod test {
assert_eq!("room name", room.display_name());
}
#[tokio::test]
#[async_test]
async fn calculate_room_names_from_summary() {
let mut response = sync_response("../test_data/sync_with_summary.json");
let mut response = sync_response(SyncResponseFile::DefaultWithSummary);
let session = Session {
access_token: "1234".to_owned(),

View File

@ -199,7 +199,7 @@ impl RoomMember {
#[cfg(test)]
mod test {
use matrix_sdk_test::EventBuilder;
use matrix_sdk_test::{async_test, EventBuilder, EventsFile};
use crate::events::collections::all::RoomEvent;
use crate::events::room::member::MembershipState;
@ -208,6 +208,9 @@ mod test {
use crate::js_int::Int;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
use std::convert::TryFrom;
fn get_client() -> BaseClient {
@ -223,18 +226,15 @@ mod test {
RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap()
}
#[tokio::test]
#[async_test]
async fn room_member_events() {
let client = get_client();
let room_id = get_room_id();
let mut response = EventBuilder::default()
.add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember)
.add_room_event_from_file(
"../test_data/events/power_levels.json",
RoomEvent::RoomPowerLevels,
)
.add_room_event(EventsFile::Member, RoomEvent::RoomMember)
.add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels)
.build_sync_response();
client.receive_sync_response(&mut response).await.unwrap();
@ -250,19 +250,16 @@ mod test {
assert_eq!(member.power_level, Int::new(100));
}
#[tokio::test]
#[async_test]
async fn member_presence_events() {
let client = get_client();
let room_id = get_room_id();
let mut response = EventBuilder::default()
.add_room_event_from_file("../test_data/events/member.json", RoomEvent::RoomMember)
.add_room_event_from_file(
"../test_data/events/power_levels.json",
RoomEvent::RoomPowerLevels,
)
.add_presence_event_from_file("../test_data/events/presence.json")
.add_room_event(EventsFile::Member, RoomEvent::RoomMember)
.add_room_event(EventsFile::PowerLevels, RoomEvent::RoomPowerLevels)
.add_presence_event(EventsFile::Presence)
.build_sync_response();
client.receive_sync_response(&mut response).await.unwrap();

View File

@ -16,7 +16,9 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[cfg(not(target_arch = "wasm32"))]
pub mod state_store;
#[cfg(not(target_arch = "wasm32"))]
pub use state_store::JsonStore;
use crate::client::{BaseClient, Token};

View File

@ -6,9 +6,9 @@ use std::sync::{
Arc,
};
use matrix_sdk_common::locks::RwLock;
use tokio::fs as async_fs;
use tokio::io::AsyncWriteExt;
use tokio::sync::RwLock;
use super::{ClientState, StateStore};
use crate::identifiers::RoomId;

View File

@ -16,3 +16,15 @@ ruma-api = "0.16.0"
ruma-client-api = "0.8.0"
ruma-events = "0.21.0"
ruma-identifiers = "0.16.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
uuid = { version = "0.8.1", features = ["v4"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "0.2.20"
default-features = false
features = ["sync", "time", "fs"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false }
uuid = { version = "0.8.1", features = ["v4", "wasm-bindgen"] }

View File

@ -6,3 +6,7 @@ pub use ruma_api::{
pub use ruma_client_api as api;
pub use ruma_events as events;
pub use ruma_identifiers as identifiers;
pub use uuid;
pub mod locks;

View File

@ -0,0 +1,13 @@
// could switch to futures-lock completely at some point, blocker:
// https://github.com/asomers/futures-locks/issues/34
// https://www.reddit.com/r/rust/comments/f4zldz/i_audited_3_different_implementation_of_async/
#[cfg(target_arch = "wasm32")]
pub use futures_locks::Mutex;
#[cfg(target_arch = "wasm32")]
pub use futures_locks::RwLock;
#[cfg(not(target_arch = "wasm32"))]
pub use tokio::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
pub use tokio::sync::RwLock;

View File

@ -15,7 +15,6 @@ default = []
sqlite-cryptostore = ["sqlx"]
[dependencies]
futures = "0.3.4"
async-trait = "0.1.30"
matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" }
@ -23,9 +22,8 @@ matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" }
olm-rs = { version = "0.5.0", features = ["serde"] }
serde = { version = "1.0.106", features = ["derive"] }
serde_json = "1.0.52"
cjson = "0.1.0"
cjson = { git = "https://github.com/stoically/cjson", rev = "24a82d94e339e27a62434493869f3dce62c60e0d" }
zeroize = { version = "1.1.0", features = ["zeroize_derive"] }
uuid = { version = "0.8.1", features = ["v4"] }
url = "2.1.1"
# Misc dependencies
@ -39,12 +37,7 @@ version = "0.2.4"
default-features = false
features = ["std", "std-future"]
[dependencies.tokio]
version = "0.2.20"
default-features = false
features = ["sync", "time"]
[dependencies.sqlx]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.sqlx]
version = "0.3.5"
optional = true
default-features = false

View File

@ -19,7 +19,6 @@ use std::mem;
use std::path::Path;
use std::result::Result as StdResult;
use std::sync::atomic::{AtomicU64, Ordering};
use uuid::Uuid;
use super::error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult, SignatureError};
use super::olm::{
@ -46,6 +45,7 @@ use matrix_sdk_common::events::{
Algorithm, EventJson, EventType,
};
use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId};
use matrix_sdk_common::uuid::Uuid;
use api::r0::keys;
use api::r0::{

View File

@ -16,7 +16,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use dashmap::{DashMap, ReadOnlyView};
use tokio::sync::Mutex;
use matrix_sdk_common::locks::Mutex;
use super::device::Device;
use super::olm::{InboundGroupSession, Session};

View File

@ -18,8 +18,8 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Instant;
use matrix_sdk_common::locks::Mutex;
use serde::Serialize;
use tokio::sync::Mutex;
use zeroize::Zeroize;
pub use olm_rs::account::IdentityKeys;

View File

@ -16,7 +16,7 @@ use std::collections::HashSet;
use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::Mutex;
use matrix_sdk_common::locks::Mutex;
use super::{Account, CryptoStore, InboundGroupSession, Result, Session};
use crate::device::Device;

View File

@ -19,9 +19,9 @@ use std::sync::Arc;
use url::ParseError;
use async_trait::async_trait;
use matrix_sdk_common::locks::Mutex;
use serde_json::Error as SerdeError;
use thiserror::Error;
use tokio::sync::Mutex;
use super::device::Device;
use super::memory_stores::UserDevices;

View File

@ -22,9 +22,9 @@ use std::time::{Duration, Instant};
use url::Url;
use async_trait::async_trait;
use matrix_sdk_common::locks::Mutex;
use olm_rs::PicklingMode;
use sqlx::{query, query_as, sqlite::SqliteQueryAs, Connect, Executor, SqliteConnection};
use tokio::sync::Mutex;
use zeroize::Zeroizing;
use super::{Account, CryptoStore, CryptoStoreError, InboundGroupSession, Result, Session};

View File

@ -14,3 +14,4 @@ version = "0.1.0"
serde_json = "1.0.52"
http = "0.2.1"
matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" }
matrix-sdk-test-macros = { path = "../matrix_sdk_test_macros" }

View File

@ -1,7 +1,5 @@
use std::convert::TryFrom;
use std::fs;
use std::panic;
use std::path::Path;
use http::Response;
@ -15,6 +13,34 @@ use matrix_sdk_common::events::{
EventJson, TryFromRaw,
};
pub use matrix_sdk_test_macros::async_test;
/// Embedded event files
#[derive(Debug)]
pub enum EventsFile {
Alias,
Aliases,
Create,
FullyRead,
HistoryVisibility,
JoinRules,
Member,
MessageEmote,
MessageNotice,
MessageText,
Name,
PowerLevels,
Presence,
RedactedInvalid,
RedactedState,
Redacted,
Redaction,
RoomAvatar,
Tag,
Topic,
Typing,
}
/// Easily create events to stream into either a Client or a `Room` for testing.
#[derive(Default)]
pub struct EventBuilder {
@ -32,13 +58,16 @@ pub struct EventBuilder {
impl EventBuilder {
/// Add an event to the room events `Vec`.
pub fn add_ephemeral_from_file<Ev: TryFromRaw, P: AsRef<Path>>(
pub fn add_ephemeral<Ev: TryFromRaw>(
mut self,
path: P,
file: EventsFile,
variant: fn(Ev) -> Event,
) -> Self {
let val = fs::read_to_string(path.as_ref())
.unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref()));
let val: &str = match file {
EventsFile::Typing => include_str!("../../test_data/events/typing.json"),
_ => panic!("unknown ephemeral event file {:?}", file),
};
let event = serde_json::from_str::<EventJson<Ev>>(&val)
.unwrap()
.deserialize()
@ -48,13 +77,16 @@ impl EventBuilder {
}
/// Add an event to the room events `Vec`.
pub fn add_account_from_file<Ev: TryFromRaw, P: AsRef<Path>>(
#[allow(clippy::match_single_binding, unused)]
pub fn add_account<Ev: TryFromRaw>(
mut self,
path: P,
file: EventsFile,
variant: fn(Ev) -> Event,
) -> Self {
let val = fs::read_to_string(path.as_ref())
.unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref()));
let val: &str = match file {
_ => panic!("unknown account event file {:?}", file),
};
let event = serde_json::from_str::<EventJson<Ev>>(&val)
.unwrap()
.deserialize()
@ -64,13 +96,17 @@ impl EventBuilder {
}
/// Add an event to the room events `Vec`.
pub fn add_room_event_from_file<Ev: TryFromRaw, P: AsRef<Path>>(
pub fn add_room_event<Ev: TryFromRaw>(
mut self,
path: P,
file: EventsFile,
variant: fn(Ev) -> RoomEvent,
) -> Self {
let val = fs::read_to_string(path.as_ref())
.unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref()));
let val = match file {
EventsFile::Member => include_str!("../../test_data/events/member.json"),
EventsFile::PowerLevels => include_str!("../../test_data/events/power_levels.json"),
_ => panic!("unknown room event file {:?}", file),
};
let event = serde_json::from_str::<EventJson<Ev>>(&val)
.unwrap()
.deserialize()
@ -80,13 +116,18 @@ impl EventBuilder {
}
/// Add a state event to the state events `Vec`.
pub fn add_state_event_from_file<Ev: TryFromRaw, P: AsRef<Path>>(
pub fn add_state_event<Ev: TryFromRaw>(
mut self,
path: P,
file: EventsFile,
variant: fn(Ev) -> StateEvent,
) -> Self {
let val = fs::read_to_string(path.as_ref())
.unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref()));
let val = match file {
EventsFile::Alias => include_str!("../../test_data/events/alias.json"),
EventsFile::Aliases => include_str!("../../test_data/events/aliases.json"),
EventsFile::Name => include_str!("../../test_data/events/name.json"),
_ => panic!("unknown state event file {:?}", file),
};
let event = serde_json::from_str::<EventJson<Ev>>(&val)
.unwrap()
.deserialize()
@ -95,10 +136,13 @@ impl EventBuilder {
self
}
/// Add a presence event to the presence events `Vec`.
pub fn add_presence_event_from_file<P: AsRef<Path>>(mut self, path: P) -> Self {
let val = fs::read_to_string(path.as_ref())
.unwrap_or_else(|_| panic!("file not found {:?}", path.as_ref()));
/// Add an presence event to the presence events `Vec`.
pub fn add_presence_event(mut self, file: EventsFile) -> Self {
let val = match file {
EventsFile::Presence => include_str!("../../test_data/events/presence.json"),
_ => panic!("unknown presence event file {:?}", file),
};
let event = serde_json::from_str::<EventJson<PresenceEvent>>(&val)
.unwrap()
.deserialize()
@ -158,3 +202,26 @@ impl EventBuilder {
SyncResponse::try_from(response).unwrap()
}
}
/// Embedded sync reponse files
pub enum SyncResponseFile {
Default,
DefaultWithSummary,
Invite,
Leave,
}
/// Get specific API responses for testing
pub fn sync_response(kind: SyncResponseFile) -> SyncResponse {
let data = match kind {
SyncResponseFile::Default => include_bytes!("../../test_data/sync.json").to_vec(),
SyncResponseFile::DefaultWithSummary => {
include_bytes!("../../test_data/sync_with_summary.json").to_vec()
}
SyncResponseFile::Invite => include_bytes!("../../test_data/invite_sync.json").to_vec(),
SyncResponseFile::Leave => include_bytes!("../../test_data/leave_sync.json").to_vec(),
};
let response = Response::builder().body(data.to_vec()).unwrap();
SyncResponse::try_from(response).unwrap()
}

View File

@ -0,0 +1,14 @@
[package]
authors = ["stoically <stoically@protonmail.com>"]
description = "Helper macros to write tests for the Matrix SDK"
edition = "2018"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ruma"]
license = "Apache-2.0"
name = "matrix-sdk-test-macros"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
version = "0.1.0"
[lib]
proc-macro = true

View File

@ -0,0 +1,14 @@
use proc_macro::TokenStream;
/// Attribute to use `wasm_bindgen_test` for wasm32 targets and `tokio::test` for everything else
#[proc_macro_attribute]
pub fn async_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
let attrs = r#"
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
"#;
let mut out: TokenStream = attrs.parse().unwrap();
out.extend(item);
out
}