rtest 0.2.2

integration test building framework
Documentation
use std::{fmt::Display, time::Duration};

use rtest::{Context, TestError};
use rtest_derive::{rtest, run, Resource};
use shop::{ItemId, UserId};
use thiserror::Error;

mod carts;
mod items;
mod multiple;
mod shop;
mod users;

#[derive(Debug, Error)]
pub enum WebserverSetupError {
    #[error("cant start webserver: {0}")]
    Start(std::io::Error),
}

impl TestError for WebserverSetupError {}

#[derive(Debug, Error)]
pub enum WebserverTestError {
    #[error("reqwest sending error: {0}")]
    Reqwest(reqwest::Error),
    #[error("response parsing error: {0}")]
    Response(reqwest::Error),
}

impl TestError for WebserverTestError {}

#[derive(Resource)]
struct Webserver {
    child: std::process::Child,
    port:  u16,
}

impl Webserver {
    fn req<T: Display>(&self, method: reqwest::Method, end_url: T) -> reqwest::RequestBuilder {
        let client = reqwest::Client::new();
        client.request(method, format!("http://localhost:{}/{}", self.port, end_url))
    }
}

/// When the Webserver is tainted and droped, make sure to destroy the process
impl Drop for Webserver {
    fn drop(&mut self) {
        std::thread::sleep(Duration::from_millis(200));
        self.child.kill().unwrap();
    }
}

#[rtest]
fn start_server() -> Result<Webserver, WebserverSetupError> {
    use rand::Rng;
    let mut rng = rand::thread_rng();
    let args: Vec<String> = std::env::args().collect();
    let port = 3000 + rng.gen::<u8>() as u16;
    let child = std::process::Command::new(&args[0])
        .arg("webserver")
        .arg(port.to_string())
        .spawn()
        .map_err(WebserverSetupError::Start)?;
    std::thread::sleep(Duration::from_millis(500));
    Ok(Webserver { child, port })
}

#[derive(Resource)]
struct WebserverWithItems {
    webserver: Webserver,
    book_id:   ItemId,
    cookie_id: ItemId,
}

#[derive(Resource)]
struct WebserverWithUsers {
    webserver: Webserver,
    book_id:   ItemId,
    cookie_id: ItemId,
    ava_id:    UserId,
    john_id:   UserId,
}

pub fn main() -> std::process::ExitCode {
    let args: Vec<String> = std::env::args().collect();

    // Run webserver in process instead of tests
    if args.len() == 3 && args[1] == "webserver" {
        let runtime = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .unwrap();

        let port = args[2].parse::<u16>().expect("Invalid Port provided as 2nd argument");

        runtime.block_on(shop::run(port));
        0.into()
    } else {
        let runconfig = rtest::RunConfig {
            context:         Context::default(),
            runtime_builder: std::sync::Arc::new(Box::new(|| {
                let mut builder = tokio::runtime::Builder::new_current_thread();
                builder.enable_all();
                builder
            })),
        };
        run!(runconfig)
    }
}