Skip to content

Commit

Permalink
feat: [torrust#634] E2E test runner: build, run and stop tracker image
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jan 23, 2024
1 parent 91c268a commit 083af0e
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tower-http = { version = "0", features = ["compression-full"] }
uuid = { version = "1", features = ["v4"] }
colored = "2.1.0"
url = "2.5.0"
tempfile = "3.9.0"

[dev-dependencies]
criterion = { version = "0.5.1", features = ["async_tokio"] }
Expand Down
1 change: 1 addition & 0 deletions cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"Swatinem",
"Swiftbit",
"taiki",
"tempfile",
"thiserror",
"tlsv",
"Torrentstorm",
Expand Down
10 changes: 10 additions & 0 deletions src/bin/e2e_tests_runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Program to run E2E tests.
//!
//! ```text
//! cargo run --bin e2e_tests_runner
//! ```
use torrust_tracker::e2e::runner;

fn main() {
runner::run();
}
131 changes: 131 additions & 0 deletions src/e2e/docker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use std::io;
use std::process::{Child, Command, Output, Stdio};
use std::thread::sleep;
use std::time::{Duration, Instant};

pub struct Docker {}

impl Docker {
/// Builds a Docker image from a given Dockerfile.
///
/// # Errors
///
/// Will fail if the docker build command fails.
pub fn build(dockerfile: &str, tag: &str) -> io::Result<()> {
let status = Command::new("docker")
.args(["build", "-f", dockerfile, "-t", tag, "."])
.status()?;

if status.success() {
Ok(())
} else {
Err(io::Error::new(io::ErrorKind::Other, "Failed to build Docker image"))
}
}

/// Runs a Docker container from a given image.
///
/// # Errors
///
/// Will fail if the docker run command fails.
pub fn run(image: &str, name: &str) -> io::Result<Output> {
let output = Command::new("docker")
.args(["run", "--detach", "--name", name, image])
.output()?;

if output.status.success() {
Ok(output)
} else {
Err(io::Error::new(io::ErrorKind::Other, "Failed to run Docker container"))
}
}

/// Runs a Docker container from a given image in the background.
///
/// # Errors
///
/// Will fail if the docker run command fails to start.
pub fn run_spawned(image: &str, name: &str) -> io::Result<Child> {
let child = Command::new("docker")
.args(["run", "--name", name, image])
.stdin(Stdio::null()) // Ignore stdin
.stdout(Stdio::null()) // Ignore stdout
.stderr(Stdio::null()) // Ignore stderr
.spawn()?;

Ok(child)
}

/// Stops a Docker container.
///
/// # Errors
///
/// Will fail if the docker stop command fails.
pub fn stop(name: &str) -> io::Result<()> {
let status = Command::new("docker").args(["stop", name]).status()?;

if status.success() {
Ok(())
} else {
Err(io::Error::new(io::ErrorKind::Other, "Failed to stop Docker container"))
}
}

/// Removes a Docker container.
///
/// # Errors
///
/// Will fail if the docker rm command fails.
pub fn remove(name: &str) -> io::Result<()> {
let status = Command::new("docker").args(["rm", "-f", name]).status()?;

if status.success() {
Ok(())
} else {
Err(io::Error::new(io::ErrorKind::Other, "Failed to remove Docker container"))
}
}

/// Fetches logs from a Docker container.
///
/// # Errors
///
/// Will fail if the docker logs command fails.
pub fn logs(container_name: &str) -> io::Result<String> {
let output = Command::new("docker").args(["logs", container_name]).output()?;

if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Failed to fetch logs from Docker container",
))
}
}

/// Checks if a Docker container is healthy.
#[must_use]
pub fn wait_until_is_healthy(name: &str, timeout: Duration) -> bool {
let start = Instant::now();

while start.elapsed() < timeout {
let Ok(output) = Command::new("docker")
.args(["ps", "-f", &format!("name={name}"), "--format", "{{.Status}}"])
.output()
else {
return false;
};

let output_str = String::from_utf8_lossy(&output.stdout);

if output_str.contains("(healthy)") {
return true;
}

sleep(Duration::from_secs(1));
}

false
}
}
3 changes: 3 additions & 0 deletions src/e2e/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod docker;
pub mod runner;
pub mod temp_dir;
81 changes: 81 additions & 0 deletions src/e2e/runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use std::env;
use std::time::Duration;

use rand::distributions::Alphanumeric;
use rand::Rng;

use crate::e2e::docker::Docker;
use crate::e2e::temp_dir::Handler;

/// # Panics
///
/// Will panic if:
///
/// - It can't build the docker image.
/// - It can't create a temp dir.
/// - It can't change to the new temp dir.
/// - It can't revert the dit to the previous one.
pub fn run() {
/* todo:
- [x] Build the docker image.
- [x] Run the docker image.
- [x] Wait until the container is healthy.
- [ ] Parse logs to get running services.
- [ ] Build config file for the tracker_checker.
- [ ] Run the tracker_checker.
- [x] Stop the container.
*/

//Docker::build("./Containerfile", "local").expect("A tracker local docker image should be built");

println!(
"Current dir: {:?}",
env::current_dir().expect("It should return the current dir")
);

println!("Create temp dir ...");
let temp_dir_handler = Handler::new().expect("A temp dir should be created");
println!("Temp dir created: {:?}", temp_dir_handler.temp_dir);

println!("Change dir to: {:?}", temp_dir_handler.temp_dir);
temp_dir_handler
.change_to_temp_dir()
.expect("The app should change dir to the temp dir");

let container_name = generate_random_container_name("tracker_");

println!("Running docker tracker image: {container_name} ...");
Docker::run("local", &container_name).expect("A tracker local docker image should be running");

println!("Waiting for the container {container_name} to be healthy ...");
let is_healthy = Docker::wait_until_is_healthy(&container_name, Duration::from_secs(10));

if !is_healthy {
println!("Unhealthy container: {container_name}");
println!("Stopping container: {container_name} ...");
Docker::stop(&container_name).expect("A tracker local docker image should be stopped");
panic!("Unhealthy container: {container_name}");
}

println!("Container {container_name} is healthy ...");

println!("Stopping docker tracker image: {container_name} ...");
Docker::stop(&container_name).expect("A tracker local docker image should be stopped");

println!("Revert current dir to: {:?}", temp_dir_handler.original_dir);
temp_dir_handler
.revert_to_original_dir()
.expect("The app should revert dir from temp dir to the original one");
}

fn generate_random_container_name(prefix: &str) -> String {
let rand_string: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(20)
.map(char::from)
.collect();

format!("{prefix}{rand_string}")
}
52 changes: 52 additions & 0 deletions src/e2e/temp_dir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::path::PathBuf;
use std::{env, io};

use tempfile::TempDir;

pub struct Handler {
pub temp_dir: TempDir,
pub original_dir: PathBuf,
}

impl Handler {
/// Creates a new temporary directory and remembers the current working directory.
///
/// # Errors
///
/// Will error if:
///
/// - It can't create the temp dir.
/// - It can't get the current dir.
pub fn new() -> io::Result<Self> {
let temp_dir = TempDir::new()?;
let original_dir = env::current_dir()?;

Ok(Handler { temp_dir, original_dir })
}

/// Changes the current working directory to the temporary directory.
///
/// # Errors
///
/// Will error if it can't change the current di to the temp dir.
pub fn change_to_temp_dir(&self) -> io::Result<()> {
env::set_current_dir(self.temp_dir.path())
}

/// Changes the current working directory back to the original directory.
///
/// # Errors
///
/// Will error if it can't revert the current dir to the original one.
pub fn revert_to_original_dir(&self) -> io::Result<()> {
env::set_current_dir(&self.original_dir)
}
}

impl Drop for Handler {
/// Ensures that the temporary directory is deleted when the struct goes out of scope.
fn drop(&mut self) {
// The temporary directory is automatically deleted when `TempDir` is dropped.
// We can add additional cleanup here if necessary.
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ pub mod app;
pub mod bootstrap;
pub mod checker;
pub mod core;
pub mod e2e;
pub mod servers;
pub mod shared;

Expand Down

0 comments on commit 083af0e

Please sign in to comment.