Lua runner with rich builtin stdlib ("lunascript")
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
luna/src/lua_tests/os_tests.rs

173 lines
5.4 KiB

//! Tests for the sandboxed `os` table.
//!
//! Ported from flowbox-rt's fb_lua os_tests. Unlike flowbox, this project
//! keeps the real Lua 5.5 os.date/os.time/os.difftime (flowbox used a
//! chrono-based shim), so a few edge cases differ - see individual tests.
//! os.sleep is async here and is covered by async_tests, not this file.
use std::time::{Duration, Instant};
#[test]
fn test_os_time_now() {
let lua = super::lua();
let result: i64 = lua.load(r#"return os.time()"#).eval().unwrap();
let now = chrono::Utc::now().timestamp();
assert!((result - now).abs() <= 5, "os.time() = {result}, expected ~{now}");
}
#[test]
fn test_os_time_from_table() {
let lua = super::lua();
// Round trip through local time - independent of the host timezone
let ok: bool = lua
.load(
r#"
local t = os.time({year = 2020, month = 1, day = 15, hour = 10, min = 30, sec = 20})
local d = os.date("*t", t)
return d.year == 2020 and d.month == 1 and d.day == 15
and d.hour == 10 and d.min == 30 and d.sec == 20
"#,
)
.eval()
.unwrap();
assert!(ok);
// Out-of-range fields are normalized like mktime; hour defaults to 12
let ok: bool = lua
.load(
r#"
local a = os.time({year = 2020, month = 14, day = 1})
local b = os.time({year = 2021, month = 2, day = 1})
return a == b
"#,
)
.eval()
.unwrap();
assert!(ok);
// The normalized fields are written back into the table
let ok: bool = lua
.load(
r#"
local tbl = {year = 2020, month = 14, day = 1}
os.time(tbl)
return tbl.year == 2021 and tbl.month == 2 and tbl.day == 1
and tbl.hour == 12 and tbl.wday == 2 -- 2021-02-01 was a Monday
"#,
)
.eval()
.unwrap();
assert!(ok);
// Missing mandatory field is an error
let ok: bool = lua.load(r#"return (pcall(os.time, {year = 2020}))"#).eval().unwrap();
assert!(!ok);
}
#[test]
fn test_os_date_format() {
let lua = super::lua();
let result: String = lua.load(r#"return os.date("!%Y-%m-%dT%H:%M:%S", 1000000000)"#).eval().unwrap();
assert_eq!(result, "2001-09-09T01:46:40");
// Epoch
let result: String = lua.load(r#"return os.date("!%Y-%m-%d %H:%M:%S", 0)"#).eval().unwrap();
assert_eq!(result, "1970-01-01 00:00:00");
// Stock Lua difference: flowbox's chrono shim truncated float time values;
// real Lua 5.5 requires an integer-representable time and raises an error.
let ok: bool = lua.load(r#"return (pcall(os.date, "!%Y-%m-%d", 0.9))"#).eval().unwrap();
assert!(!ok, "os.date with a fractional time value should raise in stock Lua");
// Default format is %c, applied to the current time
let result: String = lua.load(r#"return os.date()"#).eval().unwrap();
assert!(!result.is_empty());
// Invalid format specifier is an error, not garbage output
let ok: bool = lua.load(r#"return (pcall(os.date, "%Q"))"#).eval().unwrap();
assert!(!ok);
}
#[test]
fn test_os_date_table() {
let lua = super::lua();
// 2001-09-09T01:46:40Z was a Sunday, day 252 of the year
let ok: bool = lua
.load(
r#"
local d = os.date("!*t", 1000000000)
return d.year == 2001 and d.month == 9 and d.day == 9
and d.hour == 1 and d.min == 46 and d.sec == 40
and d.wday == 1 and d.yday == 252
"#,
)
.eval()
.unwrap();
assert!(ok);
}
/// os.clock is replaced with monotonic wall time since state creation.
#[test]
fn test_os_clock() {
let start = Instant::now();
let lua = super::lua();
std::thread::sleep(Duration::from_millis(30));
let clock: f64 = lua.load(r#"return os.clock()"#).eval().unwrap();
assert!(
(start.elapsed().as_secs_f64() - clock).abs() < 0.1,
"Clock function works"
);
// 200 ms elapses, so it should be reported accurately
std::thread::sleep(Duration::from_millis(200));
let clock2: f64 = lua.load(r#"return os.clock()"#).eval().unwrap();
assert!((clock2 - clock - 0.2) < 0.03, "Clock tracks time");
}
#[test]
fn test_os_difftime() {
let lua = super::lua();
let result: f64 = lua.load(r#"return os.difftime(10, 4)"#).eval().unwrap();
assert_eq!(result, 6.0);
let ok: bool = lua.load(r#"return (pcall(os.difftime, 10))"#).eval().unwrap();
assert!(!ok);
}
#[test]
fn test_os_missing_dangerous_functions() {
let lua = super::lua();
let os_table = lua.globals().get::<mlua::Table>("os").unwrap();
for name in ["execute", "exit", "getenv", "remove", "rename", "setlocale", "tmpname"] {
assert!(
!os_table.contains_key(name).unwrap(),
"os.{name} should be removed from the sandbox"
);
}
}
#[test]
fn test_os_microtime() {
let lua = super::lua();
let result1: f64 = lua.load(r#"return os.microtime()"#).eval().unwrap();
std::thread::sleep(Duration::from_millis(100));
let result2: f64 = lua.load(r#"return os.microtime()"#).eval().unwrap();
assert!(
(result2 - result1 - 0.1).abs() < 0.03,
"os.microtime() should return fractional seconds"
);
// It is a Unix timestamp
let now = chrono::Utc::now().timestamp() as f64;
assert!((result2 - now).abs() <= 5.0, "os.microtime() = {result2}, expected ~{now}");
}