testing: Add subprocess dbus session interface for test isolation

This commit is contained in:
Vicki Pfau 2024-07-19 18:10:02 -07:00
parent 24223a4827
commit 6e925e91d9
4 changed files with 91 additions and 12 deletions

View file

@ -327,7 +327,7 @@ mod test {
use std::time::Duration;
use tokio::fs::{create_dir_all, write};
use tokio::time::sleep;
use zbus::{Connection, ConnectionBuilder};
use zbus::Connection;
struct TestHandle {
h: testing::TestHandle,
@ -335,7 +335,7 @@ mod test {
}
async fn start() -> Result<TestHandle> {
let handle = testing::start();
let mut handle = testing::start();
create_dir_all(crate::path("/sys/class/dmi/id")).await?;
write(crate::path("/sys/class/dmi/id/board_vendor"), "Valve\n").await?;
write(crate::path("/sys/class/dmi/id/board_name"), "Jupiter\n").await?;
@ -347,7 +347,7 @@ mod test {
.await?;
let (tx, _rx) = channel::<RootContext>();
let connection = ConnectionBuilder::session()?.build().await?;
let connection = handle.new_dbus().await?;
let manager = SteamOSManager::new(connection.clone(), tx).await?;
connection
.object_server()

View file

@ -368,7 +368,7 @@ mod test {
use std::time::Duration;
use tokio::fs::read;
use tokio::time::sleep;
use zbus::{Connection, ConnectionBuilder, Interface};
use zbus::{Connection, Interface};
use zbus_xml::{Method, Node, Property};
struct TestHandle {
@ -377,9 +377,9 @@ mod test {
}
async fn start() -> Result<TestHandle> {
let handle = testing::start();
let mut handle = testing::start();
let (tx, _rx) = channel::<UserContext>();
let connection = ConnectionBuilder::session()?.build().await?;
let connection = handle.new_dbus().await?;
let manager = SteamOSManager::new(connection.clone(), &connection, tx).await?;
connection
.object_server()
@ -485,7 +485,5 @@ mod test {
assert_eq!(local_property.ty(), remote_property.ty());
assert_eq!(local_property.access(), remote_property.access());
}
test.connection.close().await.unwrap();
}
}

View file

@ -200,7 +200,7 @@ mod test {
#[tokio::test]
async fn ftrace_init() {
let _h = testing::start();
let mut h = testing::start();
let tracefs = Ftrace::base();
@ -212,7 +212,7 @@ mod test {
Mode::S_IRUSR | Mode::S_IWUSR,
)
.expect("trace_pipe");
let dbus = Connection::session().await.expect("dbus");
let dbus = h.new_dbus().await.expect("dbus");
let _ftrace = Ftrace::init(dbus).await.expect("ftrace");
assert_eq!(

View file

@ -1,9 +1,18 @@
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
use libc::pid_t;
use nix::sys::signal;
use nix::sys::signal::Signal;
use nix::unistd::Pid;
use std::cell::{Cell, RefCell};
use std::ffi::OsStr;
use std::path::Path;
use std::process::Stdio;
use std::rc::Rc;
use std::time::Duration;
use tempfile::{tempdir, TempDir};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::{Child, Command};
use zbus::connection::{Builder, Connection};
thread_local! {
static TEST: RefCell<Option<Rc<Test>>> = RefCell::new(None);
@ -47,6 +56,7 @@ pub fn start() -> TestHandle {
let test: Rc<Test> = Rc::new(Test {
base: tempdir().expect("Couldn't create test directory"),
process_cb: Cell::new(|_, _| Err(anyhow!("No current process_cb"))),
mock_dbus: Cell::new(None),
});
*lock.borrow_mut() = Some(test.clone());
TestHandle { test }
@ -54,28 +64,99 @@ pub fn start() -> TestHandle {
}
pub fn stop() {
TEST.with(|lock| *lock.borrow_mut() = None);
TEST.with(|lock| {
let test = (*lock.borrow_mut()).take();
if let Some(test) = test {
if let Some(mock_dbus) = test.mock_dbus.take() {
let _ = mock_dbus.shutdown();
}
}
});
}
pub fn current() -> Rc<Test> {
TEST.with(|lock| lock.borrow().as_ref().unwrap().clone())
}
pub struct MockDBus {
pub connection: Connection,
process: Child,
}
pub struct Test {
base: TempDir,
pub process_cb: Cell<fn(&str, &[&OsStr]) -> Result<(i32, String)>>,
pub mock_dbus: Cell<Option<MockDBus>>,
}
pub struct TestHandle {
pub test: Rc<Test>,
}
impl MockDBus {
pub async fn new() -> Result<MockDBus> {
let mut process = Command::new("/usr/bin/dbus-daemon")
.args(["--session", "--nofork", "--print-address"])
.stdout(Stdio::piped())
.spawn()?;
let stdout = BufReader::new(
process
.stdout
.take()
.ok_or(anyhow!("Couldn't capture stdout"))?,
);
let address = stdout
.lines()
.next_line()
.await?
.ok_or(anyhow!("Failed to read address"))?;
let connection = Builder::address(address.trim_end())?.build().await?;
Ok(MockDBus {
connection,
process,
})
}
pub fn shutdown(mut self) -> Result<()> {
let pid = match self.process.id() {
Some(id) => id,
None => return Ok(()),
};
let pid: pid_t = match pid.try_into() {
Ok(pid) => pid,
Err(message) => bail!("Unable to get pid_t from command {message}"),
};
signal::kill(Pid::from_raw(pid), Signal::SIGINT)?;
for _ in [0..10] {
// Wait for the process to exit synchronously, but not for too long
if self.process.try_wait()?.is_some() {
break;
}
std::thread::sleep(Duration::from_micros(100));
}
Ok(())
}
}
impl Test {
pub fn path(&self) -> &Path {
self.base.path()
}
}
impl TestHandle {
pub async fn new_dbus(&mut self) -> Result<Connection> {
let dbus = MockDBus::new().await?;
let connection = dbus.connection.clone();
self.test.mock_dbus.set(Some(dbus));
Ok(connection)
}
}
impl Drop for TestHandle {
fn drop(&mut self) {
stop();