1use mlua::prelude::*;
12use std::time::{Duration, SystemTime, UNIX_EPOCH};
13
14use crate::util::with_config;
15
16pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
17 let t = lua.create_table()?;
18
19 t.set(
20 "now",
21 lua.create_function(|_, _: ()| {
22 let dur = SystemTime::now()
23 .duration_since(UNIX_EPOCH)
24 .map_err(LuaError::external)?;
25 Ok(dur.as_secs_f64())
26 })?,
27 )?;
28
29 t.set(
30 "millis",
31 lua.create_function(|_, _: ()| {
32 let dur = SystemTime::now()
33 .duration_since(UNIX_EPOCH)
34 .map_err(LuaError::external)?;
35 let ms: i64 = dur.as_millis().try_into().unwrap_or(i64::MAX);
37 Ok(ms)
38 })?,
39 )?;
40
41 t.set(
42 "sleep",
43 lua.create_function(|lua, seconds: f64| {
44 if !seconds.is_finite() || seconds < 0.0 {
45 return Err(LuaError::external(format!(
46 "sleep duration must be a finite non-negative number, got {seconds}"
47 )));
48 }
49 let max_secs = with_config(lua, |c| c.max_sleep_secs)?;
50 if seconds > max_secs {
51 return Err(LuaError::external(format!(
52 "sleep duration must not exceed {max_secs} seconds"
53 )));
54 }
55 std::thread::sleep(Duration::from_secs_f64(seconds));
56 Ok(())
57 })?,
58 )?;
59
60 t.set(
61 "measure",
62 lua.create_function(|_, func: LuaFunction| {
63 let start = std::time::Instant::now();
64 let result: LuaMultiValue = func.call(())?;
65 let elapsed = start.elapsed().as_secs_f64();
66 let mut ret = vec![LuaValue::Number(elapsed)];
67 ret.extend(result);
68 Ok(LuaMultiValue::from_vec(ret))
69 })?,
70 )?;
71
72 Ok(t)
73}
74
75#[cfg(test)]
76mod tests {
77 use mlua::Lua;
78
79 use crate::util::test_eval as eval;
80
81 #[test]
82 fn now_returns_positive() {
83 let ts: f64 = eval("return std.time.now()");
84 assert!(ts > 1_000_000_000.0);
85 }
86
87 #[test]
88 fn millis_returns_positive() {
89 let ms: i64 = eval("return std.time.millis()");
90 assert!(ms > 1_000_000_000_000);
91 }
92
93 #[test]
94 fn now_and_millis_consistent() {
95 let diff: f64 = eval(
96 r#"
97 local sec = std.time.now()
98 local ms = std.time.millis()
99 return math.abs(sec * 1000 - ms)
100 "#,
101 );
102 assert!(diff < 100.0);
103 }
104
105 #[test]
106 fn sleep_pauses() {
107 let elapsed: f64 = eval(
108 r#"
109 local before = std.time.now()
110 std.time.sleep(0.05)
111 return std.time.now() - before
112 "#,
113 );
114 assert!(elapsed >= 0.04);
115 }
116
117 #[test]
118 fn sleep_zero_is_valid() {
119 let ok: bool = eval(
120 r#"
121 std.time.sleep(0)
122 return true
123 "#,
124 );
125 assert!(ok);
126 }
127
128 #[test]
129 fn sleep_negative_returns_error() {
130 let lua = Lua::new();
131 crate::register_all(&lua, "std").unwrap();
132 let result: mlua::Result<mlua::Value> = lua.load("std.time.sleep(-1)").eval();
133 assert!(result.is_err());
134 }
135
136 #[test]
137 fn sleep_nan_returns_error() {
138 let lua = Lua::new();
139 crate::register_all(&lua, "std").unwrap();
140 let result: mlua::Result<mlua::Value> = lua.load("std.time.sleep(0/0)").eval();
141 assert!(result.is_err());
142 }
143
144 #[test]
145 fn sleep_exceeding_max_returns_error() {
146 let lua = Lua::new();
147 crate::register_all(&lua, "std").unwrap();
148 let result: mlua::Result<mlua::Value> = lua.load("std.time.sleep(86401)").eval();
149 assert!(result.is_err());
150 }
151
152 #[test]
153 fn custom_max_sleep_enforced() {
154 let lua = Lua::new();
155 let config = crate::config::Config::builder()
156 .max_sleep_secs(1.0)
157 .build()
158 .unwrap();
159 crate::register_all_with(&lua, "std", config).unwrap();
160
161 let result: mlua::Result<mlua::Value> = lua.load("std.time.sleep(2)").eval();
162 assert!(result.is_err());
163 }
164
165 #[test]
166 fn measure_returns_elapsed_and_result() {
167 let lua = Lua::new();
168 crate::register_all(&lua, "std").unwrap();
169 let (elapsed, value): (f64, String) = lua
170 .load(
171 r#"
172 local elapsed, result = std.time.measure(function()
173 std.time.sleep(0.05)
174 return "done"
175 end)
176 return elapsed, result
177 "#,
178 )
179 .eval()
180 .unwrap();
181 assert!(elapsed >= 0.04);
182 assert_eq!(value, "done");
183 }
184
185 #[test]
186 fn measure_propagates_error() {
187 let lua = Lua::new();
188 crate::register_all(&lua, "std").unwrap();
189 let result: mlua::Result<mlua::Value> = lua
190 .load(r#"return std.time.measure(function() error("boom") end)"#)
191 .eval();
192 assert!(result.is_err());
193 }
194}