#![allow(clippy::expect_used)]
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
fn repo_root() -> PathBuf {
let manifest_dir: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest_dir
.parent()
.and_then(Path::parent)
.expect("crate manifest dir must have a grandparent (the repo root)")
.to_path_buf()
}
fn transformer_bundle_toml(lang_dir: &str) -> PathBuf {
repo_root()
.join("examples")
.join("guests")
.join(lang_dir)
.join("transformer")
.join("bundle.toml")
}
fn run_polyplugc(args: &[&std::ffi::OsStr]) -> std::process::Output {
let bin: &str = env!("CARGO_BIN_EXE_polyplugc");
Command::new(bin)
.args(args)
.output()
.expect("failed to spawn polyplugc binary")
}
fn generate_guest_glue(bundle: &Path, lang: &str, out_dir: &Path) {
let output: std::process::Output = run_polyplugc(&[
"generate".as_ref(),
"--bundle".as_ref(),
bundle.as_os_str(),
"--lang".as_ref(),
lang.as_ref(),
"--out".as_ref(),
out_dir.as_os_str(),
]);
assert!(
output.status.success(),
"polyplugc generate --lang {lang} failed (status {:?})\n--- stdout ---\n{}\n--- stderr ---\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
fn files_with_ext(dir: &Path, ext: &str) -> Vec<PathBuf> {
let mut out: Vec<PathBuf> = Vec::new();
let entries: std::fs::ReadDir = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return out,
};
for entry in entries.filter_map(Result::ok) {
let path: PathBuf = entry.path();
if path.is_dir() {
out.extend(files_with_ext(&path, ext));
} else if path.extension().and_then(|s| s.to_str()) == Some(ext) {
out.push(path);
}
}
out
}
#[test]
fn python_generated_glue_compiles_and_imports() {
let tmp: tempfile::TempDir = tempfile::tempdir().expect("tempdir");
let bundle_dir: PathBuf = tmp.path().join("bundle");
let gen_dir: PathBuf = bundle_dir.join("generated");
std::fs::create_dir_all(&bundle_dir).expect("create bundle dir");
generate_guest_glue(&transformer_bundle_toml("python"), "python", &gen_dir);
let py_files: Vec<PathBuf> = files_with_ext(&gen_dir, "py");
assert!(
!py_files.is_empty(),
"expected generated .py files under {}",
gen_dir.display()
);
for file in &py_files {
let output: std::process::Output = Command::new("python3")
.arg("-m")
.arg("py_compile")
.arg(file)
.output()
.expect("failed to spawn python3 -m py_compile");
assert!(
output.status.success(),
"python3 -m py_compile failed for {} (status {:?})\n--- stderr ---\n{}",
file.display(),
output.status.code(),
String::from_utf8_lossy(&output.stderr),
);
}
let site: PathBuf = bundle_dir.join("site-packages");
std::fs::create_dir_all(site.join("polyplug").join("abi")).expect("create site polyplug/abi");
let sdk_python: PathBuf = repo_root().join("sdks").join("python");
copy_dir_all(
&sdk_python.join("guest").join("polyplug_guest"),
&site.join("polyplug_guest"),
);
copy_dir_all(
&sdk_python.join("polyplug_abi").join("polyplug_abi"),
&site.join("polyplug_abi"),
);
std::fs::copy(
sdk_python.join("abi").join("abi.py"),
site.join("polyplug").join("abi").join("abi.py"),
)
.expect("copy polyplug/abi/abi.py");
std::fs::write(site.join("polyplug").join("__init__.py"), b"").expect("write polyplug init");
std::fs::write(site.join("polyplug").join("abi").join("__init__.py"), b"")
.expect("write polyplug.abi init");
let import_script: String = format!(
"import sys\n\
sys.path.insert(0, {site:?})\n\
sys.path.insert(0, {bundle:?})\n\
sys.path.insert(0, {gen:?})\n\
import importlib\n\
for m in ('generated.guest.types', 'generated.guest.contracts', \
'generated.guest.host_contracts', 'generated.guest.init'):\n\
\x20\x20\x20\x20importlib.import_module(m)\n",
site = site.to_string_lossy(),
bundle = bundle_dir.to_string_lossy(),
gen = gen_dir.to_string_lossy(),
);
let output: std::process::Output = Command::new("python3")
.arg("-c")
.arg(&import_script)
.output()
.expect("failed to spawn python3 import check");
assert!(
output.status.success(),
"importing generated python guest modules failed (status {:?})\n--- stderr ---\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn lua_generated_glue_compiles_and_loads() {
let tmp: tempfile::TempDir = tempfile::tempdir().expect("tempdir");
let bundle_dir: PathBuf = tmp.path().join("bundle");
let gen_dir: PathBuf = bundle_dir.join("generated");
std::fs::create_dir_all(&bundle_dir).expect("create bundle dir");
generate_guest_glue(&transformer_bundle_toml("lua"), "lua", &gen_dir);
let lua_files: Vec<PathBuf> = files_with_ext(&gen_dir, "lua");
assert!(
!lua_files.is_empty(),
"expected generated .lua files under {}",
gen_dir.display()
);
for file in &lua_files {
let output: std::process::Output = Command::new("luajit")
.arg("-bl")
.arg(file)
.arg(devnull())
.output()
.expect("failed to spawn luajit -bl");
assert!(
output.status.success(),
"luajit failed to compile {} (status {:?})\n--- stderr ---\n{}",
file.display(),
output.status.code(),
String::from_utf8_lossy(&output.stderr),
);
}
let guest_dir: PathBuf = repo_root().join("sdks").join("lua").join("guest");
let abi_dir: PathBuf = repo_root().join("sdks").join("lua").join("abi");
let bundle_fwd: String = bundle_dir.to_string_lossy().replace('\\', "/");
let guest_fwd: String = guest_dir.to_string_lossy().replace('\\', "/");
let abi_fwd: String = abi_dir.to_string_lossy().replace('\\', "/");
let load_script: String = format!(
"package.path = \"{bundle_fwd}/?.lua;{bundle_fwd}/?.init.lua;{guest_fwd}/?.lua;{abi_fwd}/?.lua;\" .. package.path\n\
local mods = {{\"generated.guest.types\", \"generated.guest.contracts\", \"generated.guest.host_contracts\"}}\n\
for _, m in ipairs(mods) do\n\
\x20\x20local ok, err = pcall(require, m)\n\
\x20\x20if not ok then\n\
\x20\x20\x20\x20io.stderr:write(\"LOAD FAIL \" .. m .. \": \" .. tostring(err) .. \"\\n\")\n\
\x20\x20\x20\x20os.exit(1)\n\
\x20\x20end\n\
end\n"
);
let output: std::process::Output = Command::new("luajit")
.arg("-e")
.arg(&load_script)
.output()
.expect("failed to spawn luajit load check");
assert!(
output.status.success(),
"requiring generated lua guest modules failed (status {:?})\n--- stderr ---\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn js_quickjs_generated_glue_type_checks() {
let tmp: tempfile::TempDir = tempfile::tempdir().expect("tempdir");
let gen_dir: PathBuf = tmp.path().join("generated");
generate_guest_glue(&transformer_bundle_toml("js"), "js-quickjs", &gen_dir);
let ts_files: Vec<PathBuf> = files_with_ext(&gen_dir, "ts");
assert!(
!ts_files.is_empty(),
"expected generated .ts files under {}",
gen_dir.display()
);
let mut args: Vec<std::ffi::OsString> = vec!["check".into(), "--sloppy-imports".into()];
for file in &ts_files {
args.push(file.clone().into_os_string());
}
let output: std::process::Output = Command::new("deno")
.args(&args)
.output()
.expect("failed to spawn deno check");
assert!(
output.status.success(),
"deno check of generated js-quickjs glue failed (status {:?})\n--- stdout ---\n{}\n--- stderr ---\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
fn copy_dir_all(src: &Path, dst: &Path) {
std::fs::create_dir_all(dst).expect("create dst dir");
for entry in std::fs::read_dir(src)
.expect("read src dir")
.filter_map(Result::ok)
{
let path: PathBuf = entry.path();
let target: PathBuf = dst.join(entry.file_name());
if path.is_dir() {
copy_dir_all(&path, &target);
} else {
std::fs::copy(&path, &target).expect("copy file");
}
}
}
fn devnull() -> &'static str {
if cfg!(windows) { "NUL" } else { "/dev/null" }
}