Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion testsuite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ publish = false
autotests = false

[lib]
doctest = false
test = false

[[test]]
Expand Down
24 changes: 0 additions & 24 deletions testsuite/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

use std::sync::LazyLock;

use anyhow::Context as _;

static JETSOCAT_BIN_PATH: LazyLock<std::path::PathBuf> = LazyLock::new(|| {
let mut build = escargot::CargoBuild::new()
.manifest_path("../jetsocat/Cargo.toml")
Expand Down Expand Up @@ -67,28 +65,6 @@ pub fn dgw_tokio_cmd() -> tokio::process::Command {
cmd
}

/// Starts Devolutions Gateway and waits for its HTTP listener.
///
/// The caller must keep `config_handle` alive until the process exits because it owns the
/// temporary configuration directory.
pub async fn start_dgw(config_handle: &crate::dgw_config::DgwConfigHandle) -> anyhow::Result<tokio::process::Child> {
let mut process = dgw_tokio_cmd()
.env("DGATEWAY_CONFIG_PATH", config_handle.config_dir())
.kill_on_drop(true)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.context("start Devolutions Gateway")?;

if let Err(error) = wait_for_tcp_port(config_handle.http_port()).await {
let _ = process.kill().await;
let _ = process.wait().await;
return Err(error).context("wait for Devolutions Gateway HTTP listener");
}

Ok(process)
}

static AGENT_BIN_PATH: LazyLock<std::path::PathBuf> = LazyLock::new(|| {
escargot::CargoBuild::new()
.manifest_path("../devolutions-agent/Cargo.toml")
Expand Down
56 changes: 56 additions & 0 deletions testsuite/src/dgw_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,63 @@ pub struct DgwConfigHandle {
http_port: u16,
}

/// A Gateway process that borrows its temporary configuration until dropped.
///
/// Dropping the guard requests process termination without waiting for exit.
/// Use [`Self::start_kill`] and [`Self::wait`] to terminate and reap it explicitly.
///
/// The configuration cannot be dropped while the guard is alive:
///
/// ```compile_fail,E0505
/// use testsuite::dgw_config::DgwConfig;
///
/// async fn drop_config_before_process() -> anyhow::Result<()> {
/// let config = DgwConfig::builder().build().init()?;
/// let _process = config.start().await?;
/// drop(config);
/// Ok(())
/// }
/// ```
pub struct DgwProcess<'a> {
child: tokio::process::Child,
_config: &'a DgwConfigHandle,
}

impl DgwProcess<'_> {
pub fn start_kill(&mut self) -> std::io::Result<()> {
self.child.start_kill()
}

pub async fn wait(&mut self) -> std::io::Result<std::process::ExitStatus> {
self.child.wait().await
}
}

#[expect(clippy::empty_drop, reason = "keep the config borrowed until the child is dropped")]
impl Drop for DgwProcess<'_> {
fn drop(&mut self) {}
}

impl DgwConfigHandle {
/// Starts Devolutions Gateway and waits for its HTTP listener.
pub async fn start(&self) -> anyhow::Result<DgwProcess<'_>> {
let mut child = crate::cli::dgw_tokio_cmd()
.env("DGATEWAY_CONFIG_PATH", self.config_dir())
.kill_on_drop(true)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.context("start Devolutions Gateway")?;

if let Err(error) = crate::cli::wait_for_tcp_port(self.http_port()).await {
let _ = child.kill().await;
let _ = child.wait().await;
return Err(error).context("wait for Devolutions Gateway HTTP listener");
}

Ok(DgwProcess { child, _config: self })
}

pub fn init(config: DgwConfig) -> anyhow::Result<Self> {
let DgwConfig {
hostname,
Expand Down
3 changes: 1 addition & 2 deletions testsuite/tests/cli/dgw/heartbeat.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Heartbeat API tests.

use anyhow::Context as _;
use testsuite::cli::start_dgw;
use testsuite::dgw_config::DgwConfig;

/// Scope token with `gateway.heartbeat.read` scope.
Expand Down Expand Up @@ -73,7 +72,7 @@ async fn heartbeat_reports_disk_space_when_recording_dir_not_yet_created() -> an
.init()
.context("init config")?;

let mut process = start_dgw(&config_handle).await?;
let mut process = config_handle.start().await?;

let heartbeat = get_heartbeat(config_handle.http_port())
.await
Expand Down
3 changes: 1 addition & 2 deletions testsuite/tests/cli/dgw/kdc_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use std::time::Duration;

use base64::Engine as _;
use testsuite::cli::start_dgw;
use testsuite::dgw_config::{AgentTunnelConfig, DgwConfig};
use tokio::net::TcpListener;
use uuid::Uuid;
Expand Down Expand Up @@ -41,7 +40,7 @@ async fn kdc_proxy_never_dials_the_kdc_directly_for_a_missing_explicit_agent() {
.build()
.init()
.expect("init gateway config");
let mut gateway = start_dgw(&config).await.expect("start gateway");
let mut gateway = config.start().await.expect("start gateway");
let kdc = TcpListener::bind("127.0.0.1:0").await.expect("bind fake kdc");
let kdc_addr = kdc.local_addr().expect("read fake kdc address");

Expand Down
3 changes: 1 addition & 2 deletions testsuite/tests/cli/dgw/subscriber.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anyhow::Context as _;
use testsuite::cli::start_dgw;
use testsuite::dgw_config::{DgwConfig, ProxyMode, SubscriberConfig};
use testsuite::http::HttpRequestCapture;

Expand All @@ -16,7 +15,7 @@ async fn subscriber_requests_identify_gateway() -> anyhow::Result<()> {
.build()
.init()
.context("initialize Gateway configuration")?;
let mut process = start_dgw(&config_handle).await?;
let mut process = config_handle.start().await?;

let request = capture.receive().await;

Expand Down