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

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(())
}