dure 0.2.0

Detachable Windows console sessions that outlive the terminal
Documentation
//! Facade over process PAL implementations.

use std::fmt;
use std::path::PathBuf;
#[cfg(test)]
use std::sync::Arc;

use crate::durability::Durability;
use crate::pal::error::PalError;
use crate::pal::ids::{AppId, JobId};
#[cfg(test)]
use crate::pal::processes::MockProcesses;
use crate::pal::processes::{
    AppSpawn, BuildTargetProcesses, ProcessLiveness, Processes, SupervisorSpawn,
};
use crate::session_record::ProcessIdentity;

/// Dispatches process operations to the real PAL or a test mock.
#[derive(Clone)]
pub(crate) enum ProcessesFacade {
    /// Real platform implementation.
    Target(&'static BuildTargetProcesses),
    /// Mock for tests.
    #[cfg(test)]
    Mock(Arc<MockProcesses>),
}

static TARGET: BuildTargetProcesses = BuildTargetProcesses;

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)]
impl fmt::Debug for ProcessesFacade {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Target(_) => f.debug_struct("ProcessesFacade::Target").finish(),
            #[cfg(test)]
            Self::Mock(_) => f.debug_struct("ProcessesFacade::Mock").finish(),
        }
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)]
impl ProcessesFacade {
    pub(crate) const fn target() -> Self {
        Self::Target(&TARGET)
    }

    #[cfg(test)]
    pub(crate) fn from_mock(mock: MockProcesses) -> Self {
        Self::Mock(Arc::new(mock))
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)]
impl Processes for ProcessesFacade {
    fn current_exe(&self) -> Result<PathBuf, PalError> {
        match self {
            Self::Target(inner) => inner.current_exe(),
            #[cfg(test)]
            Self::Mock(inner) => inner.current_exe(),
        }
    }

    fn spawn_supervisor(&self, request: &SupervisorSpawn) -> Result<ProcessIdentity, PalError> {
        match self {
            Self::Target(inner) => inner.spawn_supervisor(request),
            #[cfg(test)]
            Self::Mock(inner) => inner.spawn_supervisor(request),
        }
    }

    fn durability(&self) -> Durability {
        match self {
            Self::Target(inner) => inner.durability(),
            #[cfg(test)]
            Self::Mock(inner) => inner.durability(),
        }
    }

    fn probe(&self, identity: &ProcessIdentity) -> ProcessLiveness {
        match self {
            Self::Target(inner) => inner.probe(identity),
            #[cfg(test)]
            Self::Mock(inner) => inner.probe(identity),
        }
    }

    fn terminate(&self, identity: &ProcessIdentity) -> Result<(), PalError> {
        match self {
            Self::Target(inner) => inner.terminate(identity),
            #[cfg(test)]
            Self::Mock(inner) => inner.terminate(identity),
        }
    }

    fn create_lifetime_job(&self) -> Result<JobId, PalError> {
        match self {
            Self::Target(inner) => inner.create_lifetime_job(),
            #[cfg(test)]
            Self::Mock(inner) => inner.create_lifetime_job(),
        }
    }

    fn close_job(&self, job: JobId) {
        match self {
            Self::Target(inner) => inner.close_job(job),
            #[cfg(test)]
            Self::Mock(inner) => inner.close_job(job),
        }
    }

    fn spawn_app(&self, request: &AppSpawn) -> Result<AppId, PalError> {
        match self {
            Self::Target(inner) => inner.spawn_app(request),
            #[cfg(test)]
            Self::Mock(inner) => inner.spawn_app(request),
        }
    }

    fn wait_app(&self, app: AppId) -> Result<i32, PalError> {
        match self {
            Self::Target(inner) => inner.wait_app(app),
            #[cfg(test)]
            Self::Mock(inner) => inner.wait_app(app),
        }
    }

    fn current_identity(&self) -> Result<ProcessIdentity, PalError> {
        match self {
            Self::Target(inner) => inner.current_identity(),
            #[cfg(test)]
            Self::Mock(inner) => inner.current_identity(),
        }
    }

    fn random_nonce(&self) -> String {
        match self {
            Self::Target(inner) => inner.random_nonce(),
            #[cfg(test)]
            Self::Mock(inner) => inner.random_nonce(),
        }
    }
}

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use super::*;
    use crate::pal::processes::ProcessLiveness;
    use crate::session_record::ProcessIdentity;

    #[test]
    fn from_mock_dispatches_probe() {
        let mut mock = MockProcesses::new();
        mock.expect_probe().return_const(ProcessLiveness::Live);
        let facade = ProcessesFacade::from_mock(mock);
        assert_eq!(
            facade.probe(&ProcessIdentity {
                pid: 1,
                creation_time: 1,
            }),
            ProcessLiveness::Live
        );
        _ = format!("{facade:?}");
    }
}