use std::cell::Cell;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use tempfile::TempDir;
use crate::config::DogConfig;
use crate::daemon::Daemon;
use crate::error::Error;
use crate::state::{State, Verify, Watch};
pub const SHA: &str = "0123456789abcdef0123456789abcdef01234567";
pub const OTHER_SHA: &str = "a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3a1b2";
pub const TEST_BUDGET: Duration = Duration::from_secs(60);
pub fn dog_config() -> DogConfig {
DogConfig {
interval: Duration::from_secs(30),
retention: 5,
git_timeout: Duration::from_secs(60),
build_timeout: Duration::from_secs(60),
passthrough: Vec::new(),
}
}
pub fn state() -> State {
State {
remote: "https://example.com/x".to_owned(),
branch: "main".to_owned(),
deployed: None,
failed: None,
verify: Verify::default(),
watch: Watch::default(),
origin_cwd: None,
origin_script: None,
checkout: PathBuf::from("/srv/x"),
origin: None,
}
}
#[track_caller]
pub fn run_git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.current_dir(dir)
.args(args)
.status()
.expect("spawn git");
assert!(status.success(), "git {args:?} failed in {}", dir.display());
}
#[track_caller]
pub fn head_of(dir: &Path) -> String {
let out = Command::new("git")
.current_dir(dir)
.args(["rev-parse", "HEAD"])
.output()
.expect("rev-parse");
assert!(
out.status.success(),
"git rev-parse HEAD failed in {}: {}",
dir.display(),
String::from_utf8_lossy(&out.stderr).trim()
);
String::from_utf8(out.stdout)
.expect("utf-8 sha")
.trim()
.to_owned()
}
#[track_caller]
pub fn write_files(dir: &Path, files: &[(&str, &str)]) {
for (name, contents) in files {
let full = dir.join(name);
if let Some(parent) = full.parent() {
std::fs::create_dir_all(parent).expect("create fixture parent");
}
std::fs::write(&full, contents).expect("write fixture file");
}
}
#[track_caller]
pub fn fixture_release(files: &[(&str, &str)]) -> TempDir {
let dir = tempfile::tempdir().expect("tempdir");
write_files(dir.path(), files);
dir
}
#[track_caller]
pub fn empty_checkout() -> TempDir {
let dir = tempfile::tempdir().expect("tempdir");
run_git(dir.path(), &["init", "-q", "-b", "main"]);
run_git(dir.path(), &["config", "user.email", "test@example.com"]);
run_git(dir.path(), &["config", "user.name", "test"]);
dir
}
#[track_caller]
pub fn commit(dir: &Path, files: &[(&str, &str)], message: &str) {
write_files(dir, files);
run_git(dir, &["add", "."]);
run_git(dir, &["commit", "-q", "-m", message]);
}
#[track_caller]
pub fn checkout(files: &[(&str, &str)]) -> TempDir {
let dir = empty_checkout();
commit(dir.path(), files, "first");
dir
}
macro_rules! daemon_methods {
(@method dog_config, $body:expr) => {
async fn dog_config(&self, _name: &str) -> Result<String, crate::error::Error> {
$body
}
};
(@method list_flock, $body:expr) => {
async fn list_flock(
&self,
) -> Result<Vec<shep_client::shep_core::protocol::ProcessInfo>, crate::error::Error>
{
$body
}
};
(@method describe, $body:expr) => {
async fn describe(
&self,
_sheep: &str,
) -> Result<Vec<shep_client::shep_core::protocol::ProcessInfo>, crate::error::Error>
{
$body
}
};
(@method start, $body:expr) => {
async fn start(
&self,
_apps: Vec<shep_client::shep_core::config::AppConfig>,
) -> Result<Vec<u32>, crate::error::Error> {
$body
}
};
(@method delete, $body:expr) => {
async fn delete(&self, _id: u32) -> Result<(), crate::error::Error> {
$body
}
};
(@method reload, $body:expr) => {
async fn reload(&self, _sheep: &str) -> Result<(), crate::error::Error> {
$body
}
};
(@method restart, $body:expr) => {
async fn restart(&self, _sheep: &str) -> Result<(), crate::error::Error> {
$body
}
};
(@method save_roll, $body:expr) => {
async fn save_roll(&self) -> Result<std::path::PathBuf, crate::error::Error> {
$body
}
};
(@method set_smit, $body:expr) => {
async fn set_smit(&self, _sheep: &str, _text: &str) -> Result<(), crate::error::Error> {
$body
}
};
(unimplemented; $($method:ident),* $(,)?) => {
$(crate::fixtures::daemon_methods!(@method $method, unimplemented!(stringify!($method)));)*
};
(answering $body:expr; $($method:ident),* $(,)?) => {
$(crate::fixtures::daemon_methods!(@method $method, $body);)*
};
}
pub(crate) use daemon_methods;
#[track_caller]
pub fn tempdir() -> TempDir {
tempfile::tempdir().expect("tempdir")
}
pub struct Sections {
sections: Vec<String>,
asked: Cell<usize>,
}
impl core::fmt::Debug for Sections {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Sections")
.field(
"sections",
&format_args!("<{} sections>", self.sections.len()),
)
.field("asked", &self.asked.get())
.finish()
}
}
impl Sections {
pub fn of(first: &str, rest: &[&str]) -> Self {
Self {
sections: core::iter::once(first)
.chain(rest.iter().copied())
.map(str::to_owned)
.collect(),
asked: Cell::new(0),
}
}
pub fn asked(&self) -> usize {
self.asked.get()
}
}
impl Daemon for Sections {
async fn dog_config(&self, _name: &str) -> Result<String, Error> {
let asked = self.asked.get();
self.asked.set(asked + 1);
assert!(
asked < 200,
"the section was asked for far more often than the interval allows: the loop is \
not sleeping"
);
Ok(self.sections[asked.min(self.sections.len() - 1)].clone())
}
crate::fixtures::daemon_methods!(unimplemented;
list_flock, describe, start, delete, reload, restart, save_roll, set_smit,
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_does_not_print_the_sections() {
let daemon = Sections::of("retention = 9", &["interval = \"hunter2-distinctive\""]);
let shown = format!("{daemon:?}");
assert!(!shown.contains("hunter2"), "{shown}");
assert!(!shown.contains("retention"), "{shown}");
assert_eq!(shown, "Sections { sections: <2 sections>, asked: 0 }");
}
}