use super::*;
use std::ffi::OsStr;
fn environment(pairs: &[(&str, &str)]) -> Vec<(OsString, OsString)> {
let mut built: Vec<(OsString, OsString)> = Vec::new();
for (name, value) in pairs {
built.push((OsString::from(name), OsString::from(value)));
}
return built;
}
#[cfg(unix)]
fn shell(script: &str) -> Command {
let mut command: Command = Command::new("sh");
command.arg("-c").arg(script);
return command;
}
#[test]
fn the_deadline_comes_from_the_variable_or_the_default() {
assert_eq!(push_deadline(&[]), DEFAULT_PUSH_DEADLINE);
assert_eq!(DEFAULT_PUSH_DEADLINE, Duration::from_secs(600));
assert_eq!(
push_deadline(&environment(&[(PUSH_DEADLINE_VARIABLE, "45")])),
Duration::from_secs(45)
);
assert_eq!(
push_deadline(&environment(&[(PUSH_DEADLINE_VARIABLE, " 7 ")])),
Duration::from_secs(7)
);
assert_eq!(
push_deadline(&environment(&[
(PUSH_DEADLINE_VARIABLE, "10"),
("OTHER", "99"),
(PUSH_DEADLINE_VARIABLE, "20"),
])),
Duration::from_secs(20)
);
}
#[test]
fn an_unusable_deadline_falls_back_to_the_default() {
for value in ["0", "-5", "1.5", "", "soon", "10s"] {
assert_eq!(
push_deadline(&environment(&[(PUSH_DEADLINE_VARIABLE, value)])),
DEFAULT_PUSH_DEADLINE,
"{value:?}"
);
}
}
#[cfg(unix)]
#[test]
fn a_non_text_deadline_falls_back_to_the_default() {
use std::os::unix::ffi::OsStringExt;
let pairs: Vec<(OsString, OsString)> = vec![(
OsString::from(PUSH_DEADLINE_VARIABLE),
OsString::from_vec(vec![0xff, 0xfe]),
)];
assert_eq!(push_deadline(&pairs), DEFAULT_PUSH_DEADLINE);
}
#[test]
fn the_lock_wait_outlasts_the_holders_whole_push() {
assert_eq!(lock_wait_limit(&[]), Duration::from_secs(600 + 5 + 60));
assert_eq!(
lock_wait_limit(&environment(&[(PUSH_DEADLINE_VARIABLE, "100")])),
Duration::from_secs(100 + 5 + 60)
);
assert_eq!(
lock_wait_limit(&environment(&[(PUSH_DEADLINE_VARIABLE, "18446744073709551615")])),
Duration::MAX
);
}
#[test]
fn the_lock_wait_variable_replaces_the_derived_limit() {
assert_eq!(
lock_wait_limit(&environment(&[
(PUSH_DEADLINE_VARIABLE, "100"),
(LOCK_WAIT_VARIABLE, "7"),
])),
Duration::from_secs(7)
);
for value in ["0", "", "later", "-1"] {
assert_eq!(
lock_wait_limit(&environment(&[(LOCK_WAIT_VARIABLE, value)])),
Duration::from_secs(600 + 5 + 60),
"{value:?}"
);
}
}
#[test]
fn low_speed_defaults_never_override_the_environment() {
let mut bare: Command = Command::new("git");
apply_low_speed_defaults(&mut bare, &[]);
let added: Vec<(&OsStr, Option<&OsStr>)> = bare.get_envs().collect();
assert_eq!(
added,
vec![
(
OsStr::new("GIT_HTTP_LOW_SPEED_LIMIT"),
Some(OsStr::new("1000"))
),
(
OsStr::new("GIT_HTTP_LOW_SPEED_TIME"),
Some(OsStr::new("60"))
),
]
);
let mut limited: Command = Command::new("git");
apply_low_speed_defaults(
&mut limited,
&environment(&[("GIT_HTTP_LOW_SPEED_LIMIT", "1")]),
);
let only_time: Vec<(&OsStr, Option<&OsStr>)> = limited.get_envs().collect();
assert_eq!(
only_time,
vec![(
OsStr::new("GIT_HTTP_LOW_SPEED_TIME"),
Some(OsStr::new("60"))
)]
);
let mut both: Command = Command::new("git");
apply_low_speed_defaults(
&mut both,
&environment(&[
("GIT_HTTP_LOW_SPEED_LIMIT", "1"),
("GIT_HTTP_LOW_SPEED_TIME", "2"),
]),
);
assert_eq!(both.get_envs().count(), 0);
let mut only_limit: Command = Command::new("git");
apply_low_speed_defaults(
&mut only_limit,
&environment(&[("GIT_HTTP_LOW_SPEED_TIME", "2")]),
);
let remaining: Vec<(&OsStr, Option<&OsStr>)> = only_limit.get_envs().collect();
assert_eq!(
remaining,
vec![(
OsStr::new("GIT_HTTP_LOW_SPEED_LIMIT"),
Some(OsStr::new("1000"))
)]
);
}
#[test]
fn one_trailing_line_break_is_removed() {
assert_eq!(strip_one_line_break("a\n"), "a");
assert_eq!(strip_one_line_break("a\r\n"), "a");
assert_eq!(strip_one_line_break("a\n\n"), "a\n");
assert_eq!(strip_one_line_break("a"), "a");
assert_eq!(strip_one_line_break("a\r"), "a\r");
assert_eq!(strip_one_line_break(""), "");
}
#[cfg(unix)]
#[test]
fn a_finished_push_keeps_the_interleaved_output_and_exit_code() {
let attempt: PushAttempt = run_bounded_push(
shell("echo one; echo two >&2; echo three; exit 3"),
Duration::from_secs(60),
Duration::from_secs(1),
)
.expect("runs");
assert_eq!(attempt.exit_code, 3);
assert_eq!(attempt.output, "one\ntwo\nthree");
let clean: PushAttempt = run_bounded_push(
shell("echo remote: done"),
Duration::from_secs(60),
Duration::from_secs(1),
)
.expect("runs");
assert_eq!(clean.exit_code, 0);
assert_eq!(clean.output, "remote: done");
}
#[cfg(unix)]
#[test]
fn a_push_ended_by_a_signal_reports_one() {
let attempt: PushAttempt = run_bounded_push(
shell("echo before; kill -9 $$"),
Duration::from_secs(60),
Duration::from_secs(1),
)
.expect("runs");
assert_eq!(attempt.exit_code, 1);
assert_eq!(attempt.output, "before");
}
#[cfg(unix)]
#[test]
fn a_stalled_push_is_stopped_at_its_deadline() {
let started: Instant = Instant::now();
let attempt: PushAttempt = run_bounded_push(
shell("echo started; exec sleep 8"),
Duration::from_millis(300),
Duration::from_secs(10),
)
.expect("runs");
let elapsed: Duration = started.elapsed();
assert!(elapsed >= Duration::from_millis(300), "{elapsed:?}");
assert!(elapsed < Duration::from_secs(20), "{elapsed:?}");
assert_eq!(attempt.exit_code, STOPPED_EXIT_CODE);
assert!(
attempt.output.starts_with("started\n"),
"{}",
attempt.output
);
assert!(
attempt
.output
.contains("did not finish within 0 seconds and was stopped"),
"{}",
attempt.output
);
assert!(
attempt.output.contains(PUSH_DEADLINE_VARIABLE),
"{}",
attempt.output
);
}
#[cfg(unix)]
#[test]
fn a_silent_stalled_push_reports_only_the_reason() {
let attempt: PushAttempt = run_bounded_push(
shell("exec sleep 8"),
Duration::from_millis(200),
Duration::from_secs(10),
)
.expect("runs");
assert_eq!(attempt.exit_code, STOPPED_EXIT_CODE);
assert!(
attempt
.output
.starts_with("cli-git: git push did not finish"),
"{}",
attempt.output
);
}
#[cfg(unix)]
#[test]
fn a_push_that_ignores_the_stop_request_is_killed_after_the_grace() {
let started: Instant = Instant::now();
let attempt: PushAttempt = run_bounded_push(
shell("trap '' TERM; echo up; exec sleep 8"),
Duration::from_millis(200),
Duration::from_millis(400),
)
.expect("runs");
let elapsed: Duration = started.elapsed();
assert!(elapsed >= Duration::from_millis(600), "{elapsed:?}");
assert!(elapsed < Duration::from_secs(20), "{elapsed:?}");
assert_eq!(attempt.exit_code, STOPPED_EXIT_CODE);
assert!(attempt.output.starts_with("up\n"), "{}", attempt.output);
}
#[cfg(unix)]
#[test]
fn a_push_that_honors_the_stop_request_ends_within_the_grace() {
let attempt: PushAttempt = run_bounded_push(
shell("trap 'echo got-the-request; exit 0' TERM; echo up; while :; do sleep 0.05; done"),
Duration::from_millis(200),
Duration::from_secs(3),
)
.expect("runs");
assert_eq!(attempt.exit_code, STOPPED_EXIT_CODE);
assert!(
attempt.output.contains("got-the-request\n"),
"{}",
attempt.output
);
}
#[test]
fn the_grace_is_over_at_its_length_exactly() {
let grace: Duration = Duration::from_millis(400);
assert!(grace_remains(Duration::from_millis(399), grace));
assert!(!grace_remains(grace, grace));
assert!(!grace_remains(Duration::from_millis(401), grace));
}
#[test]
fn an_output_is_a_flood_only_past_the_cap() {
assert!(!is_flood(MAX_PUSH_OUTPUT - 1));
assert!(!is_flood(MAX_PUSH_OUTPUT));
assert!(is_flood(MAX_PUSH_OUTPUT + 1));
}
#[cfg(unix)]
#[test]
fn a_flooding_push_is_stopped_and_its_output_capped() {
assert_eq!(MAX_PUSH_OUTPUT, 4_194_304);
let attempt: PushAttempt = run_bounded_push(
shell("head -c 20000000 /dev/zero; exec sleep 8"),
Duration::from_secs(60),
Duration::from_secs(10),
)
.expect("runs");
assert_eq!(attempt.exit_code, STOPPED_EXIT_CODE);
assert!(
attempt.output.ends_with("bytes of output and was stopped."),
"{}",
&attempt.output[attempt.output.len().saturating_sub(120)..]
);
assert!(
attempt.output.len() < 2 * MAX_PUSH_OUTPUT as usize,
"{}",
attempt.output.len()
);
}
#[test]
fn a_program_that_cannot_start_is_a_start_failure() {
let failure: SingleFlightError = run_bounded_push(
Command::new("/nonexistent/cli-git-push-fixture"),
Duration::from_secs(5),
Duration::from_secs(1),
)
.expect_err("no such program");
assert!(matches!(failure, SingleFlightError::Start(_)), "{failure}");
}