use super::*;
use pg_embedded_setup_unpriv::test_support::create_partial_data_dir;
use rstest::{fixture, rstest};
use std::{
ffi::{OsStr, OsString},
fs,
os::unix::ffi::OsStrExt,
};
use tempfile::{TempDir, tempdir};
type R<T = ()> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
type TempDataDirResult = R<(TempDir, Utf8PathBuf)>;
fn ensure(is_valid: bool, msg: &str) -> R {
if is_valid { Ok(()) } else { Err(msg.into()) }
}
#[fixture]
fn temp_data_dir() -> TempDataDirResult {
let temp = tempdir()?;
let p = Utf8PathBuf::from_path_buf(temp.path().join("data"))
.map_err(|p| format!("not UTF-8: {}", p.display()))?;
Ok((temp, p))
}
#[test]
fn rejects_extra_argument() -> R {
let args = ["pg_worker", "setup", "/tmp/config.json", "unexpected"].map(OsString::from);
let err = run_worker(args.into_iter()).err().ok_or("expected error")?;
ensure(
err.to_string().contains("unexpected extra argument"),
"wrong err",
)
}
#[test]
fn parse_args_rejects_non_utf8_config_path() -> R {
let args = [
OsString::from("pg_worker"),
OsString::from("setup"),
OsStr::from_bytes(&[0x80]).to_os_string(),
];
match parse_args(args.into_iter()) {
Err(WorkerError::InvalidArgs(m)) => ensure(
m.to_lowercase().contains("utf-8") && m.contains("config"),
"bad msg",
),
o => Err(format!("expected InvalidArgs: {o:?}").into()),
}
}
#[rstest]
fn valid_data_dir_detected(temp_data_dir: TempDataDirResult) -> R {
let (_, p) = temp_data_dir?;
fs::create_dir_all(p.join("global"))?;
fs::write(p.join(PG_FILENODE_MAP_MARKER), "")?;
ensure(has_valid_data_dir(&p)?, "should be valid")
}
#[rstest]
fn missing_dir_is_invalid(temp_data_dir: TempDataDirResult) -> R {
ensure(!has_valid_data_dir(&temp_data_dir?.1)?, "should be invalid")
}
#[rstest]
fn dir_without_marker_is_invalid(temp_data_dir: TempDataDirResult) -> R {
let (_, p) = temp_data_dir?;
fs::create_dir_all(&p)?;
ensure(!has_valid_data_dir(&p)?, "should be invalid")
}
#[rstest]
fn reset_removes_partial(temp_data_dir: TempDataDirResult) -> R {
let (_, p) = temp_data_dir?;
fs::create_dir_all(p.join("x"))?;
reset_data_dir(&p)?;
ensure(!p.exists(), "should be removed")
}
#[rstest]
fn reset_ok_for_missing(temp_data_dir: TempDataDirResult) -> R {
reset_data_dir(&temp_data_dir?.1)
}
#[test]
fn reset_errors_on_root() -> R {
let e = reset_data_dir(&Utf8PathBuf::from("/"))
.err()
.ok_or("expected err")?;
ensure(
e.to_string().to_lowercase().contains("root"),
"should mention root",
)
}
#[rstest]
fn recover_skips_nonexistent(temp_data_dir: TempDataDirResult) -> R {
let (_, p) = temp_data_dir?;
recover_invalid_data_dir(&p)?;
ensure(!p.exists(), "should not exist")
}
#[rstest]
fn recover_skips_empty_dir(temp_data_dir: TempDataDirResult) -> R {
let (_, p) = temp_data_dir?;
fs::create_dir_all(&p)?;
recover_invalid_data_dir(&p)?;
ensure(p.exists(), "empty dir should remain")
}
#[rstest]
fn recover_removes_partial_initialisation(temp_data_dir: TempDataDirResult) -> R {
let (_, p) = temp_data_dir?;
create_partial_data_dir(p.as_std_path())?;
ensure(!has_valid_data_dir(&p)?, "partial dir should be invalid")?;
recover_invalid_data_dir(&p)?;
ensure(!p.exists(), "partial dir should be removed by recovery")
}