Refactor and document lune lib public members

This commit is contained in:
Filip Tibell 2023-02-10 12:14:28 +01:00
parent 3073fb8fec
commit 709a69aa82
No known key found for this signature in database
14 changed files with 434 additions and 353 deletions

View file

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `NetRequest` query parameters value has been changed to be a table of key-value pairs similar to `process.env`.
If any query parameter is specified more than once in the request url, the value chosen will be the last one that was specified.
- The internal http client for `net.request` now reuses headers and connections for more efficient requests.
- Refactored the Lune rust crate to be much more user-friendly and documented all of the public functions.
### Fixed

View file

@ -15,9 +15,9 @@ use crate::{
},
};
const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
const LUNE_DOCS_FILE_NAME: &str = "luneDocs.json";
pub(crate) const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
pub(crate) const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
pub(crate) const LUNE_DOCS_FILE_NAME: &str = "luneDocs.json";
/// Lune CLI
#[derive(Parser, Debug, Default)]
@ -160,7 +160,7 @@ impl Cli {
// Display the file path relative to cwd with no extensions in stack traces
let file_display_name = file_path.with_extension("").display().to_string();
// Create a new lune object with all globals & run the script
let lune = Lune::new().with_args(self.script_args).with_all_globals();
let lune = Lune::new().with_all_globals_and_args(self.script_args);
let result = lune.run(&file_display_name, &file_contents).await;
Ok(match result {
Err(e) => {
@ -171,64 +171,3 @@ impl Cli {
})
}
}
#[cfg(test)]
mod tests {
use std::env::{current_dir, set_current_dir};
use anyhow::{bail, Context, Result};
use serde_json::Value;
use tokio::fs::{create_dir_all, read_to_string, remove_file};
use super::{Cli, LUNE_LUAU_FILE_NAME, LUNE_SELENE_FILE_NAME};
async fn run_cli(cli: Cli) -> Result<()> {
let path = current_dir()
.context("Failed to get current dir")?
.join("bin");
create_dir_all(&path)
.await
.context("Failed to create bin dir")?;
set_current_dir(&path).context("Failed to set current dir")?;
cli.run().await?;
Ok(())
}
async fn ensure_file_exists_and_is_not_json(file_name: &str) -> Result<()> {
match read_to_string(file_name)
.await
.context("Failed to read definitions file")
{
Ok(file_contents) => match serde_json::from_str::<Value>(&file_contents) {
Err(_) => {
remove_file(file_name)
.await
.context("Failed to remove definitions file")?;
Ok(())
}
Ok(_) => bail!("Downloading selene definitions returned json, expected luau"),
},
Err(e) => bail!("Failed to download selene definitions!\n{e}"),
}
}
#[tokio::test]
async fn list() -> Result<()> {
Cli::list().run().await?;
Ok(())
}
#[tokio::test]
async fn download_selene_types() -> Result<()> {
run_cli(Cli::download_selene_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_SELENE_FILE_NAME).await?;
Ok(())
}
#[tokio::test]
async fn download_luau_types() -> Result<()> {
run_cli(Cli::download_luau_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_LUAU_FILE_NAME).await?;
Ok(())
}
}

View file

@ -13,9 +13,12 @@ use std::process::ExitCode;
use anyhow::Result;
use clap::Parser;
mod cli;
mod gen;
mod utils;
pub(crate) mod cli;
pub(crate) mod gen;
pub(crate) mod utils;
#[cfg(test)]
mod tests;
use cli::Cli;

57
packages/cli/src/tests.rs Normal file
View file

@ -0,0 +1,57 @@
use std::env::{current_dir, set_current_dir};
use anyhow::{bail, Context, Result};
use serde_json::Value;
use tokio::fs::{create_dir_all, read_to_string, remove_file};
use crate::cli::{Cli, LUNE_LUAU_FILE_NAME, LUNE_SELENE_FILE_NAME};
async fn run_cli(cli: Cli) -> Result<()> {
let path = current_dir()
.context("Failed to get current dir")?
.join("bin");
create_dir_all(&path)
.await
.context("Failed to create bin dir")?;
set_current_dir(&path).context("Failed to set current dir")?;
cli.run().await?;
Ok(())
}
async fn ensure_file_exists_and_is_not_json(file_name: &str) -> Result<()> {
match read_to_string(file_name)
.await
.context("Failed to read definitions file")
{
Ok(file_contents) => match serde_json::from_str::<Value>(&file_contents) {
Err(_) => {
remove_file(file_name)
.await
.context("Failed to remove definitions file")?;
Ok(())
}
Ok(_) => bail!("Downloading selene definitions returned json, expected luau"),
},
Err(e) => bail!("Failed to download selene definitions!\n{e}"),
}
}
#[tokio::test]
async fn list() -> Result<()> {
Cli::list().run().await?;
Ok(())
}
#[tokio::test]
async fn download_selene_types() -> Result<()> {
run_cli(Cli::download_selene_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_SELENE_FILE_NAME).await?;
Ok(())
}
#[tokio::test]
async fn download_luau_types() -> Result<()> {
run_cli(Cli::download_luau_types()).await?;
ensure_file_exists_and_is_not_json(LUNE_LUAU_FILE_NAME).await?;
Ok(())
}

View file

@ -5,9 +5,7 @@ use tokio::fs;
use crate::utils::table::TableBuilder;
pub fn create(lua: &Lua) -> LuaResult<()> {
lua.globals().raw_set(
"fs",
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_async_function("readFile", fs_read_file)?
.with_async_function("readDir", fs_read_dir)?
@ -17,8 +15,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
.with_async_function("removeDir", fs_remove_dir)?
.with_async_function("isFile", fs_is_file)?
.with_async_function("isDir", fs_is_dir)?
.build_readonly()?,
)
.build_readonly()
}
async fn fs_read_file(_: &Lua, path: String) -> LuaResult<String> {

View file

@ -1,83 +1,117 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use mlua::prelude::*;
mod fs;
mod net;
mod process;
mod require;
mod stdio;
mod task;
mod top_level;
// Global tables
pub use fs::create as create_fs;
pub use net::create as create_net;
pub use process::create as create_process;
pub use require::create as create_require;
pub use stdio::create as create_stdio;
pub use task::create as create_task;
// Individual top-level global values
use mlua::prelude::*;
use crate::utils::formatting::{format_label, pretty_format_multi_value};
pub fn create_top_level(lua: &Lua) -> LuaResult<()> {
let globals = lua.globals();
// HACK: We need to preserve the default behavior of the
// print and error functions, for pcall and such, which
// is really tricky to do from scratch so we will just
// proxy the default print and error functions here
let print_fn: LuaFunction = globals.raw_get("print")?;
let error_fn: LuaFunction = globals.raw_get("error")?;
lua.set_named_registry_value("print", print_fn)?;
lua.set_named_registry_value("error", error_fn)?;
globals.raw_set(
"print",
lua.create_function(|lua, args: LuaMultiValue| {
let formatted = pretty_format_multi_value(&args)?;
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(formatted)?;
Ok(())
})?,
)?;
globals.raw_set(
"info",
lua.create_function(|lua, args: LuaMultiValue| {
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(format!(
"{}\n{}",
format_label("info"),
pretty_format_multi_value(&args)?
))?;
Ok(())
})?,
)?;
globals.raw_set(
"warn",
lua.create_function(|lua, args: LuaMultiValue| {
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(format!(
"{}\n{}",
format_label("warn"),
pretty_format_multi_value(&args)?
))?;
Ok(())
})?,
)?;
globals.raw_set(
"error",
lua.create_function(|lua, (arg, level): (LuaValue, Option<u32>)| {
let error: LuaFunction = lua.named_registry_value("error")?;
let multi = arg.to_lua_multi(lua)?;
error.call((
format!(
"{}\n{}",
format_label("error"),
pretty_format_multi_value(&multi)?
),
level,
))?;
Ok(())
})?,
)?;
Ok(())
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LuneGlobal {
Fs,
Net,
Process { args: Vec<String> },
Require,
Stdio,
Task,
TopLevel,
}
impl Display for LuneGlobal {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(
f,
"{}",
match self {
Self::Fs => "fs",
Self::Net => "net",
Self::Process { .. } => "process",
Self::Require => "require",
Self::Stdio => "stdio",
Self::Task => "task",
Self::TopLevel => "toplevel",
}
)
}
}
impl LuneGlobal {
/**
Create a vector that contains all available Lune globals, with
the [`LuneGlobal::Process`] global containing the given args.
*/
pub fn all<S: AsRef<str>>(args: &[S]) -> Vec<Self> {
vec![
Self::Fs,
Self::Net,
Self::Process {
args: args.iter().map(|s| s.as_ref().to_string()).collect(),
},
Self::Require,
Self::Stdio,
Self::Task,
Self::TopLevel,
]
}
/**
Checks if this Lune global is a proxy global.
A proxy global is a global that re-implements or proxies functionality of one or
more existing lua globals, and may store internal references to the original global(s).
This means that proxy globals should only be injected into a lua global
environment once, since injecting twice or more will potentially break the
functionality of the proxy global and / or cause undefined behavior.
*/
pub fn is_proxy(&self) -> bool {
matches!(self, Self::Require | Self::TopLevel)
}
/**
Creates the [`mlua::Table`] value for this Lune global.
Note that proxy globals should be handled with special care and that [`LuneGlobal::inject()`]
should be preferred over manually creating and manipulating the value(s) of any Lune global.
*/
pub fn value<'a>(&'a self, lua: &'a Lua) -> LuaResult<LuaTable> {
match self {
LuneGlobal::Fs => fs::create(lua),
LuneGlobal::Net => net::create(lua),
LuneGlobal::Process { args } => process::create(lua, args.clone()),
LuneGlobal::Require => require::create(lua),
LuneGlobal::Stdio => stdio::create(lua),
LuneGlobal::Task => task::create(lua),
LuneGlobal::TopLevel => top_level::create(lua),
}
}
/**
Injects the Lune global into a lua global environment.
This takes ownership since proxy Lune globals should
only ever be injected into a lua global environment once.
Refer to [`LuneGlobal::is_top_level()`] for more info on proxy globals.
*/
pub fn inject(self, lua: &Lua) -> LuaResult<()> {
let globals = lua.globals();
let table = self.value(lua)?;
// NOTE: Top level globals are special, the values
// *in* the table they return should be set directly,
// instead of setting the table itself as the global
if self.is_proxy() {
for pair in table.pairs::<LuaValue, LuaValue>() {
let (key, value) = pair?;
globals.raw_set(key, value)?;
}
Ok(())
} else {
globals.raw_set(self.to_string(), table)
}
}
}

View file

@ -19,7 +19,7 @@ use crate::utils::{
table::TableBuilder,
};
pub fn create(lua: &Lua) -> LuaResult<()> {
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
// Create a reusable client for performing our
// web requests and store it in the lua registry
let mut default_headers = HeaderMap::new();
@ -35,15 +35,12 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
);
lua.set_named_registry_value("NetClient", client)?;
// Create the global table for net
lua.globals().raw_set(
"net",
TableBuilder::new(lua)?
.with_function("jsonEncode", net_json_encode)?
.with_function("jsonDecode", net_json_decode)?
.with_async_function("request", net_request)?
.with_async_function("serve", net_serve)?
.build_readonly()?,
)
.build_readonly()
}
fn net_json_encode(_: &Lua, (val, pretty): (LuaValue, Option<bool>)) -> LuaResult<String> {

View file

@ -10,7 +10,7 @@ use crate::utils::{
table::TableBuilder,
};
pub fn create(lua: &Lua, args_vec: Vec<String>) -> LuaResult<()> {
pub fn create(lua: &Lua, args_vec: Vec<String>) -> LuaResult<LuaTable> {
let cwd = env::current_dir()?.canonicalize()?;
let mut cwd_str = cwd.to_string_lossy().to_string();
if !cwd_str.ends_with('/') {
@ -31,16 +31,13 @@ pub fn create(lua: &Lua, args_vec: Vec<String>) -> LuaResult<()> {
)?
.build_readonly()?;
// Create the full process table
lua.globals().raw_set(
"process",
TableBuilder::new(lua)?
.with_value("args", args_tab)?
.with_value("cwd", cwd_str)?
.with_value("env", env_tab)?
.with_async_function("exit", process_exit)?
.with_async_function("spawn", process_spawn)?
.build_readonly()?,
)
.build_readonly()
}
fn process_env_get<'lua>(

View file

@ -7,10 +7,15 @@ use std::{
use mlua::prelude::*;
use os_str_bytes::{OsStrBytes, RawOsStr};
pub fn create(lua: &Lua) -> LuaResult<()> {
use crate::utils::table::TableBuilder;
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
let require: LuaFunction = lua.globals().raw_get("require")?;
// Preserve original require behavior if we have a special env var set
if env::var_os("LUAU_PWD_REQUIRE").is_some() {
return Ok(());
return TableBuilder::new(lua)?
.with_value("require", require)?
.build_readonly();
}
/*
Store the current working directory so that we can use it later
@ -28,8 +33,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
let debug: LuaTable = lua.globals().raw_get("debug")?;
let info: LuaFunction = debug.raw_get("info")?;
lua.set_named_registry_value("require_getinfo", info)?;
// Fetch the original require function and store it in the registry
let require: LuaFunction = lua.globals().raw_get("require")?;
// Store the original require function in the registry
lua.set_named_registry_value("require_original", require)?;
/*
Create a new function that fetches the file name from the current thread,
@ -90,6 +94,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
}
})?;
// Override the original require global with our monkey-patched one
lua.globals().raw_set("require", new_require)?;
Ok(())
TableBuilder::new(lua)?
.with_value("require", new_require)?
.build_readonly()
}

View file

@ -8,9 +8,7 @@ use crate::utils::{
table::TableBuilder,
};
pub fn create(lua: &Lua) -> LuaResult<()> {
lua.globals().raw_set(
"stdio",
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("color", |_, color: String| {
let ansi_string = format_style(style_from_color_str(&color)?);
@ -32,8 +30,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
Ok(())
})?
.with_function("prompt", prompt)?
.build_readonly()?,
)
.build_readonly()
}
fn prompt_theme() -> ColorfulTheme {

View file

@ -13,7 +13,7 @@ use crate::utils::{
const MINIMUM_WAIT_OR_DELAY_DURATION: f32 = 10.0 / 1_000.0; // 10ms
pub fn create(lua: &Lua) -> LuaResult<()> {
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
// HACK: There is no way to call coroutine.close directly from the mlua
// crate, so we need to fetch the function and store it in the registry
let coroutine: LuaTable = lua.globals().raw_get("coroutine")?;
@ -24,16 +24,13 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
// overwrite the original coroutine.resume function with it to fix that
coroutine.raw_set("resume", lua.create_async_function(task_spawn)?)?;
// Rest of the task library is normal, just async functions, no metatable
lua.globals().raw_set(
"task",
TableBuilder::new(lua)?
.with_async_function("cancel", task_cancel)?
.with_async_function("delay", task_delay)?
.with_async_function("defer", task_defer)?
.with_async_function("spawn", task_spawn)?
.with_async_function("wait", task_wait)?
.build_readonly()?,
)
.build_readonly()
}
fn tof_to_thread<'a>(lua: &'a Lua, tof: LuaValue<'a>) -> LuaResult<LuaThread<'a>> {

View file

@ -0,0 +1,57 @@
use mlua::prelude::*;
use crate::utils::{
formatting::{format_label, pretty_format_multi_value},
table::TableBuilder,
};
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
let globals = lua.globals();
// HACK: We need to preserve the default behavior of the
// print and error functions, for pcall and such, which
// is really tricky to do from scratch so we will just
// proxy the default print and error functions here
let print_fn: LuaFunction = globals.raw_get("print")?;
let error_fn: LuaFunction = globals.raw_get("error")?;
lua.set_named_registry_value("print", print_fn)?;
lua.set_named_registry_value("error", error_fn)?;
TableBuilder::new(lua)?
.with_function("print", |lua, args: LuaMultiValue| {
let formatted = pretty_format_multi_value(&args)?;
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(formatted)?;
Ok(())
})?
.with_function("info", |lua, args: LuaMultiValue| {
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(format!(
"{}\n{}",
format_label("info"),
pretty_format_multi_value(&args)?
))?;
Ok(())
})?
.with_function("warn", |lua, args: LuaMultiValue| {
let print: LuaFunction = lua.named_registry_value("print")?;
print.call(format!(
"{}\n{}",
format_label("warn"),
pretty_format_multi_value(&args)?
))?;
Ok(())
})?
.with_function("error", |lua, (arg, level): (LuaValue, Option<u32>)| {
let error: LuaFunction = lua.named_registry_value("error")?;
let multi = arg.to_lua_multi(lua)?;
error.call((
format!(
"{}\n{}",
format_label("error"),
pretty_format_multi_value(&multi)?
),
level,
))?;
Ok(())
})?
.build_readonly()
}

View file

@ -6,68 +6,82 @@ use tokio::{sync::mpsc, task};
pub(crate) mod globals;
pub(crate) mod utils;
use crate::{
globals::{
create_fs, create_net, create_process, create_require, create_stdio, create_task,
create_top_level,
},
utils::{formatting::pretty_format_luau_error, message::LuneMessage},
};
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LuneGlobal {
Fs,
Net,
Process,
Require,
Stdio,
Task,
TopLevel,
}
use crate::utils::{formatting::pretty_format_luau_error, message::LuneMessage};
impl LuneGlobal {
pub fn get_all() -> Vec<Self> {
vec![
Self::Fs,
Self::Net,
Self::Process,
Self::Require,
Self::Stdio,
Self::Task,
Self::TopLevel,
]
}
}
pub use globals::LuneGlobal;
#[derive(Clone, Debug, Default)]
pub struct Lune {
globals: HashSet<LuneGlobal>,
args: Vec<String>,
includes: HashSet<LuneGlobal>,
excludes: HashSet<LuneGlobal>,
}
impl Lune {
/**
Creates a new Lune script runner.
*/
pub fn new() -> Self {
Self::default()
}
pub fn with_args(mut self, args: Vec<String>) -> Self {
self.args = args;
self
}
/**
Include a global in the lua environment created for running a Lune script.
*/
pub fn with_global(mut self, global: LuneGlobal) -> Self {
self.globals.insert(global);
self.includes.insert(global);
self
}
/**
Include all globals in the lua environment created for running a Lune script.
*/
pub fn with_all_globals(mut self) -> Self {
for global in LuneGlobal::get_all() {
self.globals.insert(global);
for global in LuneGlobal::all::<String>(&[]) {
self.includes.insert(global);
}
self
}
pub async fn run(&self, name: &str, chunk: &str) -> Result<ExitCode, LuaError> {
/**
Include all globals in the lua environment created for running a
Lune script, as well as supplying args for [`LuneGlobal::Process`].
*/
pub fn with_all_globals_and_args(mut self, args: Vec<String>) -> Self {
for global in LuneGlobal::all(&args) {
self.includes.insert(global);
}
self
}
/**
Exclude a global from the lua environment created for running a Lune script.
This should be preferred over manually iterating and filtering
which Lune globals to add to the global environment.
*/
pub fn without_global(mut self, global: LuneGlobal) -> Self {
self.excludes.insert(global);
self
}
/**
Runs a Lune script.
This will create a new sandboxed Luau environment with the configured
globals and arguments, running inside of a [`tokio::task::LocalSet`].
Some Lune globals such as [`LuneGlobal::Process`] may spawn
separate tokio tasks on other threads, but the Luau environment
itself is guaranteed to run on a single thread in the local set.
*/
pub async fn run(
&self,
script_name: &str,
script_contents: &str,
) -> Result<ExitCode, LuaError> {
let task_set = task::LocalSet::new();
let (sender, mut receiver) = mpsc::channel::<LuneMessage>(64);
let lua = Arc::new(mlua::Lua::new());
@ -75,21 +89,15 @@ impl Lune {
lua.set_app_data(Arc::downgrade(&lua));
lua.set_app_data(Arc::downgrade(&snd));
// Add in wanted lune globals
for global in &self.globals {
match &global {
LuneGlobal::Fs => create_fs(&lua)?,
LuneGlobal::Net => create_net(&lua)?,
LuneGlobal::Process => create_process(&lua, self.args.clone())?,
LuneGlobal::Require => create_require(&lua)?,
LuneGlobal::Stdio => create_stdio(&lua)?,
LuneGlobal::Task => create_task(&lua)?,
LuneGlobal::TopLevel => create_top_level(&lua)?,
for global in self.includes.clone() {
if !self.excludes.contains(&global) {
global.inject(&lua)?;
}
}
// Spawn the main thread from our entrypoint script
let script_lua = lua.clone();
let script_name = name.to_string();
let script_chunk = chunk.to_string();
let script_name = script_name.to_string();
let script_chunk = script_contents.to_string();
let script_sender = snd.clone();
script_sender
.send(LuneMessage::Spawned)
@ -165,83 +173,3 @@ impl Lune {
}
}
}
#[cfg(test)]
mod tests {
use std::{env::set_current_dir, path::PathBuf, process::ExitCode};
use anyhow::Result;
use console::set_colors_enabled;
use console::set_colors_enabled_stderr;
use tokio::fs::read_to_string;
use crate::Lune;
const ARGS: &[&str] = &["Foo", "Bar"];
macro_rules! run_tests {
($($name:ident: $value:expr,)*) => {
$(
#[tokio::test]
async fn $name() -> Result<ExitCode> {
// Disable styling for stdout and stderr since
// some tests rely on output not being styled
set_colors_enabled(false);
set_colors_enabled_stderr(false);
// NOTE: This path is relative to the lib
// package, not the cwd or workspace root,
// so we need to cd to the repo root first
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root_dir = crate_dir.join("../../").canonicalize()?;
set_current_dir(root_dir)?;
// The rest of the test logic can continue as normal
let full_name = format!("tests/{}.luau", $value);
let script = read_to_string(&full_name).await?;
let lune = Lune::new()
.with_args(
ARGS
.clone()
.iter()
.map(ToString::to_string)
.collect()
)
.with_all_globals();
let script_name = full_name.strip_suffix(".luau").unwrap();
let exit_code = lune.run(&script_name, &script).await?;
Ok(exit_code)
}
)*
}
}
run_tests! {
fs_files: "fs/files",
fs_dirs: "fs/dirs",
net_request_codes: "net/request/codes",
net_request_methods: "net/request/methods",
net_request_redirect: "net/request/redirect",
net_json_decode: "net/json/decode",
net_json_encode: "net/json/encode",
net_serve: "net/serve",
process_args: "process/args",
process_cwd: "process/cwd",
process_env: "process/env",
process_exit: "process/exit",
process_spawn: "process/spawn",
require_children: "require/tests/children",
require_invalid: "require/tests/invalid",
require_nested: "require/tests/nested",
require_parents: "require/tests/parents",
require_siblings: "require/tests/siblings",
stdio_format: "stdio/format",
stdio_color: "stdio/color",
stdio_style: "stdio/style",
stdio_write: "stdio/write",
stdio_ewrite: "stdio/ewrite",
task_cancel: "task/cancel",
task_defer: "task/defer",
task_delay: "task/delay",
task_spawn: "task/spawn",
task_wait: "task/wait",
}
}

72
packages/lib/src/tests.rs Normal file
View file

@ -0,0 +1,72 @@
use std::{env::set_current_dir, path::PathBuf, process::ExitCode};
use anyhow::Result;
use console::set_colors_enabled;
use console::set_colors_enabled_stderr;
use tokio::fs::read_to_string;
use crate::Lune;
const ARGS: &[&str] = &["Foo", "Bar"];
macro_rules! create_tests {
($($name:ident: $value:expr,)*) => { $(
#[tokio::test]
async fn $name() -> Result<ExitCode> {
// Disable styling for stdout and stderr since
// some tests rely on output not being styled
set_colors_enabled(false);
set_colors_enabled_stderr(false);
// NOTE: This path is relative to the lib
// package, not the cwd or workspace root,
// so we need to cd to the repo root first
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root_dir = crate_dir.join("../../").canonicalize()?;
set_current_dir(root_dir)?;
// The rest of the test logic can continue as normal
let full_name = format!("tests/{}.luau", $value);
let script = read_to_string(&full_name).await?;
let lune = Lune::new().with_all_globals_and_args(
ARGS
.clone()
.iter()
.map(ToString::to_string)
.collect()
);
let script_name = full_name.strip_suffix(".luau").unwrap();
let exit_code = lune.run(&script_name, &script).await?;
Ok(exit_code)
}
)* }
}
create_tests! {
fs_files: "fs/files",
fs_dirs: "fs/dirs",
net_request_codes: "net/request/codes",
net_request_methods: "net/request/methods",
net_request_redirect: "net/request/redirect",
net_json_decode: "net/json/decode",
net_json_encode: "net/json/encode",
net_serve: "net/serve",
process_args: "process/args",
process_cwd: "process/cwd",
process_env: "process/env",
process_exit: "process/exit",
process_spawn: "process/spawn",
require_children: "require/tests/children",
require_invalid: "require/tests/invalid",
require_nested: "require/tests/nested",
require_parents: "require/tests/parents",
require_siblings: "require/tests/siblings",
stdio_format: "stdio/format",
stdio_color: "stdio/color",
stdio_style: "stdio/style",
stdio_write: "stdio/write",
stdio_ewrite: "stdio/ewrite",
task_cancel: "task/cancel",
task_defer: "task/defer",
task_delay: "task/delay",
task_spawn: "task/spawn",
task_wait: "task/wait",
}