1
0
Fork 0
forked from wry/wry

autocommit 2022-05-01 17:23:55 CEST

This commit is contained in:
Julian Orth 2022-05-01 17:23:55 +02:00
parent 4373ed05bf
commit e1d5bf0e5d
39 changed files with 1772 additions and 57 deletions

View file

@ -7,7 +7,7 @@ use {
},
std::rc::Rc,
thiserror::Error,
uapi::{c, format_ustr, Errno, OwnedFd, Ustring},
uapi::{c, format_ustr, Errno, OwnedFd, Ustr, Ustring},
};
#[derive(Debug, Error)]
@ -48,7 +48,7 @@ pub struct Acceptor {
struct AllocatedSocket {
// wayland-x
name: Ustring,
name: String,
// /run/user/1000/wayland-x
path: Ustring,
insecure: Rc<OwnedFd>,
@ -56,6 +56,8 @@ struct AllocatedSocket {
lock_path: Ustring,
_lock_fd: OwnedFd,
// /run/user/1000/wayland-x.jay
#[cfg_attr(not(feature = "it"), allow(dead_code))]
secure_path: Ustring,
secure: Rc<OwnedFd>,
}
@ -74,8 +76,8 @@ fn bind_socket(
) -> Result<AllocatedSocket, AcceptorError> {
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
let name = format_ustr!("wayland-{}", id);
let path = format_ustr!("{}/{}", xrd, name.display());
let name = format!("wayland-{}", id);
let path = format_ustr!("{}/{}", xrd, name);
let jay_path = format_ustr!("{}.jay", path.display());
let lock_path = format_ustr!("{}.lock", path.display());
if jay_path.len() + 1 > addr.sun_path.len() {
@ -110,6 +112,7 @@ fn bind_socket(
insecure: insecure.clone(),
lock_path,
_lock_fd: lock_fd,
secure_path: jay_path,
secure: secure.clone(),
})
}
@ -145,7 +148,7 @@ fn allocate_socket() -> Result<AllocatedSocket, AcceptorError> {
}
impl Acceptor {
pub fn install(state: &Rc<State>) -> Result<Rc<String>, AcceptorError> {
pub fn install(state: &Rc<State>) -> Result<Rc<Acceptor>, AcceptorError> {
let socket = allocate_socket()?;
log::info!("bound to socket {}", socket.path.display());
for fd in [&socket.secure, &socket.insecure] {
@ -155,7 +158,6 @@ impl Acceptor {
}
let id1 = state.el.id();
let id2 = state.el.id();
let name = socket.name.to_owned();
let acc = Rc::new(Acceptor {
ids: [id1, id2],
socket,
@ -169,10 +171,18 @@ impl Acceptor {
)?;
state
.el
.insert(id2, Some(acc.socket.secure.raw()), c::EPOLLIN, acc)?;
let name = Rc::new(name.display().to_string());
state.socket_path.set(name.clone());
Ok(name)
.insert(id2, Some(acc.socket.secure.raw()), c::EPOLLIN, acc.clone())?;
state.acceptor.set(Some(acc.clone()));
Ok(acc)
}
pub fn socket_name(&self) -> &str {
&self.socket.name
}
#[cfg_attr(not(feature = "it"), allow(dead_code))]
pub fn secure_path(&self) -> &Ustr {
self.socket.secure_path.as_ustr()
}
}

View file

@ -6,6 +6,7 @@ use {
video::drm::ConnectorType,
},
std::{
any::Any,
error::Error,
fmt::{Debug, Display, Formatter},
rc::Rc,
@ -17,6 +18,7 @@ linear_ids!(InputDeviceIds, InputDeviceId);
pub trait Backend {
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>>;
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
fn switch_to(&self, vtnr: u32) {
let _ = vtnr;
@ -30,7 +32,7 @@ pub trait Backend {
false
}
fn is_freestanding(&self) -> bool {
fn import_environment(&self) -> bool {
false
}
@ -57,6 +59,7 @@ pub struct MonitorInfo {
pub height_mm: i32,
}
#[derive(Copy, Clone, Debug)]
pub struct ConnectorKernelId {
pub ty: ConnectorType,
pub idx: u32,
@ -84,6 +87,8 @@ pub enum ConnectorEvent {
ModeChanged(Mode),
}
pub type TransformMatrix = [[f64; 2]; 2];
pub trait InputDevice {
fn id(&self) -> InputDeviceId;
fn removed(&self) -> bool;
@ -94,11 +99,11 @@ pub trait InputDevice {
fn set_left_handed(&self, left_handed: bool);
fn set_accel_profile(&self, profile: InputDeviceAccelProfile);
fn set_accel_speed(&self, speed: f64);
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]);
fn set_transform_matrix(&self, matrix: TransformMatrix);
fn name(&self) -> Rc<String>;
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub enum InputDeviceCapability {
Keyboard,
Pointer,

View file

@ -4,7 +4,7 @@ use {
backend::{Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId},
video::drm::ConnectorType,
},
std::{error::Error, rc::Rc},
std::{any::Any, error::Error, rc::Rc},
};
pub struct DummyBackend;
@ -13,6 +13,10 @@ impl Backend for DummyBackend {
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>> {
unreachable!();
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
pub struct DummyOutput {

View file

@ -7,7 +7,7 @@ use {
async_engine::{AsyncError, AsyncFd, SpawnedFuture},
backend::{
Backend, BackendEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
InputDeviceId, InputEvent, KeyState,
InputDeviceId, InputEvent, KeyState, TransformMatrix,
},
backends::metal::video::{MetalDrmDevice, PendingDrmDevice},
dbus::{DbusError, SignalHandler},
@ -40,6 +40,7 @@ use {
},
},
std::{
any::Any,
cell::{Cell, RefCell},
error::Error,
ffi::{CStr, CString},
@ -148,6 +149,10 @@ impl Backend for MetalBackend {
})
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn switch_to(&self, vtnr: u32) {
self.session.switch_to(vtnr, move |res| {
if let Err(e) = res {
@ -188,7 +193,7 @@ impl Backend for MetalBackend {
true
}
fn is_freestanding(&self) -> bool {
fn import_environment(&self) -> bool {
true
}
@ -288,7 +293,7 @@ struct MetalInputDevice {
left_handed: Cell<Option<bool>>,
accel_profile: Cell<Option<AccelProfile>>,
accel_speed: Cell<Option<f64>>,
transform_matrix: Cell<Option<[[f64; 2]; 2]>>,
transform_matrix: Cell<Option<TransformMatrix>>,
}
impl Drop for MetalInputDevice {
@ -419,7 +424,7 @@ impl InputDevice for MetalInputDevice {
}
}
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]) {
fn set_transform_matrix(&self, matrix: TransformMatrix) {
self.transform_matrix.set(Some(matrix));
}

View file

@ -4,7 +4,7 @@ use {
backend::{
AxisSource, Backend, BackendEvent, Connector, ConnectorEvent, ConnectorId,
ConnectorKernelId, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis,
InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix,
},
fixed::Fixed,
format::XRGB8888,
@ -48,6 +48,7 @@ use {
},
},
std::{
any::Any,
borrow::Cow,
cell::{Cell, RefCell},
collections::VecDeque,
@ -242,6 +243,10 @@ impl Backend for XBackend {
Ok(())
})
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
pub struct XBackend {
@ -1054,7 +1059,7 @@ impl InputDevice for XSeatKeyboard {
let _ = speed;
}
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]) {
fn set_transform_matrix(&self, matrix: TransformMatrix) {
let _ = matrix;
}
@ -1103,7 +1108,7 @@ impl InputDevice for XSeatMouse {
let _ = speed;
}
fn set_transform_matrix(&self, matrix: [[f64; 2]; 2]) {
fn set_transform_matrix(&self, matrix: TransformMatrix) {
let _ = matrix;
}

View file

@ -44,6 +44,8 @@ pub enum Cmd {
Screenshot(ScreenshotArgs),
/// Inspect/modify the idle (screensaver) settings.
Idle(IdleArgs),
#[cfg(feature = "it")]
RunTests,
}
#[derive(Args, Debug)]
@ -101,7 +103,7 @@ pub struct ScreenshotArgs {
pub filename: Option<String>,
}
#[derive(Args, Debug)]
#[derive(Args, Debug, Default)]
pub struct RunArgs {
/// The backends to try.
///
@ -184,5 +186,7 @@ pub fn main() {
Cmd::SetLogLevel(a) => set_log_level::main(cli.global, a),
Cmd::Screenshot(a) => screenshot::main(cli.global, a),
Cmd::Idle(a) => idle::main(cli.global, a),
#[cfg(feature = "it")]
Cmd::RunTests => crate::it::run_tests(),
}
}

View file

@ -42,6 +42,11 @@ impl ClientId {
pub fn raw(self) -> u64 {
self.0
}
#[cfg_attr(not(feature = "it"), allow(dead_code))]
pub fn from_raw(val: u64) -> Self {
Self(val)
}
}
impl Display for ClientId {

View file

@ -1,3 +1,5 @@
#[cfg(feature = "it")]
use crate::it::test_backend::TestBackend;
use {
crate::{
acceptor::{Acceptor, AcceptorError},
@ -36,7 +38,7 @@ use {
},
ahash::AHashSet,
forker::ForkerProxy,
std::{cell::Cell, ops::Deref, rc::Rc, sync::Arc, time::Duration},
std::{cell::Cell, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration},
thiserror::Error,
uapi::c,
};
@ -44,12 +46,9 @@ use {
pub const MAX_EXTENTS: i32 = (1 << 22) - 1;
pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
let forker = match ForkerProxy::create() {
Ok(f) => Rc::new(f),
Err(e) => fatal!("Could not create a forker process: {}", ErrorFmt(e)),
};
let forker = create_forker();
let logger = Logger::install_compositor(global.log_level.into());
if let Err(e) = start_compositor2(forker, logger.clone(), args) {
if let Err(e) = start_compositor2(forker, Some(logger.clone()), args, None) {
let e = ErrorFmt(e);
log::error!("A fatal error occurred: {}", e);
eprintln!("A fatal error occurred: {}", e);
@ -59,8 +58,21 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
log::info!("Exit");
}
#[cfg(feature = "it")]
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
let forker = create_forker();
start_compositor2(forker, None, RunArgs::default(), Some(future))
}
fn create_forker() -> Rc<ForkerProxy> {
match ForkerProxy::create() {
Ok(f) => Rc::new(f),
Err(e) => fatal!("Could not create a forker process: {}", ErrorFmt(e)),
}
}
#[derive(Debug, Error)]
enum MainError {
pub enum CompositorError {
#[error("The client acceptor caused an error")]
AcceptorError(#[from] AcceptorError),
#[error("The event loop caused an error")]
@ -86,11 +98,14 @@ const STATIC_VARS: &[(&str, &str)] = &[
("_JAVA_AWT_WM_NONREPARENTING", "1"),
];
pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;
fn start_compositor2(
forker: Rc<ForkerProxy>,
logger: Arc<Logger>,
logger: Option<Arc<Logger>>,
run_args: RunArgs,
) -> Result<(), MainError> {
test_future: Option<TestFuture>,
) -> Result<(), CompositorError> {
log::info!("pid = {}", uapi::getpid());
init_fd_limit();
leaks::init();
@ -155,19 +170,22 @@ fn start_compositor2(
handler: Default::default(),
queue: Default::default(),
},
socket_path: Default::default(),
acceptor: Default::default(),
serial: Default::default(),
idle_inhibitor_ids: Default::default(),
run_toplevel,
});
create_dummy_output(&state);
let socket_path = Acceptor::install(&state)?;
let acceptor = Acceptor::install(&state)?;
forker.install(&state);
forker.setenv(WAYLAND_DISPLAY.as_bytes(), socket_path.as_bytes());
forker.setenv(
WAYLAND_DISPLAY.as_bytes(),
acceptor.socket_name().as_bytes(),
);
for (key, val) in STATIC_VARS {
forker.setenv(key.as_bytes(), val.as_bytes());
}
let _compositor = engine.spawn(start_compositor3(state.clone()));
let _compositor = engine.spawn(start_compositor3(state.clone(), test_future));
el.run()?;
state.xwayland.handler.borrow_mut().take();
state.clients.clear();
@ -178,8 +196,8 @@ fn start_compositor2(
Ok(())
}
async fn start_compositor3(state: Rc<State>) {
let backend = match create_backend(&state).await {
async fn start_compositor3(state: Rc<State>, test_future: Option<TestFuture>) {
let backend = match create_backend(&state, test_future).await {
Some(b) => b,
_ => {
log::error!("Could not create a backend");
@ -190,8 +208,10 @@ async fn start_compositor3(state: Rc<State>) {
state.backend.set(backend.clone());
state.globals.add_singletons(&backend);
if backend.is_freestanding() {
import_environment(&state, WAYLAND_DISPLAY, &state.socket_path.get());
if backend.import_environment() {
if let Some(acc) = state.acceptor.get() {
import_environment(&state, WAYLAND_DISPLAY, acc.socket_name());
}
for (key, val) in STATIC_VARS {
import_environment(&state, key, val);
}
@ -228,7 +248,17 @@ fn start_global_event_handlers(
res
}
async fn create_backend(state: &Rc<State>) -> Option<Rc<dyn Backend>> {
async fn create_backend(
state: &Rc<State>,
#[allow(unused_variables)] test_future: Option<TestFuture>,
) -> Option<Rc<dyn Backend>> {
#[cfg(feature = "it")]
if let Some(tf) = test_future {
return Some(Rc::new(TestBackend {
state: state.clone(),
test_future: tf,
}));
}
let mut backends = &state.run_args.backends[..];
if backends.is_empty() {
backends = &[CliBackend::X11, CliBackend::Metal];

View file

@ -13,6 +13,7 @@ use {
},
wire::{jay_compositor::*, JayCompositorId},
},
bstr::ByteSlice,
log::Level,
std::{ops::Deref, rc::Rc},
thiserror::Error,
@ -80,7 +81,11 @@ impl JayCompositor {
let log_file = Rc::new(JayLogFile::new(req.id, &self.client));
track!(self.client, log_file);
self.client.add_client_obj(&log_file)?;
log_file.send_path(self.client.state.logger.path());
let path = match &self.client.state.logger {
Some(logger) => logger.path(),
_ => "".as_bytes().as_bstr(),
};
log_file.send_path(path);
Ok(())
}
@ -106,7 +111,9 @@ impl JayCompositor {
TRACE => Level::Trace,
_ => return Err(JayCompositorError::UnknownLogLevel(req.level)),
};
self.client.state.logger.set_level(level);
if let Some(logger) = &self.client.state.logger {
logger.set_level(level);
}
Ok(())
}
@ -152,6 +159,15 @@ impl JayCompositor {
self.client.add_client_obj(&idle)?;
Ok(())
}
fn get_client_id(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> {
let _req: GetClientId = self.client.parse(self, parser)?;
self.client.event(ClientId {
self_id: self.id,
client_id: self.client.id.raw(),
});
Ok(())
}
}
object_base! {
@ -163,11 +179,12 @@ object_base! {
SET_LOG_LEVEL => set_log_level,
TAKE_SCREENSHOT => take_screenshot,
GET_IDLE => get_idle,
GET_CLIENT_ID => get_client_id,
}
impl Object for JayCompositor {
fn num_requests(&self) -> u32 {
GET_IDLE + 1
GET_CLIENT_ID + 1
}
}

113
src/it.rs Normal file
View file

@ -0,0 +1,113 @@
#![cfg(feature = "it")]
use {
crate::it::{test_backend::TestBackend, testrun::TestRun, tests::TestCase},
ahash::AHashMap,
isnt::std_1::collections::IsntHashMapExt,
log::Level,
std::{
cell::{Cell, RefCell},
future::pending,
pin::Pin,
rc::Rc,
time::SystemTime,
},
uapi::c,
};
#[macro_use]
mod test_error;
#[macro_use]
mod test_object;
pub mod test_backend;
mod test_client;
mod test_ifs;
mod test_logger;
mod test_mem;
mod test_transport;
mod testrun;
mod tests;
pub fn run_tests() {
test_logger::install();
test_logger::set_level(Level::Trace);
let it_run = ItRun {
path: format!(
"{}/testruns/{}",
env!("CARGO_MANIFEST_DIR"),
humantime::format_rfc3339_millis(SystemTime::now())
),
failed: Default::default(),
};
for test in tests::tests() {
run_test(&it_run, test);
}
let failed = it_run.failed.borrow_mut();
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: RefCell<AHashMap<&'static str, Vec<String>>>,
}
fn run_test(it_run: &ItRun, test: &'static dyn TestCase) {
let dir = format!("{}/{}", it_run.path, test.name());
std::fs::create_dir_all(&dir).unwrap();
let log_path = format!("{}/log", dir);
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 server_addr = {
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
let acceptor = state.acceptor.get().unwrap();
let path = acceptor.secure_path();
let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
sun_path[..path.len()].copy_from_slice(path.as_bytes());
sun_path[path.len()] = 0;
addr
};
let backend: Rc<TestBackend> = state.backend.get().into_any().downcast().unwrap();
let testrun = Rc::new(TestRun {
state: state.clone(),
backend,
errors: Default::default(),
server_addr,
});
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());
}
errors.set(testrun.errors.take());
state.el.stop();
pending().await
})
}));
let errors = errors.take();
if errors.len() > 0 {
log::error!("The following errors occurred:");
for e in &errors {
log::error!(" {}", e);
}
it_run.failed.borrow_mut().insert(test.name(), errors);
}
test_logger::unset_file();
let _ = res;
}

136
src/it/test_backend.rs Normal file
View file

@ -0,0 +1,136 @@
use {
crate::{
async_engine::SpawnedFuture,
backend::{
Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, InputDevice,
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent,
TransformMatrix,
},
compositor::TestFuture,
state::State,
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, syncqueue::SyncQueue},
},
std::{any::Any, cell::Cell, error::Error, pin::Pin, rc::Rc},
};
pub struct TestBackend {
pub state: Rc<State>,
pub test_future: TestFuture,
}
impl Backend for TestBackend {
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>> {
let future = (self.test_future)(&self.state);
self.state.eng.spawn(async move {
let future: Pin<_> = future.into();
future.await;
Ok(())
})
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn switch_to(&self, vtnr: u32) {
let _ = vtnr;
}
fn set_idle(&self, _idle: bool) {}
fn supports_idle(&self) -> bool {
true
}
fn supports_presentation_feedback(&self) -> bool {
true
}
}
pub struct TestConnector {
pub id: ConnectorId,
pub kernel_id: ConnectorKernelId,
pub events: SyncQueue<ConnectorEvent>,
pub on_change: CloneCell<Option<Rc<dyn Fn()>>>,
}
impl Connector for TestConnector {
fn id(&self) -> ConnectorId {
self.id
}
fn kernel_id(&self) -> ConnectorKernelId {
self.kernel_id
}
fn event(&self) -> Option<ConnectorEvent> {
self.events.pop()
}
fn on_change(&self, cb: Rc<dyn Fn()>) {
self.on_change.set(Some(cb));
}
fn damage(&self) {
todo!()
}
}
pub struct TestInputDevice {
pub id: InputDeviceId,
pub remove: Cell<bool>,
pub events: SyncQueue<InputEvent>,
pub on_change: CloneCell<Option<Rc<dyn Fn()>>>,
pub capabilities: CopyHashMap<InputDeviceCapability, ()>,
pub transform_matrix: Cell<TransformMatrix>,
pub name: Rc<String>,
pub accel_speed: Cell<f64>,
pub accel_profile: Cell<InputDeviceAccelProfile>,
pub left_handed: Cell<bool>,
}
impl InputDevice for TestInputDevice {
fn id(&self) -> InputDeviceId {
self.id
}
fn removed(&self) -> bool {
self.remove.get()
}
fn event(&self) -> Option<InputEvent> {
self.events.pop()
}
fn on_change(&self, cb: Rc<dyn Fn()>) {
self.on_change.set(Some(cb));
}
fn grab(&self, _grab: bool) {
// nothing
}
fn has_capability(&self, cap: InputDeviceCapability) -> bool {
self.capabilities.contains(&cap)
}
fn set_left_handed(&self, left_handed: bool) {
self.left_handed.set(left_handed);
}
fn set_accel_profile(&self, profile: InputDeviceAccelProfile) {
self.accel_profile.set(profile);
}
fn set_accel_speed(&self, speed: f64) {
self.accel_speed.set(speed)
}
fn set_transform_matrix(&self, matrix: TransformMatrix) {
self.transform_matrix.set(matrix);
}
fn name(&self) -> Rc<String> {
self.name.clone()
}
}

34
src/it/test_client.rs Normal file
View file

@ -0,0 +1,34 @@
use {
crate::{
client::Client,
it::{
test_ifs::{
test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor,
test_registry::TestRegistry, test_shm::TestShm,
},
test_transport::TestTransport,
testrun::TestRun,
},
},
std::rc::Rc,
};
pub struct TestClient {
pub run: Rc<TestRun>,
pub server: Rc<Client>,
pub transport: Rc<TestTransport>,
pub registry: Rc<TestRegistry>,
pub jc: Rc<TestJayCompositor>,
pub comp: Rc<TestCompositor>,
pub shm: Rc<TestShm>,
}
impl TestClient {
pub fn error(&self, msg: &str) {
self.transport.error(msg)
}
pub async fn sync(self: &Rc<Self>) {
self.transport.sync().await
}
}

136
src/it/test_error.rs Normal file
View file

@ -0,0 +1,136 @@
use {
crate::utils::errorfmt::ErrorFmt,
std::{
error::Error,
fmt::{Debug, Display, Formatter},
},
};
pub struct TestError {
error: Box<dyn Error + 'static>,
source: Option<Box<TestError>>,
}
impl TestError {
pub fn new<D: Display + 'static>(d: D) -> Self {
Self {
error: Box::new(DisplayError { msg: d }),
source: None,
}
}
}
struct DisplayError<T: Display> {
msg: T,
}
impl<T: Display> Debug for DisplayError<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.msg, f)
}
}
impl<T: Display> Display for DisplayError<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.msg, f)
}
}
impl<T: Display> Error for DisplayError<T> {}
impl Debug for TestError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TestError")
.field("error", &self.error)
.field("source", &self.source)
.finish()
}
}
impl Display for TestError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut e_prev = self;
let mut e_opt = Some(self);
let mut first = true;
while let Some(e) = e_opt {
if first {
write!(f, "{}", e.error)?;
first = false;
} else {
write!(f, ": {}", e.error)?;
}
e_prev = e;
e_opt = e.source.as_deref();
}
if let Some(e) = e_prev.error.source() {
write!(f, ": ")?;
ErrorFmt(e).fmt(f)?;
}
Ok(())
}
}
impl<T: Error + 'static> From<T> for TestError {
fn from(error: T) -> Self {
Self {
error: Box::new(error),
source: None,
}
}
}
pub trait TestErrorExt {
type Context;
fn with_context<T, F>(self, f: F) -> Self::Context
where
T: Display + 'static,
F: FnOnce() -> T;
}
impl<T, E> TestErrorExt for Result<T, E>
where
E: StdError,
{
type Context = Result<T, TestError>;
fn with_context<D, F>(self, f: F) -> Self::Context
where
D: Display + 'static,
F: FnOnce() -> D,
{
match self {
Ok(v) => Ok(v),
Err(e) => Err(e.with_context(f())),
}
}
}
pub trait StdError: 'static {
fn with_context<D: Display + 'static>(self, d: D) -> TestError;
}
impl<E: Error + 'static> StdError for E {
fn with_context<D: Display + 'static>(self, d: D) -> TestError {
TestError {
error: Box::new(DisplayError { msg: d }),
source: Some(Box::new(self.into())),
}
}
}
impl StdError for TestError {
fn with_context<D: Display + 'static>(self, d: D) -> TestError {
TestError {
error: Box::new(DisplayError { msg: d }),
source: Some(Box::new(self)),
}
}
}
macro_rules! bail {
($($tt:tt)*) => {{
let msg = format!($($tt)*);
return Err(crate::it::test_error::TestError::new(msg));
}}
}

8
src/it/test_ifs.rs Normal file
View file

@ -0,0 +1,8 @@
pub mod test_callback;
pub mod test_compositor;
pub mod test_display;
pub mod test_jay_compositor;
pub mod test_registry;
pub mod test_shm;
pub mod test_shm_buffer;
pub mod test_shm_pool;

View file

@ -0,0 +1,45 @@
use {
crate::{
it::{
test_error::TestError, test_object::TestObject, test_transport::TestTransport,
testrun::ParseFull,
},
utils::buffd::MsgParser,
wire::{wl_callback::*, WlCallbackId},
},
std::{cell::Cell, rc::Rc},
};
pub struct TestCallback {
pub id: WlCallbackId,
pub transport: Rc<TestTransport>,
pub handler: Cell<Option<Box<dyn FnOnce()>>>,
pub done: Cell<bool>,
}
impl TestCallback {
fn handle_done(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let _ev = Done::parse_full(parser)?;
self.dispatch();
Ok(())
}
fn dispatch(&self) {
self.done.set(true);
if let Some(handler) = self.handler.take() {
handler();
}
}
}
test_object! {
TestCallback, WlCallback;
DONE => handle_done,
}
impl TestObject for TestCallback {
fn on_remove(&self, _transport: &TestTransport) {
self.dispatch();
}
}

View file

@ -0,0 +1,18 @@
use {
crate::{
it::{test_object::TestObject, test_transport::TestTransport},
wire::WlCompositorId,
},
std::rc::Rc,
};
pub struct TestCompositor {
pub id: WlCompositorId,
pub transport: Rc<TestTransport>,
}
test_object! {
TestCompositor, WlCompositor;
}
impl TestObject for TestCompositor {}

View file

@ -0,0 +1,55 @@
use {
crate::{
it::{
test_error::TestError, test_object::TestObject, test_transport::TestTransport,
testrun::ParseFull,
},
object::ObjectId,
utils::buffd::MsgParser,
wire::{wl_display::*, WlDisplayId},
},
std::rc::Rc,
};
pub struct TestDisplay {
pub transport: Rc<TestTransport>,
pub id: WlDisplayId,
}
impl TestDisplay {
fn handle_error(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = Error::parse_full(parser)?;
let msg = format!("Compositor sent an error: {}", ev.message);
self.transport.error(&msg);
self.transport.kill();
Ok(())
}
fn handle_delete_id(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = DeleteId::parse_full(parser)?;
match self.transport.objects.remove(&ObjectId::from_raw(ev.id)) {
None => {
let msg = format!(
"Compositor sent delete_id for object {} which does not exist",
ev.id
);
self.transport.error(&msg);
self.transport.kill();
}
Some(obj) => {
obj.on_remove(&self.transport);
self.transport.obj_ids.borrow_mut().release(ev.id);
}
}
Ok(())
}
}
test_object! {
TestDisplay, WlDisplay;
ERROR => handle_error,
DELETE_ID => handle_delete_id,
}
impl TestObject for TestDisplay {}

View file

@ -0,0 +1,48 @@
use {
crate::{
client::ClientId,
it::{
test_error::TestError, test_object::TestObject, test_transport::TestTransport,
testrun::ParseFull,
},
utils::buffd::MsgParser,
wire::{
jay_compositor::{self, *},
JayCompositorId,
},
},
std::{cell::Cell, rc::Rc},
};
pub struct TestJayCompositor {
pub id: JayCompositorId,
pub transport: Rc<TestTransport>,
pub client_id: Cell<Option<ClientId>>,
}
impl TestJayCompositor {
pub async fn get_client_id(&self) -> Result<ClientId, TestError> {
if self.client_id.get().is_none() {
self.transport.send(GetClientId { self_id: self.id });
}
self.transport.sync().await;
match self.client_id.get() {
Some(c) => Ok(c),
_ => bail!("Compositor did not send a client id"),
}
}
fn handle_client_id(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = jay_compositor::ClientId::parse_full(parser)?;
self.client_id.set(Some(ClientId::from_raw(ev.client_id)));
Ok(())
}
}
test_object! {
TestJayCompositor, JayCompositor;
CLIENT_ID => handle_client_id,
}
impl TestObject for TestJayCompositor {}

View file

@ -0,0 +1,185 @@
use {
crate::{
it::{
test_error::TestError,
test_ifs::{
test_compositor::TestCompositor, test_jay_compositor::TestJayCompositor,
test_shm::TestShm,
},
test_object::TestObject,
test_transport::TestTransport,
testrun::ParseFull,
},
utils::{buffd::MsgParser, clonecell::CloneCell, copyhashmap::CopyHashMap},
wire::{wl_registry::*, WlRegistryId},
},
std::{cell::Cell, rc::Rc},
};
pub struct TestGlobal {
pub name: u32,
pub interface: String,
pub version: u32,
}
pub struct TestRegistrySingletons {
pub jay_compositor: u32,
pub wl_compositor: u32,
pub wl_shm: u32,
}
pub struct TestRegistry {
pub id: WlRegistryId,
pub transport: Rc<TestTransport>,
pub globals: CopyHashMap<u32, Rc<TestGlobal>>,
pub singletons: CloneCell<Option<Rc<TestRegistrySingletons>>>,
pub jay_compositor: CloneCell<Option<Rc<TestJayCompositor>>>,
pub compositor: CloneCell<Option<Rc<TestCompositor>>>,
pub shm: CloneCell<Option<Rc<TestShm>>>,
}
macro_rules! singleton {
($field:expr) => {
if let Some(s) = $field.get() {
return Ok(s);
}
};
}
impl TestRegistry {
pub async fn get_singletons(&self) -> Result<Rc<TestRegistrySingletons>, TestError> {
singleton!(self.singletons);
self.transport.sync().await;
singleton!(self.singletons);
let mut jay_compositor = 0;
let mut wl_compositor = 0;
let mut wl_shm = 0;
for global in self.globals.lock().values() {
match global.interface.as_str() {
"jay_compositor" => jay_compositor = global.name,
"wl_compositor" => wl_compositor = global.name,
"wl_shm" => wl_shm = global.name,
_ => {}
}
}
macro_rules! singleton {
($($name:ident,)*) => {
TestRegistrySingletons {
$(
$name: {
if $name == 0 {
bail!("Compositor did not send {} singleton", stringify!($name));
}
$name
},
)*
}
}
}
let singletons = Rc::new(singleton! {
jay_compositor,
wl_compositor,
wl_shm,
});
self.singletons.set(Some(singletons.clone()));
Ok(singletons)
}
pub async fn get_jay_compositor(&self) -> Result<Rc<TestJayCompositor>, TestError> {
singleton!(self.jay_compositor);
let singletons = self.get_singletons().await?;
singleton!(self.jay_compositor);
let jc = Rc::new(TestJayCompositor {
id: self.transport.id(),
transport: self.transport.clone(),
client_id: Default::default(),
});
self.bind(&jc, singletons.jay_compositor, 1)?;
self.jay_compositor.set(Some(jc.clone()));
Ok(jc)
}
pub async fn get_compositor(&self) -> Result<Rc<TestCompositor>, TestError> {
singleton!(self.compositor);
let singletons = self.get_singletons().await?;
singleton!(self.compositor);
let jc = Rc::new(TestCompositor {
id: self.transport.id(),
transport: self.transport.clone(),
});
self.bind(&jc, singletons.wl_compositor, 4)?;
self.compositor.set(Some(jc.clone()));
Ok(jc)
}
pub async fn get_shm(&self) -> Result<Rc<TestShm>, TestError> {
singleton!(self.shm);
let singletons = self.get_singletons().await?;
singleton!(self.shm);
let jc = Rc::new(TestShm {
id: self.transport.id(),
transport: self.transport.clone(),
formats: Default::default(),
formats_awaited: Cell::new(false),
});
self.bind(&jc, singletons.wl_shm, 1)?;
self.shm.set(Some(jc.clone()));
Ok(jc)
}
pub fn bind<O: TestObject>(
&self,
obj: &Rc<O>,
name: u32,
version: u32,
) -> Result<(), TestError> {
self.transport.send(Bind {
self_id: self.id,
name,
interface: obj.interface().name(),
version,
id: obj.id().into(),
});
self.transport.add_obj(obj.clone())?;
Ok(())
}
fn handle_global(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = Global::parse_full(parser)?;
let prev = self.globals.set(
ev.name,
Rc::new(TestGlobal {
name: ev.name,
interface: ev.interface.to_string(),
version: ev.version,
}),
);
if prev.is_some() {
self.transport.error(&format!(
"Compositor sent global {} multiple times",
ev.name
));
}
Ok(())
}
fn handle_global_remove(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = GlobalRemove::parse_full(parser)?;
if self.globals.remove(&ev.name).is_none() {
self.transport.error(&format!(
"Compositor sent global_remove for {} which does not exist",
ev.name
));
}
Ok(())
}
}
test_object! {
TestRegistry, WlRegistry;
GLOBAL => handle_global,
GLOBAL_REMOVE => handle_global_remove,
}
impl TestObject for TestRegistry {}

View file

@ -0,0 +1,59 @@
use {
crate::{
it::{
test_error::TestError, test_ifs::test_shm_pool::TestShmPool, test_mem::TestMem,
test_object::TestObject, test_transport::TestTransport, testrun::ParseFull,
},
utils::{buffd::MsgParser, clonecell::CloneCell, copyhashmap::CopyHashMap},
wire::{wl_shm::*, WlShmId},
},
std::{cell::Cell, rc::Rc},
};
pub struct TestShm {
pub id: WlShmId,
pub transport: Rc<TestTransport>,
pub formats: CopyHashMap<u32, ()>,
pub formats_awaited: Cell<bool>,
}
impl TestShm {
pub async fn formats(&self) -> &CopyHashMap<u32, ()> {
if !self.formats_awaited.replace(true) {
self.transport.sync().await;
}
&self.formats
}
pub fn create_pool(&self, size: usize) -> Result<Rc<TestShmPool>, TestError> {
let mem = TestMem::new(size)?;
let pool = Rc::new(TestShmPool {
id: self.transport.id(),
transport: self.transport.clone(),
mem: CloneCell::new(mem.clone()),
destroyed: Cell::new(false),
});
self.transport.send(CreatePool {
self_id: self.id,
id: pool.id,
fd: mem.fd.clone(),
size: size as _,
});
self.transport.add_obj(pool.clone())?;
Ok(pool)
}
fn handle_format(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = Format::parse_full(parser)?;
self.formats.set(ev.format, ());
Ok(())
}
}
test_object! {
TestShm, WlShm;
FORMAT => handle_format,
}
impl TestObject for TestShm {}

View file

@ -0,0 +1,61 @@
use {
crate::{
it::{
test_error::TestError, test_mem::TestMem, test_object::TestObject,
test_transport::TestTransport, testrun::ParseFull,
},
utils::buffd::MsgParser,
wire::{wl_buffer::*, WlBufferId},
},
std::{
cell::Cell,
ops::{Deref, Range},
rc::Rc,
},
};
pub struct TestShmBuffer {
pub id: WlBufferId,
pub transport: Rc<TestTransport>,
pub range: Range<usize>,
pub mem: Rc<TestMem>,
pub released: Cell<bool>,
pub destroyed: Cell<bool>,
}
impl TestShmBuffer {
pub fn destroy(&self) {
if self.destroyed.replace(true) {
return;
}
self.transport.send(Destroy { self_id: self.id });
}
fn handle_release(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let _ev = Release::parse_full(parser)?;
self.released.set(true);
Ok(())
}
}
impl Deref for TestShmBuffer {
type Target = [Cell<u8>];
fn deref(&self) -> &Self::Target {
&self.mem[self.range.clone()]
}
}
impl Drop for TestShmBuffer {
fn drop(&mut self) {
self.destroy();
}
}
test_object! {
TestShmBuffer, WlBuffer;
RELEASE => handle_release,
}
impl TestObject for TestShmBuffer {}

View file

@ -0,0 +1,86 @@
use {
crate::{
format::Format,
it::{
test_error::TestError, test_ifs::test_shm_buffer::TestShmBuffer, test_mem::TestMem,
test_object::TestObject, test_transport::TestTransport,
},
utils::clonecell::CloneCell,
wire::{wl_shm_pool::*, WlShmPoolId},
},
std::{cell::Cell, rc::Rc},
};
pub struct TestShmPool {
pub id: WlShmPoolId,
pub transport: Rc<TestTransport>,
pub mem: CloneCell<Rc<TestMem>>,
pub destroyed: Cell<bool>,
}
impl TestShmPool {
pub fn create_buffer(
&self,
offset: i32,
width: i32,
height: i32,
stride: i32,
format: &Format,
) -> Result<Rc<TestShmBuffer>, TestError> {
let size = (height * stride) as usize;
let start = offset as usize;
let end = start + size;
let mem = self.mem.get();
if end > mem.len() {
bail!("Out-of-bounds buffer");
}
let buffer = Rc::new(TestShmBuffer {
id: self.transport.id(),
transport: self.transport.clone(),
range: start..end,
mem,
released: Cell::new(true),
destroyed: Cell::new(false),
});
self.transport.add_obj(buffer.clone())?;
self.transport.send(CreateBuffer {
self_id: self.id,
id: buffer.id,
offset,
width,
height,
stride,
format: format.wl_id.unwrap_or(format.drm),
});
Ok(buffer)
}
pub fn resize(&self, size: usize) -> Result<(), TestError> {
let mem = self.mem.get().grow(size)?;
self.mem.set(mem);
self.transport.send(Resize {
self_id: self.id,
size: size as _,
});
Ok(())
}
pub fn destroy(&self) {
if self.destroyed.replace(true) {
return;
}
self.transport.send(Destroy { self_id: self.id });
}
}
impl Drop for TestShmPool {
fn drop(&mut self) {
self.destroy()
}
}
test_object! {
TestShmPool, WlShmPool;
}
impl TestObject for TestShmPool {}

73
src/it/test_logger.rs Normal file
View file

@ -0,0 +1,73 @@
use {
crate::utils::clonecell::CloneCell,
log::{Level, LevelFilter, Log, Metadata, Record},
std::{cell::Cell, fmt::Write as FmtWrite, io::Write, rc::Rc, time::SystemTime},
uapi::{Fd, OwnedFd},
};
#[thread_local]
static LEVEL: Cell<Level> = Cell::new(Level::Info);
#[thread_local]
static FILE: CloneCell<Option<Rc<OwnedFd>>> = CloneCell::new(None);
pub fn install() {
log::set_logger(&Logger).unwrap();
log::set_max_level(LevelFilter::Info);
}
pub fn set_level(level: Level) {
LEVEL.set(level);
log::set_max_level(level.to_level_filter());
}
pub fn set_file(file: Rc<OwnedFd>) {
FILE.set(Some(file));
}
pub fn unset_file() {
FILE.set(None);
}
struct Logger;
impl Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= LEVEL.get()
}
fn log(&self, record: &Record) {
if record.level() > LEVEL.get() {
return;
}
let mut buf = String::new();
let now = SystemTime::now();
let _ = if let Some(mp) = record.module_path() {
writeln!(
buf,
"[{} {:5} {}] {}",
humantime::format_rfc3339_millis(now),
record.level(),
mp,
record.args(),
)
} else {
writeln!(
buf,
"[{} {:5}] {}",
humantime::format_rfc3339_millis(now),
record.level(),
record.args(),
)
};
let mut fd = match FILE.get() {
Some(f) => f.borrow(),
_ => Fd::new(2),
};
let _ = fd.write_all(buf.as_bytes());
}
fn flush(&self) {
// nothing
}
}

71
src/it/test_mem.rs Normal file
View file

@ -0,0 +1,71 @@
use {
crate::{
it::test_error::TestError,
utils::{oserror::OsError, ptr_ext::PtrExt},
},
std::{cell::Cell, ops::Deref, ptr, rc::Rc},
uapi::{c, OwnedFd},
};
pub struct TestMem {
pub fd: Rc<OwnedFd>,
slice: *const [Cell<u8>],
}
impl TestMem {
pub fn new(size: usize) -> Result<Rc<Self>, TestError> {
let fd = uapi::memfd_create("test_pool", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING)?;
uapi::fcntl_add_seals(fd.raw(), c::F_SEAL_SHRINK)?;
uapi::ftruncate(fd.raw(), size as _)?;
let slice = map(fd.raw(), size)?;
Ok(Rc::new(Self {
fd: Rc::new(fd),
slice,
}))
}
pub fn grow(&self, size: usize) -> Result<Rc<Self>, TestError> {
let cur_len = uapi::fstat(self.fd.raw())?;
if size > cur_len.st_size as _ {
uapi::ftruncate(self.fd.raw(), size as _)?;
}
let slice = map(self.fd.raw(), size)?;
Ok(Rc::new(Self {
fd: self.fd.clone(),
slice,
}))
}
}
impl Deref for TestMem {
type Target = [Cell<u8>];
fn deref(&self) -> &Self::Target {
unsafe { &*self.slice }
}
}
fn map(fd: c::c_int, size: usize) -> Result<*const [Cell<u8>], TestError> {
unsafe {
let res = c::mmap(
ptr::null_mut(),
size as _,
c::PROT_READ | c::PROT_WRITE,
c::MAP_SHARED,
fd,
0,
);
if res == c::MAP_FAILED {
bail!("Could not map memory: {}", OsError::default());
}
Ok(std::slice::from_raw_parts(res as _, size))
}
}
impl Drop for TestMem {
fn drop(&mut self) {
unsafe {
c::munmap(self.slice.deref().as_ptr() as _, self.slice.deref().len());
}
}
}

54
src/it/test_object.rs Normal file
View file

@ -0,0 +1,54 @@
use {
crate::{
it::{test_error::TestError, test_transport::TestTransport},
object::{Interface, ObjectId},
utils::buffd::MsgParser,
},
std::rc::Rc,
};
macro_rules! test_object {
($oname:ident, $ifname:ident; $($code:ident => $f:ident,)*) => {
impl crate::it::test_object::TestObjectBase for $oname {
fn id(&self) -> crate::object::ObjectId {
self.id.into()
}
#[allow(unused_variables, unreachable_code)]
fn handle_request(
self: std::rc::Rc<Self>,
request: u32,
parser: crate::utils::buffd::MsgParser<'_, '_>,
) -> Result<(), crate::it::test_error::TestError> {
use crate::it::test_error::TestErrorExt;
let res: Result<(), crate::it::test_error::TestError> = match request {
$(
$code => $oname::$f(&self, parser).with_context(|| format!("While handling a `{}` event", stringify!($f))),
)*
_ => Err(crate::it::test_error::TestError::new("Unknown event {}")),
};
res.with_context(|| format!("In object {} of type `{}`", self.id(), self.interface().name()))
}
fn interface(&self) -> crate::object::Interface {
crate::wire::$ifname
}
}
};
}
pub trait TestObjectBase: 'static {
fn id(&self) -> ObjectId;
fn handle_request(
self: Rc<Self>,
request: u32,
parser: MsgParser<'_, '_>,
) -> Result<(), TestError>;
fn interface(&self) -> Interface;
}
pub trait TestObject: TestObjectBase {
fn on_remove(&self, transport: &TestTransport) {
let _ = transport;
}
}

254
src/it/test_transport.rs Normal file
View file

@ -0,0 +1,254 @@
use {
crate::{
async_engine::{AsyncFd, SpawnedFuture},
client::{ClientId, EventFormatter},
it::{
test_error::{StdError, TestError},
test_ifs::{test_callback::TestCallback, test_registry::TestRegistry},
test_object::TestObject,
testrun::TestRun,
},
object::{ObjectId, WL_DISPLAY_ID},
utils::{
asyncevent::AsyncEvent,
bitfield::Bitfield,
buffd::{BufFdIn, BufFdOut, MsgFormatter, MsgParser, OutBuffer, OutBufferSwapchain},
copyhashmap::CopyHashMap,
stack::Stack,
vec_ext::VecExt,
},
wire::wl_display,
},
std::{
cell::{Cell, RefCell},
collections::VecDeque,
future::Future,
mem,
rc::Rc,
task::Poll,
},
};
pub struct TestTransport {
pub run: Rc<TestRun>,
pub fd: AsyncFd,
pub client_id: Cell<ClientId>,
pub bufs: Stack<Vec<u32>>,
pub swapchain: Rc<RefCell<OutBufferSwapchain>>,
pub flush_request: AsyncEvent,
pub incoming: Cell<Option<SpawnedFuture<()>>>,
pub outgoing: Cell<Option<SpawnedFuture<()>>>,
pub objects: CopyHashMap<ObjectId, Rc<dyn TestObject>>,
pub obj_ids: RefCell<Bitfield>,
pub killed: Cell<bool>,
}
impl TestTransport {
pub fn get_registry(self: &Rc<Self>) -> Rc<TestRegistry> {
let reg = Rc::new(TestRegistry {
id: self.id(),
transport: self.clone(),
globals: Default::default(),
singletons: Default::default(),
jay_compositor: Default::default(),
compositor: Default::default(),
shm: Default::default(),
});
self.send(wl_display::GetRegistry {
self_id: WL_DISPLAY_ID,
registry: reg.id,
});
let _ = self.add_obj(reg.clone());
reg
}
pub fn add_obj(&self, obj: Rc<dyn TestObject>) -> Result<(), TestError> {
if self.killed.get() {
bail!("Transport has already been killed");
}
let id = obj.id();
if self.objects.set(id, obj).is_some() {
bail!("There already is an object with id {}", id);
}
Ok(())
}
pub fn kill(&self) {
self.outgoing.take();
self.incoming.take();
for (_, object) in self.objects.lock().drain() {
object.on_remove(self);
}
}
pub fn sync(self: &Rc<Self>) -> impl Future<Output = ()> {
let cb = Rc::new(TestCallback {
id: self.id(),
transport: self.clone(),
handler: Cell::new(None),
done: Cell::new(self.killed.get()),
});
self.send(wl_display::Sync {
self_id: WL_DISPLAY_ID,
callback: cb.id,
});
let _ = self.add_obj(cb.clone());
futures_util::future::poll_fn(move |ctx| {
if cb.done.get() {
Poll::Ready(())
} else {
let waker = ctx.waker().clone();
cb.handler.set(Some(Box::new(move || waker.wake())));
Poll::Pending
}
})
}
pub fn id<T: From<ObjectId>>(&self) -> T {
ObjectId::from_raw(self.obj_ids.borrow_mut().acquire()).into()
}
pub fn error(&self, msg: &str) {
let msg = format!("In client {}: {}", self.client_id.get(), msg);
self.run.errors.push(msg);
}
pub fn init(self: &Rc<Self>) {
self.incoming.set(Some(
self.run.state.eng.spawn(
Incoming {
tc: self.clone(),
buf: BufFdIn::new(self.fd.clone()),
}
.run(),
),
));
self.outgoing.set(Some(
self.run.state.eng.spawn(
Outgoing {
tc: self.clone(),
buf: BufFdOut::new(self.fd.clone()),
buffers: Default::default(),
}
.run(),
),
));
}
pub fn send<M: EventFormatter>(&self, msg: M) {
if self.killed.get() {
return;
}
let mut fds = vec![];
let mut swapchain = self.swapchain.borrow_mut();
let mut fmt = MsgFormatter::new(&mut swapchain.cur, &mut fds);
msg.format(&mut fmt);
fmt.write_len();
if swapchain.cur.is_full() {
swapchain.commit();
}
self.flush_request.trigger();
}
}
struct Outgoing {
tc: Rc<TestTransport>,
buf: BufFdOut,
buffers: VecDeque<OutBuffer>,
}
impl Outgoing {
async fn run(mut self: Self) {
loop {
self.tc.flush_request.triggered().await;
if let Err(e) = self.flush().await {
let msg = format!(
"Could not process an outgoing message for client {}: {}",
self.tc.client_id.get(),
e
);
log::error!("{}", msg);
self.tc.run.errors.push(msg);
break;
}
}
}
async fn flush(&mut self) -> Result<(), TestError> {
{
let mut swapchain = self.tc.swapchain.borrow_mut();
swapchain.commit();
mem::swap(&mut swapchain.pending, &mut self.buffers);
}
while let Some(mut cur) = self.buffers.pop_front() {
if let Err(e) = self.buf.flush_no_timeout(&mut cur).await {
return Err(e.with_context("Could not write to wayland socket"));
}
self.tc.swapchain.borrow_mut().free.push(cur);
}
Ok(())
}
}
struct Incoming {
tc: Rc<TestTransport>,
buf: BufFdIn,
}
impl Incoming {
async fn run(mut self: Self) {
loop {
if let Err(e) = self.handle_msg().await {
let msg = format!(
"Could not process an incoming message for client {}: {}",
self.tc.client_id.get(),
e
);
log::error!("{}", msg);
self.tc.run.errors.push(msg);
break;
}
}
self.tc.kill();
}
async fn handle_msg(&mut self) -> Result<(), TestError> {
let mut hdr = [0u32, 0];
if let Err(e) = self.buf.read_full(&mut hdr[..]).await {
return Err(e.with_context("Could not read from wayland socket"));
}
let obj_id = ObjectId::from_raw(hdr[0]);
let len = (hdr[1] >> 16) as usize;
let request = hdr[1] & 0xffff;
if len < 8 {
bail!("Message size is < 8");
}
if len % 4 != 0 {
bail!("Message size is not a multiple of 4");
}
let len = len / 4 - 2;
let mut data_buf = self.tc.bufs.pop().unwrap_or_default();
data_buf.clear();
data_buf.reserve(len);
let unused = data_buf.split_at_spare_mut_ext().1;
if let Err(e) = self.buf.read_full(&mut unused[..len]).await {
return Err(e.with_context("Could not read from wayland socket"));
}
unsafe {
data_buf.set_len(len);
}
let object = match self.tc.objects.get(&obj_id) {
Some(obj) => obj,
_ => bail!(
"Compositor sent a message for object {} which does not exist",
obj_id
),
};
let parser = MsgParser::new(&mut self.buf, &data_buf);
object.handle_request(request, parser)?;
if data_buf.capacity() > 0 {
self.tc.bufs.push(data_buf);
}
Ok(())
}
}

100
src/it/testrun.rs Normal file
View file

@ -0,0 +1,100 @@
use {
crate::{
client::{ClientId, RequestParser},
it::{
test_backend::TestBackend,
test_client::TestClient,
test_error::{TestError, TestErrorExt},
test_ifs::test_display::TestDisplay,
test_transport::TestTransport,
},
object::WL_DISPLAY_ID,
state::State,
utils::{bitfield::Bitfield, buffd::MsgParser, oserror::OsErrorExt, stack::Stack},
},
std::{
cell::{Cell, RefCell},
rc::Rc,
},
uapi::c,
};
pub struct TestRun {
pub state: Rc<State>,
pub backend: Rc<TestBackend>,
pub errors: Stack<String>,
pub server_addr: c::sockaddr_un,
}
impl TestRun {
pub async fn create_client(self: &Rc<Self>) -> Result<Rc<TestClient>, TestError> {
self.create_client2()
.await
.with_context(|| "Could not create a client")
}
async fn create_client2(self: &Rc<Self>) -> Result<Rc<TestClient>, TestError> {
let socket = uapi::socket(
c::AF_UNIX,
c::SOCK_STREAM | c::SOCK_CLOEXEC | c::SOCK_NONBLOCK,
0,
)
.to_os_error()
.with_context(|| "Could not create a unix socket")?;
let socket = Rc::new(socket);
uapi::connect(socket.raw(), &self.server_addr)
.to_os_error()
.with_context(|| "Could not connect to the compositor")?;
let fd = self
.state
.eng
.fd(&socket)
.with_context(|| "Could not create an async fd")?;
let mut obj_ids = Bitfield::default();
obj_ids.take(0);
obj_ids.take(1);
let transport = Rc::new(TestTransport {
run: self.clone(),
fd,
client_id: Cell::new(ClientId::from_raw(0)),
bufs: Default::default(),
swapchain: Default::default(),
flush_request: Default::default(),
incoming: Default::default(),
outgoing: Default::default(),
objects: Default::default(),
obj_ids: RefCell::new(obj_ids),
killed: Cell::new(false),
});
transport.add_obj(Rc::new(TestDisplay {
transport: transport.clone(),
id: WL_DISPLAY_ID,
}))?;
transport.init();
let registry = transport.get_registry();
let jc = registry.get_jay_compositor().await?;
let client_id = jc.get_client_id().await?;
let client = self.state.clients.get(client_id)?;
Ok(Rc::new(TestClient {
run: self.clone(),
server: client,
transport,
jc,
comp: registry.get_compositor().await?,
shm: registry.get_shm().await?,
registry,
}))
}
}
pub trait ParseFull<'a>: Sized {
fn parse_full(parser: MsgParser<'_, 'a>) -> Result<Self, TestError>;
}
impl<'a, T: RequestParser<'a>> ParseFull<'a> for T {
fn parse_full(mut parser: MsgParser<'_, 'a>) -> Result<Self, TestError> {
let res = T::parse(&mut parser)?;
parser.eof()?;
Ok(res)
}
}

47
src/it/tests.rs Normal file
View file

@ -0,0 +1,47 @@
use {
crate::it::{test_error::TestError, testrun::TestRun},
std::{future::Future, rc::Rc},
};
macro_rules! testcase {
() => {
pub struct Test;
impl crate::it::tests::TestCase for Test {
fn name(&self) -> &'static str {
module_path!().strip_prefix("jay::it::tests::").unwrap()
}
fn run(
&self,
testrun: std::rc::Rc<crate::it::testrun::TestRun>,
) -> Box<dyn std::future::Future<Output = Result<(), TestError>>> {
Box::new(test(testrun))
}
}
};
}
macro_rules! tassert {
($cond:expr) => {
if !$cond {
bail!(
"Assert `{}` failed ({}:{})",
stringify!($cond),
file!(),
line!()
);
}
};
}
mod t0001_shm_formats;
pub trait TestCase {
fn name(&self) -> &'static str;
fn run(&self, testrun: Rc<TestRun>) -> Box<dyn Future<Output = Result<(), TestError>>>;
}
pub fn tests() -> Vec<&'static dyn TestCase> {
vec![&t0001_shm_formats::Test]
}

View file

@ -0,0 +1,18 @@
use {
crate::{
format::{ARGB8888, XRGB8888},
it::{test_error::TestError, testrun::TestRun},
},
std::rc::Rc,
};
testcase!();
/// Test that wl_shm supports the required formats
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let client = run.create_client().await?;
let formats = client.shm.formats().await;
tassert!(formats.contains(&XRGB8888.wl_id.unwrap()));
tassert!(formats.contains(&ARGB8888.wl_id.unwrap()));
Ok(())
}

View file

@ -52,6 +52,7 @@ mod forker;
mod format;
mod globals;
mod ifs;
mod it;
mod libinput;
mod logger;
mod logind;

View file

@ -1,5 +1,6 @@
use {
crate::{
acceptor::Acceptor,
async_engine::{AsyncEngine, SpawnedFuture},
backend::{
Backend, BackendEvent, Connector, ConnectorId, ConnectorIds, InputDevice,
@ -83,14 +84,14 @@ pub struct State {
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
pub dbus: Dbus,
pub fdcloser: Arc<FdCloser>,
pub logger: Arc<Logger>,
pub logger: Option<Arc<Logger>>,
pub connectors: CopyHashMap<ConnectorId, Rc<ConnectorData>>,
pub outputs: CopyHashMap<ConnectorId, Rc<OutputData>>,
pub status: CloneCell<Rc<String>>,
pub idle: IdleState,
pub run_args: RunArgs,
pub xwayland: XWaylandState,
pub socket_path: CloneCell<Rc<String>>,
pub acceptor: CloneCell<Option<Rc<Acceptor>>>,
pub serial: NumCell<Wrapping<u32>>,
pub run_toplevel: Rc<RunToplevel>,
}

View file

@ -14,7 +14,7 @@ impl Bitfield {
let idx = val as usize / SEG_SIZE;
let pos = val as usize % SEG_SIZE;
while self.vals.len() <= idx {
self.vals.push(0);
self.vals.push(!0);
}
self.vals[idx] &= !(1 << pos);
}

View file

@ -12,7 +12,7 @@ use {
},
};
pub struct CloneCell<T: UnsafeCellCloneSafe> {
pub struct CloneCell<T> {
data: UnsafeCell<T>,
}
@ -26,19 +26,22 @@ impl<T: UnsafeCellCloneSafe> Clone for CloneCell<T> {
impl<T: UnsafeCellCloneSafe + Debug> Debug for CloneCell<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
unsafe { self.data.get().deref().fmt(f) }
self.get().fmt(f)
}
}
impl<T: UnsafeCellCloneSafe> CloneCell<T> {
pub fn new(t: T) -> Self {
impl<T> CloneCell<T> {
pub const fn new(t: T) -> Self {
Self {
data: UnsafeCell::new(t),
}
}
#[inline(always)]
pub fn get(&self) -> T {
pub fn get(&self) -> T
where
T: UnsafeCellCloneSafe,
{
unsafe { self.data.get().deref().clone() }
}
@ -52,7 +55,7 @@ impl<T: UnsafeCellCloneSafe> CloneCell<T> {
where
T: Default,
{
unsafe { mem::take(self.data.get().deref_mut()) }
self.set(T::default())
}
}

View file

@ -37,10 +37,8 @@ impl<K: Eq + Hash, V> CopyHashMap<K, V> {
Self::default()
}
pub fn set(&self, k: K, v: V) {
unsafe {
self.map.get().deref_mut().insert(k, v);
}
pub fn set(&self, k: K, v: V) -> Option<V> {
unsafe { self.map.get().deref_mut().insert(k, v) }
}
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<V>

View file

@ -204,3 +204,17 @@ impl Display for OsError {
write!(f, "{} (os error {})", msg, self.0)
}
}
pub trait OsErrorExt {
type Container;
fn to_os_error(self) -> Self::Container;
}
impl<T> OsErrorExt for Result<T, Errno> {
type Container = Result<T, OsError>;
fn to_os_error(self) -> Self::Container {
self.map_err(|e| e.into())
}
}

View file

@ -100,7 +100,7 @@ pub async fn manage(state: Rc<State>) {
forker.setenv(DISPLAY.as_bytes(), display.as_bytes());
log::info!("Allocated display :{} for Xwayland", xsocket.id);
log::info!("Waiting for connection attempt");
if state.backend.get().is_freestanding() {
if state.backend.get().import_environment() {
import_environment(&state, DISPLAY, &display);
}
let res = XWaylandError::tria(async {