Compare commits
2 commits
main
...
scansion-c
Author | SHA1 | Date | |
---|---|---|---|
b5c7c2b579 | |||
e432c8b17e |
10 changed files with 291 additions and 15 deletions
16
Cargo.toml
16
Cargo.toml
|
@ -22,3 +22,19 @@ jid = { version = "*" }
|
|||
[dev-dependencies]
|
||||
syntect = "5.0"
|
||||
diff = "0.1"
|
||||
scansion = { version = "0.1" }
|
||||
|
||||
[build-dependencies]
|
||||
jid = { version = "*", features = ["minidom"] }
|
||||
minidom = { version = "*" }
|
||||
quote = "1.0"
|
||||
rand = "0.8"
|
||||
scansion = { version = "0.1", features = ["quote"] }
|
||||
|
||||
[patch.crates-io]
|
||||
sasl = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
|
||||
jid = { git = "https://code.bouah.net/pep/xmpp-rs.git", branch = "jid-quote" }
|
||||
minidom = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
|
||||
xmpp-parsers = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
|
||||
tokio-xmpp = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
|
||||
scansion = { git = "https://code.bouah.net/pep/scansion-rs.git" }
|
||||
|
|
196
build.rs
Normal file
196
build.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
// Copyright (C) 2023-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 std::ffi::OsString;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufWriter, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use minidom::Error as MinidomError;
|
||||
use quote::{format_ident, quote, TokenStreamExt};
|
||||
use scansion::{read_actions_component, read_spec, Spec};
|
||||
|
||||
fn generate_spec_tokens(spec: Spec) -> Result<impl TokenStreamExt + Display, MinidomError> {
|
||||
println!("FOO0");
|
||||
|
||||
let docstr = {
|
||||
let mut tmp = String::new();
|
||||
if let Some(ref meta) = spec.metadata {
|
||||
tmp.push_str(meta.title.as_str());
|
||||
tmp.push('\n');
|
||||
|
||||
if let Some(ref desc) = meta.description {
|
||||
tmp.push_str(desc.as_str());
|
||||
tmp.push('\n');
|
||||
}
|
||||
|
||||
for tag in &meta.tags {
|
||||
tmp.push_str("tag: ");
|
||||
tmp.push_str(tag.as_str());
|
||||
tmp.push('\n');
|
||||
}
|
||||
} else {
|
||||
tmp.push_str("No metadata");
|
||||
}
|
||||
|
||||
tmp
|
||||
};
|
||||
|
||||
let context = spec.context.clone();
|
||||
let actions = read_actions_component(spec, &context).unwrap();
|
||||
|
||||
let stanzas_in = actions
|
||||
.inbound
|
||||
.into_iter()
|
||||
.map(|elem| {
|
||||
// TODO: Prevent having to parse elements again.
|
||||
// We do need some kind of structure already though to be able to verify the presence
|
||||
// of attrs and all before the generation.
|
||||
let elem_str = String::from(&elem);
|
||||
quote! { ScanElement::new(#elem_str.parse::<Element>().unwrap()).apply_context(&context) }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let stanzas_out = actions
|
||||
.outbound
|
||||
.into_iter()
|
||||
.map(|elem| {
|
||||
let elem_str = String::from(&elem);
|
||||
quote! {
|
||||
component.expect(
|
||||
#elem_str.parse::<ScanElement>().unwrap().apply_context(&context)
|
||||
);
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (keys, values) =
|
||||
context
|
||||
.clone()
|
||||
.into_iter()
|
||||
.fold((vec![], vec![]), |(mut keys, mut values), (k, v)| {
|
||||
keys.push(quote! { #k });
|
||||
values.push(quote! { #v });
|
||||
(keys, values)
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
#![doc = #docstr]
|
||||
use crate::component::TestComponent;
|
||||
use crate::handlers::handle_stanza;
|
||||
use crate::room::Room;
|
||||
|
||||
use ::std::collections::HashMap;
|
||||
use ::xmpp_parsers::{Jid, BareJid, FullJid, Element};
|
||||
use ::scansion::{ScanElement, Entity, Client};
|
||||
|
||||
#[tokio::test]
|
||||
async fn spec() {
|
||||
let context: ::scansion::Context = {
|
||||
let mut tmp = ::std::collections::HashMap::new();
|
||||
let (keys, values) = (vec![#(#keys),*], vec![#(#values),*]);
|
||||
for (k, v) in keys.iter().zip(values.iter()) {
|
||||
tmp.insert(String::from(*k), v.clone());
|
||||
}
|
||||
tmp
|
||||
};
|
||||
|
||||
let stanzas_in = vec![#(#stanzas_in),*];
|
||||
let mut component = TestComponent::new_scan(stanzas_in);
|
||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||
|
||||
#(#stanzas_out)*
|
||||
|
||||
println!("FOO: {component:?}");
|
||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_mod_tokens(modfiles: Vec<OsString>) -> impl TokenStreamExt + Display {
|
||||
let modfiles: Vec<_> = modfiles
|
||||
.into_iter()
|
||||
.map(|s| format_ident!("{}", s.into_string().unwrap().strip_suffix(".rs").unwrap()))
|
||||
.collect();
|
||||
quote! {
|
||||
/// Scansion tests module.
|
||||
/// These tests are generated by the build script, DO NOT EDIT.
|
||||
|
||||
#(#[cfg(test)] mod #modfiles;)*
|
||||
}
|
||||
}
|
||||
|
||||
fn read_input_dir(indir: PathBuf, outdir: PathBuf) -> io::Result<Vec<OsString>> {
|
||||
// Will be used to generate mod.rs
|
||||
let mut modfiles: Vec<OsString> = Vec::new();
|
||||
|
||||
for entry in indir.read_dir()? {
|
||||
let mut p = entry?.path();
|
||||
match p.extension() {
|
||||
Some(ext) if ext == "scs" => (),
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let mut infile = File::open(p.clone())?;
|
||||
let mut contents = String::new();
|
||||
infile.read_to_string(&mut contents)?;
|
||||
|
||||
let spec = read_spec(&contents);
|
||||
match spec {
|
||||
Ok(_) => println!("Path: {p:?}: \x1b[32m OK\x1b[0m"),
|
||||
Err(err) => {
|
||||
println!("Path: {p:?}: \x1b[31mERR\x1b[0m\n{err:?}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Path is now only going to be used as .rs
|
||||
p.set_extension("rs");
|
||||
|
||||
let filename = OsString::from(p.file_name().unwrap());
|
||||
let outpath = outdir.join(filename.clone());
|
||||
println!("Outpath: {outpath:?}");
|
||||
|
||||
let tokens = generate_spec_tokens(spec.unwrap()).unwrap();
|
||||
let mut output = BufWriter::new(File::create(&outpath)?);
|
||||
write!(output, "{}", tokens)?;
|
||||
|
||||
// Add to the set of files for which generation succeeded to then generate mod.rs
|
||||
modfiles.push(filename);
|
||||
}
|
||||
|
||||
Ok(modfiles)
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let indir = Path::new("./specs");
|
||||
let outdir = Path::new("./src/tests/scansion");
|
||||
|
||||
if !indir.is_dir() || !outdir.is_dir() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Input and output paths must be directories.",
|
||||
));
|
||||
}
|
||||
|
||||
let modfiles = read_input_dir(indir.to_path_buf(), outdir.to_path_buf())?;
|
||||
|
||||
let mut modout = BufWriter::new(File::create(outdir.join("mod.rs"))?);
|
||||
let tokens = generate_mod_tokens(modfiles);
|
||||
write!(modout, "{}", tokens)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -28,6 +28,7 @@ use async_trait::async_trait;
|
|||
use diff;
|
||||
use futures::{task::Poll, Stream};
|
||||
use log::debug;
|
||||
use scansion::ScanElement;
|
||||
use syntect::{
|
||||
easy::HighlightLines,
|
||||
highlighting::{Style, ThemeSet},
|
||||
|
@ -59,17 +60,23 @@ impl fmt::Debug for Expect {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct TestElement(pub Element);
|
||||
#[derive(Clone)]
|
||||
pub struct TestElement(pub ScanElement);
|
||||
|
||||
impl Deref for TestElement {
|
||||
type Target = Element;
|
||||
type Target = ScanElement;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Element> for TestElement {
|
||||
fn eq(&self, other: &Element) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TestElement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", String::from(&self.0))
|
||||
|
@ -90,31 +97,43 @@ impl From<&TestElement> for String {
|
|||
|
||||
impl From<Element> for TestElement {
|
||||
fn from(elem: Element) -> Self {
|
||||
Self(elem)
|
||||
Self(ScanElement::new(elem))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestElement> for Element {
|
||||
fn from(elem: TestElement) -> Self {
|
||||
elem.0
|
||||
elem.0.elem
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Iq> for TestElement {
|
||||
fn from(elem: Iq) -> Self {
|
||||
Self(Element::from(elem))
|
||||
Self(Element::from(elem).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Presence> for TestElement {
|
||||
fn from(elem: Presence) -> Self {
|
||||
Self(Element::from(elem))
|
||||
Self(Element::from(elem).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for TestElement {
|
||||
fn from(elem: Message) -> Self {
|
||||
Self(Element::from(elem))
|
||||
Self(Element::from(elem).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScanElement> for TestElement {
|
||||
fn from(scan: ScanElement) -> Self {
|
||||
Self(scan)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestElement> for ScanElement {
|
||||
fn from(elem: TestElement) -> ScanElement {
|
||||
ScanElement::new(elem.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +151,21 @@ impl Component {
|
|||
in_buffer: VecDeque::from(
|
||||
in_buffer
|
||||
.into_iter()
|
||||
.map(|el| TestElement(el))
|
||||
.map(|elem| TestElement::from(elem))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
expect_buffer: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_scan(in_buffer: Vec<ScanElement>) -> Self {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
|
||||
Component {
|
||||
in_buffer: VecDeque::from(
|
||||
in_buffer
|
||||
.into_iter()
|
||||
.map(|elem| TestElement::from(elem))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
expect_buffer: HashMap::new(),
|
||||
|
@ -358,7 +391,7 @@ impl Stream for Component {
|
|||
|
||||
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
while self.in_buffer.len() > 0 {
|
||||
return Poll::Ready(self.in_buffer.pop_front().map(|el| el.0));
|
||||
return Poll::Ready(self.in_buffer.pop_front().map(|el| el.0.elem));
|
||||
}
|
||||
|
||||
Poll::Ready(None)
|
||||
|
@ -370,11 +403,25 @@ impl Drop for Component {
|
|||
// Don't assert if we're already panicking. Rustc displays a huge backtrace when "panicked
|
||||
// while panicking" even when nobody asks for it (RUST_BACKTRACE unset). Let the error
|
||||
// appear if there isn't any other error.
|
||||
if !thread::panicking() {
|
||||
assert!(
|
||||
self.expect_buffer.is_empty(),
|
||||
"Remaining expected elements in the buffer"
|
||||
);
|
||||
if !thread::panicking() && !self.expect_buffer.is_empty() {
|
||||
let mut buffer = String::new();
|
||||
for (_stream, buf) in &self.expect_buffer {
|
||||
for expect in buf {
|
||||
match expect {
|
||||
Expect::Element(el) => {
|
||||
let elem = String::from(el);
|
||||
buffer
|
||||
.push_str(format!("expected: `{}`", Component::hl(&elem)).as_str());
|
||||
}
|
||||
expect_cb => {
|
||||
buffer.push_str(format!("expected: {:?}", expect_cb).as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("\n{}", buffer);
|
||||
panic!("Remaining expected elements in the buffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
mod iq;
|
||||
mod presence;
|
||||
mod presence_msn;
|
||||
mod scansion;
|
||||
#[allow(dead_code)]
|
||||
pub mod templates;
|
||||
|
|
0
src/tests/scansion/.keep
Normal file
0
src/tests/scansion/.keep
Normal file
1
src/tests/scansion/mod.rs
Normal file
1
src/tests/scansion/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
# [doc = r" Scansion tests module."] # [doc = r" These tests are generated by the build script, DO NOT EDIT."] # [cfg (test)] mod muc_create_destroy ; # [cfg (test)] mod muc_creation ; # [cfg (test)] mod self_ping_answer ;
|
1
src/tests/scansion/muc_create_destroy.rs
Normal file
1
src/tests/scansion/muc_create_destroy.rs
Normal file
File diff suppressed because one or more lines are too long
1
src/tests/scansion/muc_creation.rs
Normal file
1
src/tests/scansion/muc_creation.rs
Normal file
|
@ -0,0 +1 @@
|
|||
# ! [doc = "MUC Creation\nSingle user MUC creation\ntag: muc\n"] use crate :: component :: TestComponent ; use crate :: handlers :: handle_stanza ; use crate :: room :: Room ; use :: std :: collections :: HashMap ; use :: xmpp_parsers :: { Jid , BareJid , FullJid , Element } ; use :: scansion :: { ScanElement , Entity , Client } ; # [tokio :: test] async fn spec () { let context : :: scansion :: Context = { let mut tmp = :: std :: collections :: HashMap :: new () ; let (keys , values) = (vec ! ["louise"] , vec ! [Entity :: Client (Client { jid : Jid :: Full (FullJid :: new ("louise@localhost/209") . unwrap ()) , password : String :: from ("password") , custom_host : None , custom_port : None , })]) ; for (k , v) in keys . iter () . zip (values . iter ()) { tmp . insert (String :: from (* k) , v . clone ()) ; } tmp } ; let stanzas_in = vec ! [ScanElement :: new ("<presence xmlns='jabber:client' from=\"louise@localhost/209\" to=\"room@muc.localhost/louise\">\n\t\t<x xmlns='http://jabber.org/protocol/muc'/>\n\t</presence>" . parse :: < Element > () . unwrap ()) . apply_context (& context)] ; let mut component = TestComponent :: new_scan (stanzas_in) ; let mut rooms : HashMap < BareJid , Room > = HashMap :: new () ; component . expect ("<presence xmlns='jabber:client' from=\"muc.localhost\" id=\"{scansion:any}\" to=\"louise@localhost/209\">\n\t\t<x xmlns='http://jabber.org/protocol/muc#user'>\n\t\t\t<status code=\"201\"/>\n\t\t\t<item affiliation=\"owner\" jid=\"louise@localhost/209\" role=\"moderator\"/>\n\t\t\t<statuc code=\"110\"/>\n\t\t</x>\n\t</presence>" . parse :: < ScanElement > () . unwrap () . apply_context (& context)) ; println ! ("FOO: {component:?}") ; handle_stanza (& mut component , & mut rooms) . await . unwrap () ; }
|
12
src/tests/scansion/muc_mediated_invite.rs
Normal file
12
src/tests/scansion/muc_mediated_invite.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::component::TestComponent;
|
||||
use crate::handlers::handle_stanza;
|
||||
use crate::room::Room;
|
||||
use std::collections::HashMap;
|
||||
use xmpp_parsers::BareJid;
|
||||
#[doc = "MUC: Mediated invites\n"]
|
||||
#[tokio::test]
|
||||
async fn spec() {
|
||||
let mut component = TestComponent::new(vec![]);
|
||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||
}
|
1
src/tests/scansion/self_ping_answer.rs
Normal file
1
src/tests/scansion/self_ping_answer.rs
Normal file
|
@ -0,0 +1 @@
|
|||
# ! [doc = "XEP-0410: MUC Self-Ping\ntag: XEP-0410\ntag: muc\ntag: self-ping\n"] use crate :: component :: TestComponent ; use crate :: handlers :: handle_stanza ; use crate :: room :: Room ; use :: std :: collections :: HashMap ; use :: xmpp_parsers :: { Jid , BareJid , FullJid , Element } ; use :: scansion :: { ScanElement , Entity , Client } ; # [tokio :: test] async fn spec () { let context : :: scansion :: Context = { let mut tmp = :: std :: collections :: HashMap :: new () ; let (keys , values) = (vec ! ["Louise"] , vec ! [Entity :: Client (Client { jid : Jid :: Full (FullJid :: new ("louise@localhost/166") . unwrap ()) , password : String :: from ("acab") , custom_host : None , custom_port : None , })]) ; for (k , v) in keys . iter () . zip (values . iter ()) { tmp . insert (String :: from (* k) , v . clone ()) ; } tmp } ; let stanzas_in = vec ! [ScanElement :: new ("<presence xmlns='jabber:client' from=\"louise@localhost/166\" to=\"news@muc.localhost/louise\">\n\t\t<x xmlns='http://jabber.org/protocol/muc'/>\n\t</presence>" . parse :: < Element > () . unwrap ()) . apply_context (& context) , ScanElement :: new ("<iq xmlns='jabber:client' from=\"louise@localhost/166\" id=\"foo\" to=\"news@muc.localhost/louise\">\n\t\t<query xmlns='urn:xmpp:ping'/>\n\t</iq>" . parse :: < Element > () . unwrap ()) . apply_context (& context)] ; let mut component = TestComponent :: new_scan (stanzas_in) ; let mut rooms : HashMap < BareJid , Room > = HashMap :: new () ; component . expect ("<presence xmlns='jabber:client' from=\"news@muc.localhost/louise\" to=\"louise@localhost/166\"/>" . parse :: < ScanElement > () . unwrap () . apply_context (& context)) ; component . expect ("<message xmlns='jabber:client' from=\"news@muc.localhost\" to=\"louise@localhost/166\" type=\"groupchat\"/>" . parse :: < ScanElement > () . unwrap () . apply_context (& context)) ; component . expect ("<iq xmlns='jabber:client' from=\"news@muc.localhost/louise\" id=\"foo\" to=\"louise@localhost/166\"/>" . parse :: < ScanElement > () . unwrap () . apply_context (& context)) ; println ! ("FOO: {component:?}") ; handle_stanza (& mut component , & mut rooms) . await . unwrap () ; }
|
Loading…
Reference in a new issue