use std::{
fs::{self},
path::{Path, PathBuf},
process::Command,
};
use test_temp_dir::TestTempDir;
const EXE: &str = env!("CARGO_BIN_EXE_gnoci");
fn is_ubuntu() -> bool {
let os_release = fs::read_to_string("/etc/os-release").unwrap_or_default();
os_release.contains("ID=ubuntu")
}
fn curl_test() -> String {
if is_ubuntu() {
"curl-ubuntu".to_string()
} else {
"curl-almalinux".to_string()
}
}
fn setup_test(fixture: &str) -> (TestTempDir, PathBuf) {
let out = test_temp_dir::TestTempDir::from_complete_item_path(&format!(
"it::{}",
std::thread::current().name().unwrap()
));
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/")
.join(fixture);
(out, root)
}
fn build_and_run(
image: &str,
cwd: &Path,
out: &Path,
should_succeed: bool,
) -> std::process::Output {
build(image, cwd, out);
let output = if is_ubuntu() {
Command::new("podman")
.arg("run")
.arg(format!("oci:{image}:test"))
.current_dir(out)
.output()
.expect("failed to run container")
} else {
let output = Command::new("skopeo")
.arg("copy")
.arg(format!("oci:{image}:test"))
.arg(format!("docker-daemon:{image}:test"))
.current_dir(out)
.output()
.expect("failed to run skopeo");
assert!(
output.status.success(),
"skopeo failed to copy image. stdout: {}. stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
Command::new("docker")
.arg("run")
.arg(format!("{image}:test"))
.output()
.expect("failed to run container")
};
let stderr = std::str::from_utf8(&output.stderr).unwrap();
eprintln!("stderr: {stderr}");
if should_succeed {
assert!(output.status.success());
} else {
assert!(!output.status.success());
}
output
}
fn build(image: &str, cwd: &Path, out: &Path) {
let out = out.join(image);
let output = Command::new(EXE)
.arg("--tag=test")
.arg(std::path::absolute(out).unwrap())
.env("RUST_LOG", "trace")
.current_dir(cwd)
.output()
.expect("failed to run gnoci");
assert!(
output.status.success(),
"gnoci failed: {}",
String::from_utf8_lossy(&output.stderr)
);
eprintln!("gnoci output: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("gnoci stderr: {}", String::from_utf8_lossy(&output.stderr));
}
#[test]
fn test_run() {
let image = curl_test();
let (tmp_dir, root) = setup_test(&image);
tmp_dir.used_by(|p| {
build_and_run(&image, &root, p, true);
});
}
#[test]
fn test_trivy() {
let image = curl_test();
let (tmp_dir, root) = setup_test(&image);
tmp_dir.used_by(|p| {
build(&image, &root, p);
let output = Command::new("trivy")
.arg("image")
.arg("--format=json")
.arg("--list-all-pkgs")
.arg("--input")
.arg(format!("./{image}"))
.current_dir(p)
.output()
.expect("failed to run trivy");
assert!(
output.status.success(),
"trivy failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = std::str::from_utf8(&output.stdout).unwrap();
let trivy_output: serde_json::Value =
serde_json::from_str(stdout).expect("failed to parse json");
eprintln!("Trivy output: {trivy_output:?}");
let package_names = trivy_output
.get("Results")
.and_then(|results| results.as_array().and_then(|arr| arr.first()))
.expect("Results should be an array")
.get("Packages")
.and_then(|packages| packages.as_array())
.expect("Packages should be an array")
.iter()
.map(|pkg| {
pkg.get("Name")
.and_then(|name| name.as_str())
.unwrap_or_default()
})
.collect::<Vec<_>>();
eprintln!("Packages: {package_names:?}");
assert!(
package_names
.iter()
.any(|&name| name.starts_with("libssl") || name == "openssl-libs")
);
assert!(
package_names
.iter()
.any(|&name| name.starts_with("libgnutls") || name == "krb5-libs")
);
assert!(package_names.contains(&"curl") || package_names.contains(&"curl-minimal"));
});
}
#[test]
fn test_syft() {
let image = curl_test();
let (tmp_dir, root) = setup_test(&image);
tmp_dir.used_by(|p| {
build(&image, &root, p);
let output = Command::new("syft")
.arg("scan")
.arg(format!("oci-dir:{image}"))
.current_dir(p)
.output()
.expect("failed to run syft");
assert!(
output.status.success(),
"syft failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = std::str::from_utf8(&output.stdout).unwrap();
eprintln!("syft stdout: {stdout}");
assert!(stdout.contains("libcurl4t64") || stdout.contains("curl"));
assert!(stdout.contains("libk5crypto3") || stdout.contains("krb5-libs"));
});
}
#[test]
fn test_grant() {
if !is_ubuntu() {
eprintln!("Skipping grant test on AlmaLinux");
return;
}
let image = curl_test();
let (tmp_dir, root) = setup_test(&image);
tmp_dir.used_by(|p| {
build(&image, &root, p);
let output = Command::new("grant")
.arg("list")
.arg(format!("oci-dir:{image}"))
.current_dir(p)
.output()
.expect("failed to run grant");
assert!(
output.status.success(),
"grant failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = std::str::from_utf8(&output.stdout).unwrap();
eprintln!("grant stdout: {stdout}");
assert!(stdout.contains("GPL-3.0-or-later"));
assert!(stdout.contains("Unicode"));
});
}
#[test]
fn test_capabilities() {
let image = if is_ubuntu() {
"capabilities"
} else {
"capabilities-almalinux"
};
let (tmp_dir, root) = setup_test(image);
tmp_dir.used_by(|p| {
let output = build_and_run(image, &root, p, true);
let stdout = std::str::from_utf8(&output.stdout).unwrap();
assert!(
stdout.contains("=ep"),
"Expected output to contain capability, got: {stdout}"
);
});
}