Skip to content

Commit

Permalink
Merge #648: E2E tests: Improve E2E tests runner
Browse files Browse the repository at this point in the history
f18e68c fix: tracker checker return error code when it fails (Jose Celano)
e5cd81b refactor: [#647] E2E tests. Extract function (Jose Celano)
68f71be refactor: [#647] E2E tests. Extract strcut TrackerContainer (Jose Celano)
ddad4a4 ci: [#647] E2E tests. Make sure there are not panics in logs (Jose Celano)
670927c ci: [#647] E2E tests. Make sure we run at least one service per type (Jose Celano)
0afab09 refactor: [#647] extract strcut RunOptions (Jose Celano)

Pull request description:

  - [x] Check that we run all services (UDP, HTTP, HealCheck API).
  - [x] Check that the logs don't contain any panics.

ACKs for top commit:
  josecelano:
    ACK f18e68c

Tree-SHA512: 02b8561a08c92220a6a558a1cc0007da31be394236f7ec6b4cc05d2404c3755e45bff0f7f04aed686d98950d45ce0577e5ff915a874ac3ce7239d7b81cf477e0
  • Loading branch information
josecelano committed Jan 26, 2024
2 parents 14f88e9 + f18e68c commit 0f573b6
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 82 deletions.
2 changes: 1 addition & 1 deletion src/bin/tracker_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ use torrust_tracker::checker::app;

#[tokio::main]
async fn main() {
app::run().await;
app::run().await.expect("Some checks fail");
}
10 changes: 7 additions & 3 deletions src/checker/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ use std::sync::Arc;

use super::config::Configuration;
use super::console::Console;
use super::service::{CheckError, Service};
use crate::checker::config::parse_from_json;
use crate::checker::service::Service;

pub const NUMBER_OF_ARGUMENTS: usize = 2;

/// # Errors
///
/// If some checks fails it will return a vector with all failing checks.
///
/// # Panics
///
/// Will panic if:
///
/// - It can't read the json configuration file.
/// - The configuration file is invalid.
pub async fn run() {
pub async fn run() -> Result<(), Vec<CheckError>> {
let args = parse_arguments();
let config = setup_config(&args);
let console_printer = Console {};
Expand All @@ -22,7 +26,7 @@ pub async fn run() {
console: console_printer,
};

service.run_checks().await;
service.run_checks().await
}

pub struct Arguments {
Expand Down
36 changes: 31 additions & 5 deletions src/checker/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@ pub struct Service {
pub(crate) console: Console,
}

#[derive(Debug)]
pub enum CheckError {
UdpError,
HttpError,
HealthCheckError { url: Url },
}

impl Service {
pub async fn run_checks(&self) {
/// # Errors
///
/// Will return OK is all checks pass or an array with the check errors.
pub async fn run_checks(&self) -> Result<(), Vec<CheckError>> {
self.console.println("Running checks for trackers ...");

self.check_udp_trackers();
self.check_http_trackers();
self.run_health_checks().await;

self.run_health_checks().await
}

fn check_udp_trackers(&self) {
Expand All @@ -38,11 +50,22 @@ impl Service {
}
}

async fn run_health_checks(&self) {
async fn run_health_checks(&self) -> Result<(), Vec<CheckError>> {
self.console.println("Health checks ...");

let mut check_errors = vec![];

for health_check_url in &self.config.health_checks {
self.run_health_check(health_check_url.clone()).await;
match self.run_health_check(health_check_url.clone()).await {
Ok(()) => {}
Err(err) => check_errors.push(err),
}
}

if check_errors.is_empty() {
Ok(())
} else {
Err(check_errors)
}
}

Expand All @@ -62,22 +85,25 @@ impl Service {
.println(&format!("{} - HTTP tracker at {} is OK (TODO)", "✓".green(), url));
}

async fn run_health_check(&self, url: Url) {
async fn run_health_check(&self, url: Url) -> Result<(), CheckError> {
let client = Client::builder().timeout(Duration::from_secs(5)).build().unwrap();

match client.get(url.clone()).send().await {
Ok(response) => {
if response.status().is_success() {
self.console
.println(&format!("{} - Health API at {} is OK", "✓".green(), url));
Ok(())
} else {
self.console
.eprintln(&format!("{} - Health API at {} failing: {:?}", "✗".red(), url, response));
Err(CheckError::HealthCheckError { url })
}
}
Err(err) => {
self.console
.eprintln(&format!("{} - Health API at {} failing: {:?}", "✗".red(), url, err));
Err(CheckError::HealthCheckError { url })
}
}
}
Expand Down
73 changes: 65 additions & 8 deletions src/e2e/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,35 @@ use std::process::{Command, Output};
use std::thread::sleep;
use std::time::{Duration, Instant};

use log::debug;
use log::{debug, info};

/// Docker command wrapper.
pub struct Docker {}

#[derive(Clone, Debug)]
pub struct RunningContainer {
pub image: String,
pub name: String,
pub output: Output,
}

impl Drop for RunningContainer {
/// Ensures that the temporary container is stopped and removed when the
/// struct goes out of scope.
/// Ensures that the temporary container is stopped when the struct goes out
/// of scope.
fn drop(&mut self) {
let _unused = Docker::stop(self);
let _unused = Docker::remove(&self.name);
info!("Dropping running container: {}", self.name);
if Docker::is_container_running(&self.name) {
let _unused = Docker::stop(self);
}
}
}

/// `docker run` command options.
pub struct RunOptions {
pub env_vars: Vec<(String, String)>,
pub ports: Vec<String>,
}

impl Docker {
/// Builds a Docker image from a given Dockerfile.
///
Expand Down Expand Up @@ -55,7 +65,7 @@ impl Docker {
/// # Errors
///
/// Will fail if the docker run command fails.
pub fn run(image: &str, container: &str, env_vars: &[(String, String)], ports: &[String]) -> io::Result<RunningContainer> {
pub fn run(image: &str, container: &str, options: &RunOptions) -> io::Result<RunningContainer> {
let initial_args = vec![
"run".to_string(),
"--detach".to_string(),
Expand All @@ -65,14 +75,14 @@ impl Docker {

// Add environment variables
let mut env_var_args: Vec<String> = vec![];
for (key, value) in env_vars {
for (key, value) in &options.env_vars {
env_var_args.push("--env".to_string());
env_var_args.push(format!("{key}={value}"));
}

// Add port mappings
let mut port_args: Vec<String> = vec![];
for port in ports {
for port in &options.ports {
port_args.push("--publish".to_string());
port_args.push(port.to_string());
}
Expand All @@ -85,6 +95,7 @@ impl Docker {

if output.status.success() {
Ok(RunningContainer {
image: image.to_owned(),
name: container.to_owned(),
output,
})
Expand Down Expand Up @@ -174,4 +185,50 @@ impl Docker {

false
}

/// Checks if a Docker container is running.
///
/// # Arguments
///
/// * `container` - The name of the Docker container.
///
/// # Returns
///
/// `true` if the container is running, `false` otherwise.
#[must_use]
pub fn is_container_running(container: &str) -> bool {
match Command::new("docker")
.args(["ps", "-f", &format!("name={container}"), "--format", "{{.Names}}"])
.output()
{
Ok(output) => {
let output_str = String::from_utf8_lossy(&output.stdout);
output_str.contains(container)
}
Err(_) => false,
}
}

/// Checks if a Docker container exists.
///
/// # Arguments
///
/// * `container` - The name of the Docker container.
///
/// # Returns
///
/// `true` if the container exists, `false` otherwise.
#[must_use]
pub fn container_exist(container: &str) -> bool {
match Command::new("docker")
.args(["ps", "-a", "-f", &format!("name={container}"), "--format", "{{.Names}}"])
.output()
{
Ok(output) => {
let output_str = String::from_utf8_lossy(&output.stdout);
output_str.contains(container)
}
Err(_) => false,
}
}
}
1 change: 1 addition & 0 deletions src/e2e/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod docker;
pub mod logs_parser;
pub mod runner;
pub mod temp_dir;
pub mod tracker_container;
Loading

0 comments on commit 0f573b6

Please sign in to comment.