1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// @trace REQ-ENG-001 [entity:JsContext]
/// Realm persistence tests — verify ECMA-262/Node realm-per-context semantics:
/// a `JsContext` owns ONE persistent realm; scripts execute inside it; the
/// realm persists across `eval` calls. This is what makes `globalThis.x` set
/// by eval A visible to eval B, and lets setTimeout/server handlers fire
/// after the registering script returns.
///
/// Under the old eval-per-global model every `eval` built a fresh
/// `JS_NewGlobalObject`, so each of these would read `undefined` / fail. Under
/// realm-per-context they must all pass.
use bao_engine::context::{JsContext, thread_realm_global};
use bao_engine::module_loader::ModuleLoader;
use mozjs::rooted;
/// Build a test JsContext with the full Node/Bun globals installed.
fn make_ctx() -> JsContext {
let mut ctx = JsContext::for_test().expect("JsContext::for_test");
ctx.set_global_setup(bun_runtime::globals::install_all);
ctx
}
/// Core realm-per-context invariant: a global property set by eval A must be
/// visible to eval B (Node semantics).
#[test]
fn cross_eval_globalthis_property_persists() {
let mut ctx = make_ctx();
ctx.eval("globalThis.x = 1;", "<a>").expect("eval A must succeed");
let r = ctx.eval("globalThis.x;", "<b>").expect("eval B must succeed");
assert_eq!(
r.as_number(),
Some(1.0),
"globalThis.x must persist across evals (realm-per-context, Node semantics)"
);
}
/// Function declarations install on the realm's global; a later eval in the
/// same realm must be able to call them.
#[test]
fn cross_eval_function_declaration_persists() {
let mut ctx = make_ctx();
ctx.eval("function double(n) { return n * 2; }", "<a>")
.expect("eval A");
let r = ctx.eval("double(21);", "<b>").expect("eval B");
assert_eq!(
r.as_number(),
Some(42.0),
"function declaration must persist across evals"
);
}
/// Object identity must be stable across evals: a mutation by eval B of an
/// object stored by eval A is observable by eval C.
#[test]
fn cross_eval_object_identity_stable() {
let mut ctx = make_ctx();
ctx.eval("globalThis.box = { count: 0 };", "<a>")
.expect("eval A");
ctx.eval("globalThis.box.count += 5;", "<b>")
.expect("eval B");
let r = ctx.eval("globalThis.box.count;", "<c>").expect("eval C");
assert_eq!(
r.as_number(),
Some(5.0),
"object identity must be stable across evals"
);
}
/// Realm global reuse: the same global object is reused across many evals
/// (not a fresh global per eval). Verified by incrementing a counter over
/// many evals and reading back the accumulated value.
#[test]
fn realm_global_reused_across_many_evals() {
let mut ctx = make_ctx();
ctx.eval("globalThis.counter = 0;", "<init>").expect("init");
for i in 1..=10 {
ctx.eval("globalThis.counter += 1;", &format!("<tick-{i}>"))
.expect("tick eval");
}
let r = ctx.eval("globalThis.counter;", "<final>").expect("final");
assert_eq!(
r.as_number(),
Some(10.0),
"single realm global must be reused across 10 evals"
);
}
/// A `require`-registered singleton must be the SAME instance across evals
/// (Node module-singleton semantics depend on realm persistence).
#[test]
fn cross_eval_require_singleton_identity() {
let mut ctx = make_ctx();
// Plant a sentinel on globalThis in A; in B verify the same value is read
// back (this stands in for module-singleton identity, which requires a
// real module loader and is exercised in module-eval path tests).
ctx.eval("globalThis.__sentinel = { id: 'stable' };", "<a>")
.expect("eval A");
let r = ctx
.eval("globalThis.__sentinel.id;", "<b>")
.expect("eval B");
assert_eq!(
r.as_string(),
Some("stable"),
"sentinel identity must persist across evals"
);
}
// ── module-vs-script same-realm (realm-per-context unification) ──
//
// Under realm-per-context a script eval and a module eval on the same context
// share ONE realm — `globalThis` is the same object, so a property planted by
// a script eval is visible to a module eval and vice versa. Under the old
// eval-per-global model each built its own global and these were isolated
// (Node/Bun/servo are all realm-per-context; the isolation was a bug).
/// Script → module → script: a `globalThis.x` planted by a script eval is
/// readable by a module eval, and a `globalThis.y` the module writes is
/// readable by a later script eval.
#[test]
fn script_and_module_share_realm_globalthis() {
let mut ctx = make_ctx();
// Script eval seeds the shared realm global.
ctx.eval("globalThis.x = 42;", "<script>")
.expect("script eval");
// Module eval in the SAME realm: read x, write y. No new global —
// `ModuleLoader::eval_module_in_realm` enters the existing realm.
let global_ptr = thread_realm_global().expect("realm global published by script eval");
let mut cx = ctx.cx();
rooted!(&in(cx) let global = global_ptr);
ModuleLoader::eval_module_in_realm(
&mut cx,
"globalThis.y = globalThis.x + 1;",
"<module>.mjs",
None,
global.handle(),
)
.expect("module eval in realm");
// Script eval reads what the module wrote — same realm, same global.
let r = ctx.eval("globalThis.y;", "<script-after>").expect("script-after eval");
assert_eq!(
r.as_number(),
Some(43.0),
"module and script must share the same realm global (realm-per-context)"
);
}
/// A module-registered side effect is observable by a subsequent module eval
/// in the same realm (no fresh global between modules).
#[test]
fn module_to_module_share_realm_globalthis() {
let mut ctx = make_ctx();
let global_ptr = thread_realm_global().or_else(|| {
// Ensure realm exists (script path initializes it lazily).
ctx.eval("void 0;", "<realm-init>").ok()?;
thread_realm_global()
}).expect("realm global");
let mut cx = ctx.cx();
rooted!(&in(cx) let global = global_ptr);
// First module plants a value.
ModuleLoader::eval_module_in_realm(
&mut cx,
"globalThis.fromModule = 'persisted';",
"<m1>.mjs",
None,
global.handle(),
)
.expect("module 1");
// Second module reads it — same realm.
ModuleLoader::eval_module_in_realm(
&mut cx,
"globalThis.fromModuleSeen = globalThis.fromModule === 'persisted';",
"<m2>.mjs",
None,
global.handle(),
)
.expect("module 2");
// Script confirms.
let r = ctx
.eval("globalThis.fromModuleSeen;", "<verify>")
.expect("verify");
assert_eq!(
r.as_bool(),
Some(true),
"module-to-module globalThis must persist across module evals"
);
}