Files
sausage/host/tests/it/hook_git.rs
T

180 lines
7.3 KiB
Rust

//! A real git component pushes an incompressible commit through the hook to native git's HTTP backend.
use sausage::{
exec::{Command, Exec, Options},
process::Runtime,
world::World,
};
use std::{io::Write, path::Path, process::Stdio, sync::Arc};
fn git(cwd: &Path, args: &[&str]) {
assert!(
std::process::Command::new("git")
.args(args)
.current_dir(cwd)
.status()
.unwrap()
.success()
);
}
#[tokio::test]
async fn several_mb_git_push() {
push().await;
}
pub async fn push() {
let component = crate::common::build_git().join("wasm32-wasip2/release/git.wasm");
let temp = tempfile::tempdir().unwrap();
let world = World::open(temp.path()).unwrap();
let root = temp.path().join("world");
std::fs::create_dir_all(root.join("bin")).unwrap();
std::fs::copy(component, root.join("bin/git.wasm")).unwrap();
let repo = root.join("repo");
std::fs::create_dir_all(&repo).unwrap();
git(&repo, &["init", "-b", "main"]);
let mut data = vec![0; 4 * 1024 * 1024];
openssl::rand::rand_bytes(&mut data).unwrap();
std::fs::write(repo.join("large"), data).unwrap();
git(&repo, &["add", "large"]);
git(
&repo,
&[
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"commit",
"--no-gpg-sign",
"-m",
"large commit",
],
);
let remote = temp.path().join("remote");
std::fs::create_dir_all(&remote).unwrap();
git(&remote, &["init", "--bare", "destination.git"]);
git(
&remote.join("destination.git"),
&["config", "http.receivepack", "true"],
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
git(
&repo,
&[
"remote",
"add",
"origin",
&format!("http://127.0.0.1:{port}/destination.git"),
],
);
let uploaded = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let counter = uploaded.clone();
let server = tokio::spawn(async move {
loop {
let (socket, _) = listener.accept().await.unwrap();
let remote = remote.clone();
let counter = counter.clone();
let service =
hyper::service::service_fn(move |request: http::Request<hyper::body::Incoming>| {
let remote = remote.clone();
let counter = counter.clone();
async move {
use http_body_util::{BodyExt, Full};
let (parts, body) = request.into_parts();
let body = body.collect().await.unwrap().to_bytes();
counter.fetch_add(body.len(), std::sync::atomic::Ordering::Relaxed);
let response = tokio::task::spawn_blocking(move || {
let mut process = std::process::Command::new("git")
.arg("http-backend")
.env("GIT_PROJECT_ROOT", remote)
.env("GIT_HTTP_EXPORT_ALL", "1")
.env("REQUEST_METHOD", parts.method.as_str())
.env("PATH_INFO", parts.uri.path())
.env("QUERY_STRING", parts.uri.query().unwrap_or(""))
.env(
"CONTENT_TYPE",
parts
.headers
.get("content-type")
.map(|h| h.to_str().unwrap())
.unwrap_or(""),
)
.env("CONTENT_LENGTH", body.len().to_string())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = process.stdin.take().unwrap();
let output = std::thread::scope(|scope| {
scope.spawn(move || {
stdin.write_all(&body).unwrap();
});
process.wait_with_output().unwrap()
});
assert!(output.status.success());
let split = output
.stdout
.windows(4)
.position(|b| b == b"\r\n\r\n")
.unwrap();
let headers = std::str::from_utf8(&output.stdout[..split]).unwrap();
let mut response = http::Response::builder();
for line in headers.split("\r\n") {
let (name, value) = line.split_once(':').unwrap();
if name.eq_ignore_ascii_case("status") {
response = response.status(
value
.trim()
.split(' ')
.next()
.unwrap()
.parse::<u16>()
.unwrap(),
);
} else {
response = response.header(name, value.trim());
}
}
response
.body(Full::new(bytes::Bytes::copy_from_slice(
&output.stdout[split + 4..],
)))
.unwrap()
})
.await
.unwrap();
Ok::<_, std::convert::Infallible>(response)
}
});
hyper::server::conn::http1::Builder::new()
.keep_alive(false)
.serve_connection(hyper_util::rt::TokioIo::new(socket), service)
.await
.unwrap();
}
});
let runtime = Runtime::new(world).unwrap();
let mut output = Vec::new();
let code = runtime
.exec(
Command::Argv(vec![
"/bin/git.wasm".into(),
"push".into(),
"origin".into(),
"main".into(),
]),
Options {
cwd: "/repo".into(),
timeout: Some(std::time::Duration::from_secs(60)),
..Default::default()
},
&mut |_, b| output.extend_from_slice(b),
)
.await
.unwrap();
server.abort();
assert_eq!(code, 0, "{}", String::from_utf8_lossy(&output));
assert!(uploaded.load(std::sync::atomic::Ordering::Relaxed) > 3 * 1024 * 1024);
git(
&temp.path().join("remote/destination.git"),
&["cat-file", "-e", "refs/heads/main:large"],
);
}