WIP Scenario

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2022-09-25 23:29:56 +02:00
parent 1f7112616a
commit c9a0745c81
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
5 changed files with 168 additions and 18 deletions

View file

@ -8,6 +8,7 @@ description = "MUC implementation allowing participants to play the Hanabi game.
[dependencies] [dependencies]
async-trait = "^0.1" async-trait = "^0.1"
chrono = "0.4.22" chrono = "0.4.22"
dyn-clone = "1.0.9"
env_logger = "^0.9" env_logger = "^0.9"
futures = "^0.3" futures = "^0.3"
lazy_static = "^1.4" lazy_static = "^1.4"

View file

@ -19,7 +19,10 @@ use crate::error::Error;
use std::collections::VecDeque; use std::collections::VecDeque;
#[cfg(test)] #[cfg(test)]
use std::fmt; use std::fmt;
use std::marker::Send; // #[cfg(test)]
// use std::iter::IntoIterator;
#[cfg(test)]
use std::marker::{Send, Sync};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::pin::Pin; use std::pin::Pin;
use std::task::Context; use std::task::Context;
@ -27,6 +30,8 @@ use std::task::Context;
use std::thread; use std::thread;
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(test)]
use dyn_clone::{clone_trait_object, DynClone};
use futures::{task::Poll, Stream}; use futures::{task::Poll, Stream};
use log::debug; use log::debug;
use tokio_xmpp::Component as TokioXMPPComponent; use tokio_xmpp::Component as TokioXMPPComponent;
@ -92,16 +97,47 @@ impl Component {
} }
} }
/*
#[cfg(test)] #[cfg(test)]
pub trait ExpectCb<T>: FnOnce(T) + Send + Sync + DynClone + 'static {}
#[cfg(test)]
clone_trait_object!(ExpectCb<Iq>);
#[cfg(test)]
clone_trait_object!(ExpectCb<Message>);
#[cfg(test)]
clone_trait_object!(ExpectCb<Presence>);
#[cfg(test)]
impl ExpectCb<T> for dyn FnOnce(T) {}
*/
#[cfg(test)]
pub trait Foo: FnOnce(Iq) + DynClone {}
#[cfg(test)]
clone_trait_object!(Foo);
#[cfg(test)]
pub trait Bar: Foo + Send + Sync + 'static {}
#[cfg(test)]
clone_trait_object!(Bar);
#[cfg(test)]
pub trait ExpectIq: FnOnce(Iq) + Send + Sync + DynClone + 'static {}
#[cfg(test)]
impl ExpectIq for dyn FnOnce(Iq) where Self: ExpectIq {}
#[cfg(test)]
clone_trait_object!(ExpectIq);
#[cfg(test)]
#[derive(Clone)]
enum Expect { enum Expect {
/// Simple Element /// Simple Element
Element(TestElement), Element(TestElement),
/// Callback taking an Iq, with a description alongside /// Callback taking an Iq, with a description alongside
Iq(Box<dyn FnOnce(Iq) + Send + 'static>, String), Iq(Box<dyn ExpectIq>, String),
/// Callback taking a Presence, with a description alongside
Presence(Box<dyn FnOnce(Presence) + Send + 'static>, String),
/// Callback taking a Message, with a description alongside /// Callback taking a Message, with a description alongside
Message(Box<dyn FnOnce(Message) + Send + 'static>, String), Message(Box<dyn FnOnce(Message) + Send + Sync + 'static>, String),
/// Callback taking a Presence, with a description alongside
Presence(Box<dyn FnOnce(Presence) + Send + Sync + 'static>, String),
} }
#[cfg(test)] #[cfg(test)]
@ -117,6 +153,63 @@ impl fmt::Debug for Expect {
} }
} }
#[cfg(test)]
#[derive(Debug)]
pub struct Scenario {
inbuf: VecDeque<TestElement>,
expectbuf: VecDeque<Expect>,
}
#[cfg(test)]
impl Scenario {
pub fn new() -> Self {
Scenario {
inbuf: VecDeque::new(),
expectbuf: VecDeque::new(),
}
}
pub fn into_inner(self) -> (VecDeque<TestElement>, VecDeque<Expect>) {
(self.inbuf, self.expectbuf)
}
pub fn with_input<E: Into<TestElement>>(mut self, el: E) -> Self {
self.inbuf.push_back(el.into());
self
}
pub fn with_expect<E: Into<TestElement>>(mut self, el: E) -> Self {
self.expectbuf.push_back(Expect::Element(el.into()));
self
}
pub fn with_expect_iq<F: ExpectIq, S: Into<String>>(mut self, callback: F, desc: S) -> Self {
self.expectbuf
.push_back(Expect::Iq(Box::new(callback), desc.into()));
self
}
pub fn with_expect_message<F: FnOnce(Message) + Send + Sync + 'static, S: Into<String>>(
mut self,
callback: F,
desc: S,
) -> Self {
self.expectbuf
.push_back(Expect::Message(Box::new(callback), desc.into()));
self
}
pub fn with_expect_presence<F: FnOnce(Presence) + Send + Sync + 'static, S: Into<String>>(
mut self,
callback: F,
desc: S,
) -> Self {
self.expectbuf
.push_back(Expect::Presence(Box::new(callback), desc.into()));
self
}
}
#[cfg(test)] #[cfg(test)]
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct TestElement(pub Element); pub struct TestElement(pub Element);
@ -197,12 +290,7 @@ pub struct TestComponent {
impl TestComponent { impl TestComponent {
pub fn new(in_buffer: Vec<Element>) -> Self { pub fn new(in_buffer: Vec<Element>) -> Self {
TestComponent { TestComponent {
in_buffer: VecDeque::from( in_buffer: VecDeque::from(in_buffer.into_iter().map(TestElement).collect::<Vec<_>>()),
in_buffer
.into_iter()
.map(|el| TestElement(el))
.collect::<Vec<_>>(),
),
expect_buffer: VecDeque::new(), expect_buffer: VecDeque::new(),
} }
} }
@ -212,16 +300,12 @@ impl TestComponent {
self.expect_buffer.push_back(Expect::Element(el.into())) self.expect_buffer.push_back(Expect::Element(el.into()))
} }
pub fn expect_iq<F: FnOnce(Iq) + Send + 'static, S: Into<String>>( pub fn expect_iq<F: ExpectIq, S: Into<String>>(&mut self, callback: F, desc: S) {
&mut self,
callback: F,
desc: S,
) {
self.expect_buffer self.expect_buffer
.push_back(Expect::Iq(Box::new(callback), desc.into())) .push_back(Expect::Iq(Box::new(callback), desc.into()))
} }
pub fn expect_message<F: FnOnce(Message) + Send + 'static, S: Into<String>>( pub fn expect_message<F: FnOnce(Message) + Send + Sync + 'static, S: Into<String>>(
&mut self, &mut self,
callback: F, callback: F,
desc: S, desc: S,
@ -230,7 +314,7 @@ impl TestComponent {
.push_back(Expect::Message(Box::new(callback), desc.into())) .push_back(Expect::Message(Box::new(callback), desc.into()))
} }
pub fn expect_presence<F: FnOnce(Presence) + Send + 'static, S: Into<String>>( pub fn expect_presence<F: FnOnce(Presence) + Send + Sync + 'static, S: Into<String>>(
&mut self, &mut self,
callback: F, callback: F,
desc: S, desc: S,
@ -239,6 +323,12 @@ impl TestComponent {
.push_back(Expect::Presence(Box::new(callback), desc.into())) .push_back(Expect::Presence(Box::new(callback), desc.into()))
} }
pub fn expect_scenario<S: Into<String>>(&mut self, scenario: Scenario, desc: S) {
let (input, expected) = scenario.into_inner();
self.in_buffer.extend(input);
self.expect_buffer.extend(expected);
}
fn send_stanza_inner<E: Into<TestElement> + Send>(&mut self, el: E) -> Result<(), Error> { fn send_stanza_inner<E: Into<TestElement> + Send>(&mut self, el: E) -> Result<(), Error> {
let out: TestElement = el.into(); let out: TestElement = el.into();
let expected = self.expect_buffer.pop_front(); let expected = self.expect_buffer.pop_front();

View file

@ -14,6 +14,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#![feature(let_chains)] #![feature(let_chains)]
#![feature(once_cell)]
// #![feature(trait_alias)]
// Maybe change that someday? // Maybe change that someday?
#![allow(clippy::result_large_err)] #![allow(clippy::result_large_err)]
@ -22,6 +24,8 @@ mod error;
mod handlers; mod handlers;
mod room; mod room;
#[cfg(test)]
mod scenarios;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

50
src/scenarios.rs Normal file
View file

@ -0,0 +1,50 @@
// Copyright (C) 2022-2099 The crate authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
// for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::component::Scenario;
use std::str::FromStr;
use std::sync::LazyLock;
use lazy_static::lazy_static;
use xmpp_parsers::BareJid;
/*
lazy_static! {
static ref COMPONENT_JID: BareJid = BareJid::from_str("commons.social").unwrap();
static ref ROOM1: BareJid = COMPONENT_JID.with_node("direct-action");
/// https://en.wikipedia.org/wiki/Louise_Michel
static ref LOUISE_BARE: BareJid = BareJid::from_str("louise@example.net").unwrap();
static ref LOUISE_NICK: String = String::from("louise");
/// https://en.wikipedia.org/wiki/Kanno_Sugako
static ref SUGAKO_BARE: BareJid = BareJid::from_str("すがこ@example.net").unwrap();
static ref SUGAKO_NICK: String = String::from("すがこ");
/// https://en.wikipedia.org/wiki/Rosa_Luxemburg
static ref ROSA_BARE: BareJid = BareJid::from_str("rosa@example.net").unwrap();
static ref ROSA_NICK: String = String::from("rosa");
/// https://en.wikipedia.org/wiki/Peter_Kropotkin
static ref PETER_BARE: BareJid = BareJid::from_str("peter@example.net").unwrap();
static ref PETER_NICK: String = String::from("peter");
*/
/// Creates a room of one participant
// pub static CREATE_ROOM_1: LazyLock<Scenario> = LazyLock::new(|| Scenario::new());
lazy_static! {
pub static ref CREATE_ROOM_1: Scenario = Scenario::new();
}

View file

@ -16,9 +16,11 @@
use crate::component::TestComponent; use crate::component::TestComponent;
use crate::handlers::handle_stanza; use crate::handlers::handle_stanza;
use crate::room::Room; use crate::room::Room;
use crate::scenarios::CREATE_ROOM_1;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::str::FromStr; use std::str::FromStr;
use std::sync::LazyLock;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use xmpp_parsers::{ use xmpp_parsers::{
@ -56,6 +58,9 @@ async fn test_join_presence_empty_room() {
let mut component = TestComponent::new(vec![join]); let mut component = TestComponent::new(vec![join]);
let mut rooms: HashMap<BareJid, Room> = HashMap::new(); let mut rooms: HashMap<BareJid, Room> = HashMap::new();
// let create_room = LazyLock::force(&CREATE_ROOM_1);
component.expect_scenario(*CREATE_ROOM_1, "Create room of 1 participant");
// Room is empty so there should be: // Room is empty so there should be:
// - No presence sent except for self-presence // - No presence sent except for self-presence
// - No message history // - No message history