use std::{
ffi::{OsStr, OsString},
fs,
os::unix::ffi::OsStrExt,
};
use pg_embedded_setup_unpriv::test_support::create_partial_data_dir;
use rstest::{fixture, rstest};
use tempfile::{TempDir, tempdir};
use super::*;
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 {
let (_temp_dir, data_dir) = temp_data_dir?;
reset_data_dir(&data_dir)
}
#[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_initialization(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")
}
#[rstest]
#[case("setup", "Setup")]
#[case("start", "Start")]
#[case("stop", "Stop")]
#[case("cleanup", "Cleanup")]
#[case("cleanup-full", "CleanupFull")]
fn operation_parses_known_verbs(#[case] verb: &str, #[case] expected: &str) -> R {
let op =
Operation::parse(OsStr::new(verb)).map_err(|e| format!("expected {verb} to parse: {e}"))?;
ensure(
format!("{op:?}") == expected,
"verb mapped to the wrong operation",
)
}
#[test]
fn operation_parse_rejects_unknown_verb() -> R {
let err = Operation::parse(OsStr::new("frobnicate"))
.err()
.ok_or("expected error")?;
ensure(err.to_string().contains("unknown operation"), "wrong err")
}
#[test]
fn parse_args_requires_operation_and_config() -> R {
let missing_op = parse_args(["pg_worker"].map(OsString::from).into_iter())
.err()
.ok_or("expected missing operation error")?;
ensure(
missing_op.to_string().contains("missing operation"),
"wrong missing-op msg",
)?;
let missing_cfg = parse_args(["pg_worker", "setup"].map(OsString::from).into_iter())
.err()
.ok_or("expected missing config error")?;
ensure(
missing_cfg.to_string().contains("missing config path"),
"wrong missing-cfg msg",
)
}