Skip to main content

zoi_cli/pkg/package/
test.rs

1use crate::{cmd, pkg};
2use anyhow::{Result, anyhow};
3use colored::*;
4use mlua::{Lua, LuaSerdeExt};
5
6pub fn run(args: &cmd::package::build::BuildCommand) -> Result<()> {
7    println!("Testing package from: {}", args.package_file.display());
8
9    let platform = if let Some(p) = args.platform.first() {
10        p.clone()
11    } else {
12        crate::pkg::utils::get_platform()?
13    };
14
15    let pkg_for_meta = pkg::lua::parser::parse_lua_package_for_platform(
16        args.package_file.to_str().ok_or_else(|| {
17            anyhow!(
18                "Path contains invalid UTF-8 characters: {:?}",
19                args.package_file
20            )
21        })?,
22        &platform,
23        args.version_override.as_deref(),
24        None,
25        false,
26    )?;
27
28    let version = if let Some(v) = &args.version_override {
29        v.clone()
30    } else {
31        pkg::resolve::get_default_version(&pkg_for_meta, None)?
32    };
33
34    let resolved_build_type = match crate::pkg::package::build::resolve_build_type(
35        args.r#type.as_deref(),
36        &pkg_for_meta.types,
37        &pkg_for_meta.name,
38    )? {
39        Some(t) => t,
40        None => {
41            println!(
42                "{} Skipping tests for package '{}': no build types supported.",
43                "::".bold().yellow(),
44                pkg_for_meta.name
45            );
46            return Ok(());
47        }
48    };
49
50    let build_dir = tempfile::Builder::new()
51        .prefix(&format!("zoi-test-{}-{}", pkg_for_meta.name, platform))
52        .tempdir()?;
53    println!("Using build directory: {}", build_dir.path().display());
54    let staging_dir = build_dir.path().join("staging");
55    std::fs::create_dir_all(&staging_dir)?;
56
57    let subs_to_test = if let Some(subs) = &args.sub {
58        subs.clone()
59    } else if let Some(subs) = &pkg_for_meta.sub_packages {
60        subs.clone()
61    } else {
62        vec!["".to_string()]
63    };
64
65    for sub_package in subs_to_test {
66        let sub_pkg_name = if sub_package.is_empty() {
67            None
68        } else {
69            Some(sub_package.as_str())
70        };
71
72        if !sub_package.is_empty() {
73            println!(
74                "{} Testing sub-package: {}",
75                "::".bold().blue(),
76                sub_package.cyan()
77            );
78        }
79
80        let lua = Lua::new();
81        pkg::lua::functions::setup_lua_environment(
82            &lua,
83            &platform,
84            Some(&version),
85            args.package_file.to_str(),
86            None,
87            Some(build_dir.path().to_str().unwrap_or("")),
88            Some(staging_dir.to_str().unwrap_or("")),
89            sub_pkg_name,
90            Some(pkg_for_meta.scope),
91            Some(resolved_build_type.as_str()),
92            false,
93        )
94        .map_err(|e| anyhow!(e.to_string()))?;
95
96        let pkg_table = lua
97            .to_value(&pkg_for_meta)
98            .map_err(|e| anyhow!(e.to_string()))?;
99        lua.globals()
100            .set("PKG", pkg_table)
101            .map_err(|e| anyhow!(e.to_string()))?;
102
103        let lua_code = std::fs::read_to_string(&args.package_file)?;
104        lua.load(&lua_code)
105            .exec()
106            .map_err(|e| anyhow!(e.to_string()))?;
107
108        let lua_args = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
109        if !sub_package.is_empty() {
110            lua_args
111                .set("sub", sub_package.clone())
112                .map_err(|e| anyhow!(e.to_string()))?;
113        }
114
115        if let Ok(prepare_fn) = lua.globals().get::<mlua::Function>("prepare") {
116            println!("Running prepare()...");
117            prepare_fn
118                .call::<()>(lua_args.clone())
119                .map_err(|e| anyhow!(e.to_string()))?;
120        }
121
122        if let Ok(package_fn) = lua.globals().get::<mlua::Function>("package") {
123            println!("Running package()...");
124            package_fn
125                .call::<()>(lua_args.clone())
126                .map_err(|e| anyhow!(e.to_string()))?;
127        }
128
129        if let Ok(test_fn) = lua.globals().get::<mlua::Function>("test") {
130            println!("Running test()...");
131            let success: bool = match test_fn.call::<mlua::Value>(lua_args.clone()) {
132                Ok(mlua::Value::Boolean(b)) => b,
133                Ok(mlua::Value::Nil) => {
134                    return Err(anyhow!(
135                        "The 'test' function in '{}' returned nil. It must explicitly return a boolean (true or false).",
136                        args.package_file.display()
137                    ));
138                }
139                Ok(v) => {
140                    return Err(anyhow!(
141                        "The 'test' function in '{}' returned a non-boolean value of type {:?}. It must return true or false.",
142                        args.package_file.display(),
143                        v.type_name()
144                    ));
145                }
146                Err(e) => return Err(anyhow!(e.to_string())),
147            };
148            if !success {
149                return Err(anyhow!(
150                    "Package tests failed for sub-package '{}'.",
151                    sub_package
152                ));
153            }
154        } else if !sub_package.is_empty() {
155            println!(
156                "No test() function found for sub-package '{}', skipping.",
157                sub_package
158            );
159        }
160    }
161
162    println!("{}", "All tests passed successfully.".green());
163    Ok(())
164}