pg_embedded_setup_unpriv/bootstrap/
mod.rs1mod env;
6mod mode;
7mod prepare;
8
9use std::time::Duration;
10
11use color_eyre::eyre::Context;
12use postgresql_embedded::Settings;
13
14use crate::{
15 PgEnvCfg,
16 error::{BootstrapResult, Result as CrateResult},
17};
18
19pub use env::{TestBootstrapEnvironment, find_timezone_dir};
20pub use mode::{ExecutionMode, ExecutionPrivileges, detect_execution_privileges};
21
22use self::{
23 env::{shutdown_timeout_from_env, worker_binary_from_env},
24 mode::determine_execution_mode,
25 prepare::prepare_bootstrap,
26};
27
28const DEFAULT_SETUP_TIMEOUT: Duration = Duration::from_secs(180);
29const DEFAULT_START_TIMEOUT: Duration = Duration::from_secs(60);
30
31#[derive(Debug, Clone)]
33pub struct TestBootstrapSettings {
34 pub privileges: ExecutionPrivileges,
36 pub execution_mode: ExecutionMode,
38 pub settings: Settings,
40 pub environment: TestBootstrapEnvironment,
42 pub worker_binary: Option<camino::Utf8PathBuf>,
44 pub setup_timeout: Duration,
46 pub start_timeout: Duration,
48 pub shutdown_timeout: Duration,
50}
51
52pub fn run() -> CrateResult<()> {
82 orchestrate_bootstrap()?;
83 Ok(())
84}
85
86pub fn bootstrap_for_tests() -> BootstrapResult<TestBootstrapSettings> {
107 orchestrate_bootstrap()
108}
109
110fn orchestrate_bootstrap() -> BootstrapResult<TestBootstrapSettings> {
111 if let Err(err) = color_eyre::install() {
112 tracing::debug!("color_eyre already installed: {err}");
113 }
114
115 let privileges = detect_execution_privileges();
116 let cfg = PgEnvCfg::load().context("failed to load configuration via OrthoConfig")?;
117 let settings = cfg.to_settings()?;
118 let worker_binary = worker_binary_from_env()?;
119 let execution_mode = determine_execution_mode(privileges, worker_binary.as_ref())?;
120 let shutdown_timeout = shutdown_timeout_from_env()?;
121 let prepared = prepare_bootstrap(privileges, settings, &cfg)?;
122
123 Ok(TestBootstrapSettings {
124 privileges,
125 execution_mode,
126 settings: prepared.settings,
127 environment: prepared.environment,
128 worker_binary,
129 setup_timeout: DEFAULT_SETUP_TIMEOUT,
130 start_timeout: DEFAULT_START_TIMEOUT,
131 shutdown_timeout,
132 })
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use camino::Utf8PathBuf;
139 use temp_env::with_vars;
140 use tempfile::tempdir;
141
142 #[test]
143 fn orchestrate_bootstrap_respects_env_overrides() {
144 if detect_execution_privileges() == ExecutionPrivileges::Root {
145 tracing::warn!(
146 "skipping orchestrate test because root privileges require PG_EMBEDDED_WORKER"
147 );
148 return;
149 }
150
151 let runtime = tempdir().expect("runtime dir");
152 let data = tempdir().expect("data dir");
153 let runtime_path =
154 Utf8PathBuf::from_path_buf(runtime.path().to_path_buf()).expect("runtime dir utf8");
155 let data_path =
156 Utf8PathBuf::from_path_buf(data.path().to_path_buf()).expect("data dir utf8");
157
158 let settings = with_vars(
159 [
160 ("PG_RUNTIME_DIR", Some(runtime_path.as_str())),
161 ("PG_DATA_DIR", Some(data_path.as_str())),
162 ("PG_SUPERUSER", Some("bootstrap_test")),
163 ("PG_PASSWORD", Some("bootstrap_test_pw")),
164 ("PG_EMBEDDED_WORKER", None),
165 ],
166 || orchestrate_bootstrap().expect("bootstrap to succeed"),
167 );
168
169 assert_paths(&settings, &runtime_path, &data_path);
170 assert_identity(&settings, "bootstrap_test", "bootstrap_test_pw");
171 assert_environment(&settings, &runtime_path);
172 }
173
174 #[test]
175 fn run_succeeds_with_customised_paths() {
176 if detect_execution_privileges() == ExecutionPrivileges::Root {
177 tracing::warn!("skipping run test because root privileges require PG_EMBEDDED_WORKER");
178 return;
179 }
180
181 let runtime = tempdir().expect("runtime dir");
182 let data = tempdir().expect("data dir");
183 let runtime_path =
184 Utf8PathBuf::from_path_buf(runtime.path().to_path_buf()).expect("runtime dir utf8");
185 let data_path =
186 Utf8PathBuf::from_path_buf(data.path().to_path_buf()).expect("data dir utf8");
187
188 with_vars(
189 [
190 ("PG_RUNTIME_DIR", Some(runtime_path.as_str())),
191 ("PG_DATA_DIR", Some(data_path.as_str())),
192 ("PG_SUPERUSER", Some("bootstrap_run")),
193 ("PG_PASSWORD", Some("bootstrap_run_pw")),
194 ("PG_EMBEDDED_WORKER", None),
195 ],
196 || {
197 run().expect("run should bootstrap successfully");
198
199 let cache_dir = runtime_path.join("cache");
200 let run_dir = runtime_path.join("run");
201 assert!(cache_dir.exists(), "cache directory should be created");
202 assert!(run_dir.exists(), "runtime directory should be created");
203 },
204 );
205 }
206
207 fn assert_paths(
208 settings: &TestBootstrapSettings,
209 runtime_path: &Utf8PathBuf,
210 data_path: &Utf8PathBuf,
211 ) {
212 let observed_install =
213 Utf8PathBuf::from_path_buf(settings.settings.installation_dir.clone())
214 .expect("installation dir utf8");
215 let observed_data =
216 Utf8PathBuf::from_path_buf(settings.settings.data_dir.clone()).expect("data dir utf8");
217
218 assert_eq!(observed_install.as_path(), runtime_path.as_path());
219 assert_eq!(observed_data.as_path(), data_path.as_path());
220 }
221
222 fn assert_identity(
223 settings: &TestBootstrapSettings,
224 expected_user: &str,
225 expected_password: &str,
226 ) {
227 assert_eq!(settings.settings.username, expected_user);
228 assert_eq!(settings.settings.password, expected_password);
229 assert_eq!(settings.privileges, ExecutionPrivileges::Unprivileged);
230 assert_eq!(settings.execution_mode, ExecutionMode::InProcess);
231 assert!(settings.worker_binary.is_none());
232 }
233
234 fn assert_environment(settings: &TestBootstrapSettings, runtime_path: &Utf8PathBuf) {
235 let env_pairs = settings.environment.to_env();
236 let pgpass = runtime_path.join(".pgpass");
237 assert!(env_pairs.contains(&("PGPASSFILE".into(), Some(pgpass.as_str().into()))));
238 assert_eq!(settings.environment.home.as_path(), runtime_path.as_path());
239 }
240}