zoi-cli 1.25.3

Advanced Package Manager & Environment Orchestrator
Documentation
use anyhow::{Result, anyhow};
use colored::Colorize;
use mlua::{Lua, LuaSerdeExt};

use crate::{cmd, pkg};

/// Runs tests for a Zoi package defined in a `.pkg.lua` file.
///
/// # Errors
///
/// Returns an error if the package file cannot be parsed, if the build
/// environment cannot be set up, or if the package tests fail.
pub fn run(args: &cmd::package::build::BuildCommand) -> Result<()> {
    println!("Testing package from: {}", args.package_file.display());

    let platform = if let Some(p) = args.platform.first() {
        p.clone()
    } else {
        crate::pkg::utils::get_platform()?
    };

    let pkg_for_meta = pkg::lua::parser::parse_lua_package_for_platform(
        args.package_file.to_str().ok_or_else(|| {
            anyhow!(
                "Path contains invalid UTF-8 characters: {}",
                args.package_file.display()
            )
        })?,
        &platform,
        args.version_override.as_deref(),
        None,
        false
    )?;

    let version = if let Some(v) = &args.version_override {
        v.clone()
    } else {
        pkg::resolve::get_default_version(&pkg_for_meta, None)?
    };

    let Some(resolved_build_type) =
        crate::pkg::package::build::resolve_build_type(
            args.r#type.as_deref(),
            &pkg_for_meta.types,
            &pkg_for_meta.name
        )?
    else {
        println!(
            "{} Skipping tests for package '{}': no build types supported.",
            "::".bold().yellow(),
            pkg_for_meta.name
        );
        return Ok(());
    };

    let build_dir = tempfile::Builder::new()
        .prefix(&format!("zoi-test-{}-{}", pkg_for_meta.name, platform))
        .tempdir()?;
    println!("Using build directory: {}", build_dir.path().display());
    let staging_dir = build_dir.path().join("staging");
    std::fs::create_dir_all(&staging_dir)?;

    let subs_to_test = if let Some(subs) = &args.sub {
        subs.clone()
    } else if let Some(subs) = &pkg_for_meta.sub_packages {
        subs.clone()
    } else {
        vec![String::new()]
    };

    for sub_package in subs_to_test {
        let sub_pkg_name = if sub_package.is_empty() {
            None
        } else {
            Some(sub_package.as_str())
        };

        if !sub_package.is_empty() {
            println!(
                "{} Testing sub-package: {}",
                "::".bold().blue(),
                sub_package.cyan()
            );
        }

        let lua = Lua::new();
        pkg::lua::functions::setup_lua_environment(
            &lua,
            &platform,
            Some(&version),
            args.package_file.to_str(),
            None,
            Some(build_dir.path().to_str().unwrap_or("")),
            Some(staging_dir.to_str().unwrap_or("")),
            sub_pkg_name,
            Some(pkg_for_meta.scope),
            Some(resolved_build_type.as_str()),
            false
        )
        .map_err(|e| anyhow!(e.to_string()))?;

        let pkg_table = lua
            .to_value(&pkg_for_meta)
            .map_err(|e| anyhow!(e.to_string()))?;
        lua.globals()
            .set("PKG", pkg_table)
            .map_err(|e| anyhow!(e.to_string()))?;

        let lua_code = std::fs::read_to_string(&args.package_file)?;
        lua.load(&lua_code)
            .exec()
            .map_err(|e| anyhow!(e.to_string()))?;

        let lua_args =
            lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
        if !sub_package.is_empty() {
            lua_args
                .set("sub", sub_package.clone())
                .map_err(|e| anyhow!(e.to_string()))?;
        }

        if let Ok(prepare_fn) = lua.globals().get::<mlua::Function>("prepare") {
            println!("Running prepare()...");
            prepare_fn
                .call::<()>(lua_args.clone())
                .map_err(|e| anyhow!(e.to_string()))?;
        }

        if let Ok(package_fn) = lua.globals().get::<mlua::Function>("package") {
            println!("Running package()...");
            package_fn
                .call::<()>(lua_args.clone())
                .map_err(|e| anyhow!(e.to_string()))?;
        }

        if let Ok(test_fn) = lua.globals().get::<mlua::Function>("test") {
            println!("Running test()...");
            let success: bool = match test_fn
                .call::<mlua::Value>(lua_args.clone())
            {
                Ok(mlua::Value::Boolean(b)) => b,
                Ok(mlua::Value::Nil) => {
                    return Err(anyhow!(
                        "The 'test' function in '{}' returned nil. It must \
                         explicitly return a boolean (true or false).",
                        args.package_file.display()
                    ));
                }
                Ok(v) => {
                    return Err(anyhow!(
                        "The 'test' function in '{}' returned a non-boolean \
                         value of type {:?}. It must return true or false.",
                        args.package_file.display(),
                        v.type_name()
                    ));
                }
                Err(e) => return Err(anyhow!(e.to_string()))
            };
            if !success {
                return Err(anyhow!(
                    "Package tests failed for sub-package '{sub_package}'."
                ));
            }
        } else if !sub_package.is_empty() {
            println!(
                "No test() function found for sub-package '{sub_package}', \
                 skipping."
            );
        }
    }

    println!("{}", "All tests passed successfully.".green());
    Ok(())
}