rustainers 0.15.1

A simple, opinionated way to run containers for tests.
Documentation
//! Contains test images.

#![allow(clippy::expect_used)]

use std::time::SystemTime;

use assert2::{check, let_assert};
use rstest::rstest;
use tokio::task::JoinSet;
use tracing::{debug, info};

use rustainers::images::{GenericImage, Minio, Mongo, Mosquitto, Nats, Postgres, Redis};
use rustainers::runner::{RunOption, Runner};
use rustainers::{ExposedPort, ImageName, Port, WaitStrategy};

mod common;
pub use self::common::*;

#[rstest]
#[tokio::test]
async fn test_image_postgres(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Postgres::default();
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    container.url().await?;
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_postgres_build_config(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Postgres::default().with_port(ExposedPort::fixed(Port::new(5432), Port::new(5432)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.config().await.expect("config");
    check!(result == "host=127.0.0.1 user=postgres password=passwd port=5432 dbname=postgres");

    let result = container.url().await.expect("url");
    check!(result == "postgresql://postgres:passwd@127.0.0.1:5432/postgres");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_image_minio(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Minio::default();
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    container.endpoint().await?;
    container.console_endpoint().await?;
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_minio_endpoint(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Minio::default().with_port(ExposedPort::fixed(Port::new(9000), Port::new(9124)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.endpoint().await;
    let_assert!(Ok(endpoint) = result);
    check!(endpoint == "http://127.0.0.1:9124");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_image_redis(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Redis::default();
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    container.endpoint().await?;
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_redis_endpoint(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Redis::default().with_port(ExposedPort::fixed(Port::new(6379), Port::new(9125)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.endpoint().await;
    let_assert!(Ok(endpoint) = result);
    check!(endpoint == "redis://127.0.0.1:9125");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_image_nats(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Nats::default();
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    container.client_endpoint().await?;
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_nats_client_endpoint(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder()
        .with_remove(true)
        .with_name("test_nats_client_endpoint")
        .build();
    let image =
        Nats::default().with_client_port(ExposedPort::fixed(Port::new(8333), Port::new(8333)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.client_endpoint().await;
    let_assert!(Ok(endpoint) = result);
    check!(endpoint == "nats://127.0.0.1:8333");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_nats_monitoring_endpoint(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder()
        .with_remove(true)
        .with_name("test_nats_monitoring_endpoint")
        .build();
    let image =
        Nats::default().with_monitoring_port(ExposedPort::fixed(Port::new(8666), Port::new(8666)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.monitoring_endpoint().await;
    let_assert!(Ok(endpoint) = result);
    check!(endpoint == "http://127.0.0.1:8666");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_nats_cluster_endpoint(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder()
        .with_name("test_nats_cluster_endpoint")
        .with_remove(true)
        .build();
    let image =
        Nats::default().with_cluster_port(ExposedPort::fixed(Port::new(8777), Port::new(8777)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.cluster_endpoint().await;
    let_assert!(Ok(endpoint) = result);
    check!(endpoint == "nats-route://127.0.0.1:8777");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_image_mongo(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Mongo::default();
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    container.endpoint().await?;
    Ok(())
}
#[rstest]
#[tokio::test]
async fn test_mongo_endpoint(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image = Mongo::default().with_port(ExposedPort::fixed(Port::new(27017), Port::new(9126)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.endpoint().await;
    let_assert!(Ok(endpoint) = result);
    check!(endpoint == "mongodb://127.0.0.1:9126");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_run_in_multiple_tasks(runner: &Runner) -> anyhow::Result<()> {
    if let Runner::Podman(_) = &runner {
        // Work with docker, but fail with podman
        // FIXME find a solution
        return Ok(());
    }
    let start = SystemTime::now();
    let mut set = JoinSet::new();
    let size = 20;

    for id in 0..size {
        let img = Redis::default();
        let runner = runner.clone();
        set.spawn(async move {
            let container = runner.start(img).await.expect("container started");
            (id, container)
        });
    }

    // wait all
    let mut finished = vec![false; size];
    #[allow(clippy::indexing_slicing)]
    while let Some(Ok((id, container))) = set.join_next().await {
        info!("Container #{id} {container:#?}");
        finished[id] = true;
    }
    let duration = start.elapsed()?;
    for (id, value) in finished.iter().enumerate() {
        check!(*value == true, "Task #{id} not finished");
    }
    info!("Took {}s", duration.as_secs_f32());

    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_mosquitto_endpoint(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let image =
        Mosquitto::default().with_port(ExposedPort::fixed(Port::new(6379), Port::new(9127)));
    let container = runner.start_with_options(image, options).await?;
    debug!("Started {container}");

    let result = container.endpoint().await;
    let_assert!(Ok(endpoint) = result);
    check!(endpoint == "mqtt://127.0.0.1:9127");
    Ok(())
}

#[rstest]
#[tokio::test]
async fn test_generic_image(runner: &Runner) -> anyhow::Result<()> {
    let options = RunOption::builder().with_remove(true).build();
    let name = ImageName::new("docker.io/nginx");
    let mut nginx = GenericImage::new(name);
    let container_port = 80;
    nginx.add_port_mapping(container_port);
    nginx.set_wait_strategy(WaitStrategy::http("/"));

    let container = runner.start_with_options(nginx, options).await?;
    debug!("Started {container}");

    let host_port = container.host_port(container_port).await;
    let_assert!(Ok(_) = host_port);

    Ok(())
}