use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::error::ErrorInner;
use crate::util::{KillOnDrop, TempDir};
use crate::Error;
use std::process::Command;
pub struct TempMongo {
tempdir: TempDir,
socket_path: PathBuf,
log_path: PathBuf,
client: mongodb::Client,
server: KillOnDrop,
}
impl std::fmt::Debug for TempMongo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TempMongo")
.field("tempdir", &self.tempdir.path())
.field("socket_path", &self.socket_path())
.field("log_path", &self.log_path())
.field("server_pid", &self.server.id())
.finish_non_exhaustive()
}
}
impl TempMongo {
pub async fn new() -> Result<Self, Error> {
Self::from_builder(&TempMongoBuilder::new()).await
}
pub fn builder() -> TempMongoBuilder {
TempMongoBuilder::new()
}
pub fn process_id(&self) -> u32 {
self.server.id()
}
pub fn directory(&self) -> &Path {
self.tempdir.path()
}
pub fn socket_path(&self) -> &Path {
&self.socket_path
}
pub fn log_path(&self) -> &Path {
&self.log_path
}
pub fn client(&self) -> &mongodb::Client {
&self.client
}
pub fn set_clean_on_drop(&mut self, clean_on_drop: bool) {
self.tempdir.set_clean_on_drop(clean_on_drop);
}
pub async fn kill_and_clean(mut self) -> Result<(), Error> {
self.client.shutdown_immediate().await;
self.server.kill()
.map_err(ErrorInner::KillServer)?;
let path = self.tempdir.path().to_owned();
self.tempdir.close()
.map_err(|e| ErrorInner::CleanDir(path, e))?;
Ok(())
}
pub async fn kill_no_clean(mut self) -> Result<(), Error> {
let _path = self.tempdir.into_path();
self.client.shutdown_immediate().await;
self.server.kill()
.map_err(ErrorInner::KillServer)?;
Ok(())
}
async fn from_builder(builder: &TempMongoBuilder) -> Result<Self, Error> {
let tempdir = builder.make_temp_dir().map_err(ErrorInner::MakeTempDir)?;
let db_dir = tempdir.path().join("db");
let socket_path = tempdir.path().join("mongod.sock");
let log_path = tempdir.path().join("mongod.log");
std::fs::create_dir(&db_dir)
.map_err(|e| ErrorInner::MakeDbDir(db_dir.clone(), e))?;
let server = Command::new(builder.get_command())
.arg("--bind_ip")
.arg(&socket_path)
.arg("--dbpath")
.arg(db_dir)
.arg("--logpath")
.arg(&log_path)
.arg("--nounixsocket")
.arg("--noauth")
.spawn()
.map_err(|e| ErrorInner::SpawnServer(builder.get_command_string(), e))?;
let server = KillOnDrop::new(server);
let client_options = mongodb::options::ClientOptions::builder()
.hosts(vec![mongodb::options::ServerAddress::Unix { path: socket_path.clone() }])
.connect_timeout(Duration::from_millis(10))
.retry_reads(false)
.retry_writes(false)
.build();
let client = mongodb::Client::with_options(client_options)
.map_err(|e| ErrorInner::Connect(socket_path.display().to_string(), e))?;
client.list_databases(None, None).await
.map_err(|e| ErrorInner::Connect(socket_path.display().to_string(), e))?;
Ok(Self {
tempdir,
socket_path,
log_path,
server,
client,
})
}
}
#[derive(Debug)]
pub struct TempMongoBuilder {
parent_directory: Option<PathBuf>,
clean_on_drop: bool,
command: Option<OsString>,
}
impl TempMongoBuilder {
pub fn new() -> Self {
Self {
parent_directory: None,
command: None,
clean_on_drop: true,
}
}
pub async fn spawn(&self) -> Result<TempMongo, Error> {
TempMongo::from_builder(self).await
}
pub fn clean_on_drop(mut self, clean_on_drop: bool) -> Self {
self.clean_on_drop = clean_on_drop;
self
}
pub fn mongod_command(mut self, command: impl Into<OsString>) -> Self {
self.command = Some(command.into());
self
}
fn get_command(&self) -> &OsStr {
self.command
.as_deref()
.unwrap_or("mongod".as_ref())
}
fn get_command_string(&self) -> String {
self.get_command().to_string_lossy().into()
}
fn make_temp_dir(&self) -> std::io::Result<TempDir> {
match &self.parent_directory {
Some(dir) => TempDir::new_in(dir, self.clean_on_drop),
None => TempDir::new(self.clean_on_drop),
}
}
}
impl Default for TempMongoBuilder {
fn default() -> Self {
Self::new()
}
}