Use a p2p dbus connection during tests
This removes the requirement of having the DBus daemon running locally, which is particularly helpful e.g. in a container or for CI.
This commit is contained in:
parent
256267c213
commit
20072cf1ea
5 changed files with 101 additions and 64 deletions
|
@ -6,52 +6,48 @@ include!(concat!(
|
|||
"/src/policy_checker/dbus.rs"
|
||||
));
|
||||
|
||||
use {std::collections::HashMap, zbus::dbus_interface};
|
||||
use {
|
||||
std::collections::HashMap,
|
||||
std::sync::atomic::{AtomicUsize, Ordering},
|
||||
zbus::dbus_interface,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MalcontentDBusMock {
|
||||
responses: HashMap<Uid, Vec<Restrictions>>,
|
||||
invocations_left: usize,
|
||||
invocations_left: AtomicUsize,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "com.endlessm.ParentalControls.Dns")]
|
||||
impl MalcontentDBusMock {
|
||||
fn get_restrictions(&mut self, user_id: u32) -> Restrictions {
|
||||
let answers = self
|
||||
.responses
|
||||
.get_mut(&Uid::from_raw(user_id))
|
||||
.expect(&format!(
|
||||
"MockError: No mocked invocations available for user with id {}",
|
||||
user_id
|
||||
));
|
||||
let restrictions = answers.pop().expect(&format!(
|
||||
"MockError: DBus mock is saturated for user with id {}",
|
||||
user_id
|
||||
let uid = Uid::from_raw(user_id);
|
||||
let answers = self.responses.get_mut(&uid).expect(&format!(
|
||||
"MockError: No mocked invocations available for user with id {}",
|
||||
uid
|
||||
));
|
||||
|
||||
self.invocations_left -= 1;
|
||||
let restrictions = answers.pop().expect(&format!(
|
||||
"MockError: DBus mock is saturated for user with id {}",
|
||||
uid
|
||||
));
|
||||
|
||||
self.invocations_left.fetch_sub(1, Ordering::SeqCst);
|
||||
restrictions
|
||||
}
|
||||
}
|
||||
|
||||
impl MalcontentDBusMock {
|
||||
pub fn new(mut responses: HashMap<Uid, Vec<Restrictions>>) -> Self {
|
||||
let responses_size: usize = responses
|
||||
.values()
|
||||
.map(|v| {
|
||||
std::cmp::max(
|
||||
v.len(),
|
||||
1, /* 'No restrictions' still counts as one message */
|
||||
)
|
||||
})
|
||||
.sum();
|
||||
let responses_size: usize = responses.values().map(Vec::len).sum();
|
||||
|
||||
for r in responses.values_mut() {
|
||||
r.reverse(); // we pop responses from the back, so...
|
||||
}
|
||||
|
||||
let ret = Self {
|
||||
responses,
|
||||
invocations_left: responses_size,
|
||||
invocations_left: AtomicUsize::new(responses_size),
|
||||
};
|
||||
|
||||
ret
|
||||
|
@ -60,24 +56,57 @@ impl MalcontentDBusMock {
|
|||
|
||||
impl Drop for MalcontentDBusMock {
|
||||
fn drop(&mut self) {
|
||||
let invocations_left = self.invocations_left.load(Ordering::Acquire);
|
||||
assert_eq!(
|
||||
self.invocations_left, 0,
|
||||
invocations_left, 0,
|
||||
"MockError: During teardown, {} invocations are still left on the mock object",
|
||||
self.invocations_left
|
||||
invocations_left
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mock_dbus(responses: HashMap<Uid, Vec<Restrictions>>) -> Result<zbus::Connection> {
|
||||
let mock = MalcontentDBusMock::new(responses);
|
||||
let connection = zbus::ConnectionBuilder::session()?
|
||||
.serve_at("/com/endlessm/ParentalControls/Dns", mock)?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
std::env::set_var(
|
||||
"TEST_DBUS_SERVICE_NAME",
|
||||
connection.unique_name().unwrap().as_str(),
|
||||
);
|
||||
Ok(connection)
|
||||
pub struct DBusServerGuard {
|
||||
handle: tokio::task::JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Drop for DBusServerGuard {
|
||||
fn drop(&mut self) {
|
||||
self.handle.abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mock_dbus(responses: HashMap<Uid, Vec<Restrictions>>) -> Result<DBusServerGuard> {
|
||||
let guid = zbus::Guid::generate();
|
||||
let socket_path =
|
||||
std::path::PathBuf::from(std::env!("CARGO_TARGET_TMPDIR")).join(guid.as_str());
|
||||
|
||||
if socket_path.exists() {
|
||||
std::fs::remove_file(&socket_path)?;
|
||||
}
|
||||
|
||||
let socket = tokio::net::UnixListener::bind(&socket_path)?;
|
||||
std::env::set_var("TEST_DBUS_SOCKET", &socket_path);
|
||||
|
||||
let mock = MalcontentDBusMock::new(responses);
|
||||
let handle = tokio::spawn(async move {
|
||||
let (stream, _) = socket
|
||||
.accept()
|
||||
.await
|
||||
.expect("Server socket closed unexpectedly");
|
||||
std::fs::remove_file(socket_path).unwrap(); // Once we accepted, we can already remove the socket
|
||||
|
||||
let _ = zbus::ConnectionBuilder::unix_stream(stream)
|
||||
.server(&guid)
|
||||
.p2p()
|
||||
.name("com.endlessm.ParentalControls")
|
||||
.expect("Unable to serve given dbus name")
|
||||
.serve_at("/com/endlessm/ParentalControls/Dns", mock)
|
||||
.expect("Unable to server malcontent dbus mock object")
|
||||
.build()
|
||||
.await;
|
||||
|
||||
std::future::pending::<()>().await;
|
||||
});
|
||||
|
||||
Ok(DBusServerGuard { handle })
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue