use super::commit::remove_settled;
use super::fault::{Effect, Injection, Ran, When, run_injected_with_environment, staged_repository, verify_recovery};
use super::strace::strace_available;
use super::support::{Fixture, fixture};
use git_policy_cli::child_class::{CHILD_WAIT_VARIABLE, ChildClass, class_of};
use std::ffi::OsString;
use std::path::PathBuf;
const BOUND_SECONDS: u64 = 1;
const MARGIN_SECONDS: u64 = 1;
fn bounded_commit(fixture: &Fixture, repo: &std::path::Path, label: &str) -> Ran {
let fault: Injection = Injection {
syscall: "execve",
effect: Effect::SlowReturn((BOUND_SECONDS + MARGIN_SECONDS) * 1_000_000),
when: When::FromNth(1),
follow_forks: true,
};
let bound: String = BOUND_SECONDS.to_string();
return run_injected_with_environment(
fixture,
repo,
label,
&fault,
&[(CHILD_WAIT_VARIABLE, bound.as_str()), ("CLI_GIT_WAIT_NOTICE_SECONDS", bound.as_str())],
);
}
#[test]
fn a_git_step_past_its_bound_fails_the_commit_and_names_the_class() {
if !strace_available() {
eprintln!("skipped: strace is not installed, so no start was delayed");
return;
}
let fixture: Fixture = fixture("child-bound");
let repo: PathBuf = staged_repository(&fixture, "repo");
let ran: Ran = bounded_commit(&fixture, repo.as_path(), "child-bound");
let text: String = ran.text();
assert!(ran.injected(), "no start was delayed, so nothing was tested: {text}");
assert_ne!(ran.code(), 0, "a stopped step must fail the commit: {text}");
assert!(text.contains(format!("did not finish within {BOUND_SECONDS} seconds and was stopped").as_str()), "{text}");
assert!(text.contains(CHILD_WAIT_VARIABLE), "{text}");
let (before, found): (&str, ChildClass) = [ChildClass::Plumbing, ChildClass::Object]
.iter()
.filter_map(|class| {
return text
.find(format!(", {} did not finish within", class.label()).as_str())
.map(|at| { return (at, *class); });
})
.min_by_key(|(at, _)| { return *at; })
.map(|(at, class)| { return (&text[..at], class); })
.expect("the sentence names a class");
let start: usize = before
.match_indices("git ")
.map(|(at, _)| { return at; })
.filter(|at| { return *at == 0 || before[..*at].ends_with(|previous: char| { return " :\"(".contains(previous); }); })
.last()
.expect("the stopped command is named");
let command: &str = &before[start..];
let words: Vec<OsString> = command.split_whitespace().skip(1).map(OsString::from).collect();
assert_eq!(found, class_of(words.as_slice()), "the class of `{command}`: {text}");
assert_eq!(verify_recovery(&fixture, repo.as_path(), "child-bound", text.as_str()), Ok(()));
remove_settled(&fixture);
}