#![allow(dead_code, missing_docs, unreachable_pub)]
use std::fmt::Debug;
use std::fs::File;
use std::io::{BufReader, Write};
use std::path::Path;
use std::process::{Command, Stdio};
use quick_xml::Reader;
use serde::Deserialize;
use xsd_parser::{
config::{Config, Generate, IdentTriple, OptimizerFlags, Schema},
generate,
generator::GeneratorFlags,
quick_xml::{DeserializeSync, ErrorReader, Event, IoReader, WithSerializer, Writer, XmlReader},
};
pub trait ConfigEx {
fn test_default() -> Self;
}
impl ConfigEx for Config {
fn test_default() -> Self {
let mut config = Config::default();
config.optimizer.flags |= OptimizerFlags::RESOLVE_TYPEDEFS;
config.generator.generate = Generate::Named;
config.generator.type_postfix.element_type = "Type".into();
config.generator.flags |= GeneratorFlags::FLATTEN_STRUCT_CONTENT;
config
}
}
pub fn generate_test<P1, P2>(input_xsd: P1, expected_rs: P2, mut config: Config)
where
P1: AsRef<Path>,
P2: AsRef<Path>,
{
config
.parser
.schemas
.push(Schema::File(input_xsd.as_ref().to_path_buf()));
let actual = generate(config).unwrap();
let actual = actual.to_string();
generate_test_validate(actual, expected_rs);
}
pub fn generate_test_validate<P1, P2>(actual: P1, expected_rs: P2)
where
P1: AsRef<str>,
P2: AsRef<Path>,
{
let actual = actual.as_ref();
let actual = fmt_code(actual);
#[cfg(not(feature = "update-expectations"))]
{
use std::fs::read_to_string;
use std::str::FromStr;
use proc_macro2::TokenStream;
use text_diff::print_diff;
let expected = read_to_string(expected_rs).unwrap();
let expected = TokenStream::from_str(&expected).unwrap();
let expected = fmt_code(&expected.to_string());
if expected != actual {
println!("=== expected:\n{expected}");
println!("=== actual:\n{actual}");
println!("=== diff:\n");
print_diff(&expected, &actual, "\n");
panic!("Code does not match!");
}
}
#[cfg(feature = "update-expectations")]
{
std::fs::write(expected_rs, actual).unwrap();
}
}
pub fn optimizer_test<P1, P2, P3, T>(
input_xsd: P1,
expected_0: P2,
expected_1: P3,
types: T,
flags: OptimizerFlags,
) where
P1: AsRef<Path>,
P2: AsRef<Path>,
P3: AsRef<Path>,
T: IntoIterator,
T::Item: Into<IdentTriple>,
{
let mut config = Config::test_default();
config.optimizer.flags = OptimizerFlags::RESOLVE_TYPEDEFS;
optimizer_test_with_config(input_xsd, expected_0, expected_1, types, flags, config);
}
pub fn optimizer_test_with_config<P1, P2, P3, T>(
input_xsd: P1,
expected_0: P2,
expected_1: P3,
types: T,
flags: OptimizerFlags,
mut config: Config,
) where
P1: AsRef<Path>,
P2: AsRef<Path>,
P3: AsRef<Path>,
T: IntoIterator,
T::Item: Into<IdentTriple>,
{
config
.parser
.schemas
.push(Schema::File(input_xsd.as_ref().to_path_buf()));
config.generator.generate = Generate::Types(types.into_iter().map(Into::into).collect());
config.generator.derive = Some(Vec::new());
config.generator.flags -= GeneratorFlags::USE_MODULES;
let input_xsd = input_xsd.as_ref();
generate_test(&input_xsd, expected_0, config.clone());
config.optimizer.flags |= flags;
generate_test(&input_xsd, expected_1, config.clone());
}
pub fn quick_xml_read_test<T, P>(path: P) -> T
where
P: AsRef<Path>,
T: DeserializeSync<'static, ErrorReader<IoReader<BufReader<File>>>>,
T::Error: Debug,
{
let reader = File::open(path).unwrap();
let reader = BufReader::new(reader);
let mut reader = IoReader::new(reader).with_error_info();
let ret = T::deserialize(&mut reader).unwrap();
ret
}
pub fn quick_xml_write_test<T, P>(value: &T, root: &str, path: P)
where
P: AsRef<Path>,
T: WithSerializer,
{
let mut content = Vec::new();
let mut writer = Writer::new(&mut content);
let mut actual = value.serializer(Some(root), true).unwrap();
let mut expected = Reader::from_file(path).unwrap();
let mut buffer = Vec::new();
let (actual, expected) = loop {
let expected = match expected.read_event_into(&mut buffer).unwrap() {
Event::Decl(_) => continue,
Event::Text(x) if x.trim_ascii_start().trim_ascii_end().is_empty() => continue,
expected => expected,
};
let actual = actual.next().transpose().unwrap().unwrap_or(Event::Eof);
match quick_xml_event_cmp(&actual, &expected) {
None => return,
Some(true) => (),
Some(false) => break (actual, expected),
}
writer.write_event(actual).unwrap();
};
println!("=== actual: {actual:?}");
println!("=== expected: {expected:?}");
let content = std::str::from_utf8(&content).unwrap();
println!("=== content: {content}");
panic!("Unexpected event")
}
pub fn serde_quick_xml_read_test<T, P>(path: P) -> T
where
P: AsRef<Path>,
T: for<'de> Deserialize<'de>,
{
let reader = File::open(path).unwrap();
let reader = BufReader::new(reader);
let ret = quick_xml::de::from_reader::<_, T>(reader).unwrap();
ret
}
pub fn serde_xml_rs_read_test<T, P>(path: P) -> T
where
P: AsRef<Path>,
T: for<'de> Deserialize<'de>,
{
let reader = File::open(path).unwrap();
let ret = serde_xml_rs::from_reader::<_, T>(reader).unwrap();
ret
}
fn fmt_code(s: &str) -> String {
let mut child = Command::new("rustfmt")
.arg("--emit")
.arg("stdout")
.arg("--edition")
.arg("2021")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to spawn rustfmt: {}", e))
.expect("Unable to spawn rustfmt command");
child
.stdin
.as_mut()
.unwrap()
.write_all(s.as_bytes())
.expect("Unable to write data to stdin");
let output = child
.wait_with_output()
.expect("Unable to get formatted output");
#[cfg(not(feature = "update-expectations"))]
if !output.status.success() {
panic!(
"rustfmt failed with status {}: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
)
}
#[cfg(feature = "update-expectations")]
if !output.status.success() {
return s.into();
}
String::from_utf8(output.stdout).expect("Invalid output")
}
fn quick_xml_event_cmp(a: &Event<'_>, b: &Event<'_>) -> Option<bool> {
match (a, b) {
(Event::Start(a), Event::Start(b)) | (Event::Empty(a), Event::Empty(b)) => {
let mut attribs_a = a.attributes().collect::<Result<Vec<_>, _>>().unwrap();
let mut attribs_b = b.attributes().collect::<Result<Vec<_>, _>>().unwrap();
attribs_a.sort_by(|a, b| (&a.key, &a.value).cmp(&(&b.key, &b.value)));
attribs_b.sort_by(|a, b| (&a.key, &a.value).cmp(&(&b.key, &b.value)));
Some(a.name() == b.name() && attribs_a == attribs_b)
}
(Event::End(a), Event::End(b)) => Some(a.name() == b.name()),
(Event::Text(a), Event::Text(b)) => Some(a.trim_ascii() == b.trim_ascii()),
(Event::Eof, Event::Eof) => None,
(_, _) => Some(false),
}
}