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 pub binary_cache_dir: Option<camino::Utf8PathBuf>,
55}
56
57pub fn run() -> CrateResult<()> {
87 orchestrate_bootstrap()?;
88 Ok(())
89}
90
91pub fn bootstrap_for_tests() -> BootstrapResult<TestBootstrapSettings> {
112 orchestrate_bootstrap()
113}
114
115fn orchestrate_bootstrap() -> BootstrapResult<TestBootstrapSettings> {
116 if let Err(err) = color_eyre::install() {
117 tracing::debug!("color_eyre already installed: {err}");
118 }
119
120 let privileges = detect_execution_privileges();
121 let cfg = PgEnvCfg::load().context("failed to load configuration via OrthoConfig")?;
122 let settings = cfg.to_settings()?;
123 let worker_binary = worker_binary_from_env(privileges)?;
124 let execution_mode = determine_execution_mode(privileges, worker_binary.as_ref())?;
125 let shutdown_timeout = shutdown_timeout_from_env()?;
126 let prepared = prepare_bootstrap(privileges, settings, &cfg)?;
127
128 Ok(TestBootstrapSettings {
129 privileges,
130 execution_mode,
131 settings: prepared.settings,
132 environment: prepared.environment,
133 worker_binary,
134 setup_timeout: DEFAULT_SETUP_TIMEOUT,
135 start_timeout: DEFAULT_START_TIMEOUT,
136 shutdown_timeout,
137 binary_cache_dir: cfg.binary_cache_dir,
138 })
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::test_support::scoped_env;
145 use camino::Utf8PathBuf;
146 use rstest::{fixture, rstest};
147 use std::ffi::OsString;
148 use tempfile::tempdir;
149
150 fn env_vars<const N: usize>(
152 pairs: [(&str, Option<&str>); N],
153 ) -> Vec<(OsString, Option<OsString>)> {
154 pairs
155 .into_iter()
156 .map(|(k, v)| (OsString::from(k), v.map(OsString::from)))
157 .collect()
158 }
159
160 #[test]
161 fn orchestrate_bootstrap_respects_env_overrides() {
162 if detect_execution_privileges() == ExecutionPrivileges::Root {
163 tracing::warn!(
164 "skipping orchestrate test because root privileges require PG_EMBEDDED_WORKER"
165 );
166 return;
167 }
168
169 let runtime = tempdir().expect("runtime dir");
170 let data = tempdir().expect("data dir");
171 let runtime_path =
172 Utf8PathBuf::from_path_buf(runtime.path().to_path_buf()).expect("runtime dir utf8");
173 let data_path =
174 Utf8PathBuf::from_path_buf(data.path().to_path_buf()).expect("data dir utf8");
175
176 let _guard = scoped_env(env_vars([
177 ("PG_RUNTIME_DIR", Some(runtime_path.as_str())),
178 ("PG_DATA_DIR", Some(data_path.as_str())),
179 ("PG_SUPERUSER", Some("bootstrap_test")),
180 ("PG_PASSWORD", Some("bootstrap_test_pw")),
181 ("PG_EMBEDDED_WORKER", None),
182 ]));
183 let settings = orchestrate_bootstrap().expect("bootstrap to succeed");
184
185 assert_paths(&settings, &runtime_path, &data_path);
186 assert_identity(&settings, "bootstrap_test", "bootstrap_test_pw");
187 assert_environment(&settings, &runtime_path);
188 }
189
190 struct RunTestPaths {
192 _runtime: tempfile::TempDir,
193 _data: tempfile::TempDir,
194 runtime_path: Utf8PathBuf,
195 data_path: Utf8PathBuf,
196 }
197
198 #[fixture]
200 fn run_test_paths() -> Option<RunTestPaths> {
201 if detect_execution_privileges() == ExecutionPrivileges::Root {
202 tracing::warn!("skipping run test because root privileges require PG_EMBEDDED_WORKER");
203 return None;
204 }
205
206 let runtime = tempdir().expect("runtime dir");
207 let data = tempdir().expect("data dir");
208 let runtime_path =
209 Utf8PathBuf::from_path_buf(runtime.path().to_path_buf()).expect("runtime dir utf8");
210 let data_path =
211 Utf8PathBuf::from_path_buf(data.path().to_path_buf()).expect("data dir utf8");
212
213 Some(RunTestPaths {
214 _runtime: runtime,
215 _data: data,
216 runtime_path,
217 data_path,
218 })
219 }
220
221 #[rstest]
222 fn run_succeeds_with_customised_paths(run_test_paths: Option<RunTestPaths>) {
223 let Some(paths) = run_test_paths else {
224 return;
225 };
226
227 let _guard = scoped_env(env_vars([
228 ("PG_RUNTIME_DIR", Some(paths.runtime_path.as_str())),
229 ("PG_DATA_DIR", Some(paths.data_path.as_str())),
230 ("PG_SUPERUSER", Some("bootstrap_run")),
231 ("PG_PASSWORD", Some("bootstrap_run_pw")),
232 ("PG_EMBEDDED_WORKER", None),
233 ]));
234
235 run().expect("run should bootstrap successfully");
236
237 assert!(
238 paths.runtime_path.join("cache").exists(),
239 "cache directory should be created"
240 );
241 assert!(
242 paths.runtime_path.join("run").exists(),
243 "runtime directory should be created"
244 );
245 }
246
247 struct BootstrapPaths {
249 _runtime: tempfile::TempDir,
250 _data: tempfile::TempDir,
251 _cache: tempfile::TempDir,
252 runtime_path: Utf8PathBuf,
253 data_path: Utf8PathBuf,
254 cache_path: Utf8PathBuf,
255 }
256
257 #[fixture]
259 fn bootstrap_paths() -> Option<BootstrapPaths> {
260 if detect_execution_privileges() == ExecutionPrivileges::Root {
261 tracing::warn!(
262 "skipping orchestrate test because root privileges require PG_EMBEDDED_WORKER"
263 );
264 return None;
265 }
266
267 let runtime = tempdir().expect("runtime dir");
268 let data = tempdir().expect("data dir");
269 let cache = tempdir().expect("cache dir");
270 let runtime_path =
271 Utf8PathBuf::from_path_buf(runtime.path().to_path_buf()).expect("runtime dir utf8");
272 let data_path =
273 Utf8PathBuf::from_path_buf(data.path().to_path_buf()).expect("data dir utf8");
274 let cache_path =
275 Utf8PathBuf::from_path_buf(cache.path().to_path_buf()).expect("cache dir utf8");
276
277 Some(BootstrapPaths {
278 _runtime: runtime,
279 _data: data,
280 _cache: cache,
281 runtime_path,
282 data_path,
283 cache_path,
284 })
285 }
286
287 fn orchestrate_with_cache_env(paths: &BootstrapPaths) -> TestBootstrapSettings {
291 let _guard = scoped_env(env_vars([
292 ("PG_RUNTIME_DIR", Some(paths.runtime_path.as_str())),
293 ("PG_DATA_DIR", Some(paths.data_path.as_str())),
294 ("PG_BINARY_CACHE_DIR", Some(paths.cache_path.as_str())),
295 ("PG_SUPERUSER", Some("cache_test")),
296 ("PG_PASSWORD", Some("cache_test_pw")),
297 ("PG_EMBEDDED_WORKER", None),
298 ]));
299 orchestrate_bootstrap().expect("bootstrap to succeed")
300 }
301
302 #[rstest]
303 fn orchestrate_bootstrap_propagates_binary_cache_dir(bootstrap_paths: Option<BootstrapPaths>) {
304 let Some(paths) = bootstrap_paths else {
305 return;
306 };
307
308 let settings = orchestrate_with_cache_env(&paths);
309
310 assert_eq!(
311 settings.binary_cache_dir,
312 Some(paths.cache_path.clone()),
313 "binary_cache_dir should propagate from PG_BINARY_CACHE_DIR"
314 );
315 }
316
317 fn assert_paths(
318 settings: &TestBootstrapSettings,
319 runtime_path: &Utf8PathBuf,
320 data_path: &Utf8PathBuf,
321 ) {
322 let observed_install =
323 Utf8PathBuf::from_path_buf(settings.settings.installation_dir.clone())
324 .expect("installation dir utf8");
325 let observed_data =
326 Utf8PathBuf::from_path_buf(settings.settings.data_dir.clone()).expect("data dir utf8");
327
328 assert_eq!(observed_install.as_path(), runtime_path.as_path());
329 assert_eq!(observed_data.as_path(), data_path.as_path());
330 }
331
332 fn assert_identity(
333 settings: &TestBootstrapSettings,
334 expected_user: &str,
335 expected_password: &str,
336 ) {
337 assert_eq!(settings.settings.username, expected_user);
338 assert_eq!(settings.settings.password, expected_password);
339 assert_eq!(settings.privileges, ExecutionPrivileges::Unprivileged);
340 assert_eq!(settings.execution_mode, ExecutionMode::InProcess);
341 assert!(settings.worker_binary.is_none());
342 }
343
344 fn assert_environment(settings: &TestBootstrapSettings, runtime_path: &Utf8PathBuf) {
345 let env_pairs = settings.environment.to_env();
346 let pgpass = runtime_path.join(".pgpass");
347 assert!(env_pairs.contains(&("PGPASSFILE".into(), Some(pgpass.as_str().into()))));
348 assert_eq!(settings.environment.home.as_path(), runtime_path.as_path());
349 }
350}