From 6e925e91d9dca1fde64a226f6d37ff6b3f989931 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 19 Jul 2024 18:10:02 -0700 Subject: [PATCH] testing: Add subprocess dbus session interface for test isolation --- src/manager/root.rs | 6 ++-- src/manager/user.rs | 8 ++--- src/sls/ftrace.rs | 4 +-- src/testing.rs | 85 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/manager/root.rs b/src/manager/root.rs index 9549d29..f7cb5c8 100644 --- a/src/manager/root.rs +++ b/src/manager/root.rs @@ -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 { - 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::(); - let connection = ConnectionBuilder::session()?.build().await?; + let connection = handle.new_dbus().await?; let manager = SteamOSManager::new(connection.clone(), tx).await?; connection .object_server() diff --git a/src/manager/user.rs b/src/manager/user.rs index ceb05d7..a7441de 100644 --- a/src/manager/user.rs +++ b/src/manager/user.rs @@ -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 { - let handle = testing::start(); + let mut handle = testing::start(); let (tx, _rx) = channel::(); - 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(); } } diff --git a/src/sls/ftrace.rs b/src/sls/ftrace.rs index 3253384..4a2f37c 100644 --- a/src/sls/ftrace.rs +++ b/src/sls/ftrace.rs @@ -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!( diff --git a/src/testing.rs b/src/testing.rs index 5585289..303e030 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -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>> = RefCell::new(None); @@ -47,6 +56,7 @@ pub fn start() -> TestHandle { let test: Rc = 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.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 Result<(i32, String)>>, + pub mock_dbus: Cell>, } pub struct TestHandle { pub test: Rc, } +impl MockDBus { + pub async fn new() -> Result { + 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 { + 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();