#![allow(clippy::unwrap_used)]
use std::fs;
use std::path::Path;
use std::process::{Command, Output};
fn mkit_bin() -> &'static str {
env!("CARGO_BIN_EXE_mkit")
}
fn run_in(cwd: &Path, args: &[&str]) -> Output {
run_in_with_env(cwd, args, &[])
}
fn run_in_with_env(cwd: &Path, args: &[&str], extra_env: &[(&str, &str)]) -> Output {
let xdg = tempfile::tempdir().expect("xdg tempdir");
let mut cmd = Command::new(mkit_bin());
cmd.args(args)
.current_dir(cwd)
.env("XDG_CONFIG_HOME", xdg.path());
for (k, v) in extra_env {
cmd.env(k, v);
}
let out = cmd.output().expect("spawn mkit");
drop(xdg);
out
}
fn file_url(dir: &Path) -> String {
format!("mkit+file://{}", dir.display())
}
fn repo_with_many_files(n: usize) -> tempfile::TempDir {
let td = tempfile::tempdir().unwrap();
assert!(run_in(td.path(), &["init"]).status.success());
assert!(run_in(td.path(), &["keygen"]).status.success());
for i in 0..n {
fs::write(td.path().join(format!("f{i}.txt")), format!("file {i}")).unwrap();
}
assert!(run_in(td.path(), &["add", "."]).status.success());
assert!(
run_in(td.path(), &["commit", "-m", "many files"])
.status
.success()
);
td
}
#[test]
fn push_reports_honest_monotonic_progress_when_forced_on() {
let td = repo_with_many_files(40);
let remote = tempfile::tempdir().unwrap();
let url = file_url(remote.path());
assert!(
run_in(td.path(), &["remote", "add", "origin", &url])
.status
.success()
);
let out = run_in_with_env(
td.path(),
&["push", "origin"],
&[("MKIT_PROGRESS", "always")],
);
assert!(out.status.success(), "push failed: {out:?}");
let stderr = String::from_utf8(out.stderr).unwrap();
let counts = extract_counts(&stderr, "Writing objects");
assert!(
counts.len() >= 2,
"expected multiple progress updates crossing the report threshold, got stderr: {stderr}"
);
assert!(
counts.windows(2).all(|w| w[0] <= w[1]),
"counts must be monotonically non-decreasing: {counts:?} (stderr: {stderr})"
);
assert!(
*counts.last().unwrap() > 0,
"final count must reflect real objects sent: {counts:?}"
);
assert!(
stderr.contains("done."),
"expected a final ', done.' line: {stderr}"
);
assert!(!stderr.contains("Compressing objects"), "stderr: {stderr}");
assert!(!stderr.contains("Enumerating objects"), "stderr: {stderr}");
assert!(!stderr.contains("Total "), "stderr: {stderr}");
}
#[test]
fn push_suppresses_progress_by_default_on_non_tty_stderr() {
let td = repo_with_many_files(40);
let remote = tempfile::tempdir().unwrap();
let url = file_url(remote.path());
assert!(
run_in(td.path(), &["remote", "add", "origin", &url])
.status
.success()
);
let out = run_in(td.path(), &["push", "origin"]);
assert!(out.status.success(), "push failed: {out:?}");
let stderr = String::from_utf8(out.stderr).unwrap();
assert!(
!stderr.contains("Writing objects"),
"progress must be suppressed on non-tty stderr by default: {stderr}"
);
assert!(stderr.contains("To "), "stderr: {stderr}");
}
#[test]
fn push_quiet_flag_wins_over_forced_progress() {
let td = repo_with_many_files(10);
let remote = tempfile::tempdir().unwrap();
let url = file_url(remote.path());
assert!(
run_in(td.path(), &["remote", "add", "origin", &url])
.status
.success()
);
let out = run_in_with_env(
td.path(),
&["push", "origin", "--quiet"],
&[("MKIT_PROGRESS", "always")],
);
assert!(out.status.success(), "push failed: {out:?}");
let stderr = String::from_utf8(out.stderr).unwrap();
assert!(
!stderr.contains("Writing objects"),
"--quiet must suppress progress even under MKIT_PROGRESS=always: {stderr}"
);
}
#[test]
fn clone_reports_honest_monotonic_progress_across_chain_packs_when_forced_on() {
let td = tempfile::tempdir().unwrap();
assert!(run_in(td.path(), &["init"]).status.success());
assert!(run_in(td.path(), &["keygen"]).status.success());
let remote = tempfile::tempdir().unwrap();
let url = file_url(remote.path());
assert!(
run_in(td.path(), &["remote", "add", "origin", &url])
.status
.success()
);
for i in 0..5 {
fs::write(td.path().join(format!("g{i}.txt")), format!("gen {i}")).unwrap();
assert!(run_in(td.path(), &["add", "."]).status.success());
assert!(
run_in(td.path(), &["commit", "-m", &format!("gen {i}")])
.status
.success()
);
assert!(
run_in(td.path(), &["push", "origin"]).status.success(),
"push gen {i}"
);
}
let dst = tempfile::tempdir().unwrap();
let clone_dir = dst.path().join("clone");
let out = run_in_with_env(
dst.path(),
&["clone", &url, clone_dir.to_str().unwrap()],
&[("MKIT_PROGRESS", "always")],
);
assert!(out.status.success(), "clone failed: {out:?}");
let stderr = String::from_utf8(out.stderr).unwrap();
let counts = extract_counts(&stderr, "Unpacking objects");
assert!(
counts.len() >= 2,
"expected progress across multiple chain packs, got stderr: {stderr}"
);
assert!(
counts.windows(2).all(|w| w[0] <= w[1]),
"counts must be monotonically non-decreasing: {counts:?} (stderr: {stderr})"
);
}
fn extract_counts(stderr: &str, label: &str) -> Vec<u64> {
let mut out = Vec::new();
for line in stderr.split(['\r', '\n']) {
let Some(rest) = line.strip_prefix(label) else {
continue;
};
let rest = rest.trim_start_matches(':').trim_start();
let digits: String = rest.chars().take_while(char::is_ascii_digit).collect();
if let Ok(n) = digits.parse::<u64>() {
out.push(n);
}
}
out
}