pg-embed-setup-unpriv 0.5.2

Initializes postgresql_embedded clusters with platform-appropriate setup
Documentation
//! Property tests for cleanup lifecycle invariants.

use std::{
    fs,
    path::{Path, PathBuf},
};

use postgresql_embedded::Settings;
use proptest::{
    prelude::*,
    test_runner::{TestCaseError, TestCaseResult},
};

use super::{
    cleanup_in_process,
    is_dangerous_cleanup_path,
    should_remove_data,
    should_remove_install,
};
use crate::CleanupMode;

#[derive(Debug, Clone, Copy)]
enum PathCase {
    Empty,
    Root,
    RelativeNested,
    AbsoluteNested,
    CurrentDirNested,
}

#[derive(Debug, Clone)]
struct DirectoryState {
    data: DirectoryEntryState,
    install: DirectoryEntryState,
}

#[derive(Debug, Clone, Copy)]
enum DirectoryEntryState {
    Missing,
    Empty,
    Marked,
}

impl DirectoryEntryState {
    const fn is_present(self) -> bool { matches!(self, Self::Empty | Self::Marked) }

    const fn has_marker(self) -> bool { matches!(self, Self::Marked) }
}

fn cleanup_mode_strategy() -> impl Strategy<Value = CleanupMode> {
    prop_oneof![
        Just(CleanupMode::DataOnly),
        Just(CleanupMode::Full),
        Just(CleanupMode::None),
    ]
}

fn path_case_strategy() -> impl Strategy<Value = PathCase> {
    prop_oneof![
        Just(PathCase::Empty),
        Just(PathCase::Root),
        Just(PathCase::RelativeNested),
        Just(PathCase::AbsoluteNested),
        Just(PathCase::CurrentDirNested),
    ]
}

fn directory_state_strategy() -> impl Strategy<Value = DirectoryState> {
    (
        directory_entry_state_strategy(),
        directory_entry_state_strategy(),
    )
        .prop_map(|(data, install)| DirectoryState { data, install })
}

fn directory_entry_state_strategy() -> impl Strategy<Value = DirectoryEntryState> {
    prop_oneof![
        Just(DirectoryEntryState::Missing),
        Just(DirectoryEntryState::Empty),
        Just(DirectoryEntryState::Marked),
    ]
}

fn path_for_case(path_case: PathCase) -> PathBuf {
    let temp_dir = std::env::temp_dir();
    let temp_root = temp_dir
        .ancestors()
        .fold(temp_dir.clone(), |_, ancestor| ancestor.to_path_buf());

    match path_case {
        PathCase::Empty => PathBuf::new(),
        PathCase::Root => temp_root,
        PathCase::RelativeNested => PathBuf::from("relative/nested"),
        PathCase::AbsoluteNested => temp_dir.join("pg-embed-safe-nested"),
        PathCase::CurrentDirNested => PathBuf::from("./relative/nested"),
    }
}

fn expected_dangerous(path_case: PathCase) -> bool {
    matches!(path_case, PathCase::Empty | PathCase::Root)
}

fn io_failure(context: &str, err: &std::io::Error) -> TestCaseError {
    TestCaseError::fail(format!("{context}: {err}"))
}

fn create_dir_if_present(path: &Path, is_present: bool) -> TestCaseResult {
    if is_present {
        fs::create_dir_all(path).map_err(|err| io_failure("create directory", &err))?;
    }
    Ok(())
}

fn write_marker_if_present(path: &Path, dir_present: bool, marker_present: bool) -> TestCaseResult {
    if dir_present && marker_present {
        fs::write(path.join("marker"), b"marker")
            .map_err(|err| io_failure("write marker", &err))?;
    }
    Ok(())
}

fn seed_directories(settings: &Settings, state: &DirectoryState) -> TestCaseResult {
    create_dir_if_present(&settings.data_dir, state.data.is_present())?;
    create_dir_if_present(&settings.installation_dir, state.install.is_present())?;
    write_marker_if_present(
        &settings.data_dir,
        state.data.is_present(),
        state.data.has_marker(),
    )?;
    write_marker_if_present(
        &settings.installation_dir,
        state.install.is_present(),
        state.install.has_marker(),
    )?;
    Ok(())
}

fn assert_cleanup_postcondition(
    settings: &Settings,
    cleanup_mode: CleanupMode,
    expected_data_exists: &mut bool,
    expected_install_exists: &mut bool,
) -> TestCaseResult {
    match cleanup_mode {
        CleanupMode::DataOnly => {
            *expected_data_exists = false;
            prop_assert!(!settings.data_dir.exists());
            prop_assert_eq!(settings.installation_dir.exists(), *expected_install_exists);
        }
        CleanupMode::Full => {
            *expected_data_exists = false;
            *expected_install_exists = false;
            prop_assert!(!settings.data_dir.exists());
            prop_assert!(!settings.installation_dir.exists());
        }
        CleanupMode::None => {
            prop_assert_eq!(settings.data_dir.exists(), *expected_data_exists);
            prop_assert_eq!(settings.installation_dir.exists(), *expected_install_exists);
        }
    }
    prop_assert!(!is_dangerous_cleanup_path(&settings.data_dir));
    prop_assert!(!is_dangerous_cleanup_path(&settings.installation_dir));
    Ok(())
}

proptest! {
    #[test]
    fn cleanup_removal_predicates_are_consistent(mode in cleanup_mode_strategy()) {
        match mode {
            CleanupMode::DataOnly => {
                prop_assert!(should_remove_data(mode));
                prop_assert!(!should_remove_install(mode));
            }
            CleanupMode::Full => {
                prop_assert!(should_remove_data(mode));
                prop_assert!(should_remove_install(mode));
            }
            CleanupMode::None => {
                prop_assert!(!should_remove_data(mode));
                prop_assert!(!should_remove_install(mode));
            }
        }
    }

    #[test]
    fn dangerous_cleanup_path_detection_matches_path_shape(path_case in path_case_strategy()) {
        let path = path_for_case(path_case);

        prop_assert_eq!(is_dangerous_cleanup_path(&path), expected_dangerous(path_case));
    }

    #[test]
    fn cleanup_in_process_is_idempotent_for_generated_states(
        initial_state in directory_state_strategy(),
        operations in prop::collection::vec(cleanup_mode_strategy(), 1..16),
    ) {
        let sandbox = tempfile::tempdir()
            .map_err(|err| TestCaseError::fail(format!("create sandbox: {err}")))?;
        let settings = Settings {
            data_dir: sandbox.path().join("data"),
            installation_dir: sandbox.path().join("install"),
            password_file: sandbox.path().join("install/.pgpass"),
            ..Settings::default()
        };
        seed_directories(&settings, &initial_state)?;
        let mut expected_data_exists = initial_state.data.is_present();
        let mut expected_install_exists = initial_state.install.is_present();

        for cleanup_mode in operations {
            cleanup_in_process(cleanup_mode, &settings, "property-cleanup");
            cleanup_in_process(cleanup_mode, &settings, "property-cleanup");
            assert_cleanup_postcondition(
                &settings,
                cleanup_mode,
                &mut expected_data_exists,
                &mut expected_install_exists,
            )?;
        }
    }
}