dstest 0.1.3

Deterministic Simulation Testing for containerised services
use crate::fault::Fault;
use std::fmt::{self, Display};

pub mod docker;

pub struct Subject {
    pub id: String,
}

impl Subject {
    pub fn new(id: String) -> Self {
        Subject { id }
    }
}

impl Display for Subject {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Subject {{ id: {} }}", self.id)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Stream {
    StdOut,
    StdErr,
}

impl Display for Stream {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Stream::StdOut => write!(f, "stdout"),
            Stream::StdErr => write!(f, "stderr"),
        }
    }
}

#[derive(Clone, Debug)]
pub struct LogEntry {
    pub stream: Stream,
    pub message: String,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ContainerState {
    Running,
    Paused,
    Exited,
    Dead,
}

impl Display for ContainerState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ContainerState::Running => write!(f, "running"),
            ContainerState::Paused => write!(f, "paused"),
            ContainerState::Exited => write!(f, "exited"),
            ContainerState::Dead => write!(f, "dead"),
        }
    }
}

#[derive(Clone, Debug)]
pub struct InspectResult {
    pub state: ContainerState,
    pub pid: Option<u32>,
    pub ip: Option<String>,
    pub memory_limit: Option<u64>,
    pub cpu_quota: Option<f64>,
}

#[derive(Clone, Debug)]
pub struct ExecResult {
    pub exit_code: i32,
    pub stdout: String,
    pub stderr: String,
}

#[derive(Clone, Debug)]
pub struct LogOptions {
    pub stdout: bool,
    pub stderr: bool,
    pub tail: Option<String>,
    pub since: Option<i32>,
    pub timestamps: bool,
}

impl Default for LogOptions {
    fn default() -> Self {
        Self {
            stdout: true,
            stderr: true,
            tail: None,
            since: None,
            timestamps: false,
        }
    }
}

pub trait Substrate {
    fn affect(&self, subject: &Subject, fault: &Fault) -> Result<(), String>;
    fn clear_faults(&self, subject: &Subject) -> Result<(), String>;
    fn teardown(&self, subject: Subject) -> Result<(), String>;

    fn logs(&self, subject: &Subject, opts: LogOptions) -> Result<Vec<LogEntry>, String>;
    fn inspect(&self, subject: &Subject) -> Result<InspectResult, String>;
    fn exec(&self, subject: &Subject, cmd: &[String]) -> Result<ExecResult, String>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_subject_new() {
        let subject = Subject::new("docker/abc123".to_string());
        assert_eq!(subject.id, "docker/abc123");
    }

    #[test]
    fn test_subject_display() {
        let subject = Subject::new("docker/abc123".to_string());
        assert_eq!(format!("{subject}"), "Subject { id: docker/abc123 }");
    }
}