sbd_o_bahn_server_tester/
lib.rs

1#![deny(missing_docs)]
2// uhhh... clippy...
3#![allow(clippy::manual_async_fn)]
4
5//! Test suite for sbd server compliance.
6//!
7//! The command supplied to the run function must:
8//! - Print on stdout: `CMD:READY` when it is ready to receive commands.
9//! - Listen on stdin for: `CMD:START`, and on receiving, close any running
10//!   sbd servers, start a new one on a random ephemeral port (plain text,
11//!   not TLS), then print out `CMD:START:<addr-list>` where addr-list is
12//!   a json representation of an array of addresses (ip:port) e.g.
13//!   `CMD:START:["127.0.0.1:44556", "[::1]:44557"]`.
14
15use std::io::Result;
16use tokio::io::AsyncBufReadExt;
17
18mod it;
19
20/// Results of the test suite run.
21#[derive(Debug, Default)]
22pub struct Report {
23    /// The names of tests that pass.
24    pub passed: Vec<String>,
25
26    /// Failed tests: (Name, Notes).
27    pub failed: Vec<(String, String)>,
28}
29
30/// Run the test suite.
31pub async fn run<S: AsRef<std::ffi::OsStr>>(cmd: Vec<S>) -> Report {
32    let mut server = Server::spawn(cmd).await.unwrap();
33
34    it::exec_all(&mut server).await
35}
36
37struct Server {
38    _child: tokio::process::Child,
39    stdin: tokio::process::ChildStdin,
40    stdout: tokio::io::Lines<tokio::io::BufReader<tokio::process::ChildStdout>>,
41}
42
43impl Server {
44    pub async fn spawn<S: AsRef<std::ffi::OsStr>>(
45        mut args: Vec<S>,
46    ) -> Result<Self> {
47        let prog = args.remove(0);
48        let mut cmd = tokio::process::Command::new(prog);
49        cmd.args(args)
50            .kill_on_drop(true)
51            .stdin(std::process::Stdio::piped())
52            .stdout(std::process::Stdio::piped());
53
54        println!("RUNNING {cmd:?}");
55        let mut child = cmd.spawn()?;
56        let stdin = child.stdin.take().unwrap();
57
58        let mut stdout =
59            tokio::io::BufReader::new(child.stdout.take().unwrap()).lines();
60
61        if let Some(line) = stdout.next_line().await? {
62            if line != "CMD/READY" {
63                panic!("unexpected: {line}");
64            }
65        } else {
66            panic!("no stdout");
67        }
68
69        println!("GOT CMD/READY");
70
71        Ok(Self {
72            _child: child,
73            stdin,
74            stdout,
75        })
76    }
77
78    pub async fn start(&mut self) -> Vec<String> {
79        use tokio::io::AsyncWriteExt;
80        self.stdin.write_all(b"CMD/START\n").await.unwrap();
81        self.stdin.flush().await.unwrap();
82        let line = self.stdout.next_line().await.unwrap().unwrap();
83        if !line.starts_with("CMD/START/") {
84            panic!("unexpected: {line}");
85        }
86        line.split('/').skip(2).map(|s| s.to_string()).collect()
87    }
88}