Files
rlua-searcher/tests/tests.rs
Andy Weidenbaum 10391c419a add FunctionLoader and StaticFunctionSearcher
an alternative to boxing closures
2021-05-12 21:07:22 +10:00

403 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use rlua::{Context, Function, Lua, Table, UserData, UserDataMethods, Value};
use rlua_searcher::{AddSearcher, Result};
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
#[test]
fn add_searcher_works() {
let lume = read_lume_to_string();
let name = "lume".to_string();
let mut map = HashMap::new();
map.insert(name, lume);
let lua = Lua::new();
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_searcher(map)?;
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello lume", hello);
}
#[test]
fn add_static_searcher_works() {
let lume = read_lume_to_str();
let name = "lume";
let mut map = HashMap::new();
map.insert(name, lume);
let lua = Lua::new();
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_static_searcher(map)?;
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello lume", hello);
}
#[test]
fn add_path_searcher_works() {
let name = "lume".to_string();
let path = PathBuf::new()
.join(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("tests")
.join("data")
.join("lume.lua");
let mut map = HashMap::new();
map.insert(name, path);
let lua = Lua::new();
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_path_searcher(map)?;
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello lume", hello);
}
#[test]
fn module_reloading_works() {
let name = "lume".to_string();
let path = PathBuf::new()
.join(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("tests")
.join("data")
.join("lume.lua");
let mut map = HashMap::new();
map.insert(name.clone(), path.clone());
let lua = Lua::new();
// Add searcher for lume module on disk, and read from it.
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_path_searcher(map)?;
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello lume", hello);
// Twice.
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello lume", hello);
// Modify lume module on disk.
let mut out = File::create(path.clone()).expect("Could not create Lume module on disk");
write!(out, "{}\n", r#"return "hello again lume""#)
.expect("Could not modify Lume module on disk");
// Thrice. Should still be unchanged due to caching.
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello lume", hello);
// Remove lume module from Luas `package.loaded` cache to facilitate reload.
lua.context::<_, rlua::Result<()>>(|lua_ctx| {
let globals = lua_ctx.globals();
let loaded: Table = globals.get::<_, Table>("package")?.get("loaded")?;
loaded.set(name.clone(), Value::Nil)
})
.unwrap();
// Re-read from lume module on disk.
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello again lume", hello);
// Twice.
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello again lume", hello);
// Revert changes to lume module on disk.
let mut out = File::create(path).expect("Could not create Lume module on disk");
write!(out, "{}\n", r#"return "hello lume""#).expect("Could not modify Lume module on disk");
// Clear cache again.
lua.context::<_, rlua::Result<()>>(|lua_ctx| {
let globals = lua_ctx.globals();
let loaded: Table = globals.get::<_, Table>("package")?.get("loaded")?;
loaded.set(name, Value::Nil)
})
.unwrap();
// Ensure changes have been successfully reverted.
let hello = lua
.context::<_, Result<String>>(|lua_ctx| {
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
})
.unwrap();
assert_eq!("hello lume", hello);
}
fn read_lume_to_string() -> String {
r#"return "hello lume""#.to_string()
}
fn read_lume_to_str() -> &'static str {
r#"return "hello lume""#
}
#[test]
fn add_closure_searcher_works() {
let lua = Lua::new();
let mut modules: HashMap<
String,
Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>
+ Send,
>,
> = HashMap::new();
let instrument_loader: Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>> + Send,
> = Box::new(|lua_ctx, env, name| {
let globals = lua_ctx.globals();
let new = lua_ctx.create_function(|_, (name, sound): (String, String)| {
Ok(Instrument::new(name, sound))
})?;
let tbl = lua_ctx.create_table()?;
tbl.set("new", new)?;
globals.set("instrument", tbl)?;
Ok(lua_ctx
.load("return instrument")
.set_name(name)?
.set_environment(env)?
.into_function()?)
});
modules.insert("instrument".to_string(), instrument_loader);
let sound = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_closure_searcher(modules)?;
// Ensure global variable `instrument` is unset.
let nil: String = lua_ctx.load("return type(instrument)").eval()?;
assert_eq!(nil, "nil");
Ok(lua_ctx
.load(
r#"local instrument = require("instrument")
local ukulele = instrument.new("ukulele", "twang")
return ukulele:play()"#,
)
.eval()?)
})
.unwrap();
assert_eq!(sound, "The ukulele goes twang");
}
#[test]
fn add_static_closure_searcher_works() {
let lua = Lua::new();
let mut modules: HashMap<
&'static str,
Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>
+ Send,
>,
> = HashMap::new();
let instrument_loader: Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>> + Send,
> = Box::new(|lua_ctx, env, name| {
let globals = lua_ctx.globals();
let new = lua_ctx.create_function(|_, (name, sound): (String, String)| {
Ok(Instrument::new(name, sound))
})?;
let tbl = lua_ctx.create_table()?;
tbl.set("new", new)?;
globals.set("instrument", tbl)?;
Ok(lua_ctx
.load("return instrument")
.set_name(name)?
.set_environment(env)?
.into_function()?)
});
modules.insert("instrument", instrument_loader);
let sound = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_static_closure_searcher(modules)?;
// Ensure global variable `instrument` is unset.
let nil: String = lua_ctx.load("return type(instrument)").eval()?;
assert_eq!(nil, "nil");
Ok(lua_ctx
.load(
r#"local instrument = require("instrument")
local ukulele = instrument.new("ukulele", "twang")
return ukulele:play()"#,
)
.eval()?)
})
.unwrap();
assert_eq!(sound, "The ukulele goes twang");
}
struct Instrument {
name: String,
sound: String,
}
impl Instrument {
pub fn new(name: String, sound: String) -> Self {
Self { name, sound }
}
pub fn play(&self) -> String {
format!("The {} goes {}", self.name, self.sound)
}
}
impl UserData for Instrument {
fn add_methods<'lua, M>(methods: &mut M)
where
M: UserDataMethods<'lua, Self>,
{
methods.add_method("play", |_, instrument, ()| Ok(instrument.play()));
}
}
#[test]
fn add_function_searcher_works() {
let lua = Lua::new();
let mut modules: HashMap<
String,
for<'ctx> fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>,
> = HashMap::new();
modules.insert("cartridge".to_string(), cartridge_loader);
let title = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_function_searcher(modules)?;
// Ensure global variable `cartridge` is unset.
let nil: String = lua_ctx.load("return type(cartridge)").eval()?;
assert_eq!(nil, "nil");
Ok(lua_ctx
.load(
r#"local cartridge = require("cartridge")
local smash = cartridge.pick()
return smash:play()"#,
)
.eval()?)
})
.unwrap();
assert_eq!(title, "Super Smash Brothers 64");
}
#[test]
fn add_static_function_searcher_works() {
let lua = Lua::new();
let mut modules: HashMap<
&'static str,
for<'ctx> fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>,
> = HashMap::new();
modules.insert("cartridge", cartridge_loader);
let title = lua
.context::<_, Result<String>>(|lua_ctx| {
lua_ctx.add_static_function_searcher(modules)?;
// Ensure global variable `cartridge` is unset.
let nil: String = lua_ctx.load("return type(cartridge)").eval()?;
assert_eq!(nil, "nil");
Ok(lua_ctx
.load(
r#"local cartridge = require("cartridge")
local smash = cartridge.pick()
return smash:play()"#,
)
.eval()?)
})
.unwrap();
assert_eq!(title, "Super Smash Brothers 64");
}
struct Cartridge {
title: String,
}
impl Cartridge {
pub fn pick() -> Self {
let title = "Super Smash Brothers 64".to_string();
Self { title }
}
pub fn play(&self) -> String {
self.title.clone()
}
}
impl UserData for Cartridge {
fn add_methods<'lua, M>(methods: &mut M)
where
M: UserDataMethods<'lua, Self>,
{
methods.add_method("play", |_, cartridge, ()| Ok(cartridge.play()));
}
}
fn cartridge_loader<'ctx>(
lua_ctx: Context<'ctx>,
env: Table<'ctx>,
name: &str,
) -> rlua::Result<Function<'ctx>> {
let globals = lua_ctx.globals();
let pick = lua_ctx.create_function(|_, ()| Ok(Cartridge::pick()))?;
let tbl = lua_ctx.create_table()?;
tbl.set("pick", pick)?;
globals.set("cartridge", tbl)?;
Ok(lua_ctx
.load("return cartridge")
.set_name(name)?
.set_environment(env)?
.into_function()?)
}