summaryrefslogtreecommitdiff
path: root/tests/random.rs
blob: d92ce799407ac78270a3061cf54fd13db897b32e (plain)
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();
}