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.
173 lines
5.4 KiB
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}");
|
|
}
|
|
|