feat: replace event channel with broadcast channel

This makes `EventTracker` receive events immediately
instead of being moved from event emitter to event tracker
by a task spawned from `TestContext::new_internal`.

This makes `EventTracker.clear_events` reliable
as it is guaranteed to remove all events emitted
by the time it is called rather than only events
that have been moved already.
This commit is contained in:
link2xt
2024-04-19 02:19:21 +00:00
parent 72d5a387fb
commit 34f4ec02f6
11 changed files with 194 additions and 139 deletions

13
Cargo.lock generated
View File

@@ -227,6 +227,18 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "async-broadcast"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb"
dependencies = [
"event-listener 5.2.0",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]] [[package]]
name = "async-channel" name = "async-channel"
version = "1.9.0" version = "1.9.0"
@@ -1149,6 +1161,7 @@ version = "1.137.3"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"anyhow", "anyhow",
"async-broadcast",
"async-channel 2.2.0", "async-channel 2.2.0",
"async-imap", "async-imap",
"async-native-tls", "async-native-tls",

View File

@@ -39,6 +39,7 @@ format-flowed = { path = "./format-flowed" }
ratelimit = { path = "./deltachat-ratelimit" } ratelimit = { path = "./deltachat-ratelimit" }
anyhow = { workspace = true } anyhow = { workspace = true }
async-broadcast = "0.7.0"
async-channel = "2.0.0" async-channel = "2.0.0"
async-imap = { version = "0.9.7", default-features = false, features = ["runtime-tokio"] } async-imap = { version = "0.9.7", default-features = false, features = ["runtime-tokio"] }
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] } async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }

View File

@@ -362,8 +362,12 @@ uint32_t dc_get_id (dc_context_t* context);
* Must be freed using dc_event_emitter_unref() after usage. * Must be freed using dc_event_emitter_unref() after usage.
* *
* Note: Use only one event emitter per context. * Note: Use only one event emitter per context.
* Having more than one event emitter running at the same time on the same context * The result of having multiple event emitters is unspecified.
* will result in events being randomly delivered to one of the emitters. * Currently events are broadcasted to all existing event emitters,
* but previous versions delivered events to only one event emitter
* and this behavior may change again in the future.
* Events emitted before creation of event emitter
* may or may not be available to event emitter.
*/ */
dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context); dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context);

View File

@@ -4922,7 +4922,9 @@ mod jsonrpc {
} }
let account_manager = &*account_manager; let account_manager = &*account_manager;
let cmd_api = deltachat_jsonrpc::api::CommandApi::from_arc(account_manager.inner.clone()); let cmd_api = block_on(deltachat_jsonrpc::api::CommandApi::from_arc(
account_manager.inner.clone(),
));
let (request_handle, receiver) = RpcClient::new(); let (request_handle, receiver) = RpcClient::new();
let handle = RpcSession::new(request_handle, cmd_api); let handle = RpcSession::new(request_handle, cmd_api);

View File

@@ -29,6 +29,7 @@ use deltachat::reaction::{get_msg_reactions, send_reaction};
use deltachat::securejoin; use deltachat::securejoin;
use deltachat::stock_str::StockMessage; use deltachat::stock_str::StockMessage;
use deltachat::webxdc::StatusUpdateSerial; use deltachat::webxdc::StatusUpdateSerial;
use deltachat::EventEmitter;
use sanitize_filename::is_sanitized; use sanitize_filename::is_sanitized;
use tokio::fs; use tokio::fs;
use tokio::sync::{watch, Mutex, RwLock}; use tokio::sync::{watch, Mutex, RwLock};
@@ -81,21 +82,30 @@ impl Default for AccountState {
pub struct CommandApi { pub struct CommandApi {
pub(crate) accounts: Arc<RwLock<Accounts>>, pub(crate) accounts: Arc<RwLock<Accounts>>,
/// Receiver side of the event channel.
///
/// Events from it can be received by calling `get_next_event` method.
event_emitter: Arc<EventEmitter>,
states: Arc<Mutex<BTreeMap<u32, AccountState>>>, states: Arc<Mutex<BTreeMap<u32, AccountState>>>,
} }
impl CommandApi { impl CommandApi {
pub fn new(accounts: Accounts) -> Self { pub fn new(accounts: Accounts) -> Self {
let event_emitter = Arc::new(accounts.get_event_emitter());
CommandApi { CommandApi {
accounts: Arc::new(RwLock::new(accounts)), accounts: Arc::new(RwLock::new(accounts)),
event_emitter,
states: Arc::new(Mutex::new(BTreeMap::new())), states: Arc::new(Mutex::new(BTreeMap::new())),
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn from_arc(accounts: Arc<RwLock<Accounts>>) -> Self { pub async fn from_arc(accounts: Arc<RwLock<Accounts>>) -> Self {
let event_emitter = Arc::new(accounts.read().await.get_event_emitter());
CommandApi { CommandApi {
accounts, accounts,
event_emitter,
states: Arc::new(Mutex::new(BTreeMap::new())), states: Arc::new(Mutex::new(BTreeMap::new())),
} }
} }
@@ -158,8 +168,7 @@ impl CommandApi {
/// Get the next event. /// Get the next event.
async fn get_next_event(&self) -> Result<Event> { async fn get_next_event(&self) -> Result<Event> {
let event_emitter = self.accounts.read().await.get_event_emitter(); self.event_emitter
event_emitter
.recv() .recv()
.await .await
.map(|event| event.into()) .map(|event| event.into())

View File

@@ -68,7 +68,7 @@ async fn main_impl() -> Result<()> {
log::info!("Creating JSON-RPC API."); log::info!("Creating JSON-RPC API.");
let accounts = Arc::new(RwLock::new(accounts)); let accounts = Arc::new(RwLock::new(accounts));
let state = CommandApi::from_arc(accounts.clone()); let state = CommandApi::from_arc(accounts.clone()).await;
let (client, mut out_receiver) = RpcClient::new(); let (client, mut out_receiver) = RpcClient::new();
let session = RpcSession::new(client.clone(), state.clone()); let session = RpcSession::new(client.clone(), state.clone());

View File

@@ -2730,7 +2730,7 @@ Hi."#;
let sent_msg = alice.send_text(chat.id, "moin").await; let sent_msg = alice.send_text(chat.id, "moin").await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?; let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
assert!(!contact.was_seen_recently()); assert!(!contact.was_seen_recently());
while bob.evtracker.try_recv().is_ok() {} bob.evtracker.clear_events();
bob.recv_msg(&sent_msg).await; bob.recv_msg(&sent_msg).await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?; let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
assert!(contact.was_seen_recently()); assert!(contact.was_seen_recently());
@@ -2742,7 +2742,7 @@ Hi."#;
.await; .await;
// Wait for `was_seen_recently()` to turn off. // Wait for `was_seen_recently()` to turn off.
while bob.evtracker.try_recv().is_ok() {} bob.evtracker.clear_events();
SystemTime::shift(Duration::from_secs(SEEN_RECENTLY_SECONDS as u64 * 2)); SystemTime::shift(Duration::from_secs(SEEN_RECENTLY_SECONDS as u64 * 2));
recently_seen_loop.interrupt(ContactId::UNDEFINED, 0).await; recently_seen_loop.interrupt(ContactId::UNDEFINED, 0).await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?; let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;

View File

@@ -1,6 +1,7 @@
//! # Events specification. //! # Events specification.
use async_channel::{self as channel, Receiver, Sender, TrySendError}; use anyhow::Result;
use tokio::sync::Mutex;
pub(crate) mod chatlist_events; pub(crate) mod chatlist_events;
mod payload; mod payload;
@@ -10,8 +11,11 @@ pub use self::payload::EventType;
/// Event channel. /// Event channel.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Events { pub struct Events {
receiver: Receiver<Event>, /// Unused receiver to prevent the channel from closing.
sender: Sender<Event>, _receiver: async_broadcast::InactiveReceiver<Event>,
/// Sender side of the event channel.
sender: async_broadcast::Sender<Event>,
} }
impl Default for Events { impl Default for Events {
@@ -23,33 +27,30 @@ impl Default for Events {
impl Events { impl Events {
/// Creates a new event channel. /// Creates a new event channel.
pub fn new() -> Self { pub fn new() -> Self {
let (sender, receiver) = channel::bounded(1_000); let (mut sender, _receiver) = async_broadcast::broadcast(1_000);
Self { receiver, sender } // We only keep this receiver around
// to prevent the channel from closing.
// Deactivating it to prevent it from consuming memory
// holding events that are not going to be received.
let _receiver = _receiver.deactivate();
// Remove oldest event on overflow.
sender.set_overflow(true);
Self { _receiver, sender }
} }
/// Emits an event into event channel. /// Emits an event into event channel.
/// ///
/// If the channel is full, deletes the oldest event first. /// If the channel is full, deletes the oldest event first.
pub fn emit(&self, event: Event) { pub fn emit(&self, event: Event) {
match self.sender.try_send(event) { self.sender.try_broadcast(event).ok();
Ok(()) => {}
Err(TrySendError::Full(event)) => {
// when we are full, we pop remove the oldest event and push on the new one
let _ = self.receiver.try_recv();
// try again
self.emit(event);
}
Err(TrySendError::Closed(_)) => {
unreachable!("unable to emit event, channel disconnected");
}
}
} }
/// Creates an event emitter. /// Creates an event emitter.
pub fn get_emitter(&self) -> EventEmitter { pub fn get_emitter(&self) -> EventEmitter {
EventEmitter(self.receiver.clone()) EventEmitter(Mutex::new(self.sender.new_receiver()))
} }
} }
@@ -61,13 +62,32 @@ impl Events {
/// ///
/// [`Context`]: crate::context::Context /// [`Context`]: crate::context::Context
/// [`Context::get_event_emitter`]: crate::context::Context::get_event_emitter /// [`Context::get_event_emitter`]: crate::context::Context::get_event_emitter
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct EventEmitter(Receiver<Event>); pub struct EventEmitter(Mutex<async_broadcast::Receiver<Event>>);
impl EventEmitter { impl EventEmitter {
/// Async recv of an event. Return `None` if the `Sender` has been dropped. /// Async recv of an event. Return `None` if the `Sender` has been dropped.
///
/// [`try_recv`]: Self::try_recv
pub async fn recv(&self) -> Option<Event> { pub async fn recv(&self) -> Option<Event> {
self.0.recv().await.ok() let mut lock = self.0.lock().await;
lock.recv().await.ok()
}
/// Tries to receive an event without blocking.
///
/// Returns error if no events are available for reception
/// or if receiver mutex is locked by a concurrent call to [`recv`]
/// or `try_recv`.
///
/// [`recv`]: Self::recv
pub fn try_recv(&self) -> Result<Event> {
// Using `try_lock` instead of `lock`
// to avoid blocking
// in case there is a concurrent call to `recv`.
let mut lock = self.0.try_lock()?;
let event = lock.try_recv()?;
Ok(event)
} }
} }

View File

@@ -181,7 +181,7 @@ mod test_chatlist_events {
.await?; .await?;
set_muted(&bob, bob_chat.id, MuteDuration::Forever).await?; set_muted(&bob, bob_chat.id, MuteDuration::Forever).await?;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
let sent_msg = alice.send_text(chat.id, "moin2").await; let sent_msg = alice.send_text(chat.id, "moin2").await;
bob.recv_msg(&sent_msg).await; bob.recv_msg(&sent_msg).await;
@@ -216,7 +216,7 @@ mod test_chatlist_events {
let sent_msg = alice.send_text(chat.id, "moin2").await; let sent_msg = alice.send_text(chat.id, "moin2").await;
bob.recv_msg(&sent_msg).await; bob.recv_msg(&sent_msg).await;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
chat::marknoticed_chat(&bob, DC_CHAT_ID_ARCHIVED_LINK).await?; chat::marknoticed_chat(&bob, DC_CHAT_ID_ARCHIVED_LINK).await?;
wait_for_chatlist_specific_item(&bob, DC_CHAT_ID_ARCHIVED_LINK).await; wait_for_chatlist_specific_item(&bob, DC_CHAT_ID_ARCHIVED_LINK).await;
@@ -233,7 +233,7 @@ mod test_chatlist_events {
let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await; let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await;
bob.recv_msg(&sent_msg).await; bob.recv_msg(&sent_msg).await;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
// set alice name then receive messagefrom her with bob // set alice name then receive messagefrom her with bob
alice.set_config(Config::Displayname, Some("Alice")).await?; alice.set_config(Config::Displayname, Some("Alice")).await?;
let sent_msg = alice let sent_msg = alice
@@ -245,7 +245,7 @@ mod test_chatlist_events {
wait_for_chatlist_all_items(&bob).await; wait_for_chatlist_all_items(&bob).await;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
// set name // set name
let addr = alice_on_bob.get_addr(); let addr = alice_on_bob.get_addr();
Contact::create(&bob, "Alice2", addr).await?; Contact::create(&bob, "Alice2", addr).await?;
@@ -266,7 +266,7 @@ mod test_chatlist_events {
let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await; let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await;
bob.recv_msg(&sent_msg).await; bob.recv_msg(&sent_msg).await;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
// set alice avatar then receive messagefrom her with bob // set alice avatar then receive messagefrom her with bob
let file = alice.dir.path().join("avatar.png"); let file = alice.dir.path().join("avatar.png");
let bytes = include_bytes!("../../test-data/image/avatar64x64.png"); let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
@@ -292,7 +292,7 @@ mod test_chatlist_events {
let alice = tcm.alice().await; let alice = tcm.alice().await;
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?; let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
chat.delete(&alice).await?; chat.delete(&alice).await?;
wait_for_chatlist(&alice).await; wait_for_chatlist(&alice).await;
Ok(()) Ok(())
@@ -303,7 +303,7 @@ mod test_chatlist_events {
async fn test_create_group_chat() -> Result<()> { async fn test_create_group_chat() -> Result<()> {
let mut tcm = TestContextManager::new(); let mut tcm = TestContextManager::new();
let alice = tcm.alice().await; let alice = tcm.alice().await;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?; let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
wait_for_chatlist_and_specific_item(&alice, chat).await; wait_for_chatlist_and_specific_item(&alice, chat).await;
Ok(()) Ok(())
@@ -314,7 +314,7 @@ mod test_chatlist_events {
async fn test_create_broadcastlist() -> Result<()> { async fn test_create_broadcastlist() -> Result<()> {
let mut tcm = TestContextManager::new(); let mut tcm = TestContextManager::new();
let alice = tcm.alice().await; let alice = tcm.alice().await;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
create_broadcast_list(&alice).await?; create_broadcast_list(&alice).await?;
wait_for_chatlist(&alice).await; wait_for_chatlist(&alice).await;
Ok(()) Ok(())
@@ -327,11 +327,11 @@ mod test_chatlist_events {
let alice = tcm.alice().await; let alice = tcm.alice().await;
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?; let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
chat::set_muted(&alice, chat, MuteDuration::Forever).await?; chat::set_muted(&alice, chat, MuteDuration::Forever).await?;
wait_for_chatlist_specific_item(&alice, chat).await; wait_for_chatlist_specific_item(&alice, chat).await;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
chat::set_muted(&alice, chat, MuteDuration::NotMuted).await?; chat::set_muted(&alice, chat, MuteDuration::NotMuted).await?;
wait_for_chatlist_specific_item(&alice, chat).await; wait_for_chatlist_specific_item(&alice, chat).await;
@@ -352,7 +352,7 @@ mod test_chatlist_events {
.unwrap(), .unwrap(),
); );
chat::set_muted(&alice, chat, mute_duration).await?; chat::set_muted(&alice, chat, mute_duration).await?;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
SystemTime::shift(Duration::from_secs(3)); SystemTime::shift(Duration::from_secs(3));
wait_for_chatlist_specific_item(&alice, chat).await; wait_for_chatlist_specific_item(&alice, chat).await;
@@ -366,7 +366,7 @@ mod test_chatlist_events {
let alice = tcm.alice().await; let alice = tcm.alice().await;
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?; let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
chat::set_chat_name(&alice, chat, "New Name").await?; chat::set_chat_name(&alice, chat, "New Name").await?;
wait_for_chatlist_specific_item(&alice, chat).await; wait_for_chatlist_specific_item(&alice, chat).await;
@@ -380,7 +380,7 @@ mod test_chatlist_events {
let alice = tcm.alice().await; let alice = tcm.alice().await;
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?; let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
let file = alice.dir.path().join("avatar.png"); let file = alice.dir.path().join("avatar.png");
let bytes = include_bytes!("../../test-data/image/avatar64x64.png"); let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
tokio::fs::write(&file, bytes).await?; tokio::fs::write(&file, bytes).await?;
@@ -405,7 +405,7 @@ mod test_chatlist_events {
wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await; wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
chat_id_for_bob.accept(&bob).await?; chat_id_for_bob.accept(&bob).await?;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
chat::set_chat_name(&alice, chat, "New Name").await?; chat::set_chat_name(&alice, chat, "New Name").await?;
let sent_msg = alice.send_text(chat, "Hello").await; let sent_msg = alice.send_text(chat, "Hello").await;
bob.recv_msg(&sent_msg).await; bob.recv_msg(&sent_msg).await;
@@ -426,7 +426,7 @@ mod test_chatlist_events {
let sent_msg = alice.send_text(chat, "Hello").await; let sent_msg = alice.send_text(chat, "Hello").await;
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id; let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
chat_id_for_bob.accept(&bob).await?; chat_id_for_bob.accept(&bob).await?;
wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await; wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
@@ -445,7 +445,7 @@ mod test_chatlist_events {
let sent_msg = alice.send_text(chat, "Hello").await; let sent_msg = alice.send_text(chat, "Hello").await;
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id; let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
chat_id_for_bob.block(&bob).await?; chat_id_for_bob.block(&bob).await?;
wait_for_chatlist(&bob).await; wait_for_chatlist(&bob).await;
@@ -460,7 +460,7 @@ mod test_chatlist_events {
let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?; let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
let message = chat::send_text_msg(&alice, chat, "Hello World".to_owned()).await?; let message = chat::send_text_msg(&alice, chat, "Hello World".to_owned()).await?;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
message::delete_msgs(&alice, &[message]).await?; message::delete_msgs(&alice, &[message]).await?;
wait_for_chatlist_specific_item(&alice, chat).await; wait_for_chatlist_specific_item(&alice, chat).await;
@@ -485,7 +485,7 @@ mod test_chatlist_events {
let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id; let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
assert!(chat_id_for_bob.get_fresh_msg_cnt(&bob).await? >= 1); assert!(chat_id_for_bob.get_fresh_msg_cnt(&bob).await? >= 1);
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
chat::marknoticed_chat(&bob, chat_id_for_bob).await?; chat::marknoticed_chat(&bob, chat_id_for_bob).await?;
wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await; wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
@@ -500,11 +500,11 @@ mod test_chatlist_events {
let contact_id = Contact::create(&alice, "example", "example@example.com").await?; let contact_id = Contact::create(&alice, "example", "example@example.com").await?;
let _ = ChatId::create_for_contact(&alice, contact_id).await; let _ = ChatId::create_for_contact(&alice, contact_id).await;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
Contact::block(&alice, contact_id).await?; Contact::block(&alice, contact_id).await?;
wait_for_chatlist(&alice).await; wait_for_chatlist(&alice).await;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
Contact::unblock(&alice, contact_id).await?; Contact::unblock(&alice, contact_id).await?;
wait_for_chatlist(&alice).await; wait_for_chatlist(&alice).await;
@@ -547,7 +547,7 @@ Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
First thread."#; First thread."#;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
receive_imf(&alice, mime, false).await?; receive_imf(&alice, mime, false).await?;
wait_for_chatlist(&alice).await; wait_for_chatlist(&alice).await;
@@ -568,34 +568,34 @@ First thread."#;
let qr = get_securejoin_qr(&alice.ctx, Some(alice_chatid)).await?; let qr = get_securejoin_qr(&alice.ctx, Some(alice_chatid)).await?;
// Step 2: Bob scans QR-code, sends vg-request // Step 2: Bob scans QR-code, sends vg-request
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
let bob_chatid = join_securejoin(&bob.ctx, &qr).await?; let bob_chatid = join_securejoin(&bob.ctx, &qr).await?;
wait_for_chatlist(&bob).await; wait_for_chatlist(&bob).await;
let sent = bob.pop_sent_msg().await; let sent = bob.pop_sent_msg().await;
// Step 3: Alice receives vg-request, sends vg-auth-required // Step 3: Alice receives vg-request, sends vg-auth-required
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
alice.recv_msg_trash(&sent).await; alice.recv_msg_trash(&sent).await;
let sent = alice.pop_sent_msg().await; let sent = alice.pop_sent_msg().await;
// Step 4: Bob receives vg-auth-required, sends vg-request-with-auth // Step 4: Bob receives vg-auth-required, sends vg-request-with-auth
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
bob.recv_msg_trash(&sent).await; bob.recv_msg_trash(&sent).await;
wait_for_chatlist_and_specific_item(&bob, bob_chatid).await; wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
let sent = bob.pop_sent_msg().await; let sent = bob.pop_sent_msg().await;
// Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added // Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
alice.recv_msg_trash(&sent).await; alice.recv_msg_trash(&sent).await;
wait_for_chatlist_and_specific_item(&alice, alice_chatid).await; wait_for_chatlist_and_specific_item(&alice, alice_chatid).await;
let sent = alice.pop_sent_msg().await; let sent = alice.pop_sent_msg().await;
// Step 7: Bob receives vg-member-added // Step 7: Bob receives vg-member-added
bob.evtracker.clear_events().await; bob.evtracker.clear_events();
bob.recv_msg(&sent).await; bob.recv_msg(&sent).await;
wait_for_chatlist_and_specific_item(&bob, bob_chatid).await; wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
@@ -617,7 +617,7 @@ First thread."#;
let message = Message::load_from_db(&alice, msg_id).await?; let message = Message::load_from_db(&alice, msg_id).await?;
assert_eq!(message.get_state(), MessageState::OutDelivered); assert_eq!(message.get_state(), MessageState::OutDelivered);
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
chat::resend_msgs(&alice, &[msg_id]).await?; chat::resend_msgs(&alice, &[msg_id]).await?;
wait_for_chatlist_specific_item(&alice, chat).await; wait_for_chatlist_specific_item(&alice, chat).await;
@@ -633,7 +633,7 @@ First thread."#;
let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?; let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
let _ = alice.pop_sent_msg().await; let _ = alice.pop_sent_msg().await;
alice.evtracker.clear_events().await; alice.evtracker.clear_events();
reaction::send_reaction(&alice, msg_id, "👍").await?; reaction::send_reaction(&alice, msg_id, "👍").await?;
let _ = alice.pop_sent_msg().await; let _ = alice.pop_sent_msg().await;
wait_for_chatlist_specific_item(&alice, chat).await; wait_for_chatlist_specific_item(&alice, chat).await;

View File

@@ -1007,8 +1007,6 @@ pub fn repeat_vars(count: usize) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use async_channel as channel;
use super::*; use super::*;
use crate::{test_utils::TestContext, EventType}; use crate::{test_utils::TestContext, EventType};
@@ -1085,8 +1083,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let (event_sink, event_source) = channel::unbounded(); let event_source = t.get_event_emitter();
t.add_event_sender(event_sink).await;
let a = t.get_config(Config::Selfavatar).await.unwrap().unwrap(); let a = t.get_config(Config::Selfavatar).await.unwrap().unwrap();
assert_eq!(avatar_bytes, &tokio::fs::read(&a).await.unwrap()[..]); assert_eq!(avatar_bytes, &tokio::fs::read(&a).await.unwrap()[..]);

View File

@@ -19,7 +19,6 @@ use pretty_assertions::assert_eq;
use rand::Rng; use rand::Rng;
use tempfile::{tempdir, TempDir}; use tempfile::{tempdir, TempDir};
use tokio::runtime::Handle; use tokio::runtime::Handle;
use tokio::sync::RwLock;
use tokio::{fs, task}; use tokio::{fs, task};
use crate::chat::{ use crate::chat::{
@@ -34,7 +33,7 @@ use crate::constants::{Blocked, Chattype};
use crate::contact::{Contact, ContactId, Modifier, Origin}; use crate::contact::{Contact, ContactId, Modifier, Origin};
use crate::context::Context; use crate::context::Context;
use crate::e2ee::EncryptHelper; use crate::e2ee::EncryptHelper;
use crate::events::{Event, EventType, Events}; use crate::events::{Event, EventEmitter, EventType, Events};
use crate::key::{self, DcKey, KeyPairUse}; use crate::key::{self, DcKey, KeyPairUse};
use crate::message::{update_msg_state, Message, MessageState, MsgId, Viewtype}; use crate::message::{update_msg_state, Message, MessageState, MsgId, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage}; use crate::mimeparser::{MimeMessage, SystemMessage};
@@ -57,20 +56,19 @@ static CONTEXT_NAMES: Lazy<std::sync::RwLock<BTreeMap<u32, String>>> =
/// occurred rather than grouped by context like would happen when you use separate /// occurred rather than grouped by context like would happen when you use separate
/// [`TestContext`]s without managing your own [`LogSink`]. /// [`TestContext`]s without managing your own [`LogSink`].
pub struct TestContextManager { pub struct TestContextManager {
log_tx: Sender<LogEvent>, log_sink: LogSink,
_log_sink: LogSink,
} }
impl TestContextManager { impl TestContextManager {
pub fn new() -> Self { pub fn new() -> Self {
let (log_tx, _log_sink) = LogSink::create(); let log_sink = LogSink::new();
Self { log_tx, _log_sink } Self { log_sink }
} }
pub async fn alice(&mut self) -> TestContext { pub async fn alice(&mut self) -> TestContext {
TestContext::builder() TestContext::builder()
.configure_alice() .configure_alice()
.with_log_sink(self.log_tx.clone()) .with_log_sink(self.log_sink.clone())
.build() .build()
.await .await
} }
@@ -78,7 +76,7 @@ impl TestContextManager {
pub async fn bob(&mut self) -> TestContext { pub async fn bob(&mut self) -> TestContext {
TestContext::builder() TestContext::builder()
.configure_bob() .configure_bob()
.with_log_sink(self.log_tx.clone()) .with_log_sink(self.log_sink.clone())
.build() .build()
.await .await
} }
@@ -86,7 +84,7 @@ impl TestContextManager {
pub async fn fiona(&mut self) -> TestContext { pub async fn fiona(&mut self) -> TestContext {
TestContext::builder() TestContext::builder()
.configure_fiona() .configure_fiona()
.with_log_sink(self.log_tx.clone()) .with_log_sink(self.log_sink.clone())
.build() .build()
.await .await
} }
@@ -94,7 +92,7 @@ impl TestContextManager {
/// Creates a new unconfigured test account. /// Creates a new unconfigured test account.
pub async fn unconfigured(&mut self) -> TestContext { pub async fn unconfigured(&mut self) -> TestContext {
TestContext::builder() TestContext::builder()
.with_log_sink(self.log_tx.clone()) .with_log_sink(self.log_sink.clone())
.build() .build()
.await .await
} }
@@ -103,7 +101,8 @@ impl TestContextManager {
/// ///
/// ========== `msg` goes here ========== /// ========== `msg` goes here ==========
pub fn section(&self, msg: &str) { pub fn section(&self, msg: &str) {
self.log_tx self.log_sink
.sender
.try_send(LogEvent::Section(msg.to_string())) .try_send(LogEvent::Section(msg.to_string()))
.expect( .expect(
"The events channel should be unbounded and not closed, so try_send() shouldn't fail", "The events channel should be unbounded and not closed, so try_send() shouldn't fail",
@@ -194,7 +193,7 @@ impl TestContextManager {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct TestContextBuilder { pub struct TestContextBuilder {
key_pair: Option<KeyPair>, key_pair: Option<KeyPair>,
log_sink: Option<Sender<LogEvent>>, log_sink: LogSink,
} }
impl TestContextBuilder { impl TestContextBuilder {
@@ -234,8 +233,8 @@ impl TestContextBuilder {
/// using a single [`LogSink`] for both contexts. This shows the log messages in /// using a single [`LogSink`] for both contexts. This shows the log messages in
/// sequence as they occurred rather than all messages from each context in a single /// sequence as they occurred rather than all messages from each context in a single
/// block. /// block.
pub fn with_log_sink(mut self, sink: Sender<LogEvent>) -> Self { pub fn with_log_sink(mut self, sink: LogSink) -> Self {
self.log_sink = Some(sink); self.log_sink = sink;
self self
} }
@@ -243,7 +242,7 @@ impl TestContextBuilder {
pub async fn build(self) -> TestContext { pub async fn build(self) -> TestContext {
let name = self.key_pair.as_ref().map(|key| key.addr.local.clone()); let name = self.key_pair.as_ref().map(|key| key.addr.local.clone());
let test_context = TestContext::new_internal(name, self.log_sink).await; let test_context = TestContext::new_internal(name, Some(self.log_sink.clone())).await;
if let Some(key_pair) = self.key_pair { if let Some(key_pair) = self.key_pair {
test_context test_context
@@ -266,18 +265,16 @@ pub struct TestContext {
pub dir: TempDir, pub dir: TempDir,
pub evtracker: EventTracker, pub evtracker: EventTracker,
/// Channels which should receive events from this context.
event_senders: Arc<RwLock<Vec<Sender<Event>>>>,
/// Reference to implicit [`LogSink`] so it is dropped together with the context. /// Reference to implicit [`LogSink`] so it is dropped together with the context.
/// ///
/// Only used if no explicit `log_sender` is passed into [`TestContext::new_internal`] /// Only used if no explicit `log_sender` is passed into [`TestContext::new_internal`]
/// (which is assumed to be the sending end of a [`LogSink`]). /// (which is assumed to be the sending end of a [`LogSink`]).
/// ///
/// This is a convenience in case only a single [`TestContext`] is used to avoid dealing /// This is a convenience in case only a single [`TestContext`] is used to avoid dealing
/// with [`LogSink`]. Never read, thus "dead code", since the only purpose is to /// with [`LogSink`]. Never read, since the only purpose is to
/// control when Drop is invoked. /// control when Drop is invoked.
#[allow(dead_code)] _log_sink: Option<LogSink>,
log_sink: Option<LogSink>,
} }
impl TestContext { impl TestContext {
@@ -337,7 +334,7 @@ impl TestContext {
/// `log_sender` is assumed to be the sender for a [`LogSink`]. If not supplied a new /// `log_sender` is assumed to be the sender for a [`LogSink`]. If not supplied a new
/// [`LogSink`] will be created so that events are logged to this test when the /// [`LogSink`] will be created so that events are logged to this test when the
/// [`TestContext`] is dropped. /// [`TestContext`] is dropped.
async fn new_internal(name: Option<String>, log_sender: Option<Sender<LogEvent>>) -> Self { async fn new_internal(name: Option<String>, log_sink: Option<LogSink>) -> Self {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite"); let dbfile = dir.path().join("db.sqlite");
let id = rand::thread_rng().gen(); let id = rand::thread_rng().gen();
@@ -345,35 +342,23 @@ impl TestContext {
let mut context_names = CONTEXT_NAMES.write().unwrap(); let mut context_names = CONTEXT_NAMES.write().unwrap();
context_names.insert(id, name); context_names.insert(id, name);
} }
let ctx = Context::new(&dbfile, id, Events::new(), StockStrings::new()) let events = Events::new();
let evtracker_receiver = events.get_emitter();
let ctx = Context::new(&dbfile, id, events, StockStrings::new())
.await .await
.expect("failed to create context"); .expect("failed to create context");
let events = ctx.get_event_emitter(); let _log_sink = if let Some(log_sink) = log_sink {
// Subscribe existing LogSink and don't store reference to it.
let (log_sender, log_sink) = match log_sender { log_sink.subscribe(ctx.get_event_emitter());
Some(sender) => (sender, None), None
None => { } else {
let (sender, sink) = LogSink::create(); // Create new LogSink and store it inside the `TestContext`.
(sender, Some(sink)) let log_sink = LogSink::new();
} log_sink.subscribe(ctx.get_event_emitter());
Some(log_sink)
}; };
let (evtracker_sender, evtracker_receiver) = channel::unbounded();
let event_senders = Arc::new(RwLock::new(vec![evtracker_sender]));
let senders = Arc::clone(&event_senders);
task::spawn(async move {
while let Some(event) = events.recv().await {
for sender in senders.read().await.iter() {
// Don't block because someone wanted to use a oneshot receiver, use
// an unbounded channel if you want all events.
sender.try_send(event.clone()).ok();
}
log_sender.try_send(LogEvent::Event(event.clone())).ok();
}
});
ctx.set_config(Config::SkipStartMessages, Some("1")) ctx.set_config(Config::SkipStartMessages, Some("1"))
.await .await
.unwrap(); .unwrap();
@@ -383,8 +368,7 @@ impl TestContext {
ctx, ctx,
dir, dir,
evtracker: EventTracker(evtracker_receiver), evtracker: EventTracker(evtracker_receiver),
event_senders, _log_sink,
log_sink,
} }
} }
@@ -407,14 +391,6 @@ impl TestContext {
context_names.get(id).unwrap_or(&id.to_string()).to_string() context_names.get(id).unwrap_or(&id.to_string()).to_string()
} }
/// Adds a new [`Event`]s sender.
///
/// Once added, all events emitted by this context will be sent to this channel. This
/// is useful if you need to wait for events or make assertions on them.
pub async fn add_event_sender(&self, sink: Sender<Event>) {
self.event_senders.write().await.push(sink)
}
/// Configure as a given email address. /// Configure as a given email address.
/// ///
/// The context will be configured but the key will not be pre-generated so if a key is /// The context will be configured but the key will not be pre-generated so if a key is
@@ -849,22 +825,62 @@ pub enum LogEvent {
/// This sink achieves this by printing the events, in the order received, at the time it is /// This sink achieves this by printing the events, in the order received, at the time it is
/// dropped. Thus to use you must only make sure this sink is dropped in the test itself. /// dropped. Thus to use you must only make sure this sink is dropped in the test itself.
/// ///
/// To use this create an instance using [`LogSink::create`] and then use the /// To use this create an instance using [`LogSink::new`] and then use the
/// [`TestContextBuilder::with_log_sink`]. /// [`TestContextBuilder::with_log_sink`] or use [`TestContextManager`].
#[derive(Debug)] #[derive(Debug, Clone, Default)]
pub struct LogSink { pub struct LogSink(Arc<InnerLogSink>);
events: Receiver<LogEvent>,
}
impl LogSink { impl LogSink {
/// Creates a new [`LogSink`] and returns the attached event sink. /// Creates a new [`LogSink`] and returns the attached event sink.
pub fn create() -> (Sender<LogEvent>, Self) { pub fn new() -> Self {
let (tx, rx) = channel::unbounded(); Default::default()
(tx, Self { events: rx })
} }
} }
impl Drop for LogSink { impl Deref for LogSink {
type Target = InnerLogSink;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct InnerLogSink {
events: Receiver<LogEvent>,
/// Sender side of the log receiver.
///
/// It is cloned when log sink is subscribed
/// to new event emitter
/// and can be used directly from the test to
/// add "sections" to the log.
sender: Sender<LogEvent>,
}
impl Default for InnerLogSink {
fn default() -> Self {
let (tx, rx) = channel::unbounded();
Self {
events: rx,
sender: tx,
}
}
}
impl InnerLogSink {
/// Subscribes this log sink to event emitter.
pub fn subscribe(&self, event_emitter: EventEmitter) {
let sender = self.sender.clone();
task::spawn(async move {
while let Some(event) = event_emitter.recv().await {
sender.try_send(LogEvent::Event(event.clone())).ok();
}
});
}
}
impl Drop for InnerLogSink {
fn drop(&mut self) { fn drop(&mut self) {
while let Ok(event) = self.events.try_recv() { while let Ok(event) = self.events.try_recv() {
print_logevent(&event); print_logevent(&event);
@@ -975,10 +991,10 @@ pub fn fiona_keypair() -> KeyPair {
/// be attached to a single [`TestContext`] and therefore the context is already known as /// be attached to a single [`TestContext`] and therefore the context is already known as
/// you will be accessing it as [`TestContext::evtracker`]. /// you will be accessing it as [`TestContext::evtracker`].
#[derive(Debug)] #[derive(Debug)]
pub struct EventTracker(Receiver<Event>); pub struct EventTracker(EventEmitter);
impl Deref for EventTracker { impl Deref for EventTracker {
type Target = Receiver<Event>; type Target = EventEmitter;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@@ -1025,15 +1041,8 @@ impl EventTracker {
} }
/// Clears event queue. /// Clears event queue.
/// pub fn clear_events(&self) {
/// This spends 1 second instead of using `try_recv` while let Ok(_ev) = self.try_recv() {}
/// to avoid accidentally leaving an event that
/// was emitted right before calling `clear_events()`.
///
/// Avoid using this function if you can
/// by waiting for specific events you expect to receive.
pub async fn clear_events(&self) {
while let Ok(_ev) = tokio::time::timeout(Duration::from_secs(1), self.recv()).await {}
} }
} }