use std::{
collections::HashMap,
fs,
io::{ErrorKind, Write, stdout},
panic::{catch_unwind, resume_unwind},
time::{Duration, SystemTime},
};
use rand::{prelude::*, random, rng};
use sbof::{de::from_bytes, ser::to_bytes, *};
use serde::*;
#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum TestEnum {
Unit,
Newtype(u8),
Tuple(u8, u16),
Struct { field1: i32, field2: char },
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct TestStruct {
character: char,
unsigned8: u8,
integer8: i8,
unsigned16: u16,
integer16: i16,
unsigned32: u32,
integer32: i32,
unsigned64: u64,
integer64: i64,
unsigned128: u128,
integer128: i128,
float32: f32,
float64: f64,
vector: Vec<u8>,
map: HashMap<String, TestEnum>,
tuple: (u8, u16, u8),
enumeration: TestEnum,
string: String,
}
impl TestStruct {
fn random() -> Self {
let mut rng = rng();
TestStruct {
character: rng.random(),
unsigned8: rng.random(),
integer8: rng.random(),
unsigned16: rng.random(),
integer16: rng.random(),
unsigned32: rng.random(),
integer32: rng.random(),
unsigned64: rng.random(),
integer64: rng.random(),
unsigned128: rng.random(),
integer128: rng.random(),
float32: rng.random(),
float64: rng.random(),
vector: (0..random::<u8>() as usize).map(|_| rng.random()).collect(),
map: {
let len = 0..random::<u8>() as usize;
let entries = len.clone().map(|_| {
(
{
(0..random::<u8>() as usize)
.map(|_| rng.random::<char>())
.collect::<String>()
},
match random::<u8>() % 4 {
0 => TestEnum::Unit,
1 => TestEnum::Newtype(rng.random()),
2 => TestEnum::Tuple(rng.random(), rng.random()),
_ => TestEnum::Struct {
field1: rng.random(),
field2: rng.random(),
},
},
)
});
let mut map = HashMap::new();
for (k, v) in entries {
map.insert(k, v);
}
map
},
tuple: (rng.random(), rng.random(), rng.random()),
enumeration: match random::<u8>() % 4 {
0 => TestEnum::Unit,
1 => TestEnum::Newtype(rng.random()),
2 => TestEnum::Tuple(rng.random(), rng.random()),
_ => TestEnum::Struct {
field1: rng.random(),
field2: rng.random(),
},
},
string: (0..random::<u8>() as usize)
.map(|_| rng.random::<char>())
.collect(),
}
}
}
#[test]
fn run_repeat() -> Result<()> {
let start = SystemTime::now();
const QUIT_DURATION: Duration = Duration::from_secs(10);
loop {
all()?;
let duration = SystemTime::now()
.duration_since(start)
.expect("system time error");
if duration >= QUIT_DURATION {
break;
}
}
Ok(())
}
fn all() -> Result<()> {
let test_struct = if fs::exists("failed_case.json")? {
println!("loading failed_case.json...");
let str = fs::read_to_string("failed_case.json")?;
let test_struct: TestStruct =
serde_json::from_str(&str).expect("could not parse failed_case.json");
println!("done\n");
test_struct
} else {
TestStruct::random()
};
let bytes = to_bytes(&test_struct)?;
println!("serialization complete");
let unwind = catch_unwind(|| from_bytes::<TestStruct>(&bytes));
let deser = match unwind {
Err(e) => {
failed_case(&test_struct, &bytes)?;
resume_unwind(e)
}
Ok(Err(e)) => {
failed_case(&test_struct, &bytes)?;
stdout().flush()?;
Err(e)?
}
Ok(Ok(deser)) => deser,
};
if test_struct != deser {
failed_case_eq(&test_struct, &deser, &bytes)?;
return Err(Error::Io(std::io::Error::new(
ErrorKind::InvalidData,
"failed",
)));
} else {
println!("success!");
let _ = fs::remove_file("failed_case.bin");
let _ = fs::remove_file("failed_case.json");
let _ = fs::remove_file("deser.json");
}
Ok(())
}
fn failed_case(test_struct: &TestStruct, bytes: &[u8]) -> Result<()> {
fs::write(
"failed_case.json",
serde_json::to_string_pretty(test_struct).expect("failed to serialize to JSON"),
)?;
println!("wrote input to failed_case.json");
fs::write("failed_case.bin", bytes)?;
println!("wrote serialized input to failed_case.bin");
Ok(())
}
fn failed_case_eq(test_struct: &TestStruct, deser: &TestStruct, bytes: &[u8]) -> Result<()> {
println!("\n\nfound bad input: {test_struct:?}");
fs::write(
"failed_case.json",
serde_json::to_string_pretty(test_struct).expect("failed to serialize to JSON"),
)?;
println!("wrote input to failed_case.json");
fs::write("failed_case.bin", bytes)?;
println!("wrote serialized input to failed_case.bin");
fs::write(
"deser.json",
serde_json::to_string_pretty(deser).expect("failed to serialize to JSON"),
)?;
println!("wrote deserialized output to deser.json");
println!("use `diff failed_case.json deser.json` to find differences");
Ok(())
}