#[cfg(feature = "resource_manager")] mod test { use std::fs; use std::io::IoSlice; use std::path::{Path, PathBuf}; use x11rb::connection::{ BufWithFds, Connection, DiscardMode, RawEventAndSeqNumber, ReplyOrError, RequestConnection, RequestKind, }; use x11rb::cookie::{Cookie, CookieWithFds, VoidCookie}; use x11rb::errors::{ConnectionError, ParseError, ReplyOrIdError}; use x11rb::protocol::xproto::{Screen, Setup}; use x11rb::protocol::Event; use x11rb::resource_manager::{new_from_default, Database}; use x11rb::utils::RawFdContainer; use x11rb::x11_utils::{ExtensionInformation, Serialize, TryParse, TryParseFd, X11Error}; use x11rb_protocol::SequenceNumber; // Most tests in here are based on [1], which is: Copyright © 2016 Ingo Bürk // [1]: https://github.com/Airblader/xcb-util-xrm/blob/master/tests/tests_database.c /// Get the path to the `target` directory for the current build. /// /// This is inspired by cargo: /// https://github.com/rust-lang/cargo/blob/7302186d7beb2b11bf7366c0aa66269313ebe18f/crates/cargo-test-support/src/paths.rs#L18-L31 fn get_temporary_dir(unique_name: &str) -> PathBuf { let mut path = std::env::current_exe().unwrap(); while path.file_name().and_then(|n| n.to_str()) != Some("target") { path.pop(); } path.push("test_data"); path.push(unique_name); fs::create_dir_all(&path).unwrap(); path } fn write_file(base: impl AsRef, path: impl AsRef, content: &[u8]) -> PathBuf { let mut file_path = PathBuf::from(base.as_ref()); file_path.push(path.as_ref()); if let Some(parent) = file_path.parent() { fs::create_dir_all(parent).unwrap(); } fs::write(&file_path, content).unwrap(); file_path } fn database_from_file(file: impl AsRef) -> Database { // I decided against adding something like this to the API... let file = file.as_ref(); let mut include = Vec::new(); include.extend(b"#include \""); include.extend(file.file_name().unwrap().to_str().unwrap().bytes()); include.extend(b"\"\n"); Database::new_from_data_with_base_directory(&include, file.parent().unwrap()) } fn check_db(db: &Database, queries: &[(&str, &[u8])]) { let mut errors = 0; for (query, expected) in queries { let result = db.get_bytes(query, ""); if result != Some(expected) { eprintln!( "Queried database for \"{query}\" and expected {expected:?}, but got {result:?}" ); errors += 1; } } assert_eq!(errors, 0, "Had {errors} errors"); } #[test] fn relative_include() { let dir = get_temporary_dir("relative_include"); let file = write_file( &dir, "Xresources1", b"First: 1\n\n#include \"xresources2\"\n", ); write_file( &dir, "xresources2", b"#include \"sub/xresources3\"\nSecond: 2\n", ); write_file(&dir, "sub/xresources3", b"Third: 3\n"); let db = database_from_file(file); check_db(&db, &[("First", b"1"), ("Second", b"2"), ("Third", b"3")]); } #[test] fn include_loop() { let dir = get_temporary_dir("include_loop"); let file = write_file( dir, "loop.xresources", b"First: 1\n! Provoke an endless chain of self-inclusion\n#include \"loop.xresources\"\nSecond: 2\n", ); let db = database_from_file(file); check_db(&db, &[("First", b"1")]); } #[test] fn home_resolution() { let mut dir = get_temporary_dir("home_resolution"); write_file(&dir, ".Xresources", b"First: 1\n"); write_file(&dir, "xenvironment", b"Second: 2\n"); std::env::set_var("HOME", &dir); dir.push("xenvironment"); std::env::set_var("XENVIRONMENT", dir); let conn = mock_connection(None); let db = new_from_default(&conn).unwrap(); check_db(&db, &[("First", b"1"), ("Second", b"2")]); } #[test] fn from_resource_manager() { let conn = mock_connection(Some(b"First: 1\n*Second: 2\n")); let db = new_from_default(&conn).unwrap(); check_db(&db, &[("First", b"1"), ("Second", b"2")]); } /// Create a mock connection that produces the given answer for queries of the RESOURCE_MANAGER /// property. fn mock_connection(resource_manager: Option<&[u8]>) -> impl Connection { // Ugly way to get a default screen: Parse enough zero bytes let screen = Screen::try_parse(&[0; 100]).unwrap().0; let mut setup = Setup::try_parse(&[0; 100]).unwrap().0; setup.roots.push(screen); MockConnection(setup, resource_manager.map(|v| v.to_vec())) } struct MockConnection(Setup, Option>); impl RequestConnection for MockConnection { type Buf = Vec; fn send_request_with_reply( &self, _: &[IoSlice<'_>], _: Vec, ) -> Result, ConnectionError> where R: TryParse, { Ok(Cookie::new(self, 42)) } fn send_request_with_reply_with_fds( &self, _: &[IoSlice<'_>], _: Vec, ) -> Result, ConnectionError> where R: TryParseFd, { unimplemented!() } fn send_request_without_reply( &self, _: &[IoSlice<'_>], _: Vec, ) -> Result, ConnectionError> { unimplemented!() } fn discard_reply(&self, _: SequenceNumber, _: RequestKind, _: DiscardMode) { unimplemented!() } fn prefetch_extension_information(&self, _: &'static str) -> Result<(), ConnectionError> { unimplemented!() } fn extension_information( &self, _: &'static str, ) -> Result, ConnectionError> { unimplemented!() } fn wait_for_reply_or_raw_error( &self, sequence: SequenceNumber, ) -> Result, ConnectionError> { let value = self.1.as_ref().map(|v| &v[..]).unwrap_or(&[]); // response type let mut reply = vec![1]; // format if value.is_empty() { reply.push(0); } else { reply.push(8); } // sequence (sequence as u16).serialize_into(&mut reply); // length 0u32.serialize_into(&mut reply); // type (STRING) 31u32.serialize_into(&mut reply); // bytes_after 0u32.serialize_into(&mut reply); (value.len() as u32).serialize_into(&mut reply); // padding reply.extend([0; 12].iter().copied()); reply.extend(value); Ok(ReplyOrError::Reply(reply)) } fn wait_for_reply(&self, _: SequenceNumber) -> Result, ConnectionError> { unimplemented!() } fn wait_for_reply_with_fds_raw( &self, _: SequenceNumber, ) -> Result, Self::Buf>, ConnectionError> { unimplemented!() } fn check_for_raw_error( &self, _: SequenceNumber, ) -> Result, ConnectionError> { unimplemented!() } fn prefetch_maximum_request_bytes(&self) { unimplemented!() } fn maximum_request_bytes(&self) -> usize { unimplemented!() } fn parse_error(&self, _: &[u8]) -> Result { unimplemented!() } fn parse_event(&self, _: &[u8]) -> Result { unimplemented!() } } impl Connection for MockConnection { fn wait_for_raw_event_with_sequence( &self, ) -> Result, ConnectionError> { unimplemented!() } fn poll_for_raw_event_with_sequence( &self, ) -> Result>, ConnectionError> { unimplemented!() } fn flush(&self) -> Result<(), ConnectionError> { unimplemented!() } fn setup(&self) -> &Setup { &self.0 } fn generate_id(&self) -> Result { unimplemented!() } } }