use crate::async_engine::Phase;
use crate::it::test_backend::TestBackend;
use crate::it::test_config::TestConfig;
use crate::it::test_config::with_test_config;
use crate::it::testrun::TestRun;
use crate::it::tests::TestCase;
use crate::leaks;
use crate::utils::bhash::BHashMap;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::hash_map_ext::HashMapExt;
use crate::utils::num_cpus::num_cpus;
use futures_util::future;
use futures_util::future::Either;
use log::Level;
use parking_lot::Mutex;
use std::any::Any;
use std::cell::Cell;
use std::collections::VecDeque;
use std::future::pending;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use std::time::SystemTime;
use uapi::c;
#[macro_use]
mod test_error;
#[macro_use]
mod test_macros;
pub mod test_backend;
mod test_client;
pub mod test_config;
mod test_gfx_api;
mod test_ifs;
mod test_logger;
mod test_mem;
mod test_utils;
mod testrun;
mod tests;
const SINGLE_THREAD: bool = false;
pub fn run_tests() {
run_tests_(tests::tests());
}
fn run_tests_(tests: Vec<&'static dyn TestCase>) {
leaks::init();
test_logger::install();
test_logger::set_level(Level::Trace);
let it_run = Arc::new(ItRun {
path: format!(
"{}/testruns/{}",
env!("CARGO_MANIFEST_DIR"),
humantime::format_rfc3339_millis(SystemTime::now())
),
failed: Default::default(),
});
if SINGLE_THREAD {
for test in tests {
with_test_config(|cfg| {
run_test(&it_run, test, cfg);
});
}
} else {
let queue = Arc::new(Mutex::new(VecDeque::from_iter(tests)));
let mut threads = vec![];
let num_cpus = match num_cpus() {
Ok(n) => n,
Err(e) => fatal!("Could not determine the number of cpus: {}", ErrorFmt(e)),
};
log::info!("Running {num_cpus} tests in parallel");
for _ in 0..num_cpus {
let queue = queue.clone();
let it_run = it_run.clone();
threads.push(std::thread::spawn(move || {
loop {
let Some(test) = queue.lock().pop_front() else {
break;
};
with_test_config(|cfg| {
run_test(&it_run, test, cfg);
});
}
}));
}
for thread in threads {
thread.join().unwrap();
}
}
let failed = it_run.failed.lock();
if failed.is_not_empty() {
let mut failed: Vec<_> = failed.iter().collect();
failed.sort_by_key(|f| f.0);
log::error!("The following tests failed:");
for (name, errors) in failed {
log::error!(" {name}:");
for error in errors {
log::error!(" {error}");
}
}
fatal!("Some tests failed");
}
}
struct ItRun {
path: String,
failed: Mutex<BHashMap<&'static str, Vec<String>>>,
}
fn run_test(it_run: &ItRun, test: &'static dyn TestCase, cfg: Rc<TestConfig>) {
log::info!("Running {}", test.name());
let dir = format!("{}/{}", it_run.path, test.name());
std::fs::create_dir_all(&dir).unwrap();
let log_path = format!("{dir}/log");
let log_file = Rc::new(uapi::open(log_path.as_str(), c::O_WRONLY | c::O_CREAT, 0o644).unwrap());
test_logger::set_file(log_file);
let errors = Rc::new(Cell::new(Vec::new()));
let errors2 = errors.clone();
let res = crate::compositor::start_compositor_for_test(Box::new(move |state| {
let state = state.clone();
let backend: Rc<TestBackend> = (state.backend.get() as Rc<dyn Any>).downcast().unwrap();
let testrun = Rc::new(TestRun {
state: state.clone(),
backend,
errors: Default::default(),
out_dir: dir.clone(),
in_dir: format!("{}/{}", env!("CARGO_MANIFEST_DIR"), test.dir()),
cfg: cfg.clone(),
});
let errors = errors2.clone();
Box::new(async move {
let future: Pin<_> = test.run(testrun.clone()).into();
let future = state.eng.spawn2("testrun", Phase::Present, future);
let timeout = state.wheel.timeout(5000);
match future::select(future, timeout).await {
Either::Left((Ok(..), _)) => {}
Either::Left((Err(e), _)) => {
testrun.errors.push(e.to_string());
}
Either::Right(..) => {
testrun.errors.push("Test timed out".to_string());
}
}
errors.set(testrun.errors.take());
state.ring.stop();
pending::<()>().await;
})
}));
let mut errors = errors.take();
if let Err(e) = res {
errors.push(format!("The compositor failed: {}", ErrorFmt(e)));
}
if errors.len() > 0 {
log::error!("The following errors occurred:");
for e in &errors {
log::error!(" {e}");
}
it_run.failed.lock().insert(test.name(), errors);
}
test_logger::unset_file();
}