ftest/src/runner/suite.rs

329 lines
11 KiB
Rust

// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::collections::HashMap;
use tokio::sync::mpsc::{self, Receiver};
use url::Url;
use super::results::*;
use crate::complaince::result::Result as CResult;
use crate::complaince::suite::Suite;
use crate::complaince::suite::Test;
use crate::utils::get_random;
pub struct SuiteRunnerState {
suite: Suite,
tests: HashMap<String, TestRunnerState>,
}
#[derive(Debug)]
pub struct TestRunnerState {
rx: Receiver<CResult>,
container_name: String,
test: Test,
}
const FTEST_NGINX_PROXY: &str = "ftest";
impl SuiteRunnerState {
pub async fn run(
container_host: &Url,
suite: &crate::complaince::suite::Suite,
ctx: &crate::ctx::ArcCtx,
) -> ArchivableSuiteResult {
let state = Self::launch_suite(container_host, suite, ctx);
state.collect_results(ctx).await
}
pub fn run_proxy(ctx: &crate::ctx::ArcCtx) {
Self::stop_proxy(ctx);
let mut default = "";
let nets = std::process::Command::new("/sbin/ip")
.arg("route")
.output()
.unwrap();
let nets = String::from_utf8(nets.stdout).unwrap();
for l in nets.lines() {
let f = l.split(' ').collect::<Vec<&str>>();
if let Some("default") = f.get(0).map(|s| *s) {
default = f.get(8).unwrap();
log::info!("Found default net: {default}");
}
}
let args = [
"run",
"-d",
"-p",
"9080:9000",
"--name",
FTEST_NGINX_PROXY,
"--network",
"ftest",
"--add-host",
&format!("ftest_backend:{default}"),
"forgeflux/ftest-nginx-proxy",
];
let mut child = std::process::Command::new("docker")
.args(&args)
.spawn()
.expect("unable to obtain Docker version");
child.wait().unwrap();
log::info!("Started {FTEST_NGINX_PROXY} nginx proxy");
}
pub fn stop_proxy(ctx: &crate::ctx::ArcCtx) {
ctx.docker.rm_container(FTEST_NGINX_PROXY, true);
log::info!("Stopped {FTEST_NGINX_PROXY} nginx proxy");
}
fn launch_suite(
container_host: &Url,
suite: &crate::complaince::suite::Suite,
ctx: &crate::ctx::ArcCtx,
) -> Self {
let mut tests = HashMap::with_capacity(suite.tests.len());
for test in suite.tests.iter() {
let auth = get_random(32);
println!("starting test {}", test.name);
let mut env = HashMap::new();
env.insert("FTEST_AUTH".into(), auth.to_owned());
env.insert(
"FTEST_HOST".into(),
format!("http://ftest:{}", ctx.settings.server.port),
);
env.insert("FTEST_TARGET_HOST".into(), container_host.to_string());
env.insert("FTEST_USER".into(), "alice".into());
if let Some(custom_vars) = test.env_vars.clone() {
env.extend(custom_vars);
}
let name = format!("{}---{}--{}", suite.name, test.name, &auth[0..5]);
tracing::info!("Starting {name} with env vars {:?}", &env);
ctx.docker.run_container(
&name,
&test.container,
true,
&env,
Some("ftest".to_string()),
true,
);
let (tx, rx) = mpsc::channel(1);
{
let mut w = ctx.results.write().unwrap();
w.insert(auth.to_owned(), tx);
}
tests.insert(
auth.to_owned(),
TestRunnerState {
container_name: name,
rx,
test: test.clone(),
},
);
}
println!("{:?}", tests);
SuiteRunnerState {
suite: suite.clone(),
tests,
}
}
async fn collect_results(mut self, ctx: &crate::ctx::ArcCtx) -> ArchivableSuiteResult {
let mut tests = Vec::with_capacity(self.tests.len());
for (_auth, mut r) in self.tests.drain() {
let result = r.rx.recv().await.unwrap();
let log = ctx.docker.get_logs(&r.container_name);
ctx.docker.rm_container(&r.container_name, true);
let container = ArchivableContainer {
name: r.container_name,
logs: log,
};
let res = ArchivableTestResult {
success: result.success,
logs: result.logs,
container,
};
let s = ArchivableTest {
name: r.test.name,
url: r.test.url,
version: r.test.version,
container: r.test.container,
env_vars: r.test.env_vars,
result: res,
};
tests.push(s);
}
ArchivableSuiteResult {
suite: self.suite.clone(),
tests,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::complaince::result::Result as CResult;
use crate::complaince::suite::Test;
use crate::{AppCtx, Ctx, Settings};
use std::sync::Arc;
use url::Url;
// use std::sync::RwLock;
// #[derive(Clone, Eq, PartialEq, Debug)]
// enum ContainerState {
// NoContainer,
// Running(String),
// Stopped,
// Removed,
// }
//
// #[derive(Clone)]
// struct TestDocker {
// state: Arc<RwLock<ContainerState>>,
// }
//
// impl TestDocker {
// pub fn new() -> Self {
// Self {
// state: Arc::new(RwLock::new(ContainerState::NoContainer)),
// }
// }
// }
// impl DockerLike for TestDocker {
// fn version(&self) -> String {
// unimplemented!();
// }
// fn run_container(
// &self,
// name: &str,
// img: &str,
// detached: bool,
// env: &HashMap<String, String>,
// network: Option<String>,
// pull: bool,
// ) {
// let mut w = self.state.write().unwrap();
// if *w == ContainerState::NoContainer {
// *w = ContainerState::Running(name.to_string());
// } else {
// panic!("Container is {:?}", w);
// }
// }
// fn get_exit_status(&self, name: &str) -> isize {
// 0
// }
// fn get_logs(&self, name: &str) -> String {
// "".into()
// }
// fn rm_container(&self, name: &str, force: bool) {
// let mut w = self.state.write().unwrap();
// if *w == ContainerState::Stopped {
// *w = ContainerState::Removed;
// } else {
// panic!("Container is {:?}", w);
// }
// }
// }
//
#[actix_rt::test]
async fn suite_runner_works() {
const LOGS: &str = "SUITE RUNNER LOG STRING";
let settings = Settings::new().unwrap();
let ctx = Ctx::new(settings.clone()).await;
// ctx.docker = Arc::new(TestDocker::new());
let ctx = AppCtx::new(Arc::new(ctx));
let mut dummy_test = Test {
name: "suite_runner_works".into(),
url: Url::parse("https://git.batsense.net/ForgeFlux/ftest").unwrap(),
version: semver::Version::parse("1.0.1").unwrap(),
container: "forgeflux/ftest-docker-cmd-tester".into(),
env_vars: None,
};
let mut env = HashMap::new();
env.insert("TEST_NAME".into(), dummy_test.name.clone());
dummy_test.env_vars = Some(env);
let mut dummy_test2 = dummy_test.clone();
dummy_test2.name = "suite_runner_works2".into();
let mut env = HashMap::new();
env.insert("TEST_NAME".into(), dummy_test2.name.clone());
dummy_test2.env_vars = Some(env);
let suite = Suite {
name: "suite_runner_works".into(),
description: "testing suite runner".into(),
version: semver::Version::parse("1.0.1").unwrap(),
tests: vec![dummy_test.clone(), dummy_test2.clone()],
};
let state = SuiteRunnerState::launch_suite(
&Url::parse("http://suite_runner_works.service").unwrap(),
&suite,
&ctx,
);
assert_eq!(state.tests.len(), 2);
std::thread::sleep(std::time::Duration::new(13, 0));
for (k, v) in state.tests.iter() {
assert_eq!(ctx.docker.get_exit_status(&v.container_name), 0);
let tx = {
let r = ctx.results.read().unwrap();
r.get(k).unwrap().to_owned()
};
let tx_result = CResult {
success: true,
// sent by the app
logs: format!("{}{LOGS}", v.container_name),
};
tx.send(tx_result).await.unwrap();
}
let results = state.collect_results(&ctx).await;
assert_eq!(results.tests.len(), 2);
for archivable_test in results.tests.iter() {
//let archivable_test = results.tests.get(0).unwrap();
let t = if archivable_test.name == dummy_test.name.as_ref() {
dummy_test.clone()
} else {
dummy_test2.clone()
};
assert_eq!(archivable_test.name, t.name);
assert!(archivable_test.result.success);
assert!(archivable_test.result.container.name.contains(&t.name));
assert_eq!(
archivable_test.result.logs,
format!("{}{LOGS}", archivable_test.result.container.name)
);
println!("{}", archivable_test.result.container.logs);
assert!(archivable_test.result.container.logs.contains("FTEST_AUTH"));
assert!(archivable_test.result.container.logs.contains("FTEST_HOST"));
assert!(archivable_test
.result
.container
.logs
.contains("FTEST_TARGET_HOST"));
assert!(archivable_test.result.container.logs.contains("FTEST_USER"));
assert!(archivable_test
.result
.container
.logs
.contains(&format!("TEST_NAME={}", t.name)));
}
}
}