#![cfg(test)]
use std::path::PathBuf;
fn repo_root() -> PathBuf {
let dir = env!("CARGO_MANIFEST_DIR");
PathBuf::from(dir)
}
fn release_yml() -> String {
let path = repo_root()
.join(".github")
.join("workflows")
.join("release.yml");
std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()))
}
fn build_script(rel: &str) -> String {
let path = repo_root().join(rel);
std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()))
}
mod build_time_invariants {
use super::*;
#[test]
fn build_step_does_not_link_ort_at_build_time() {
let yml = release_yml();
let build_section = yml
.split("Build release binaries")
.nth(1)
.and_then(|s| s.split("Strip binaries").next())
.expect("release.yml must contain a 'Build release binaries' step");
assert!(
!build_section.contains("ORT_LIB_PATH"),
"build step must not set ORT_LIB_PATH (load-dynamic only)"
);
assert!(
!build_section.contains("ORT_PREFER_DYNAMIC_LINK"),
"build step must not set ORT_PREFER_DYNAMIC_LINK (load-dynamic only)"
);
assert!(
!build_section.contains("download-binaries"),
"build step must not enable the download-binaries ort feature"
);
assert!(
!build_section.contains("copy-dylibs"),
"build step must not enable the copy-dylibs ort feature"
);
}
#[test]
fn build_scripts_have_no_origin_rpath() {
for rel in ["build.rs", "crates/leindex-embed/build.rs"] {
let script = build_script(rel);
let non_comment: String = script
.lines()
.filter(|line| {
let trimmed = line.trim_start();
!trimmed.starts_with("//")
})
.collect::<Vec<_>>()
.join("\n");
assert!(
!non_comment.contains("$ORIGIN"),
"{rel} must not emit a $ORIGIN rpath (load-dynamic does not need it)"
);
assert!(
!non_comment.contains("-Wl,-rpath"),
"{rel} must not emit any -Wl,-rpath directive (load-dynamic does not need it)"
);
assert!(
!non_comment.contains("cargo:rustc-link-arg"),
"{rel} must not emit cargo:rustc-link-arg (load-dynamic does not need it)"
);
}
}
}
mod bundle_layout {
use super::*;
#[test]
fn bundle_contains_both_binaries() {
let yml = release_yml();
let package_section = yml
.split("Package release bundle")
.nth(1)
.and_then(|s| s.split("Upload artifact").next())
.expect("release.yml must contain a 'Package release bundle' step");
assert!(
package_section.contains("bin/leindex"),
"package step must lay out bin/leindex"
);
assert!(
package_section.contains("leindex-embed"),
"package step must lay out bin/leindex-embed"
);
}
#[test]
fn bundle_contains_model_assets() {
let yml = release_yml();
let package_section = yml
.split("Package release bundle")
.nth(1)
.and_then(|s| s.split("Upload artifact").next())
.expect("release.yml must contain a 'Package release bundle' step");
assert!(
package_section.contains("models/qwen3-embed-0.6b.onnx"),
"bundle must ship the ONNX model"
);
assert!(
package_section.contains("tokenizer.json"),
"bundle must ship tokenizer.json"
);
assert!(
package_section.contains("config.json"),
"bundle must ship config.json"
);
}
#[test]
fn bundle_includes_ort_runtime_lib_directory() {
let yml = release_yml();
assert!(
yml.contains("lib/libonnxruntime") || yml.contains("BUNDLE_DIR/lib"),
"package step must lay out lib/ for the ORT shared libraries"
);
let obtains_from_wheel = yml.contains("pip install onnxruntime")
|| yml.contains("pip download") && yml.contains("onnxruntime");
assert!(
obtains_from_wheel,
"release.yml must extract ORT from the onnxruntime pip wheel (pip install or pip download)"
);
assert!(
yml.contains("capi/libonnxruntime")
|| yml.contains("CAPI") && yml.contains("libonnxruntime"),
"release.yml must copy libonnxruntime from the wheel's capi/ directory"
);
}
#[test]
fn amd_bundle_includes_migraphx_provider() {
let yml = release_yml();
assert!(
yml.contains("libonnxruntime_providers_migraphx"),
"linux-x86_64 bundle must ship the MIGraphX provider .so"
);
assert!(
yml.contains("libonnxruntime_providers_shared"),
"linux-x86_64 bundle must ship the shared providers .so"
);
assert!(
yml.contains("linux-x86_64") && yml.contains("migraphx"),
"MIGraphX .so inclusion must be scoped to the linux-x86_64 (AMD) bundle"
);
}
#[test]
fn bundle_directory_tree_matches_spec() {
let yml = release_yml();
let package_section = yml
.split("Package release bundle")
.nth(1)
.and_then(|s| s.split("Upload artifact").next())
.expect("release.yml must contain a 'Package release bundle' step");
assert!(
package_section.contains("BUNDLE_DIR/bin"),
"package step must create bin/ under the bundle root"
);
assert!(
package_section.contains("BUNDLE_DIR/lib"),
"package step must create lib/ under the bundle root"
);
assert!(
package_section.contains("BUNDLE_DIR/models"),
"package step must create models/ under the bundle root"
);
}
#[test]
fn bundle_includes_install_txt() {
let yml = release_yml();
let package_section = yml
.split("Package release bundle")
.nth(1)
.and_then(|s| s.split("Upload artifact").next())
.expect("release.yml must contain a 'Package release bundle' step");
assert!(
package_section.contains("INSTALL.txt"),
"package step must stage an INSTALL.txt at the bundle root"
);
assert!(
yml.contains("leindex setup"),
"INSTALL.txt template must reference `leindex setup`"
);
}
}
mod checksum_coverage {
use super::*;
#[test]
fn checksums_cover_lib_directory() {
let yml = release_yml();
let github_release_section = yml
.split("Create GitHub Release")
.nth(1)
.and_then(|s| s.split("softprops/action-gh-release").next())
.unwrap_or_else(|| panic!("release.yml missing GitHub Release step"));
assert!(
github_release_section.contains("SHA256SUMS"),
"release must publish a top-level SHA256SUMS"
);
assert!(
yml.contains("sha256sum") && (yml.contains("find") || yml.contains("BUNDLE_DIR/lib")),
"release.yml must generate per-file SHA256 checksums inside the bundle, covering lib/"
);
}
#[test]
fn bundle_checksums_are_portable_on_macos() {
let yml = release_yml();
let package_section = yml
.split("Package release bundle")
.nth(1)
.and_then(|s| s.split("Upload artifact").next())
.expect("release.yml must contain a package release bundle step");
assert!(
package_section.contains(r#""$RUNNER_OS" == "macOS""#)
&& package_section.contains("shasum -a 256"),
"macOS bundle packaging must use shasum instead of assuming GNU sha256sum"
);
assert!(
!package_section.contains("sort -z") && !package_section.contains("xargs -0 sha256sum"),
"bundle checksum generation must avoid GNU-only sort -z / sha256sum pipelines on macOS runners"
);
assert!(
!package_section.contains("libonnxruntime.*.dylib | sort -V"),
"macOS dylib selection must not use GNU sort -V on BSD macOS runners"
);
}
}
mod binary_verification {
use super::*;
#[test]
fn release_verifies_no_needed_libonnxruntime() {
let yml = release_yml();
assert!(
(yml.contains("readelf") || yml.contains("ldd"))
&& yml.contains("onnxruntime")
&& yml.contains("NEEDED"),
"release.yml must include a step that checks for NEEDED libonnxruntime"
);
}
#[test]
fn release_verifies_no_origin_rpath() {
let yml = release_yml();
assert!(
(yml.contains("readelf") || yml.contains("patchelf")) && yml.contains("rpath"),
"release.yml must include a step that checks for $ORIGIN/rpath in binaries"
);
}
}
mod ci_gates {
use super::*;
#[test]
fn release_runs_cargo_test() {
let yml = release_yml();
assert!(
yml.contains("cargo test --workspace"),
"release.yml must run `cargo test --workspace` before publishing bundles"
);
}
#[test]
fn release_runs_clippy() {
let yml = release_yml();
assert!(
yml.contains("cargo clippy") && yml.contains("-D warnings"),
"release.yml must run clippy with -D warnings"
);
}
}
mod distribution_coverage {
use super::*;
#[test]
fn release_workflow_triggers_on_distribution_files() {
let workflow = release_yml();
for path in [
"crates/**",
"install.sh",
".github/workflows/release.yml",
"docs/**",
] {
assert!(
workflow.contains(path),
"release workflow paths must include `{}`",
path
);
}
}
}