use arbitrary::{Arbitrary, Unstructured};
use oxixml_xml::events::Event;
use oxixml_xml::events::{
BytesCData, BytesDecl, BytesEnd, BytesPI, BytesRef, BytesStart, BytesText,
};
fn pseudo_random_bytes(seed: u32, len: usize) -> Vec<u8> {
let mut x = seed.wrapping_mul(2_654_435_761) ^ 0xdead_beef;
(0..len)
.map(|_| {
x = x.wrapping_mul(1_103_515_245).wrapping_add(12345);
(x >> 16) as u8
})
.collect()
}
#[test]
fn bytes_start_arbitrary_accepts_an_alphanumeric_buffer() {
let data: Vec<u8> = (0..64u8).map(|i| b'a' + (i % 26)).collect();
let mut u = Unstructured::new(&data);
let start = BytesStart::arbitrary(&mut u).expect("an all-alphanumeric buffer must be accepted");
let name = start.name();
let name = std::str::from_utf8(name.as_ref()).expect("valid UTF-8 name");
assert!(!name.is_empty());
assert!(name.chars().all(char::is_alphanumeric));
}
#[test]
fn bytes_start_arbitrary_rejects_an_empty_buffer() {
let mut u = Unstructured::new(&[]);
assert_eq!(
BytesStart::arbitrary(&mut u),
Err(arbitrary::Error::IncorrectFormat),
);
}
#[test]
fn bytes_start_arbitrary_rejects_a_non_alphanumeric_buffer() {
let data = vec![b'<'; 32];
let mut u = Unstructured::new(&data);
assert_eq!(
BytesStart::arbitrary(&mut u),
Err(arbitrary::Error::IncorrectFormat),
);
}
#[test]
fn bytes_start_arbitrary_invariant_holds_across_many_buffers() {
let mut ok = 0;
let mut err = 0;
for seed in 0u32..2000 {
let data = pseudo_random_bytes(seed, 40);
let mut u = Unstructured::new(&data);
match BytesStart::arbitrary(&mut u) {
Ok(start) => {
ok += 1;
let name = start.name();
let name = std::str::from_utf8(name.as_ref())
.unwrap_or_else(|e| panic!("seed={seed}: non-UTF-8 name: {e}"));
assert!(!name.is_empty(), "seed={seed}");
assert!(
name.chars().all(char::is_alphanumeric),
"seed={seed} name={name:?}",
);
}
Err(arbitrary::Error::IncorrectFormat) => err += 1,
Err(other) => panic!("seed={seed}: unexpected error {other:?}"),
}
}
assert!(ok > 0, "expected at least one accepted buffer in the sweep");
assert!(
err > 0,
"expected at least one rejected buffer in the sweep"
);
}
#[test]
fn unvalidated_bytes_types_always_succeed() {
for seed in 0u32..64 {
let data = pseudo_random_bytes(seed, 40);
assert!(BytesEnd::arbitrary(&mut Unstructured::new(&data)).is_ok());
assert!(BytesCData::arbitrary(&mut Unstructured::new(&data)).is_ok());
assert!(BytesPI::arbitrary(&mut Unstructured::new(&data)).is_ok());
assert!(BytesRef::arbitrary(&mut Unstructured::new(&data)).is_ok());
assert!(BytesDecl::arbitrary(&mut Unstructured::new(&data)).is_ok());
}
}
#[test]
fn bytes_text_arbitrary_rejects_non_alphanumeric_but_allows_empty() {
let punctuation = vec![b'&'; 16];
assert_eq!(
BytesText::arbitrary(&mut Unstructured::new(&punctuation)),
Err(arbitrary::Error::IncorrectFormat),
);
assert!(BytesText::arbitrary(&mut Unstructured::new(&[])).is_ok());
}
#[test]
fn event_arbitrary_covers_every_variant_without_panicking() {
use std::collections::BTreeSet;
let mut kinds = BTreeSet::new();
let mut produced_any = false;
for seed in 0u32..2000 {
let data = pseudo_random_bytes(seed, 64);
let mut u = Unstructured::new(&data);
if let Ok(event) = Event::arbitrary(&mut u) {
produced_any = true;
kinds.insert(match event {
Event::Start(_) => "Start",
Event::End(_) => "End",
Event::Empty(_) => "Empty",
Event::Text(_) => "Text",
Event::CData(_) => "CData",
Event::Comment(_) => "Comment",
Event::Decl(_) => "Decl",
Event::PI(_) => "PI",
Event::DocType(_) => "DocType",
Event::GeneralRef(_) => "GeneralRef",
Event::Eof => "Eof",
});
}
}
assert!(produced_any);
assert_eq!(
kinds,
BTreeSet::from([
"Start",
"End",
"Empty",
"Text",
"CData",
"Comment",
"Decl",
"PI",
"DocType",
"GeneralRef",
"Eof",
]),
"every Event variant must be reachable through Event::arbitrary",
);
}
#[test]
fn event_size_hint_is_well_formed() {
let (lower, upper) = Event::size_hint(0);
if let Some(upper) = upper {
assert!(lower <= upper);
}
}