use std::io;
use structio::beve::header;
use structio::{Complex, ErrorCode, StreamError};
struct Dribble<'a>(&'a [u8]);
impl io::Read for Dribble<'_> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if self.0.is_empty() || out.is_empty() {
return Ok(0);
}
out[0] = self.0[0];
self.0 = &self.0[1..];
Ok(1)
}
}
fn code(e: &StreamError) -> ErrorCode {
e.as_parse()
.unwrap_or_else(|| panic!("expected a parse failure, got {e}"))
.code
}
macro_rules! check {
($($t:ty),* $(,)?) => {$({
for len in [0usize, 1, 2, 7, 64, 1000] {
let values: Vec<$t> = (0..len).map(|i| i as $t).collect();
for doc in [structio::to_beve(&values), structio::to_beve_aligned(&values)] {
let whole: Vec<$t> = structio::from_beve_reader_array(&doc[..]).unwrap();
assert_eq!(whole, values, "{} len {len}", stringify!($t));
let dribbled: Vec<$t> =
structio::from_beve_reader_array(Dribble(&doc)).unwrap();
assert_eq!(dribbled, values, "{} len {len} dribbled", stringify!($t));
assert_eq!(
structio::from_beve::<Vec<$t>>(&doc).unwrap(),
values,
"{} len {len} batch",
stringify!($t)
);
}
}
})*};
}
#[test]
fn it_agrees_with_the_batch_reader_for_every_numeric_type() {
check!(
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64
);
}
#[test]
fn awkward_float_payloads_survive() {
let values = vec![
0.0f64,
-0.0,
f64::MIN,
f64::MAX,
f64::EPSILON,
f64::INFINITY,
f64::NEG_INFINITY,
];
let doc = structio::to_beve(&values);
let got: Vec<f64> = structio::from_beve_reader_array(&doc[..]).unwrap();
assert_eq!(got, values);
let doc = structio::to_beve(&vec![f64::NAN]);
let got: Vec<f64> = structio::from_beve_reader_array(&doc[..]).unwrap();
assert!(got[0].is_nan());
}
#[test]
#[cfg_attr(miri, ignore)]
fn an_array_longer_than_one_chunk_reads_whole() {
let values: Vec<f64> = (0..400_000).map(|i| i as f64 * 0.5).collect();
let doc = structio::to_beve(&values);
assert!(doc.len() > 3 * 1024 * 1024);
let got: Vec<f64> = structio::from_beve_reader_array(&doc[..]).unwrap();
assert_eq!(got, values);
}
#[test]
fn reading_into_a_vector_reuses_its_allocation() {
let first = structio::to_beve(&(0..1000).map(|i| i as f64).collect::<Vec<f64>>());
let second = structio::to_beve(&vec![9.5f64; 100]);
let mut out: Vec<f64> = Vec::new();
structio::read_beve_array_into(&mut out, &first[..]).unwrap();
assert_eq!(out.len(), 1000);
let (buffer, capacity) = (out.as_ptr(), out.capacity());
structio::read_beve_array_into(&mut out, &second[..]).unwrap();
assert_eq!(out, vec![9.5f64; 100]);
assert_eq!(out.as_ptr(), buffer, "the buffer moved");
assert_eq!(out.capacity(), capacity, "the buffer was resized");
}
#[test]
fn a_failed_read_empties_the_destination() {
let doc = structio::to_beve(&(0..1000).map(|i| i as f64).collect::<Vec<f64>>());
let mut out: Vec<f64> = vec![1.0, 2.0, 3.0];
let err = structio::read_beve_array_into(&mut out, &doc[..doc.len() - 8]).unwrap_err();
assert_eq!(code(&err), ErrorCode::UnexpectedEnd);
assert!(out.is_empty());
}
#[test]
fn a_different_element_type_is_an_error_not_a_conversion() {
let doc = structio::to_beve(&vec![1.0f32, 2.0, 3.0]);
assert_eq!(
structio::from_beve::<Vec<f64>>(&doc).unwrap(),
[1.0f64, 2.0, 3.0]
);
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
let doc = structio::to_beve(&vec![1i32, 2, 3]);
let err = structio::from_beve_reader_array::<u32, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
let doc = structio::to_beve(&vec![1.0f32, 2.0, 3.0]);
let mut docs = structio::beve::Documents::array(&doc[..]);
let got: Vec<f64> = docs.iter::<f64>().map(Result::unwrap).collect();
assert_eq!(got, [1.0, 2.0, 3.0]);
}
#[test]
fn the_other_typed_arrays_are_not_numeric_blocks() {
for doc in [
structio::to_beve(&vec![true, false, true]),
structio::to_beve(&vec!["a".to_string(), "b".into()]),
] {
let err = structio::from_beve_reader_array::<u8, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch, "{doc:02x?}");
}
}
#[test]
fn a_value_that_is_not_an_array_says_so() {
#[derive(Default)]
struct Rec {
samples: Vec<f64>,
}
structio::object!(Rec { samples });
for doc in [
structio::to_beve(&7u32),
structio::to_beve(&"text"),
structio::to_beve(&Rec {
samples: vec![1.0, 2.0],
}),
structio::to_beve(&vec![vec![1.0f64], vec![2.0]]),
] {
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ExpectedArray, "{doc:02x?}");
}
let err = structio::from_beve_reader_array::<f64, _>(&[][..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::UnexpectedEnd);
}
#[test]
fn bytes_after_the_array_are_trailing_content() {
let mut doc = structio::to_beve(&vec![1.0f64, 2.0, 3.0]);
doc.extend_from_slice(&structio::to_beve(&7u8));
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::TrailingContent);
}
#[test]
fn every_truncation_fails() {
let values: Vec<f64> = (0..64).map(|i| i as f64).collect();
for doc in [
structio::to_beve(&values),
structio::to_beve_aligned(&values),
] {
for cut in 0..doc.len() {
let err = structio::from_beve_reader_array::<f64, _>(&doc[..cut]).unwrap_err();
assert_eq!(code(&err), ErrorCode::UnexpectedEnd, "cut at {cut}");
}
}
}
#[test]
fn a_count_the_stream_does_not_deliver_fails_rather_than_reserving() {
let mut doc = vec![header::array_of(header::CAT_FLOAT, 3)];
let mut size = [0u8; 8];
let used = header::encode_size(4_000_000_000, &mut size);
doc.extend_from_slice(&size[..used]);
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::UnexpectedEnd);
doc.extend_from_slice(&[0u8; 64]);
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::UnexpectedEnd);
}
#[test]
fn the_aligned_form_reads_at_every_offset() {
let values: Vec<f64> = (0..16).map(|i| i as f64).collect();
for prefix in 0..24usize {
let mut doc = vec![0u8; prefix];
structio::append_beve_aligned(&values, &mut doc);
let got: Vec<f64> = structio::from_beve_reader_array(&doc[prefix..]).unwrap();
assert_eq!(got, values, "prefix {prefix}");
}
}
#[test]
fn an_io_failure_stays_an_io_failure() {
struct Broken;
impl io::Read for Broken {
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
Err(io::Error::other("no"))
}
}
let err = structio::from_beve_reader_array::<f64, _>(Broken).unwrap_err();
assert!(err.as_io().is_some(), "{err}");
}
#[test]
fn a_malformed_header_is_not_reported_as_the_wrong_element_type() {
let mut bad: Vec<Vec<u8>> = vec![
vec![header::array_of(header::CAT_FLOAT, 7), 0],
vec![header::array_of(header::CAT_SIGNED, 6), 0],
vec![header::array_of(header::CAT_OTHER, 5), 0],
];
for inner in [
header::number(header::CAT_FLOAT, 3),
header::array_of(header::CAT_FLOAT, 7),
header::ALIGNED_ARRAY,
] {
bad.push(vec![header::ALIGNED_ARRAY, inner, 0, 0]);
}
for doc in bad {
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::InvalidHeader, "{doc:02x?}");
assert_eq!(
structio::from_beve::<Vec<f64>>(&doc).unwrap_err().code,
ErrorCode::InvalidHeader,
"{doc:02x?} disagrees with the batch reader"
);
}
}
#[test]
fn the_aligned_form_checks_its_element_type_too() {
let doc = structio::to_beve_aligned(&vec![1.0f32, 2.0, 3.0]);
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
let doc = structio::to_beve_aligned(&vec![1u16, 2, 3]);
let err = structio::from_beve_reader_array::<i16, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
}
#[test]
fn a_generic_array_of_numbers_is_not_this_shape() {
let doc = structio::to_beve(&(1.0f64, 2u8));
assert_eq!(structio::from_beve::<Vec<f64>>(&doc).unwrap(), [1.0, 2.0]);
let err = structio::from_beve_reader_array::<f64, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ExpectedArray);
let mut docs = structio::beve::Documents::array(&doc[..]);
let got: Vec<f64> = docs.iter::<f64>().map(Result::unwrap).collect();
assert_eq!(got, [1.0, 2.0]);
}
#[test]
fn trailing_content_empties_the_destination_too() {
let mut doc = structio::to_beve(&vec![1.0f64, 2.0, 3.0]);
doc.extend_from_slice(&structio::to_beve(&7u8));
let mut out: Vec<f64> = vec![9.0; 4];
let err = structio::read_beve_array_into(&mut out, &doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::TrailingContent);
assert!(out.is_empty());
}
#[test]
fn a_complex_array_reads_as_a_block() {
macro_rules! check_complex {
($($t:ty),* $(,)?) => {$({
for len in [0usize, 1, 2, 7, 64, 1000] {
let values: Vec<Complex<$t>> = (0..len)
.map(|i| Complex { re: i as $t, im: (i + 1) as $t })
.collect();
for doc in [structio::to_beve(&values), structio::to_beve_aligned(&values)] {
let whole: Vec<Complex<$t>> =
structio::from_beve_reader_array(&doc[..]).unwrap();
assert_eq!(whole, values, "Complex<{}> len {len}", stringify!($t));
let dribbled: Vec<Complex<$t>> =
structio::from_beve_reader_array(Dribble(&doc)).unwrap();
assert_eq!(
dribbled, values,
"Complex<{}> len {len} dribbled", stringify!($t)
);
assert_eq!(
structio::from_beve::<Vec<Complex<$t>>>(&doc).unwrap(),
values,
"Complex<{}> len {len} batch",
stringify!($t)
);
}
}
})*};
}
check_complex!(f32, f64, i8, i16, i32, i64, u8, u16, u32, u64);
}
#[test]
fn each_component_keeps_its_own_byte_order() {
let values = vec![
Complex {
re: 1.0f64,
im: 2.0,
},
Complex {
re: f64::MIN,
im: f64::MAX,
},
Complex {
re: -0.0,
im: f64::EPSILON,
},
Complex {
re: f64::INFINITY,
im: f64::NEG_INFINITY,
},
];
let doc = structio::to_beve(&values);
let streamed: Vec<Complex<f64>> = structio::from_beve_reader_array(&doc[..]).unwrap();
assert_eq!(streamed, values);
assert_eq!(
streamed,
structio::from_beve::<Vec<Complex<f64>>>(&doc).unwrap()
);
}
#[test]
fn a_complex_array_is_not_a_numeric_array_of_its_components() {
let complex = structio::to_beve(&vec![Complex {
re: 1.0f32,
im: 2.0,
}]);
let numeric = structio::to_beve(&vec![1.0f32, 2.0]);
let err = structio::from_beve_reader_array::<f32, _>(&complex[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
let err = structio::from_beve_reader_array::<Complex<f32>, _>(&numeric[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
let err = structio::from_beve_reader_array::<Complex<f64>, _>(&complex[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
}
#[test]
fn only_the_run_forms_of_a_complex_value_are_arrays() {
let lone = structio::to_beve(&Complex {
re: 1.0f64,
im: 2.0,
});
let err = structio::from_beve_reader_array::<Complex<f64>, _>(&lone[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::InvalidHeader);
let mut doc = structio::to_beve(&vec![Complex {
re: 1.0f64,
im: 2.0,
}]);
for form in [3u8, 4, 5, 6, 7] {
doc[1] = header::complex_class(header::CAT_FLOAT, 3, form);
let err = structio::from_beve_reader_array::<Complex<f64>, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::InvalidHeader, "form {form}");
}
doc[1] = header::complex_class(header::CAT_OTHER, 3, header::COMPLEX_MANY);
let err = structio::from_beve_reader_array::<Complex<f64>, _>(&doc[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::InvalidHeader);
}
#[test]
fn an_aligned_complex_array_holds_only_the_array_it_may() {
let good = structio::to_beve_aligned(&vec![
Complex {
re: 1.0f64,
im: 2.0,
},
Complex { re: 3.0, im: 4.0 },
]);
assert_eq!(&good[..6], [0x1e, 0x62, 0x5c, 0x64, 0x10, 0x02]);
let read = |doc: &[u8]| {
let e = structio::from_beve_reader_array::<Complex<f64>, _>(doc).unwrap_err();
let e = e.as_parse().copied().expect("a parse failure");
(e.code, e.index)
};
let with = |at: usize, byte: u8| {
let mut doc = good.clone();
doc[at] = byte;
doc
};
let refused = ErrorCode::InvalidHeader;
let f64s = header::array_of(header::CAT_FLOAT, 3);
assert_eq!(read(&with(2, f64s)), (refused, 2), "not aligned");
let f32s = header::array_of(header::CAT_FLOAT, 2);
assert_eq!(read(&with(3, f32s)), (refused, 3), "another element type");
assert_eq!(read(&with(4, 3 << 2)), (refused, 4), "an odd count");
let err = structio::from_beve_reader_array::<Complex<f32>, _>(&good[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
let err = structio::from_beve_reader_array::<f64, _>(&good[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::ElementTypeMismatch);
}
#[test]
fn a_complex_array_is_bounded_like_a_numeric_one() {
let values: Vec<Complex<f64>> = (0..8)
.map(|i| Complex {
re: i as f64,
im: 0.0,
})
.collect();
let doc = structio::to_beve(&values);
let mut out: Vec<Complex<f64>> = vec![Complex { re: 9.0, im: 9.0 }; 4];
let err = structio::read_beve_array_into(&mut out, &doc[..doc.len() - 8]).unwrap_err();
assert_eq!(code(&err), ErrorCode::UnexpectedEnd);
assert!(out.is_empty());
let mut over = doc.clone();
over.extend_from_slice(&structio::to_beve(&7u8));
let mut out: Vec<Complex<f64>> = vec![Complex { re: 9.0, im: 9.0 }; 4];
let err = structio::read_beve_array_into(&mut out, &over[..]).unwrap_err();
assert_eq!(code(&err), ErrorCode::TrailingContent);
assert!(out.is_empty());
}