#![cfg(feature = "blocking")]
use std::time::Instant;
use testcontainers::{
core::{
client::ClientError,
error::TestcontainersError,
logs::{consumer::logging_consumer::LoggingConsumer, LogFrame},
wait::LogWaitStrategy,
BuildImageOptions, CmdWaitFor, CopyFromContainerError, ExecCommand, Host,
IntoContainerPort, WaitFor,
},
runners::{SyncBuilder, SyncRunner},
GenericBuildableImage, *,
};
fn get_server_image(msg: Option<WaitFor>) -> GenericImage {
let generic_image = GenericBuildableImage::new("simple_web_server", "latest")
.with_file(
std::fs::canonicalize("../testimages/simple_web_server").unwrap(),
".",
)
.build_image_with(BuildImageOptions::new())
.unwrap();
let msg = msg.unwrap_or(WaitFor::message_on_stdout("server is ready"));
generic_image.with_wait_for(msg)
}
#[derive(Debug, Default)]
pub struct HelloWorld;
impl Image for HelloWorld {
fn name(&self) -> &str {
"testcontainers/helloworld"
}
fn tag(&self) -> &str {
"1.3.0"
}
fn ready_conditions(&self) -> Vec<WaitFor> {
vec![WaitFor::message_on_stderr("Ready, listening on")]
}
}
#[test]
fn sync_can_run_hello_world() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let _container = HelloWorld.start()?;
Ok(())
}
#[cfg(feature = "http_wait_plain")]
#[test]
fn sync_wait_for_http() -> anyhow::Result<()> {
use crate::core::wait::HttpWaitStrategy;
let _ = pretty_env_logger::try_init();
use reqwest::StatusCode;
let waitfor_http_status =
WaitFor::http(HttpWaitStrategy::new("/").with_expected_status_code(StatusCode::OK));
let image = get_server_image(Some(waitfor_http_status)).with_exposed_port(80.tcp());
let _container = image.start()?;
Ok(())
}
#[test]
fn generic_image_with_custom_entrypoint() -> anyhow::Result<()> {
let generic = get_server_image(None);
let node = generic.start()?;
let port = node.get_host_port_ipv4(80.tcp())?;
assert_eq!(
"foo",
reqwest::blocking::get(format!("http://{}:{port}", node.get_host()?))?.text()?
);
let generic = get_server_image(None).with_entrypoint("./bar");
let node = generic.start()?;
let port = node.get_host_port_ipv4(80.tcp())?;
assert_eq!(
"bar",
reqwest::blocking::get(format!("http://{}:{port}", node.get_host()?))?.text()?
);
Ok(())
}
#[test]
fn generic_image_exposed_ports() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let target_port = 8080;
let generic_image = GenericBuildableImage::new("no_expose_port", "latest")
.with_file(std::fs::canonicalize("../testimages/no_expose_port")?, ".")
.build_image()?;
let generic_server = generic_image
.with_wait_for(WaitFor::message_on_stdout("listening on 0.0.0.0:8080"))
.with_exposed_port(target_port.tcp());
let node = generic_server.start()?;
let port = node.get_host_port_ipv4(target_port.tcp())?;
assert!(reqwest::blocking::get(format!("http://127.0.0.1:{port}"))?
.status()
.is_success());
Ok(())
}
#[test]
fn generic_image_running_with_extra_hosts_added() -> anyhow::Result<()> {
let server_1 = get_server_image(None);
let node = server_1.start()?;
let port = node.get_host_port_ipv4(80.tcp())?;
let msg = WaitFor::message_on_stdout("foo");
let server_2 = GenericImage::new("curlimages/curl", "latest")
.with_wait_for(msg)
.with_entrypoint("curl");
let server_2 = server_2
.with_cmd([format!("http://custom-host:{port}")])
.with_host("custom-host", Host::HostGateway);
server_2.start()?;
Ok(())
}
#[test]
fn generic_image_port_not_exposed() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let target_port = 8080;
let node = GenericBuildableImage::new("no_expose_port", "latest")
.with_file(std::fs::canonicalize("../testimages/no_expose_port")?, ".")
.build_image()?
.with_wait_for(WaitFor::message_on_stdout("listening on 0.0.0.0:8080"))
.start()?;
let res = node.get_host_port_ipv4(target_port.tcp());
assert!(res.is_err());
Ok(())
}
#[test]
fn start_multiple_containers() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let image =
GenericImage::new("testcontainers/helloworld", "1.3.0").with_wait_for(WaitFor::seconds(2));
let _container_1 = image.clone().start()?;
let _container_2 = image.clone().start()?;
let _container_3 = image.start()?;
Ok(())
}
#[test]
fn sync_run_exec() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let waitfor = WaitFor::log(LogWaitStrategy::stdout("server is ready").with_times(2));
let image = get_server_image(Some(waitfor)).with_wait_for(WaitFor::seconds(1));
let container = image.start()?;
let before = Instant::now();
let res = container
.exec(ExecCommand::new(["sleep", "2"]).with_cmd_ready_condition(CmdWaitFor::exit()))?;
assert_eq!(res.exit_code()?, Some(0));
assert!(
before.elapsed().as_secs() > 1,
"should have waited for 2 seconds"
);
let before = Instant::now();
let res = container.exec(
ExecCommand::new(["sleep", "2"]).with_cmd_ready_condition(CmdWaitFor::exit_code(0)),
)?;
assert_eq!(res.exit_code()?, Some(0));
assert!(
before.elapsed().as_secs() > 1,
"should have waited for 2 seconds"
);
let mut res = container.exec(
ExecCommand::new(vec!["ls".to_string()])
.with_cmd_ready_condition(CmdWaitFor::message_on_stdout("foo")),
)?;
let stdout = String::from_utf8(res.stdout_to_vec()?)?;
assert!(stdout.contains("foo"), "stdout must contain 'foo'");
let mut res = container.exec(ExecCommand::new([
"/bin/bash",
"-c",
"echo 'stdout 1' >&1 && echo 'stderr 1' >&2 \
&& echo 'stderr 2' >&2 && echo 'stdout 2' >&1",
]))?;
let stdout = String::from_utf8(res.stdout_to_vec()?)?;
assert_eq!(stdout, "stdout 1\nstdout 2\n");
let stderr = String::from_utf8(res.stderr_to_vec()?)?;
assert_eq!(stderr, "stderr 1\nstderr 2\n");
let mut res = container.exec(ExecCommand::new([
"/bin/bash",
"-c",
"echo 'stdout 1' >&1 && echo 'stderr 1' >&2 \
&& echo 'stderr 2' >&2 && echo 'stdout 2' >&1",
]))?;
let mut stdout = String::new();
res.stdout().read_to_string(&mut stdout)?;
assert_eq!(stdout, "stdout 1\nstdout 2\n");
let mut stderr = String::new();
res.stderr().read_to_string(&mut stderr)?;
assert_eq!(stderr, "stderr 1\nstderr 2\n");
Ok(())
}
#[test]
fn sync_run_with_log_consumer() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let (tx, rx) = std::sync::mpsc::sync_channel(1);
let _container = HelloWorld
.with_log_consumer(move |frame: &LogFrame| {
if String::from_utf8_lossy(frame.bytes()).contains("Ready") {
let _ = tx.send(());
}
})
.with_log_consumer(LoggingConsumer::new().with_stderr_level(log::Level::Error))
.start()?;
rx.recv()?; Ok(())
}
#[test]
fn sync_copy_bytes_to_container() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let container = GenericImage::new("alpine", "latest")
.with_wait_for(WaitFor::seconds(2))
.with_copy_to("/tmp/somefile", "foobar".to_string().into_bytes())
.with_cmd(vec!["cat", "/tmp/somefile"])
.start()?;
let mut out = String::new();
container.stdout(false).read_to_string(&mut out)?;
assert!(out.contains("foobar"));
Ok(())
}
#[test]
fn sync_copy_files_to_container() -> anyhow::Result<()> {
let temp_dir = temp_dir::TempDir::new()?;
let f1 = temp_dir.child("foo.txt");
let sub_dir = temp_dir.child("subdir");
std::fs::create_dir(&sub_dir)?;
let mut f2 = sub_dir.clone();
f2.push("bar.txt");
std::fs::write(&f1, "foofoofoo")?;
std::fs::write(&f2, "barbarbar")?;
let container = GenericImage::new("alpine", "latest")
.with_wait_for(WaitFor::seconds(2))
.with_copy_to("/tmp/somefile", f1)
.with_copy_to("/", temp_dir.path())
.with_cmd(vec!["cat", "/tmp/somefile", "&&", "cat", "/subdir/bar.txt"])
.start()?;
let mut out = String::new();
container.stdout(false).read_to_string(&mut out)?;
println!("{}", out);
assert!(out.contains("foofoofoo"));
assert!(out.contains("barbarbar"));
Ok(())
}
#[test]
fn sync_copy_file_from_container_to_path() -> anyhow::Result<()> {
let container = GenericImage::new("alpine", "latest")
.with_wait_for(WaitFor::seconds(1))
.with_cmd(["sh", "-c", "echo 'sync path' > /tmp/result.txt && sleep 30"])
.start()?;
let destination_dir = tempfile::tempdir()?;
let destination = destination_dir.path().join("result.txt");
container.copy_file_from("/tmp/result.txt", destination.as_path())?;
let copied = std::fs::read_to_string(&destination)?;
assert_eq!(copied, "sync path\n");
container.stop()?;
Ok(())
}
#[test]
fn sync_copy_file_from_container_into_mut_vec() -> anyhow::Result<()> {
let container = GenericImage::new("alpine", "latest")
.with_wait_for(WaitFor::seconds(1))
.with_cmd(["sh", "-c", "echo 'sync vec' > /tmp/result.txt && sleep 30"])
.start()?;
let mut buffer = Vec::new();
container.copy_file_from("/tmp/result.txt", &mut buffer)?;
assert_eq!(buffer, b"sync vec\n");
container.stop()?;
Ok(())
}
#[test]
fn sync_copy_file_from_container_directory_errors() -> anyhow::Result<()> {
let container = GenericImage::new("alpine", "latest")
.with_wait_for(WaitFor::seconds(1))
.with_cmd(["sh", "-c", "mkdir -p /tmp/result_dir && sleep 30"])
.start()?;
let err = container
.copy_file_from("/tmp/result_dir", Vec::<u8>::new())
.expect_err("expected directory copy to fail");
match err {
TestcontainersError::Client(ClientError::CopyFromContainerError(
CopyFromContainerError::IsDirectory,
)) => {}
other => panic!("unexpected error: {other:?}"),
}
container.stop()?;
Ok(())
}
#[test]
fn sync_container_is_running() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let container = GenericImage::new("simple_web_server", "latest")
.with_wait_for(WaitFor::message_on_stdout("server is ready"))
.start()?;
assert!(container.is_running()?);
container.stop()?;
assert!(!container.is_running()?);
Ok(())
}
#[test]
fn sync_container_exit_code() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let container = get_server_image(None).start()?;
assert_eq!(container.exit_code()?, None);
container.stop()?;
assert_eq!(container.exit_code()?, Some(0));
Ok(())
}