Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions crates/opy-rs/src/macro_js/tests/isolation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,151 @@ fn concurrent_runtime_instances_are_independent() {
}
});
}

#[test]
fn memory_limit_recovery_keeps_each_invocation_bounded() {
// An invocation that exhausts the memory limit must neither leak its
// allocation accounting nor disable the limit for the next invocation.
let limits = Limits {
memory_limit_bytes: 2 * 1024 * 1024,
..Limits::default()
};
let rt = MacroRuntime::new(limits);
let allocation_loop =
r#"var a = []; for (var i = 0; i < 1000000; i++) { a.push("x".repeat(1024)); }"#;

for _ in 0..2 {
let error = rt.run_macro(allocation_loop, &[], "mem.js").unwrap_err();
match error {
MacroError::Script(ref script) => assert_eq!(script.message, "out of memory"),
ref other => panic!("expected Script error, got {other:?}"),
}
}
let result = rt.run_macro(r#""recovered";"#, &[], "after.js").unwrap();
assert_eq!(result.text, "recovered");
}

#[test]
fn stack_limit_recovery_keeps_each_invocation_bounded() {
let limits = Limits {
max_stack_bytes: 128 * 1024,
..Limits::default()
};
let rt = MacroRuntime::new(limits);

for _ in 0..2 {
let error = rt
.run_macro("function f() { return f(); } f();", &[], "stack.js")
.unwrap_err();
match error {
MacroError::Script(ref script) => {
assert_eq!(script.message, "Maximum call stack size exceeded")
}
ref other => panic!("expected Script error, got {other:?}"),
}
}
let result = rt.run_macro(r#""recovered";"#, &[], "after.js").unwrap();
assert_eq!(result.text, "recovered");
}

#[test]
fn deadline_state_is_re_armed_per_invocation() {
// After an interrupted invocation the interrupt handler must be reset:
// the next call gets a fresh deadline, not the expired one.
let rt = MacroRuntime::new(Limits {
macro_time_budget: Duration::from_millis(50),
..Limits::default()
});
rt.run_macro("while (true) {}", &[], "first.js")
.unwrap_err();
// Succeeding invocations after the abort get a full fresh budget.
for _ in 0..3 {
let result = rt
.run_macro(
r#"var t = Date.now(); while (Date.now() - t < 1) {} "ok";"#,
&[],
"second.js",
)
.unwrap();
assert_eq!(result.text, "ok");
}
}

#[test]
fn console_sink_is_rebound_per_invocation() {
// `console.log` writes to the invocation's own buffer: a previous call's
// sink must not receive or retain lines.
let rt = runtime();
let first = rt
.run_macro(r#"console.log("a"); "ok";"#, &[], "first.js")
.unwrap();
assert_eq!(first.console_output, vec!["a"]);

let error = rt
.run_macro(r#"console.log("b"); throw new Error("x");"#, &[], "err.js")
.unwrap_err();
assert!(matches!(error, MacroError::Script(_)));

let second = rt
.run_macro(r#"console.log("c", 1 + 1); "ok";"#, &[], "second.js")
.unwrap();
assert_eq!(second.console_output, vec!["c 2"]);
}

#[test]
fn many_consecutive_invocations_stay_independent() {
// Sweep: interleave leaking, throwing, and clean invocations on one
// runtime; every result must reflect only its own invocation.
let rt = runtime();
for i in 0..32 {
// Set marker{i} and verify it is visible in the same invocation;
// a later script then asserts it is gone again.
let leak =
format!(r#"globalThis.marker{i} = {i}; (globalThis.marker{i} === {i}).toString();"#);
let leaked = rt.run_macro(&leak, &[], "leak.js").unwrap();
assert_eq!(leaked.text, "true");
let check = rt
.run_macro(&format!(r#"typeof globalThis.marker{i};"#), &[], "check.js")
.unwrap();
assert_eq!(check.text, "undefined");
if i > 0 {
let previous = rt
.run_macro(
&format!(r#"typeof globalThis.marker{};"#, i - 1),
&[],
"check-prev.js",
)
.unwrap();
assert_eq!(previous.text, "undefined");
}
if i % 3 == 0 {
rt.run_macro("throw new Error(\"x\");", &[], "err.js")
.unwrap_err();
}
}
}

#[test]
fn builtin_intrinsics_are_fully_reset() {
// Mutation of shared-looking intrinsics (Object.prototype, Array methods,
// the builtins script's `vect`) must not reach the next invocation.
let rt = runtime();
rt.run_macro(
r#"Array.prototype.push = function() { return -1; };
vect = function() { return "tampered"; };
Date.now = function() { return 0; };
"ok";"#,
&[],
"poison.js",
)
.unwrap();
let result = rt
.run_macro(
r#"var a = [1]; a.push(2);
(a.length === 2 && typeof vect(1,2,3).x === "number" && Date.now() !== 0).toString();"#,
&[],
"verify.js",
)
.unwrap();
assert_eq!(result.text, "true");
}
Loading