1#![deny(missing_docs)]
11#![warn(clippy::all, clippy::pedantic)]
12
13#[cfg(feature = "tokio-process")]
15pub mod asynchronous;
16pub mod errors;
18mod search;
19pub mod synchronous;
21
22use std::fmt::Write as FmtWrite;
23use std::fs::{metadata, set_permissions};
24use std::io::{BufRead, BufReader};
25use std::path::{Path, PathBuf};
26use std::sync::atomic::AtomicU32;
27use std::sync::{Arc, OnceLock, RwLock};
28use std::{fs::File, io::Write};
29
30use ctor::dtor;
31use nix::unistd::{Gid, Uid};
32use tempfile::{Builder, TempDir};
33use tracing::{debug, info, instrument};
34
35use crate::errors::{TmpPostgrustError, TmpPostgrustResult};
36use crate::search::PostgresBinaries;
37
38const TMP_POSTGRUST_DB_NAME: &str = "tmp-postgrust";
39const TMP_POSTGRUST_USER_NAME: &str = "tmp-postgrust-user";
40
41pub(crate) static POSTGRES_UID_GID: OnceLock<(Uid, Gid)> = OnceLock::new();
42
43#[dtor]
47fn cleanup_static() {
48 #[cfg(feature = "tokio-process")]
49 if let Some(factory_mutex) = TOKIO_POSTGRES_FACTORY.get() {
50 let mut guard = factory_mutex.blocking_write();
51 drop(guard.take());
52 }
53
54 if let Some(factory_mutex) = DEFAULT_POSTGRES_FACTORY.get() {
55 let mut guard = factory_mutex
56 .write()
57 .expect("Failed to lock default factory mutex.");
58 drop(guard.take());
59 }
60}
61
62static DEFAULT_POSTGRES_FACTORY: OnceLock<RwLock<Option<TmpPostgrustFactory>>> = OnceLock::new();
63
64pub fn new_default_process() -> TmpPostgrustResult<synchronous::ProcessGuard> {
78 let factory_mutex = DEFAULT_POSTGRES_FACTORY.get_or_init(|| {
79 RwLock::new(Some(
80 TmpPostgrustFactory::try_new(&TmpPostgrustFactoryConfig {
81 disable_fsync: true,
82 ..Default::default()
83 })
84 .expect("Failed to initialize default postgres factory."),
85 ))
86 });
87 let guard = factory_mutex
88 .read()
89 .expect("Failed to lock default factory mutex.");
90 let factory = guard
91 .as_ref()
92 .expect("Default factory is uninitialized or has been dropped.");
93 factory.new_instance()
94}
95
96pub fn new_default_process_with_migrations(
111 migrate: impl Fn(&str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>,
112) -> TmpPostgrustResult<synchronous::ProcessGuard> {
113 let factory_mutex = DEFAULT_POSTGRES_FACTORY.get_or_init(|| {
114 let factory = TmpPostgrustFactory::try_new(&TmpPostgrustFactoryConfig {
115 disable_fsync: true,
116 ..Default::default()
117 })
118 .expect("Failed to initialize default postgres factory.");
119 factory
120 .run_migrations(migrate)
121 .expect("Failed to run migrations");
122
123 RwLock::new(Some(factory))
124 });
125 let guard = factory_mutex
126 .read()
127 .expect("Failed to lock default factory mutex.");
128 let factory = guard
129 .as_ref()
130 .expect("Default factory is uninitialized or has been dropped.");
131 factory.new_instance()
132}
133
134#[cfg(feature = "tokio-process")]
136static TOKIO_POSTGRES_FACTORY: tokio::sync::OnceCell<
137 tokio::sync::RwLock<Option<TmpPostgrustFactory>>,
138> = tokio::sync::OnceCell::const_new();
139
140#[cfg(feature = "tokio-process")]
154pub async fn new_default_process_async() -> TmpPostgrustResult<asynchronous::ProcessGuard> {
155 let factory_mutex = TOKIO_POSTGRES_FACTORY
156 .get_or_try_init(|| async {
157 TmpPostgrustFactory::try_new_async(&TmpPostgrustFactoryConfig {
158 disable_fsync: true,
159 ..Default::default()
160 })
161 .await
162 .map(|factory| tokio::sync::RwLock::new(Some(factory)))
163 })
164 .await?;
165 let guard = factory_mutex.read().await;
166 let factory = guard
167 .as_ref()
168 .expect("Default tokio factory is uninitialized or has been dropped.");
169 factory.new_instance_async().await
170}
171
172#[cfg(feature = "tokio-process")]
187pub async fn new_default_process_async_with_migrations<F>(
188 migrate: F,
189) -> TmpPostgrustResult<asynchronous::ProcessGuard>
190where
191 F: for<'r> Fn(
192 &'r str,
193 ) -> futures_util::future::BoxFuture<
194 'r,
195 Result<(), Box<dyn std::error::Error + Send + Sync>>,
196 >,
197{
198 let factory_mutex = TOKIO_POSTGRES_FACTORY
199 .get_or_try_init(|| async {
200 let factory = TmpPostgrustFactory::try_new_async(&TmpPostgrustFactoryConfig {
201 disable_fsync: true,
202 ..Default::default()
203 })
204 .await?;
205 factory.run_migrations_async(migrate).await?;
206 Ok(tokio::sync::RwLock::new(Some(factory)))
207 })
208 .await?;
209 let guard = factory_mutex.read().await;
210 let factory = guard
211 .as_ref()
212 .expect("Default tokio factory is uninitialized or has been dropped.");
213 factory.new_instance_async().await
214}
215
216#[derive(Debug)]
218pub struct TmpPostgrustFactory {
219 socket_dir: Arc<TempDir>,
220 cache_dir: Arc<TempDir>,
221 config: String,
222 next_port: AtomicU32,
223 binaries: PostgresBinaries,
224}
225
226#[derive(Default, Debug)]
228#[non_exhaustive]
229pub struct TmpPostgrustFactoryConfig {
230 pub disable_fsync: bool,
234 pub postgresql_bin_dir: Option<PathBuf>,
238}
239
240impl TmpPostgrustFactory {
241 fn build_config(factory_config: &TmpPostgrustFactoryConfig, socket_dir: &Path) -> String {
243 let mut config = String::new();
244 config.push_str("shared_buffers = '12MB'\n");
246 config.push_str("listen_addresses = ''\n");
248 writeln!(
250 &mut config,
251 "unix_socket_directories = \'{}\'",
252 socket_dir.to_str().unwrap()
253 )
254 .expect("Failed to append unix socket to config");
255 writeln!(&mut config, "fsync = \'{}\'", !factory_config.disable_fsync)
259 .expect("Failed to append fsync option to config");
260
261 config
262 }
263
264 #[instrument]
266 pub fn try_new(
267 factory_config: &TmpPostgrustFactoryConfig,
268 ) -> TmpPostgrustResult<TmpPostgrustFactory> {
269 let binaries = PostgresBinaries::resolve(factory_config.postgresql_bin_dir.as_deref())?;
270
271 let socket_dir = Builder::new()
272 .prefix("tmp-postgrust-socket")
273 .tempdir()
274 .map_err(TmpPostgrustError::CreateSocketDirFailed)?;
275 let cache_dir = Builder::new()
276 .prefix("tmp-postgrust-cache")
277 .tempdir()
278 .map_err(TmpPostgrustError::CreateCacheDirFailed)?;
279
280 synchronous::chown_to_non_root(cache_dir.path())?;
281 synchronous::chown_to_non_root(socket_dir.path())?;
282 synchronous::exec_init_db(&binaries.initdb, cache_dir.path())?;
283
284 let config = TmpPostgrustFactory::build_config(factory_config, socket_dir.path());
285
286 let factory = TmpPostgrustFactory {
287 socket_dir: Arc::new(socket_dir),
288 cache_dir: Arc::new(cache_dir),
289 config,
290 next_port: AtomicU32::new(5432),
291 binaries,
292 };
293 let process = factory.start_postgresql(&factory.cache_dir)?;
294 synchronous::exec_create_user(
295 &factory.binaries.createuser,
296 process.socket_dir.path(),
297 process.port,
298 &process.user_name,
299 )?;
300 synchronous::exec_create_db(
301 &factory.binaries.createdb,
302 process.socket_dir.path(),
303 process.port,
304 &process.user_name,
305 &process.db_name,
306 None,
307 )?;
308
309 Ok(factory)
310 }
311
312 #[cfg(feature = "tokio-process")]
314 #[instrument]
315 pub async fn try_new_async(
316 factory_config: &TmpPostgrustFactoryConfig,
317 ) -> TmpPostgrustResult<TmpPostgrustFactory> {
318 let binaries = PostgresBinaries::resolve(factory_config.postgresql_bin_dir.as_deref())?;
319
320 let socket_dir = Builder::new()
321 .prefix("tmp-postgrust-socket")
322 .tempdir()
323 .map_err(TmpPostgrustError::CreateSocketDirFailed)?;
324 let cache_dir = Builder::new()
325 .prefix("tmp-postgrust-cache")
326 .tempdir()
327 .map_err(TmpPostgrustError::CreateCacheDirFailed)?;
328
329 asynchronous::chown_to_non_root(cache_dir.path()).await?;
330 asynchronous::chown_to_non_root(socket_dir.path()).await?;
331 asynchronous::exec_init_db(&binaries.initdb, cache_dir.path()).await?;
332
333 let config = TmpPostgrustFactory::build_config(factory_config, socket_dir.path());
334
335 let factory = TmpPostgrustFactory {
336 socket_dir: Arc::new(socket_dir),
337 cache_dir: Arc::new(cache_dir),
338 config,
339 next_port: AtomicU32::new(5432),
340 binaries,
341 };
342 let process = factory.start_postgresql_async(&factory.cache_dir).await?;
343 asynchronous::exec_create_user(
344 &factory.binaries.createuser,
345 process.socket_dir.path(),
346 process.port,
347 &process.user_name,
348 )
349 .await?;
350 asynchronous::exec_create_db(
351 &factory.binaries.createdb,
352 process.socket_dir.path(),
353 process.port,
354 &process.user_name,
355 &process.db_name,
356 None,
357 )
358 .await?;
359
360 Ok(factory)
361 }
362
363 pub fn run_migrations(
371 &self,
372 migrate: impl Fn(&str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>,
373 ) -> TmpPostgrustResult<()> {
374 let process = self.start_postgresql(&self.cache_dir)?;
375
376 migrate(&process.connection_string()).map_err(TmpPostgrustError::MigrationsFailed)?;
377
378 Ok(())
379 }
380
381 #[cfg(feature = "tokio-process")]
389 pub async fn run_migrations_async<F>(&self, migrate: F) -> TmpPostgrustResult<()>
390 where
391 F: for<'r> Fn(
392 &'r str,
393 ) -> futures_util::future::BoxFuture<
394 'r,
395 Result<(), Box<dyn std::error::Error + Send + Sync>>,
396 >,
397 {
398 let process = self.start_postgresql_async(&self.cache_dir).await?;
399
400 migrate(&process.connection_string())
401 .await
402 .map_err(TmpPostgrustError::MigrationsFailed)?;
403
404 Ok(())
405 }
406
407 #[instrument(skip(self))]
410 pub fn new_instance(&self) -> TmpPostgrustResult<synchronous::ProcessGuard> {
411 let data_directory = Builder::new()
412 .prefix("tmp-postgrust-db")
413 .tempdir()
414 .map_err(TmpPostgrustError::CreateDataDirFailed)?;
415 let data_directory_path = data_directory.path();
416
417 set_permissions(
418 &data_directory,
419 metadata(self.cache_dir.path()).unwrap().permissions(),
420 )
421 .unwrap();
422 synchronous::exec_copy_dir(self.cache_dir.path(), data_directory_path)?;
423
424 if !data_directory_path.join("PG_VERSION").exists() {
425 return Err(TmpPostgrustError::EmptyDataDirectory);
426 }
427
428 self.start_postgresql(&Arc::new(data_directory))
429 }
430
431 #[instrument(skip(self))]
432 fn start_postgresql(
433 &self,
434 dir: &Arc<TempDir>,
435 ) -> TmpPostgrustResult<synchronous::ProcessGuard> {
436 File::create(dir.path().join("postgresql.conf"))
437 .map_err(TmpPostgrustError::CreateConfigFailed)?
438 .write_all(self.config.as_bytes())
439 .map_err(TmpPostgrustError::CreateConfigFailed)?;
440
441 let port = self
442 .next_port
443 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
444
445 synchronous::chown_to_non_root(dir.path())?;
446 let mut postgres_process_handle =
447 synchronous::start_postgres_subprocess(&self.binaries.postgres, dir.path(), port)?;
448 let stdout = postgres_process_handle.stdout.take().unwrap();
449 let stderr = postgres_process_handle.stderr.take().unwrap();
450
451 let stdout_reader = BufReader::new(stdout).lines();
452 let mut stderr_reader = BufReader::new(stderr).lines();
453
454 while let Some(Ok(line)) = stderr_reader.next() {
455 debug!("Postgresql: {}", line);
456 if line.contains("database system is ready to accept connections") {
457 info!("temporary database system is read to accept connections");
458 break;
459 }
460 }
461
462 Ok(synchronous::ProcessGuard {
463 stdout_reader: Some(stdout_reader),
464 stderr_reader: Some(stderr_reader),
465 port,
466 db_name: TMP_POSTGRUST_DB_NAME.to_string(),
467 user_name: TMP_POSTGRUST_USER_NAME.to_string(),
468 postgres_process: postgres_process_handle,
469 _data_directory: Arc::clone(dir),
470 _cache_directory: Arc::clone(&self.cache_dir),
471 socket_dir: Arc::clone(&self.socket_dir),
472 createdb_bin: self.binaries.createdb.clone(),
473 })
474 }
475
476 #[cfg(feature = "tokio-process")]
479 #[instrument(skip(self))]
480 pub async fn new_instance_async(&self) -> TmpPostgrustResult<asynchronous::ProcessGuard> {
481 use tokio::fs::{metadata, set_permissions};
482
483 let data_directory = Builder::new()
484 .prefix("tmp-postgrust-db")
485 .tempdir()
486 .map_err(TmpPostgrustError::CreateDataDirFailed)?;
487 let data_directory_path = data_directory.path();
488
489 set_permissions(
490 &data_directory,
491 metadata(self.cache_dir.path()).await.unwrap().permissions(),
492 )
493 .await
494 .unwrap();
495 asynchronous::exec_copy_dir(self.cache_dir.path(), data_directory_path).await?;
496
497 if !data_directory_path.join("PG_VERSION").exists() {
498 return Err(TmpPostgrustError::EmptyDataDirectory);
499 }
500
501 self.start_postgresql_async(&Arc::new(data_directory)).await
502 }
503
504 #[cfg(feature = "tokio-process")]
505 #[instrument(skip(self))]
506 async fn start_postgresql_async(
507 &self,
508 dir: &Arc<TempDir>,
509 ) -> TmpPostgrustResult<asynchronous::ProcessGuard> {
510 use tokio::io::AsyncBufReadExt;
511
512 File::create(dir.path().join("postgresql.conf"))
513 .map_err(TmpPostgrustError::CreateConfigFailed)?
514 .write_all(self.config.as_bytes())
515 .map_err(TmpPostgrustError::CreateConfigFailed)?;
516
517 let port = self
518 .next_port
519 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
520
521 asynchronous::chown_to_non_root(dir.path()).await?;
522
523 let permit = crate::asynchronous::MAX_CONCURRENT_PROCESSES
524 .get_or_init(|| tokio::sync::Semaphore::new(num_cpus::get()))
525 .acquire()
526 .await
527 .map_err(TmpPostgrustError::AsyncProcessPermitAcquireError)?;
528
529 let mut postgres_process_handle =
530 asynchronous::start_postgres_subprocess(&self.binaries.postgres, dir.path(), port)?;
531 let stdout = postgres_process_handle.stdout.take().unwrap();
532 let stderr = postgres_process_handle.stderr.take().unwrap();
533
534 let stdout_reader = tokio::io::BufReader::new(stdout).lines();
535 let mut stderr_reader = tokio::io::BufReader::new(stderr).lines();
536
537 while let Some(line) = stderr_reader.next_line().await.unwrap() {
538 debug!("Postgresql: {}", line);
539 if line.contains("database system is ready to accept connections") {
540 info!("temporary database system is read to accept connections");
541 break;
542 }
543 }
544
545 Ok(asynchronous::ProcessGuard {
546 stdout_reader: Some(stdout_reader),
547 stderr_reader: Some(stderr_reader),
548 port,
549 db_name: TMP_POSTGRUST_DB_NAME.to_string(),
550 user_name: TMP_POSTGRUST_USER_NAME.to_string(),
551 _data_directory: Arc::clone(dir),
552 _cache_directory: Arc::clone(&self.cache_dir),
553 socket_dir: Arc::clone(&self.socket_dir),
554 createdb_bin: self.binaries.createdb.clone(),
555 postgres_process: postgres_process_handle,
556 _process_permit: permit,
557 })
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use super::*;
564
565 use test_log::test;
566 use tokio_postgres::NoTls;
567 use tracing::error;
568
569 #[test(tokio::test)]
570 async fn it_works() {
571 let factory = TmpPostgrustFactory::try_new(&TmpPostgrustFactoryConfig {
572 disable_fsync: false,
573 ..Default::default()
574 })
575 .expect("failed to create factory");
576
577 let postgresql_proc = factory
578 .new_instance()
579 .expect("failed to create a new instance");
580
581 let (client, conn) = tokio_postgres::connect(&postgresql_proc.connection_string(), NoTls)
582 .await
583 .expect("failed to connect to postgresql");
584
585 tokio::spawn(async move {
586 if let Err(e) = conn.await {
587 error!("connection error: {}", e);
588 }
589 });
590
591 client.query("SELECT 1;", &[]).await.unwrap();
592 }
593
594 #[test(tokio::test)]
595 async fn it_works_fsync_disabled() {
596 let factory = TmpPostgrustFactory::try_new(&TmpPostgrustFactoryConfig {
597 disable_fsync: true,
598 ..Default::default()
599 })
600 .expect("failed to create factory");
601
602 let postgresql_proc = factory
603 .new_instance()
604 .expect("failed to create a new instance");
605
606 let (client, conn) = tokio_postgres::connect(&postgresql_proc.connection_string(), NoTls)
607 .await
608 .expect("failed to connect to postgresql");
609
610 tokio::spawn(async move {
611 if let Err(e) = conn.await {
612 error!("connection error: {}", e);
613 }
614 });
615
616 client.query("SELECT 1;", &[]).await.unwrap();
617 }
618
619 #[cfg(feature = "tokio-process")]
620 #[test(tokio::test)]
621 async fn it_works_async() {
622 let factory = TmpPostgrustFactory::try_new_async(&TmpPostgrustFactoryConfig {
623 disable_fsync: false,
624 ..Default::default()
625 })
626 .await
627 .expect("failed to create factory");
628
629 let postgresql_proc = factory
630 .new_instance_async()
631 .await
632 .expect("failed to create a new instance");
633
634 let (client, conn) = tokio_postgres::connect(&postgresql_proc.connection_string(), NoTls)
635 .await
636 .expect("failed to connect to postgresql");
637
638 tokio::spawn(async move {
639 if let Err(e) = conn.await {
640 error!("connection error: {}", e);
641 }
642 });
643
644 client.query("SELECT 1;", &[]).await.unwrap();
645 }
646
647 #[test(tokio::test)]
648 async fn two_simulatenous_processes() {
649 let factory = TmpPostgrustFactory::try_new(&TmpPostgrustFactoryConfig {
650 disable_fsync: false,
651 ..Default::default()
652 })
653 .expect("failed to create factory");
654
655 let proc1 = factory
656 .new_instance()
657 .expect("failed to create a new instance");
658
659 let proc2 = factory
660 .new_instance()
661 .expect("failed to create a new instance");
662
663 let (client1, conn1) = tokio_postgres::connect(&proc1.connection_string(), NoTls)
664 .await
665 .expect("failed to connect to postgresql");
666
667 tokio::spawn(async move {
668 if let Err(e) = conn1.await {
669 error!("connection error: {}", e);
670 }
671 });
672
673 let (client2, conn2) = tokio_postgres::connect(&proc2.connection_string(), NoTls)
674 .await
675 .expect("failed to connect to postgresql");
676
677 tokio::spawn(async move {
678 if let Err(e) = conn2.await {
679 error!("connection error: {}", e);
680 }
681 });
682
683 client1.query("SELECT 1;", &[]).await.unwrap();
684 client2.query("SELECT 1;", &[]).await.unwrap();
685 }
686
687 #[cfg(feature = "tokio-process")]
688 #[test(tokio::test)]
689 async fn two_simulatenous_processes_async() {
690 let factory = TmpPostgrustFactory::try_new_async(&TmpPostgrustFactoryConfig {
691 disable_fsync: false,
692 ..Default::default()
693 })
694 .await
695 .expect("failed to create factory");
696
697 let proc1 = factory
698 .new_instance_async()
699 .await
700 .expect("failed to create a new instance");
701
702 let proc2 = factory
703 .new_instance_async()
704 .await
705 .expect("failed to create a new instance");
706
707 let (client1, conn1) = tokio_postgres::connect(&proc1.connection_string(), NoTls)
708 .await
709 .expect("failed to connect to postgresql");
710
711 tokio::spawn(async move {
712 if let Err(e) = conn1.await {
713 error!("connection error: {}", e);
714 }
715 });
716
717 let (client2, conn2) = tokio_postgres::connect(&proc2.connection_string(), NoTls)
718 .await
719 .expect("failed to connect to postgresql");
720
721 tokio::spawn(async move {
722 if let Err(e) = conn2.await {
723 error!("connection error: {}", e);
724 }
725 });
726
727 client1.query("SELECT 1;", &[]).await.unwrap();
728 client2.query("SELECT 1;", &[]).await.unwrap();
729 }
730
731 #[cfg(feature = "tokio-process")]
732 #[test(tokio::test)]
733 async fn default_process_factory_1() {
734 let proc = new_default_process_async().await.unwrap();
735
736 let (client, conn) = tokio_postgres::connect(&proc.connection_string(), NoTls)
737 .await
738 .expect("failed to connect to postgresql");
739
740 tokio::spawn(async move {
741 if let Err(e) = conn.await {
742 error!("connection error: {}", e);
743 }
744 });
745
746 client.execute("CREATE TABLE lock ();", &[]).await.unwrap();
748 }
749
750 #[cfg(feature = "tokio-process")]
751 #[test(tokio::test)]
752 async fn default_process_factory_2() {
753 let proc = new_default_process_async().await.unwrap();
754
755 let (client, conn) = tokio_postgres::connect(&proc.connection_string(), NoTls)
756 .await
757 .expect("failed to connect to postgresql");
758
759 tokio::spawn(async move {
760 if let Err(e) = conn.await {
761 error!("connection error: {}", e);
762 }
763 });
764
765 client.execute("CREATE TABLE lock ();", &[]).await.unwrap();
767 }
768
769 #[cfg(feature = "tokio-process")]
770 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
771 async fn default_process_factory_multithread_1() {
772 let proc = new_default_process_async().await.unwrap();
773
774 let (client, conn) = tokio_postgres::connect(&proc.connection_string(), NoTls)
775 .await
776 .expect("failed to connect to postgresql");
777
778 tokio::spawn(async move {
779 if let Err(e) = conn.await {
780 error!("connection error: {}", e);
781 }
782 });
783
784 client.execute("CREATE TABLE lock ();", &[]).await.unwrap();
786 }
787
788 #[cfg(feature = "tokio-process")]
789 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
790 async fn default_process_factory_multithread_2() {
791 let proc = new_default_process_async().await.unwrap();
792
793 let (client, conn) = tokio_postgres::connect(&proc.connection_string(), NoTls)
794 .await
795 .expect("failed to connect to postgresql");
796
797 tokio::spawn(async move {
798 if let Err(e) = conn.await {
799 error!("connection error: {}", e);
800 }
801 });
802
803 client.execute("CREATE TABLE lock ();", &[]).await.unwrap();
805 }
806
807 #[test(tokio::test)]
808 async fn explicit_bin_dir_works() {
809 let postgres_path = crate::search::find_postgresql_command(None, "postgres").expect(
810 "cannot derive bin_dir: postgres not found via PATH or known install locations",
811 );
812 let bin_dir = postgres_path
813 .parent()
814 .expect("postgres path has no parent dir")
815 .to_owned();
816
817 let factory = TmpPostgrustFactory::try_new(&TmpPostgrustFactoryConfig {
818 disable_fsync: true,
819 postgresql_bin_dir: Some(bin_dir),
820 })
821 .expect("failed to create factory with explicit bin dir");
822
823 let proc = factory
824 .new_instance()
825 .expect("failed to create a new instance");
826
827 let (client, conn) = tokio_postgres::connect(&proc.connection_string(), NoTls)
828 .await
829 .expect("failed to connect to postgresql");
830
831 tokio::spawn(async move {
832 if let Err(e) = conn.await {
833 error!("connection error: {}", e);
834 }
835 });
836
837 client.query("SELECT 1;", &[]).await.unwrap();
838 }
839
840 #[test]
841 fn bogus_bin_dir_returns_error() {
842 let result = TmpPostgrustFactory::try_new(&TmpPostgrustFactoryConfig {
843 disable_fsync: true,
844 postgresql_bin_dir: Some(std::path::PathBuf::from("/nonexistent/bin/dir")),
845 });
846 assert!(
847 matches!(
848 result,
849 Err(TmpPostgrustError::PostgresCommandNotFound { .. })
850 ),
851 "expected PostgresCommandNotFound, got: {:?}",
852 result
853 );
854 }
855}