Skip to content

Commit

Permalink
refactor: [#647] E2E tests. Extract strcut TrackerContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jan 26, 2024
1 parent ddad4a4 commit 68f71be
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 90 deletions.
13 changes: 7 additions & 6 deletions src/e2e/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ 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)]

Check warning on line 12 in src/e2e/docker.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/docker.rs#L12

Added line #L12 was not covered by tests
pub struct RunningContainer {
pub image: String,

Check warning on line 14 in src/e2e/docker.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/docker.rs#L14

Added line #L14 was not covered by tests
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) {
info!("Dropping running container: {}", self.name);
if Docker::is_container_running(&self.name) {
let _unused = Docker::stop(self);
}

Check warning on line 26 in src/e2e/docker.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/docker.rs#L23-L26

Added lines #L23 - L26 were not covered by tests
if Docker::container_exist(&self.name) {
let _unused = Docker::remove(&self.name);
}
}
}

Expand Down Expand Up @@ -95,6 +95,7 @@ impl Docker {

if output.status.success() {
Ok(RunningContainer {
image: image.to_owned(),

Check warning on line 98 in src/e2e/docker.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/docker.rs#L98

Added line #L98 was not covered by tests
name: container.to_owned(),
output,
})
Expand Down
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;
117 changes: 33 additions & 84 deletions src/e2e/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use std::{env, io};

use log::{debug, info, LevelFilter};
use rand::distributions::Alphanumeric;
use rand::Rng;

use super::docker::RunningContainer;
use crate::e2e::docker::{Docker, RunOptions};
use super::tracker_container::TrackerContainer;
use crate::e2e::docker::RunOptions;
use crate::e2e::logs_parser::RunningServices;
use crate::e2e::temp_dir::Handler;

/* code-review:
- We use always the same docker image name. Should we use a random image name (tag)?
- We use the name image name we use in other workflows `torrust-tracker:local`.
Should we use a different one like `torrust-tracker:e2e`?
- We remove the container after running tests but not the container image.
Should we remove the image too?
*/

pub const NUMBER_OF_ARGUMENTS: usize = 2;
const CONTAINER_TAG: &str = "torrust-tracker:local";
const CONTAINER_IMAGE: &str = "torrust-tracker:local";
const CONTAINER_NAME_PREFIX: &str = "tracker_";
const TRACKER_CHECKER_CONFIG_FILE: &str = "tracker_checker.json";

pub struct Arguments {
Expand All @@ -34,11 +40,9 @@ pub fn run() {

let tracker_config = load_tracker_configuration(&args.tracker_config_path);

build_tracker_container_image(CONTAINER_TAG);

let temp_dir = create_temp_dir();
let mut tracker_container = TrackerContainer::new(CONTAINER_IMAGE, CONTAINER_NAME_PREFIX);

Check warning on line 43 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L43

Added line #L43 was not covered by tests

let container_name = generate_random_container_name("tracker_");
tracker_container.build_image();

Check warning on line 45 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L45

Added line #L45 was not covered by tests

// code-review: if we want to use port 0 we don't know which ports we have to open.
// Besides, if we don't use port 0 we should get the port numbers from the tracker configuration.
Expand All @@ -53,30 +57,32 @@ pub fn run() {
],
};

Check warning on line 58 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L58

Added line #L58 was not covered by tests

let container = run_tracker_container(CONTAINER_TAG, &container_name, &options);
tracker_container.run(&options);

Check warning on line 60 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L60

Added line #L60 was not covered by tests

let running_services = parse_running_services_from_logs(&container);

assert_there_are_no_panics_in_logs(&container);
let running_services = tracker_container.running_services();

Check warning on line 62 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L62

Added line #L62 was not covered by tests

assert_there_is_at_least_one_service_per_type(&running_services);

Check warning on line 64 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L64

Added line #L64 was not covered by tests

let tracker_checker_config =
serde_json::to_string_pretty(&running_services).expect("Running services should be serialized into JSON");

let temp_dir = create_temp_dir();

Check warning on line 69 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L69

Added line #L69 was not covered by tests

let mut tracker_checker_config_path = PathBuf::from(&temp_dir.temp_dir.path());
tracker_checker_config_path.push(TRACKER_CHECKER_CONFIG_FILE);

write_tracker_checker_config_file(&tracker_checker_config_path, &tracker_checker_config);

run_tracker_checker(&tracker_checker_config_path).expect("Tracker checker should check running services");
run_tracker_checker(&tracker_checker_config_path).expect("All tracker services should be running correctly");

Check warning on line 76 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L76

Added line #L76 was not covered by tests

// More E2E tests could be added here in the future.
// For example: `cargo test ...` for only E2E tests, using this shared test env.

stop_tracker_container(&container);
tracker_container.stop();

Check warning on line 81 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L81

Added line #L81 was not covered by tests

tracker_container.remove();

Check warning on line 83 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L83

Added line #L83 was not covered by tests

remove_tracker_container(&container_name);
info!("Tracker container final state:\n{:#?}", tracker_container);

Check warning on line 85 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L85

Added line #L85 was not covered by tests
}

fn setup_runner_logging(level: LevelFilter) {
Expand Down Expand Up @@ -125,11 +131,6 @@ fn read_file(path: &str) -> String {
std::fs::read_to_string(path).unwrap_or_else(|_| panic!("Can't read file {path}"))
}

fn build_tracker_container_image(tag: &str) {
info!("Building tracker container image with tag: {} ...", tag);
Docker::build("./Containerfile", tag).expect("A tracker local docker image should be built");
}

fn create_temp_dir() -> Handler {
debug!(
"Current dir: {:?}",
Expand All @@ -143,63 +144,6 @@ fn create_temp_dir() -> Handler {
temp_dir_handler
}

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}")
}

fn run_tracker_container(image: &str, container_name: &str, options: &RunOptions) -> RunningContainer {
info!("Running docker tracker image: {container_name} ...");

let container = Docker::run(image, container_name, options).expect("A tracker local docker image should be running");

info!("Waiting for the container {container_name} to be healthy ...");

let is_healthy = Docker::wait_until_is_healthy(container_name, Duration::from_secs(10));

assert!(is_healthy, "Unhealthy tracker container: {container_name}");

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

container
}

fn stop_tracker_container(container: &RunningContainer) {
info!("Stopping docker tracker image: {} ...", container.name);
Docker::stop(container).expect("Container should be stopped");
assert_there_are_no_panics_in_logs(container);
}

fn remove_tracker_container(container_name: &str) {
info!("Removing docker tracker image: {container_name} ...");
Docker::remove(container_name).expect("Container should be removed");
}

fn assert_there_are_no_panics_in_logs(container: &RunningContainer) -> RunningServices {
let logs = Docker::logs(&container.name).expect("Logs should be captured from running container");

assert!(
!(logs.contains(" panicked at ") || logs.contains("RUST_BACKTRACE=1")),
"{}",
format!("Panics found is logs:\n{logs}")
);

RunningServices::parse_from_logs(&logs)
}

fn parse_running_services_from_logs(container: &RunningContainer) -> RunningServices {
let logs = Docker::logs(&container.name).expect("Logs should be captured from running container");

debug!("Logs after starting the container:\n{logs}");

RunningServices::parse_from_logs(&logs)
}

fn assert_there_is_at_least_one_service_per_type(running_services: &RunningServices) {
assert!(
!running_services.udp_trackers.is_empty(),

Check warning on line 149 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L147-L149

Added lines #L147 - L149 were not covered by tests
Expand All @@ -216,15 +160,20 @@ fn assert_there_is_at_least_one_service_per_type(running_services: &RunningServi
}

fn write_tracker_checker_config_file(config_file_path: &Path, config: &str) {
info!(

Check warning on line 163 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L163

Added line #L163 was not covered by tests
"Writing Tracker Checker configuration file: {:?} \n{config}",
config_file_path
);

let mut file = File::create(config_file_path).expect("Tracker checker config file to be created");

file.write_all(config.as_bytes())
.expect("Tracker checker config file to be written");

info!("Tracker checker configuration file: {:?} \n{config}", config_file_path);
}

/// Runs the tracker checker
/// Runs the Tracker Checker.
///
/// For example:
///
/// ```text
/// cargo run --bin tracker_checker "./share/default/config/tracker_checker.json"
Expand All @@ -239,7 +188,7 @@ fn write_tracker_checker_config_file(config_file_path: &Path, config: &str) {
/// Will panic if the config path is not a valid string.
pub fn run_tracker_checker(config_path: &Path) -> io::Result<()> {
info!(
"Running tacker checker: cargo --bin tracker_checker {}",
"Running Tracker Checker: cargo --bin tracker_checker {}",
config_path.display()
);

Expand All @@ -254,7 +203,7 @@ pub fn run_tracker_checker(config_path: &Path) -> io::Result<()> {
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to run tracker checker with config file {path}"),
format!("Failed to run Tracker Checker with config file {path}"),

Check warning on line 206 in src/e2e/runner.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/runner.rs#L206

Added line #L206 was not covered by tests
))
}
}
138 changes: 138 additions & 0 deletions src/e2e/tracker_container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::time::Duration;

use log::{debug, error, info};
use rand::distributions::Alphanumeric;
use rand::Rng;

use super::docker::{RunOptions, RunningContainer};
use super::logs_parser::RunningServices;
use crate::e2e::docker::Docker;

#[derive(Debug)]

Check warning on line 11 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L11

Added line #L11 was not covered by tests
pub struct TrackerContainer {
pub image: String,
pub name: String,
pub running: Option<RunningContainer>,

Check warning on line 15 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L14-L15

Added lines #L14 - L15 were not covered by tests
}

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

Check warning on line 26 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L21-L26

Added lines #L21 - L26 were not covered by tests
}

impl TrackerContainer {
#[must_use]
pub fn new(tag: &str, container_name_prefix: &str) -> Self {
Self {
image: tag.to_owned(),
name: Self::generate_random_container_name(container_name_prefix),
running: None,
}
}

Check warning on line 37 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L31-L37

Added lines #L31 - L37 were not covered by tests

/// # Panics
///
/// Will panic if it can't build the docker image.
pub fn build_image(&self) {
info!("Building tracker container image with tag: {} ...", self.image);
Docker::build("./Containerfile", &self.image).expect("A tracker local docker image should be built");
}

Check warning on line 45 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L42-L45

Added lines #L42 - L45 were not covered by tests

/// # Panics
///
/// Will panic if it can't run the container.
pub fn run(&mut self, options: &RunOptions) {
info!("Running docker tracker image: {} ...", self.name);

Check warning on line 51 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L50-L51

Added lines #L50 - L51 were not covered by tests

let container = Docker::run(&self.image, &self.name, options).expect("A tracker local docker image should be running");

Check warning on line 53 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L53

Added line #L53 was not covered by tests

info!("Waiting for the container {} to be healthy ...", self.name);

Check warning on line 55 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L55

Added line #L55 was not covered by tests

let is_healthy = Docker::wait_until_is_healthy(&self.name, Duration::from_secs(10));

Check warning on line 57 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L57

Added line #L57 was not covered by tests

assert!(is_healthy, "Unhealthy tracker container: {}", &self.name);

Check warning on line 59 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L59

Added line #L59 was not covered by tests

info!("Container {} is healthy ...", &self.name);

Check warning on line 61 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L61

Added line #L61 was not covered by tests

self.running = Some(container);

Check warning on line 63 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L63

Added line #L63 was not covered by tests

self.assert_there_are_no_panics_in_logs();
}

Check warning on line 66 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L65-L66

Added lines #L65 - L66 were not covered by tests

/// # Panics
///
/// Will panic if it can't get the logs from the running container.
#[must_use]
pub fn running_services(&self) -> RunningServices {
let logs = Docker::logs(&self.name).expect("Logs should be captured from running container");

Check warning on line 73 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L72-L73

Added lines #L72 - L73 were not covered by tests

debug!("Parsing running services from logs. Logs :\n{logs}");

Check warning on line 75 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L75

Added line #L75 was not covered by tests

RunningServices::parse_from_logs(&logs)
}

Check warning on line 78 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L77-L78

Added lines #L77 - L78 were not covered by tests

/// # Panics
///
/// Will panic if it can't stop the container.
pub fn stop(&mut self) {
match &self.running {
Some(container) => {
info!("Stopping docker tracker container: {} ...", self.name);

Check warning on line 86 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L83-L86

Added lines #L83 - L86 were not covered by tests

Docker::stop(container).expect("Container should be stopped");

Check warning on line 88 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L88

Added line #L88 was not covered by tests

self.assert_there_are_no_panics_in_logs();
}

Check warning on line 91 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L90-L91

Added lines #L90 - L91 were not covered by tests
None => {
if Docker::is_container_running(&self.name) {
error!("Tracker container {} was started manually", self.name);

Check warning on line 94 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L93-L94

Added lines #L93 - L94 were not covered by tests
} else {
info!("Docker tracker container is not running: {} ...", self.name);
}

Check warning on line 97 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L96-L97

Added lines #L96 - L97 were not covered by tests
}
}

self.running = None;
}

Check warning on line 102 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L101-L102

Added lines #L101 - L102 were not covered by tests

/// # Panics
///
/// Will panic if it can't remove the container.
pub fn remove(&self) {
match &self.running {
Some(_running_container) => {
error!("Can't remove running container: {} ...", self.name);
}
None => {
info!("Removing docker tracker container: {} ...", self.name);
Docker::remove(&self.name).expect("Container should be removed");

Check warning on line 114 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L107-L114

Added lines #L107 - L114 were not covered by tests
}
}
}

Check warning on line 117 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L117

Added line #L117 was not covered by tests

fn generate_random_container_name(prefix: &str) -> String {
let rand_string: String = rand::thread_rng()

Check warning on line 120 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L119-L120

Added lines #L119 - L120 were not covered by tests
.sample_iter(&Alphanumeric)
.take(20)
.map(char::from)
.collect();

Check warning on line 124 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L123-L124

Added lines #L123 - L124 were not covered by tests

format!("{prefix}{rand_string}")
}

Check warning on line 127 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L126-L127

Added lines #L126 - L127 were not covered by tests

fn assert_there_are_no_panics_in_logs(&self) {
let logs = Docker::logs(&self.name).expect("Logs should be captured from running container");

Check warning on line 130 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L129-L130

Added lines #L129 - L130 were not covered by tests

assert!(
!(logs.contains(" panicked at ") || logs.contains("RUST_BACKTRACE=1")),

Check warning on line 133 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L132-L133

Added lines #L132 - L133 were not covered by tests
"{}",
format!("Panics found is logs:\n{logs}")

Check warning on line 135 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L135

Added line #L135 was not covered by tests
);
}

Check warning on line 137 in src/e2e/tracker_container.rs

View check run for this annotation

Codecov / codecov/patch

src/e2e/tracker_container.rs#L137

Added line #L137 was not covered by tests
}

0 comments on commit 68f71be

Please sign in to comment.