1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
use libtest_mimic::{Arguments, Trial};
use mt_net::{generate_random::GenerateRandomVariant, rand, ToCltPkt, ToSrvPkt};
use mt_ser::{DefCfg, MtDeserialize, MtSerialize};
use std::{
error::Error,
fmt::Debug,
io::{Cursor, Write},
path::Path,
process::{Command, Stdio},
};
fn test_reserialize<'a, T>(type_name: &'static str, reserialize: &Path) -> Vec<Trial>
where
T: MtSerialize + MtDeserialize + GenerateRandomVariant + PartialEq + Debug,
{
(0..T::num_variants())
.map(move |i| {
let pkt_name = format!("{type_name}::{}", T::variant_name(i));
let reserialize = reserialize.as_os_str().to_os_string();
Trial::test(pkt_name.clone(), move || {
let mut rng = rand::thread_rng();
let mut printed_stderr = false;
for _ in 0..100 {
// use buffered IO instead of directly reading from the process
// this enables printing out payloads for debugging
let input = T::generate_random_variant(&mut rng, i);
let mut writer = Vec::new();
input
.mt_serialize::<DefCfg>(&mut writer)
.map_err(|e| format!("serialize error: {e}\ninput: {input:?}"))?;
let mut child = Command::new(&reserialize)
.arg(type_name)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn reserialize");
let mut stdin = child.stdin.take().expect("failed to open stdin");
let payload = writer.clone();
std::thread::spawn(move || {
stdin.write_all(&payload).expect("failed to write to stdin");
});
let command_out = child.wait_with_output().expect("failed to read stdout");
let stderr = String::from_utf8_lossy(&command_out.stderr);
if command_out.status.success() {
if stderr.len() > 0 && !printed_stderr {
printed_stderr = true;
eprintln!("stderr for {pkt_name}: {stderr}");
}
} else {
return Err(format!(
"reserialize returned failure\n\
input: {input:?}\n\
input payload: {writer:?}\n\
stderr: {stderr}"
)
.into());
}
let mut reader = Cursor::new(command_out.stdout);
let output = T::mt_deserialize::<DefCfg>(&mut reader).map_err(|e| {
format!(
"deserialize error: {e}\n\
input: {input:?}\n\
input payload: {writer:?}\n\
output payload: {:?}\n\
stderr: {stderr}",
reader.get_ref()
)
})?;
if input != output {
return Err(format!(
"output does not match input\n\
input: {input:?}\n\
output: {output:?}\n\
input payload: {writer:?}\n\
output payload: {:?}\n\
stderr: {stderr}",
reader.get_ref(),
)
.into());
}
}
Ok(())
})
.with_kind("random")
})
.collect()
}
fn main() -> Result<(), Box<dyn Error>> {
let reserialize = Path::new(file!()).with_file_name("reserialize/reserialize");
if !reserialize.exists() {
if !Command::new("go")
.arg("build")
.current_dir(reserialize.parent().unwrap())
.spawn()
.expect("go is required for random tests")
.wait()
.expect("go build didn't run")
.success()
{
panic!("go build failed");
}
}
let args = Arguments::from_args();
let mut tests = Vec::new();
tests.extend(test_reserialize::<ToSrvPkt>("ToSrvPkt", &reserialize));
tests.extend(test_reserialize::<ToCltPkt>("ToCltPkt", &reserialize));
libtest_mimic::run(&args, tests).exit();
}
|