277 lines
8.8 KiB
Rust
277 lines
8.8 KiB
Rust
#[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>, path: impl AsRef<Path>, 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<Path>) -> 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<Vec<u8>>);
|
|
|
|
impl RequestConnection for MockConnection {
|
|
type Buf = Vec<u8>;
|
|
|
|
fn send_request_with_reply<R>(
|
|
&self,
|
|
_: &[IoSlice<'_>],
|
|
_: Vec<RawFdContainer>,
|
|
) -> Result<Cookie<'_, Self, R>, ConnectionError>
|
|
where
|
|
R: TryParse,
|
|
{
|
|
Ok(Cookie::new(self, 42))
|
|
}
|
|
|
|
fn send_request_with_reply_with_fds<R>(
|
|
&self,
|
|
_: &[IoSlice<'_>],
|
|
_: Vec<RawFdContainer>,
|
|
) -> Result<CookieWithFds<'_, Self, R>, ConnectionError>
|
|
where
|
|
R: TryParseFd,
|
|
{
|
|
unimplemented!()
|
|
}
|
|
|
|
fn send_request_without_reply(
|
|
&self,
|
|
_: &[IoSlice<'_>],
|
|
_: Vec<RawFdContainer>,
|
|
) -> Result<VoidCookie<'_, Self>, 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<Option<ExtensionInformation>, ConnectionError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn wait_for_reply_or_raw_error(
|
|
&self,
|
|
sequence: SequenceNumber,
|
|
) -> Result<ReplyOrError<Self::Buf>, 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<Option<Self::Buf>, ConnectionError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn wait_for_reply_with_fds_raw(
|
|
&self,
|
|
_: SequenceNumber,
|
|
) -> Result<ReplyOrError<BufWithFds<Self::Buf>, Self::Buf>, ConnectionError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn check_for_raw_error(
|
|
&self,
|
|
_: SequenceNumber,
|
|
) -> Result<Option<Self::Buf>, ConnectionError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn prefetch_maximum_request_bytes(&self) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn maximum_request_bytes(&self) -> usize {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn parse_error(&self, _: &[u8]) -> Result<X11Error, ParseError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn parse_event(&self, _: &[u8]) -> Result<Event, ParseError> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
impl Connection for MockConnection {
|
|
fn wait_for_raw_event_with_sequence(
|
|
&self,
|
|
) -> Result<RawEventAndSeqNumber<Self::Buf>, ConnectionError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn poll_for_raw_event_with_sequence(
|
|
&self,
|
|
) -> Result<Option<RawEventAndSeqNumber<Self::Buf>>, ConnectionError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn flush(&self) -> Result<(), ConnectionError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn setup(&self) -> &Setup {
|
|
&self.0
|
|
}
|
|
|
|
fn generate_id(&self) -> Result<u32, ReplyOrIdError> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
}
|