use std::{ffi::OsString, time::Duration};
use camino::Utf8PathBuf;
use color_eyre::eyre::{Result, eyre};
use postgresql_embedded::Settings;
#[cfg(not(doc))]
use rstest::fixture;
use tokio::runtime::{Builder, Runtime};
use super::{scoped_env::scoped_env, worker_env};
use crate::{
CleanupMode,
ClusterHandle,
ExecutionMode,
ExecutionPrivileges,
TestBootstrapEnvironment,
TestBootstrapSettings,
TestCluster,
detect_execution_privileges,
env::ScopedEnv,
};
pub fn test_runtime() -> Result<Runtime> {
Builder::new_current_thread()
.enable_all()
.build()
.map_err(|err| eyre!(err))
}
#[must_use]
pub fn dummy_environment() -> TestBootstrapEnvironment {
TestBootstrapEnvironment {
home: Utf8PathBuf::from("/tmp/pg-home"),
xdg_cache_home: Utf8PathBuf::from("/tmp/pg-cache"),
xdg_runtime_dir: Utf8PathBuf::from("/tmp/pg-run"),
pgpass_file: Utf8PathBuf::from("/tmp/.pgpass"),
tz_dir: Some(Utf8PathBuf::from("/usr/share/zoneinfo")),
timezone: "UTC".into(),
}
}
#[must_use]
pub fn dummy_settings(privileges: ExecutionPrivileges) -> TestBootstrapSettings {
TestBootstrapSettings {
privileges,
execution_mode: match privileges {
ExecutionPrivileges::Unprivileged => ExecutionMode::InProcess,
ExecutionPrivileges::Root => ExecutionMode::Subprocess,
},
settings: Settings::default(),
environment: dummy_environment(),
worker_binary: None,
setup_timeout: Duration::from_secs(180),
start_timeout: Duration::from_secs(60),
shutdown_timeout: Duration::from_secs(15),
cleanup_mode: CleanupMode::default(),
binary_cache_dir: None,
}
}
#[must_use]
#[cfg_attr(not(doc), fixture)]
pub fn test_cluster() -> TestCluster {
let worker_guard = ensure_worker_env();
let cluster = match TestCluster::new() {
Ok(cluster) => cluster,
Err(err) => {
panic!("SKIP-TEST-CLUSTER: test_cluster fixture failed to start PostgreSQL: {err:?}")
}
};
cluster.with_worker_guard(worker_guard)
}
pub fn ensure_worker_env() -> Option<ScopedEnv> {
let worker_path = resolve_worker_path(
detect_execution_privileges(),
std::env::var_os("PG_EMBEDDED_WORKER").is_some(),
worker_env::worker_binary,
)?;
Some(scoped_env(vec![(
OsString::from("PG_EMBEDDED_WORKER"),
Some(worker_path),
)]))
}
fn is_worker_env_required(privileges: ExecutionPrivileges, worker_env_present: bool) -> bool {
privileges == ExecutionPrivileges::Root && !worker_env_present
}
fn resolve_worker_path(
privileges: ExecutionPrivileges,
worker_env_present: bool,
worker_finder: impl FnOnce() -> Option<OsString>,
) -> Option<OsString> {
if !is_worker_env_required(privileges, worker_env_present) {
return None;
}
let Some(worker) = worker_finder() else {
panic!(
"SKIP-TEST-CLUSTER: PG_EMBEDDED_WORKER is not set and pg_worker binary was not found"
);
};
Some(worker)
}
pub use super::shared_singleton::{shared_cluster, shared_cluster_handle};
#[must_use]
#[cfg_attr(not(doc), fixture)]
pub fn shared_test_cluster() -> &'static TestCluster {
match shared_cluster() {
Ok(cluster) => cluster,
Err(err) => panic!(
"SKIP-TEST-CLUSTER: shared_test_cluster fixture failed to start PostgreSQL: {err:?}"
),
}
}
#[must_use]
#[cfg_attr(not(doc), fixture)]
pub fn shared_test_cluster_handle() -> &'static ClusterHandle {
match shared_cluster_handle() {
Ok(handle) => handle,
Err(err) => panic!(
"SKIP-TEST-CLUSTER: shared_test_cluster_handle fixture failed to start PostgreSQL: \
{err:?}"
),
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case::worker_would_be_found(true)]
#[case::worker_would_not_be_found(false)]
fn unprivileged_user_does_not_require_worker(#[case] worker_exists: bool) {
let worker_finder = move || worker_exists.then(|| OsString::from("/fake/worker"));
let result = resolve_worker_path(
ExecutionPrivileges::Unprivileged,
false, worker_finder,
);
assert!(
result.is_none(),
"unprivileged execution should not resolve worker path"
);
}
#[test]
fn privileged_user_with_existing_worker_env_does_not_override() {
let worker_finder = || panic!("worker_finder should not be called when env var is set");
let result = resolve_worker_path(
ExecutionPrivileges::Root,
true, worker_finder,
);
assert!(
result.is_none(),
"should not resolve worker path when PG_EMBEDDED_WORKER is set"
);
}
#[test]
fn privileged_user_without_worker_env_resolves_worker_path() {
let worker_path = OsString::from("/path/to/pg_worker");
let expected_path = worker_path.clone();
let worker_finder = move || Some(worker_path);
let result = resolve_worker_path(
ExecutionPrivileges::Root,
false, worker_finder,
);
assert_eq!(
result,
Some(expected_path),
"should return worker path for privileged execution"
);
}
#[test]
#[should_panic(expected = "SKIP-TEST-CLUSTER")]
fn privileged_user_without_worker_binary_panics() {
let worker_finder = || None;
let _result = resolve_worker_path(
ExecutionPrivileges::Root,
false, worker_finder,
);
}
}