agama/rust/agama-server/tests/common/mod.rs
2024-11-05 12:38:02 +00:00

132 lines
3.6 KiB
Rust

// Copyright (c) [2024] SUSE LLC
//
// All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, contact SUSE LLC.
//
// To contact SUSE LLC about this file by physical or electronic mail, you may
// find current contact information at www.suse.com.
use agama_lib::error::ServiceError;
use axum::body::{to_bytes, Body};
use std::{
future::Future,
process::{Child, Command},
time::Duration,
};
use uuid::Uuid;
/// D-Bus server to be used on tests.
///
/// Takes care of starting and stopping a dbus-daemon to be used on integration tests. Each server
/// uses a different socket, so they do not collide.
///
/// NOTE: this struct implements the [typestate pattern](http://cliffle.com/blog/rust-typestate/).
pub struct DBusServer<S: ServerState> {
address: String,
extra: S,
}
pub struct Started {
connection: zbus::Connection,
child: Child,
}
impl Drop for Started {
fn drop(&mut self) {
self.child.kill().unwrap();
}
}
pub struct Stopped;
pub trait ServerState {}
impl ServerState for Started {}
impl ServerState for Stopped {}
impl Default for DBusServer<Stopped> {
fn default() -> Self {
Self::new()
}
}
impl DBusServer<Stopped> {
pub fn new() -> Self {
let uuid = Uuid::new_v4();
DBusServer {
address: format!("unix:path=/tmp/agama-tests-{uuid}"),
extra: Stopped,
}
}
pub async fn start(self) -> Result<DBusServer<Started>, ServiceError> {
let child = Command::new("/usr/bin/dbus-daemon")
.args([
"--config-file",
"../share/dbus-test.conf",
"--address",
&self.address,
])
.spawn()
.expect("to start the testing D-Bus daemon");
let connection = async_retry(|| agama_lib::connection_to(&self.address)).await?;
Ok(DBusServer {
address: self.address,
extra: Started { child, connection },
})
}
}
impl DBusServer<Started> {
pub fn connection(&self) -> zbus::Connection {
self.extra.connection.clone()
}
}
/// Run and retry an async function.
///
/// Beware that, if the function is failing for a legit reason, you will
/// introduce a delay in your code.
///
/// * `func`: async function to run.
pub async fn async_retry<O, F, T, E>(func: F) -> Result<T, E>
where
F: Fn() -> O,
O: Future<Output = Result<T, E>>,
{
const RETRIES: u8 = 10;
const INTERVAL: u64 = 500;
let mut retry = 0;
loop {
match func().await {
Ok(result) => return Ok(result),
Err(error) => {
if retry > RETRIES {
return Err(error);
}
retry += 1;
let wait_time = Duration::from_millis(INTERVAL);
tokio::time::sleep(wait_time).await;
}
}
}
}
pub async fn body_to_string(body: Body) -> String {
let bytes = to_bytes(body, usize::MAX).await.unwrap();
String::from_utf8(bytes.to_vec()).unwrap()
}