#![cfg(unix)]
use crate::bounded_push::{Patience, StopCause};
use crate::push_progress::{Clock, Meter, SAMPLE_INTERVAL};
use std::net::{TcpListener, TcpStream};
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
const PROBE_NAME: &str = "push::push_wait_tests::tcp_probe";
const PORT_VARIABLE: &str = "CLI_GIT_TCP_PROBE_PORT";
const MODE_VARIABLE: &str = "CLI_GIT_TCP_PROBE_MODE";
const CHILD_VARIABLE: &str = "CLI_GIT_TCP_PROBE_CHILD";
const STALL_WINDOW: Duration = Duration::from_millis(1500);
const WINDOWS_WAITED: u32 = 3;
const TRICKLE_PAUSE: Duration = Duration::from_millis(200);
const TRICKLE_SENDS: u32 = 100;
const LOOK: Duration = Duration::from_millis(50);
const PATIENCE: Duration = Duration::from_secs(20);
const HOLD: Duration = Duration::from_secs(600);
struct Owned(Child);
impl Owned {
fn pid(&self) -> u32 {
return self.0.id();
}
}
fn die_with_the_parent() {
unsafe {
libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL);
}
}
#[test]
fn tcp_probe() {
let Some(port) = std::env::var(PORT_VARIABLE).ok().and_then(|text| { return text.parse::<u16>().ok(); }) else {
return;
};
die_with_the_parent();
let mode: String = std::env::var(MODE_VARIABLE).unwrap_or_default();
if mode == "idle" {
std::thread::sleep(HOLD);
return;
}
let mut stream: TcpStream = TcpStream::connect(("127.0.0.1", port)).expect("the control's listener");
let mut started: Vec<Child> = Vec::new();
if let Ok(subcommand) = std::env::var(CHILD_VARIABLE) {
started.push(spawn_named_child(subcommand.as_str()));
}
if mode == "trickle" {
for _ in 0..TRICKLE_SENDS {
std::io::Write::write_all(&mut stream, &[0]).expect("send one byte");
std::thread::sleep(TRICKLE_PAUSE);
}
}
std::thread::sleep(HOLD);
drop(stream);
drop(started);
}
fn spawn_named_child(subcommand: &str) -> Child {
let test_binary: std::path::PathBuf = crate::test_support::this_test_binary().expect("this test binary");
return Command::new(test_binary)
.arg0("git")
.arg(subcommand)
.args(["--exact", PROBE_NAME, "--nocapture", "--test-threads", "1"])
.env(PORT_VARIABLE, "0")
.env(MODE_VARIABLE, "idle")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("start the named child");
}
fn start_probe(port: u16, mode: &str, child: Option<&str>) -> Owned {
let test_binary: std::path::PathBuf = crate::test_support::this_test_binary().expect("this test binary");
let mut command: Command = Command::new(test_binary);
command
.args(["--exact", PROBE_NAME, "--nocapture", "--test-threads", "1"])
.env(PORT_VARIABLE, port.to_string())
.env(MODE_VARIABLE, mode)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
if let Some(subcommand) = child {
command.env(CHILD_VARIABLE, subcommand);
}
return Owned(crate::test_support::spawn_owned(&mut command).expect("start the probe"));
}
fn accepted(listener: &TcpListener) -> TcpStream {
listener.set_nonblocking(true).expect("a non-blocking listener");
let began: Instant = Instant::now();
loop {
if let Ok((stream, _)) = listener.accept() {
return stream;
}
assert!(began.elapsed() < PATIENCE, "the probe never connected");
std::thread::sleep(LOOK);
}
}
fn listening() -> (TcpListener, u16) {
let listener: TcpListener = TcpListener::bind("127.0.0.1:0").expect("a loopback listener");
let port: u16 = listener.local_addr().expect("its address").port();
return (listener, port);
}
fn push_clock(pid: u32) -> Clock {
return Clock::start(pid, Patience::Progress { stall: STALL_WINDOW });
}
fn drive(clock: &mut Clock, span: Duration) -> Option<StopCause> {
let began: Instant = Instant::now();
while began.elapsed() < span {
if let Some(cause) = clock.check() {
return Some(cause);
}
std::thread::sleep(LOOK);
}
return None;
}
fn assert_stalled_at_the_window(cause: Option<StopCause>, began: Instant) {
let Some(StopCause::Stalled(report)) = cause else { panic!("a stall, not another limit or none") };
assert_eq!(report.limit, STALL_WINDOW);
assert!(began.elapsed() >= STALL_WINDOW, "{:?}", began.elapsed());
}
#[test]
fn the_bytes_of_a_socket_are_progress_when_nothing_else_moves() {
let (listener, port): (TcpListener, u16) = listening();
let probe: Owned = start_probe(port, "trickle", None);
let _held: TcpStream = accepted(&listener);
let mut meter: Meter = Meter::new();
let pid: i32 = i32::try_from(probe.pid()).expect("pid");
let began: Instant = Instant::now();
while meter.socket_bytes() == 0 {
assert!(began.elapsed() < PATIENCE, "the bytes the probe sent on its socket never counted");
std::thread::sleep(SAMPLE_INTERVAL);
let _progress: bool = meter.sample(pid);
}
assert!(meter.socket_bytes() > 0);
}
#[test]
fn a_push_that_sent_its_pack_and_holds_its_connection_is_waiting_not_stalled() {
let (listener, port): (TcpListener, u16) = listening();
let probe: Owned = start_probe(port, "connected", Some("send-pack"));
let _held: TcpStream = accepted(&listener);
let mut clock: Clock = push_clock(probe.pid());
let stopped: Option<StopCause> = drive(&mut clock, STALL_WINDOW * WINDOWS_WAITED);
assert!(stopped.is_none(), "the connection is open: the push waits");
let waited = clock.wait_report().expect("the wait is reported once the window has run out");
assert!(waited.silent >= STALL_WINDOW, "{waited:?}");
}
#[test]
fn a_wait_ends_when_the_connection_closes_and_the_stall_window_runs_again() {
let (listener, port): (TcpListener, u16) = listening();
let probe: Owned = start_probe(port, "connected", Some("send-pack"));
let held: TcpStream = accepted(&listener);
let mut clock: Clock = push_clock(probe.pid());
assert!(drive(&mut clock, STALL_WINDOW * WINDOWS_WAITED).is_none(), "waiting while the connection is open");
let closed: Instant = Instant::now();
drop(held);
let cause: Option<StopCause> = drive(&mut clock, PATIENCE);
let Some(StopCause::Stalled(_)) = cause else { panic!("a stall once the connection is closed") };
assert!(closed.elapsed() + SAMPLE_INTERVAL >= STALL_WINDOW, "a full window after the close, not before: {:?}", closed.elapsed());
}
#[test]
fn a_connected_push_is_waited_for_before_any_pack_was_sent() {
let (listener, port): (TcpListener, u16) = listening();
let probe: Owned = start_probe(port, "connected", None);
let _held: TcpStream = accepted(&listener);
let mut clock: Clock = push_clock(probe.pid());
let stopped: Option<StopCause> = drive(&mut clock, STALL_WINDOW * WINDOWS_WAITED);
assert!(stopped.is_none(), "the connection is open: the push waits for the remote");
}
#[test]
fn a_connected_push_is_waited_for_while_its_pack_is_made() {
let (listener, port): (TcpListener, u16) = listening();
let probe: Owned = start_probe(port, "connected", Some("pack-objects"));
let _held: TcpStream = accepted(&listener);
let mut clock: Clock = push_clock(probe.pid());
let stopped: Option<StopCause> = drive(&mut clock, STALL_WINDOW * WINDOWS_WAITED);
assert!(stopped.is_none(), "the connection is open: the push waits");
}
#[test]
fn a_connected_push_is_not_stopped_for_time_however_long_it_waits() {
let (listener, port): (TcpListener, u16) = listening();
let probe: Owned = start_probe(port, "connected", Some("send-pack"));
let _held: TcpStream = accepted(&listener);
let mut clock: Clock = push_clock(probe.pid());
let stopped: Option<StopCause> = drive(&mut clock, STALL_WINDOW * WINDOWS_WAITED * 2);
assert!(stopped.is_none(), "an open connection is waited for however long it takes");
}
#[test]
fn a_tree_with_no_connection_is_a_stall() {
let owned: Owned = Owned(crate::test_support::spawn_owned(Command::new("sleep").arg("600").stdin(Stdio::null())).expect("sleep"));
let mut clock: Clock = push_clock(owned.pid());
let began: Instant = Instant::now();
let cause: Option<StopCause> = drive(&mut clock, PATIENCE);
assert_stalled_at_the_window(cause, began);
}