test_utils.rs improvements from my AEAP PR (#3401)

This commit is contained in:
Hocuri
2022-06-12 19:29:22 +02:00
committed by GitHub
parent 7b369c9107
commit 1ae67c4549

View File

@@ -2,6 +2,7 @@
//! //!
//! This private module is only compiled for test runs. //! This private module is only compiled for test runs.
#![allow(clippy::indexing_slicing)] #![allow(clippy::indexing_slicing)]
#![allow(dead_code)] // Can be removed once PR #3385 is merged
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ops::Deref; use std::ops::Deref;
use std::panic; use std::panic;
@@ -39,7 +40,7 @@ static CONTEXT_NAMES: Lazy<std::sync::RwLock<BTreeMap<u32, String>>> =
Lazy::new(|| std::sync::RwLock::new(BTreeMap::new())); Lazy::new(|| std::sync::RwLock::new(BTreeMap::new()));
pub struct TestContextManager { pub struct TestContextManager {
log_tx: Sender<Event>, log_tx: Sender<LogEvent>,
_log_sink: LogSink, _log_sink: LogSink,
} }
@@ -64,12 +65,67 @@ impl TestContextManager {
.build() .build()
.await .await
} }
pub async fn fiona(&mut self) -> TestContext {
TestContext::builder()
.configure_fiona()
.with_log_sink(self.log_tx.clone())
.build()
.await
}
/// Writes info events to the log that mark a section, e.g.:
///
/// ========== `msg` goes here ==========
pub fn section(&self, msg: &str) {
self.log_tx
.try_send(LogEvent::Section(msg.to_string()))
.expect(
"The events channel should be unbounded and not closed, so try_send() shouldn't fail",
);
}
/// - Let one TestContext send a message
/// - Let the other TestContext receive it and accept the chat
/// - Assert that the message arrived
pub async fn send_recv_accept(&self, from: &TestContext, to: &TestContext, msg: &str) {
self.section(&format!(
"{} sends a message '{}' to {}",
from.name(),
msg,
to.name()
));
let chat = from.create_chat(to).await;
let sent = from.send_text(chat.id, msg).await;
let received_msg = to.recv_msg(&sent).await;
received_msg.chat_id.accept(to).await.unwrap();
assert_eq!(received_msg.text.as_deref().unwrap(), msg);
}
pub async fn change_addr(&self, test_context: &TestContext, new_addr: &str) {
self.section(&format!(
"{} changes her self address and reconfigures",
test_context.name()
));
test_context.set_primary_self_addr(new_addr).await.unwrap();
// ensure_secret_key_exists() is called during configure
crate::e2ee::ensure_secret_key_exists(test_context)
.await
.unwrap();
assert_eq!(
test_context.get_primary_self_addr().await.unwrap(),
new_addr
);
}
} }
#[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<Event>>, log_sink: Option<Sender<LogEvent>>,
} }
impl TestContextBuilder { impl TestContextBuilder {
@@ -109,7 +165,7 @@ 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<Event>) -> Self { pub fn with_log_sink(mut self, sink: Sender<LogEvent>) -> Self {
self.log_sink = Some(sink); self.log_sink = Some(sink);
self self
} }
@@ -202,7 +258,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<Event>>) -> Self { async fn new_internal(name: Option<String>, log_sender: Option<Sender<LogEvent>>) -> 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();
@@ -225,7 +281,7 @@ impl TestContext {
}; };
let (evtracker_sender, evtracker_receiver) = channel::unbounded(); let (evtracker_sender, evtracker_receiver) = channel::unbounded();
let event_senders = Arc::new(RwLock::new(vec![log_sender, evtracker_sender])); let event_senders = Arc::new(RwLock::new(vec![evtracker_sender]));
let senders = Arc::clone(&event_senders); let senders = Arc::clone(&event_senders);
task::spawn(async move { task::spawn(async move {
@@ -235,6 +291,7 @@ impl TestContext {
// an unbounded channel if you want all events. // an unbounded channel if you want all events.
sender.try_send(event.clone()).ok(); sender.try_send(event.clone()).ok();
} }
log_sender.try_send(LogEvent::Event(event.clone())).ok();
} }
}); });
@@ -257,6 +314,15 @@ impl TestContext {
.or_insert_with(|| name.into()); .or_insert_with(|| name.into());
} }
/// Returns the name of this [`TestContext`].
///
/// This is the same name that is shown in events logged in the test output.
pub fn name(&self) -> String {
let context_names = CONTEXT_NAMES.read().unwrap();
let id = &self.ctx.id;
context_names.get(id).unwrap_or(&id.to_string()).to_string()
}
/// Adds a new [`Event`]s sender. /// Adds a new [`Event`]s sender.
/// ///
/// Once added, all events emitted by this context will be sent to this channel. This /// Once added, all events emitted by this context will be sent to this channel. This
@@ -352,7 +418,10 @@ impl TestContext {
/// Receive a message using the `dc_receive_imf()` pipeline. Panics if it's not shown /// Receive a message using the `dc_receive_imf()` pipeline. Panics if it's not shown
/// in the chat as exactly one message. /// in the chat as exactly one message.
pub async fn recv_msg(&self, msg: &SentMessage) -> Message { pub async fn recv_msg(&self, msg: &SentMessage) -> Message {
let received = self.recv_msg_opt(msg).await.unwrap(); let received = self
.recv_msg_opt(msg)
.await
.expect("dc_receive_imf() seems not to have added a new message to the db");
assert_eq!( assert_eq!(
received.msg_ids.len(), received.msg_ids.len(),
@@ -500,8 +569,13 @@ impl TestContext {
/// the message. /// the message.
pub async fn send_msg(&self, chat_id: ChatId, msg: &mut Message) -> SentMessage { pub async fn send_msg(&self, chat_id: ChatId, msg: &mut Message) -> SentMessage {
chat::prepare_msg(self, chat_id, msg).await.unwrap(); chat::prepare_msg(self, chat_id, msg).await.unwrap();
chat::send_msg(self, chat_id, msg).await.unwrap(); let msg_id = chat::send_msg(self, chat_id, msg).await.unwrap();
self.pop_sent_msg().await let res = self.pop_sent_msg().await;
assert_eq!(
res.sender_msg_id, msg_id,
"Apparently the message was not actually sent out"
);
res
} }
/// Prints out the entire chat to stdout. /// Prints out the entire chat to stdout.
@@ -596,6 +670,28 @@ impl Deref for TestContext {
} }
} }
impl Drop for TestContext {
fn drop(&mut self) {
async_std::task::block_on(async {
println!("\n========== Chats of {}: ==========", self.name());
if let Ok(chats) = Chatlist::try_load(self, 0, None, None).await {
for (chat, _) in chats.iter() {
self.print_chat(*chat).await;
}
}
println!();
});
}
}
pub enum LogEvent {
/// Logged event.
Event(Event),
/// Test output section.
Section(String),
}
/// A receiver of [`Event`]s which will log the events to the captured test stdout. /// A receiver of [`Event`]s which will log the events to the captured test stdout.
/// ///
/// Tests redirect the stdout of the test thread and capture this, showing the captured /// Tests redirect the stdout of the test thread and capture this, showing the captured
@@ -609,12 +705,12 @@ impl Deref for TestContext {
/// [`TestContextBuilder::with_log_sink`]. /// [`TestContextBuilder::with_log_sink`].
#[derive(Debug)] #[derive(Debug)]
pub struct LogSink { pub struct LogSink {
events: Receiver<Event>, 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<Event>, Self) { pub fn create() -> (Sender<LogEvent>, Self) {
let (tx, rx) = channel::unbounded(); let (tx, rx) = channel::unbounded();
(tx, Self { events: rx }) (tx, Self { events: rx })
} }
@@ -623,7 +719,7 @@ impl LogSink {
impl Drop for LogSink { impl Drop for LogSink {
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_event(&event); print_logevent(&event);
} }
} }
} }
@@ -758,7 +854,6 @@ impl EventTracker {
/// Gets a specific message from a chat and asserts that the chat has a specific length. /// Gets a specific message from a chat and asserts that the chat has a specific length.
/// ///
/// Panics if the length of the chat is not `asserted_msgs_count` or if the chat item at `index` is not a Message. /// Panics if the length of the chat is not `asserted_msgs_count` or if the chat item at `index` is not a Message.
#[allow(clippy::indexing_slicing)]
pub(crate) async fn get_chat_msg( pub(crate) async fn get_chat_msg(
t: &TestContext, t: &TestContext,
chat_id: ChatId, chat_id: ChatId,
@@ -775,6 +870,13 @@ pub(crate) async fn get_chat_msg(
Message::load_from_db(&t.ctx, msg_id).await.unwrap() Message::load_from_db(&t.ctx, msg_id).await.unwrap()
} }
fn print_logevent(logevent: &LogEvent) {
match logevent {
LogEvent::Event(event) => print_event(event),
LogEvent::Section(msg) => println!("\n========== {} ==========", msg),
}
}
/// Pretty-print an event to stdout /// Pretty-print an event to stdout
/// ///
/// Done during tests this is captured by `cargo test` and associated with the test itself. /// Done during tests this is captured by `cargo test` and associated with the test itself.
@@ -846,9 +948,13 @@ fn print_event(event: &Event) {
/// ///
/// This includes a bunch of the message meta-data as well. /// This includes a bunch of the message meta-data as well.
async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) { async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let contact = Contact::get_by_id(context, msg.get_from_id()) let contact = match Contact::get_by_id(context, msg.get_from_id()).await {
.await Ok(contact) => contact,
.expect("invalid contact"); Err(e) => {
println!("Can't log message: invalid contact: {}", e);
return;
}
};
let contact_name = contact.get_name(); let contact_name = contact.get_name();
let contact_id = contact.get_id(); let contact_id = contact.get_id();