#![cfg(unix)]
use std::collections::HashMap;
use std::fs;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Command;
const TAG: &str = "v9.9.9";
const MARKER: &str = "fake semlith from the install.sh fixture";
fn target() -> String {
let arch = match std::env::consts::ARCH {
"x86_64" => "x86_64",
"aarch64" => "aarch64",
other => panic!("install.sh supports x86_64 and aarch64; this host is {other}"),
};
match std::env::consts::OS {
"linux" => format!("{arch}-unknown-linux-gnu"),
"macos" => "aarch64-apple-darwin".to_string(),
other => panic!("install.sh supports linux and macos; this host is {other}"),
}
}
fn sha256(path: &Path) -> String {
for tool in ["sha256sum", "shasum"] {
let mut command = Command::new(tool);
if tool == "shasum" {
command.args(["-a", "256"]);
}
let Ok(out) = command.arg(path).output() else {
continue;
};
if out.status.success() {
let text = String::from_utf8_lossy(&out.stdout);
let digest = text
.split_whitespace()
.next()
.unwrap_or_else(|| panic!("{tool} printed no digest: {text:?}"));
return digest.to_string();
}
}
panic!("need sha256sum or shasum to build the SHA256SUMS fixture; install.sh needs one too");
}
fn serve(routes: HashMap<String, Vec<u8>>) -> String {
let listener = TcpListener::bind("127.0.0.1:0").expect("an ephemeral port");
let port = listener.local_addr().expect("the bound address").port();
std::thread::spawn(move || {
for stream in listener.incoming() {
let Ok(mut stream) = stream else { continue };
let Ok(peek) = stream.try_clone() else {
continue;
};
let mut reader = BufReader::new(peek);
let mut request = String::new();
if reader.read_line(&mut request).is_err() {
continue;
}
let path = request.split(' ').nth(1).unwrap_or("").to_string();
loop {
let mut header = String::new();
match reader.read_line(&mut header) {
Ok(0) | Err(_) => break,
Ok(_) if header == "\r\n" => break,
Ok(_) => {}
}
}
let (status, body) = match routes.get(&path) {
Some(body) => ("200 OK", body.clone()),
None => ("404 Not Found", Vec::new()),
};
let head = format!(
"HTTP/1.1 {status}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = stream.write_all(head.as_bytes());
let _ = stream.write_all(&body);
let _ = stream.flush();
}
});
format!("http://127.0.0.1:{port}")
}
struct Release {
_dir: tempfile::TempDir,
home: PathBuf,
semlith_home: PathBuf,
origin: String,
}
fn release(corrupt: bool) -> Release {
let dir = tempfile::tempdir().expect("a temp dir");
let root = dir.path();
let name = format!("semlith-{TAG}-{}", target());
let archive_name = format!("{name}.tar.gz");
let stage = root.join("stage");
fs::create_dir_all(stage.join(&name)).expect("stage the release directory");
let fake = stage.join(&name).join("semlith");
fs::write(&fake, format!("#!/bin/sh\necho '{MARKER}'\nexit 1\n")).expect("write the stand-in");
fs::set_permissions(&fake, fs::Permissions::from_mode(0o755)).expect("make it executable");
fs::write(
stage.join(&name).join("libonnxruntime.so"),
b"not really a library",
)
.expect("stage the runtime");
let archive = root.join(&archive_name);
let tar = Command::new("tar")
.arg("czf")
.arg(&archive)
.arg("-C")
.arg(&stage)
.arg(&name)
.status()
.expect("run tar");
assert!(tar.success(), "tar failed to build {archive_name}");
let mut digest = sha256(&archive);
if corrupt {
let first = digest.remove(0);
digest.insert(0, if first == '0' { '1' } else { '0' });
}
let base = format!("/semlith/semlith/releases/download/{TAG}");
let routes = HashMap::from([
(
format!("{base}/{archive_name}"),
fs::read(&archive).expect("read the archive back"),
),
(
format!("{base}/SHA256SUMS"),
format!("{digest} {archive_name}\n").into_bytes(),
),
]);
let home = root.join("home");
let semlith_home = root.join("semlith-home");
fs::create_dir_all(&home).expect("create a fake HOME");
Release {
_dir: dir,
home,
semlith_home,
origin: serve(routes),
}
}
fn script_pointed_at(origin: &str, into: &Path) -> PathBuf {
let shipped = Path::new(env!("CARGO_MANIFEST_DIR")).join("install.sh");
let text = std::fs::read_to_string(&shipped).expect("reading install.sh");
assert!(
text.contains("origin=https://github.com"),
"install.sh no longer pins one origin; this test rewrites that line"
);
let redirected = text.replace("origin=https://github.com", &format!("origin={origin}"));
let copy = into.join("install.sh");
std::fs::write(©, redirected).expect("writing the redirected script");
copy
}
fn install(release: &Release) -> std::process::Output {
let script = script_pointed_at(&release.origin, &release.home);
Command::new("sh")
.arg(&script)
.env("SEMLITH_VERSION", TAG)
.env("SEMLITH_HOME", &release.semlith_home)
.env("HOME", &release.home)
.output()
.expect("run install.sh")
}
#[test]
fn installs_a_verified_release() {
let release = release(false);
let out = install(&release);
assert!(
out.status.success(),
"install.sh exited {:?}\nstdout:\n{}\nstderr:\n{}",
out.status.code(),
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let installed = release.semlith_home.join("bin").join("semlith");
assert!(
installed.exists(),
"install.sh reported success but {} is missing\nstdout:\n{}",
installed.display(),
String::from_utf8_lossy(&out.stdout)
);
let ran = Command::new(&installed)
.output()
.expect("run the installed file");
let printed = String::from_utf8_lossy(&ran.stdout);
assert!(
printed.contains(MARKER),
"the installed file is not the one from the archive; it printed {printed:?}"
);
}
#[test]
fn the_runtime_lands_beside_the_binary() {
let release = release(false);
let out = install(&release);
assert!(
out.status.success(),
"install.sh exited {:?}\nstdout:\n{}\nstderr:\n{}",
out.status.code(),
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let bin = release.semlith_home.join("bin");
assert!(bin.join("semlith").exists(), "the binary was not installed");
assert_eq!(
fs::read(bin.join("libonnxruntime.so")).expect("the runtime was not installed"),
b"not really a library",
"the runtime beside the binary is not the one the archive carried"
);
assert!(
String::from_utf8_lossy(&out.stdout).contains("libonnxruntime.so"),
"the install did not say it placed the runtime:\n{}",
String::from_utf8_lossy(&out.stdout)
);
}
#[test]
fn refuses_a_release_whose_checksum_does_not_match() {
let release = release(true);
let out = install(&release);
assert!(
!out.status.success(),
"install.sh accepted a bad checksum\nstdout:\n{}",
String::from_utf8_lossy(&out.stdout)
);
let said = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
said.contains("checksum mismatch"),
"install.sh failed without naming the mismatch:\n{said}"
);
let bin = release.semlith_home.join("bin");
assert!(
!bin.exists(),
"{} was created despite the checksum mismatch",
bin.display()
);
}