diff --git a/src/compositor.rs b/src/compositor.rs index 7d2e35b4..93f54465 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -238,7 +238,7 @@ async fn start_compositor3(state: Rc, test_future: Option) { fn load_config(state: &Rc, #[allow(unused_variables)] for_test: bool) -> ConfigProxy { #[cfg(feature = "it")] if for_test { - // todo + return ConfigProxy::for_test(state); } match ConfigProxy::from_config_dir(state) { Ok(c) => c, diff --git a/src/config.rs b/src/config.rs index 340f07db..03b866ea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,6 +5,7 @@ use { backend::{ConnectorId, InputDeviceId}, config::handler::ConfigProxyHandler, ifs::wl_seat::SeatId, + it::test_config::TEST_CONFIG_ENTRY, state::State, utils::{ clonecell::CloneCell, numcell::NumCell, oserror::OsError, ptr_ext::PtrExt, @@ -180,6 +181,11 @@ impl ConfigProxy { Self::new(None, &entry, state) } + #[cfg(feature = "it")] + pub fn for_test(state: &Rc) -> Self { + Self::new(None, &TEST_CONFIG_ENTRY, state) + } + pub fn from_config_dir(state: &Rc) -> Result { let dir = match state.config_dir.as_deref() { Some(d) => d, diff --git a/src/it.rs b/src/it.rs index f6f22c09..4a552ff9 100644 --- a/src/it.rs +++ b/src/it.rs @@ -1,9 +1,15 @@ use { crate::{ - it::{test_backend::TestBackend, testrun::TestRun, tests::TestCase}, + it::{ + test_backend::TestBackend, + test_config::{with_test_config, TestConfig}, + testrun::TestRun, + tests::TestCase, + }, utils::errorfmt::ErrorFmt, }, ahash::AHashMap, + futures_util::{future, future::Either}, isnt::std_1::collections::IsntHashMapExt, log::Level, std::{ @@ -22,6 +28,7 @@ mod test_error; mod test_object; pub mod test_backend; mod test_client; +pub mod test_config; mod test_ifs; mod test_logger; mod test_mem; @@ -42,7 +49,9 @@ pub fn run_tests() { failed: Default::default(), }; for test in tests::tests() { - run_test(&it_run, test); + with_test_config(|cfg| { + run_test(&it_run, test, cfg); + }) } let failed = it_run.failed.borrow_mut(); if failed.is_not_empty() { @@ -64,7 +73,7 @@ struct ItRun { failed: RefCell>>, } -fn run_test(it_run: &ItRun, test: &'static dyn TestCase) { +fn run_test(it_run: &ItRun, test: &'static dyn TestCase, cfg: Rc) { log::info!("Running {}", test.name()); let dir = format!("{}/{}", it_run.path, test.name()); std::fs::create_dir_all(&dir).unwrap(); @@ -92,12 +101,20 @@ fn run_test(it_run: &ItRun, test: &'static dyn TestCase) { errors: Default::default(), server_addr, dir: dir.clone(), + cfg: cfg.clone(), }); let errors = errors2.clone(); Box::new(async move { let future: Pin<_> = test.run(testrun.clone()).into(); - if let Err(e) = future.await { - testrun.errors.push(e.to_string()); + let timeout = state.eng.timeout(5000).unwrap(); + 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.el.stop(); diff --git a/src/it/test_config.rs b/src/it/test_config.rs new file mode 100644 index 00000000..e78081e9 --- /dev/null +++ b/src/it/test_config.rs @@ -0,0 +1,127 @@ +use { + crate::it::test_error::TestError, + isnt::std_1::primitive::IsntConstPtrExt, + jay_config::_private::{ + bincode_ops, + ipc::{ClientMessage, ServerMessage}, + ConfigEntry, VERSION, + }, + std::{cell::Cell, ops::Deref, ptr, rc::Rc}, +}; + +pub static TEST_CONFIG_ENTRY: ConfigEntry = ConfigEntry { + version: VERSION, + init, + unref, + handle_msg, +}; + +#[thread_local] +static mut CONFIG: *const TestConfig = ptr::null(); + +pub fn with_test_config(f: F) -> T +where + F: FnOnce(Rc) -> T, +{ + unsafe { + let tc = Rc::new(TestConfig { + srv: Cell::new(None), + }); + let old = CONFIG; + CONFIG = tc.deref(); + let res = f(tc.clone()); + CONFIG = old; + res + } +} + +unsafe extern "C" fn init( + srv_data: *const u8, + srv_unref: unsafe extern "C" fn(data: *const u8), + srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), + _msg: *const u8, + _size: usize, +) -> *const u8 { + let tc = CONFIG; + assert!(tc.is_not_null()); + Rc::increment_strong_count(tc); + { + let tc = &*tc; + tc.srv.set(Some(ServerData { + srv_data, + srv_unref, + srv_handler, + })); + } + tc.cast() +} + +unsafe extern "C" fn unref(data: *const u8) { + Rc::decrement_strong_count(data.cast::()); +} + +unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) { + let _tc = &*data.cast::(); + let msg = std::slice::from_raw_parts(msg, size); + let res = bincode::decode_from_slice::(msg, bincode_ops()); + let (msg, _) = match res { + Ok(msg) => msg, + Err(e) => { + log::error!("could not deserialize message: {}", e); + return; + } + }; + match msg { + ServerMessage::Configure { .. } => {} + ServerMessage::Response { .. } => {} + ServerMessage::InvokeShortcut { .. } => {} + ServerMessage::NewInputDevice { .. } => {} + ServerMessage::DelInputDevice { .. } => {} + ServerMessage::ConnectorConnect { .. } => {} + ServerMessage::ConnectorDisconnect { .. } => {} + ServerMessage::NewConnector { .. } => {} + ServerMessage::DelConnector { .. } => {} + ServerMessage::TimerExpired { .. } => {} + ServerMessage::GraphicsInitialized => {} + } +} + +#[derive(Copy, Clone)] +struct ServerData { + srv_data: *const u8, + srv_unref: unsafe extern "C" fn(data: *const u8), + srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), +} + +pub struct TestConfig { + srv: Cell>, +} + +impl TestConfig { + fn send(&self, msg: ClientMessage) -> Result<(), TestError> { + let srv = match self.srv.get() { + Some(srv) => srv, + _ => bail!("srv not set"), + }; + let mut buf = vec![]; + bincode::encode_into_std_write(msg, &mut buf, bincode_ops()).unwrap(); + unsafe { + (srv.srv_handler)(srv.srv_data, buf.as_ptr(), buf.len()); + } + Ok(()) + } + + pub fn quit(&self) -> Result<(), TestError> { + self.send(ClientMessage::Quit) + } +} + +impl Drop for TestConfig { + fn drop(&mut self) { + unsafe { + if let Some(srv) = self.srv.take() { + (srv.srv_unref)(srv.srv_data); + } + } + } +} diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 6c013e9e..d4d72469 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -4,6 +4,7 @@ use { it::{ test_backend::TestBackend, test_client::TestClient, + test_config::TestConfig, test_error::{TestError, TestErrorExt}, test_ifs::test_display::TestDisplay, test_transport::TestTransport, @@ -25,6 +26,7 @@ pub struct TestRun { pub errors: Stack, pub server_addr: c::sockaddr_un, pub dir: String, + pub cfg: Rc, } impl TestRun { diff --git a/src/it/tests.rs b/src/it/tests.rs index 1bd79600..6a1c7d04 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -56,6 +56,7 @@ macro_rules! tassert_eq { mod t0001_shm_formats; mod t0002_window; mod t0003_multi_window; +mod t0004_quit; pub trait TestCase { fn name(&self) -> &'static str; @@ -76,5 +77,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0001_shm_formats, t0002_window, t0003_multi_window, + t0004_quit, } } diff --git a/src/it/tests/t0004_quit.rs b/src/it/tests/t0004_quit.rs new file mode 100644 index 00000000..c59edd75 --- /dev/null +++ b/src/it/tests/t0004_quit.rs @@ -0,0 +1,15 @@ +use { + crate::it::{test_error::TestError, testrun::TestRun}, + std::{future::pending, rc::Rc}, +}; + +testcase!(); + +/// Quit +async fn test(run: Rc) -> Result<(), TestError> { + for _ in 0..2 { + run.state.eng.yield_now().await; + } + run.cfg.quit()?; + pending().await +}