autocommit 2022-05-01 17:23:55 CEST
This commit is contained in:
parent
4373ed05bf
commit
e1d5bf0e5d
39 changed files with 1772 additions and 57 deletions
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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
113
src/it.rs
Normal 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
136
src/it/test_backend.rs
Normal 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
34
src/it/test_client.rs
Normal 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
136
src/it/test_error.rs
Normal 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
8
src/it/test_ifs.rs
Normal 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;
|
||||
45
src/it/test_ifs/test_callback.rs
Normal file
45
src/it/test_ifs/test_callback.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
18
src/it/test_ifs/test_compositor.rs
Normal file
18
src/it/test_ifs/test_compositor.rs
Normal 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 {}
|
||||
55
src/it/test_ifs/test_display.rs
Normal file
55
src/it/test_ifs/test_display.rs
Normal 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 {}
|
||||
48
src/it/test_ifs/test_jay_compositor.rs
Normal file
48
src/it/test_ifs/test_jay_compositor.rs
Normal 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 {}
|
||||
185
src/it/test_ifs/test_registry.rs
Normal file
185
src/it/test_ifs/test_registry.rs
Normal 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 {}
|
||||
59
src/it/test_ifs/test_shm.rs
Normal file
59
src/it/test_ifs/test_shm.rs
Normal 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 {}
|
||||
61
src/it/test_ifs/test_shm_buffer.rs
Normal file
61
src/it/test_ifs/test_shm_buffer.rs
Normal 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 {}
|
||||
86
src/it/test_ifs/test_shm_pool.rs
Normal file
86
src/it/test_ifs/test_shm_pool.rs
Normal 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
73
src/it/test_logger.rs
Normal 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
71
src/it/test_mem.rs
Normal 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
54
src/it/test_object.rs
Normal 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
254
src/it/test_transport.rs
Normal 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
100
src/it/testrun.rs
Normal 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
47
src/it/tests.rs
Normal 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]
|
||||
}
|
||||
18
src/it/tests/t0001_shm_formats.rs
Normal file
18
src/it/tests/t0001_shm_formats.rs
Normal 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(())
|
||||
}
|
||||
|
|
@ -52,6 +52,7 @@ mod forker;
|
|||
mod format;
|
||||
mod globals;
|
||||
mod ifs;
|
||||
mod it;
|
||||
mod libinput;
|
||||
mod logger;
|
||||
mod logind;
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue