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
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(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue