Files
OxideJS-New/crates/oxide_vm/tests/promise_tests.rs
T

458 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::promise::promise_settled_value;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&Arc::new(module))
}
/// 顶层表达式返回 Promise 时,run() 末尾 drain 已执行微任务,读其结算值。
fn settled(vm: &mut Vm, source: &str) -> (bool, JsValue) {
let result = eval(vm, source).expect("run should succeed");
assert!(result.is_object(), "expected a promise, got {result:?}");
let obj = unsafe { &*result.as_js_object_ptr() };
promise_settled_value(obj).expect("promise should be settled")
}
#[test]
fn resolve_then_drains_chain() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve(42).then(v => v * 2)");
assert!(ok);
assert_eq!(val.as_double(), 84.0);
}
#[test]
fn chain_orders_fifo() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve(1).then(v => v + 1).then(v => v * 10)");
assert!(ok);
assert_eq!(val.as_double(), 20.0);
}
#[test]
fn settle_only_once() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "new Promise((res, rej) => { res(1); res(2); rej(3); }).then(v => v)");
assert!(ok);
assert_eq!(val.as_int(), 1);
}
#[test]
fn throw_in_handler_goes_to_catch() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve(1).then(v => { throw 'x' }).catch(e => e)");
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("x"));
}
#[test]
fn thenable_delegation() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.resolve({ then: function(r) { r(7) } }).then(v => v)");
assert!(ok);
assert_eq!(val.as_int(), 7);
}
#[test]
fn finally_passes_through() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "new Promise(r => { r(10) }).finally(() => {}).then(v => v)");
assert!(ok);
assert_eq!(val.as_int(), 10);
}
#[test]
fn reject_propagates_through_empty_then() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "Promise.reject('e').then(undefined, e => 'caught:' + e)");
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("caught:e"));
}
#[test]
fn ctor_receiver_used_for_capability() {
let mut vm = Vm::new();
// Promise.resolve.call(NotPromise) 应构造 NotPromise 并捕获其 executor。
let result = eval(
&mut vm,
"var ef; function NotP(exec) { ef = exec; exec(function(){}, function(){}); } \
Promise.resolve.call(NotP); ef.length",
)
.unwrap();
assert_eq!(result.as_int(), 2);
}
#[test]
fn custom_ctor_failure_is_type_error() {
let mut vm = Vm::new();
// executor 未捕获 resolve/reject → TypeError。
let result = eval(
&mut vm,
"function NotP(exec) { exec(function(){}); } \
try { Promise.resolve.call(NotP); 'no-throw' } catch (e) { e.name }",
)
.unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("TypeError"));
}
#[test]
fn resolve_thenable_then_reject_keeps_fulfilled() {
// P3:resolve(thenable) 置位 alreadyResolved,随后同调用内 reject 不覆盖。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"new Promise((res, rej) => { res({ then(res2) { res2({ then(r3) { r3('final'); } }); } }); rej('oops'); }).then(v => v, e => 'rejected:' + e)",
);
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("final"));
}
#[test]
fn double_resolve_second_noop() {
let mut vm = Vm::new();
let (ok, val) = settled(&mut vm, "new Promise((res) => { res(1); res(2); }).then(v => v)");
assert!(ok);
assert_eq!(val.as_int(), 1);
}
#[test]
fn reject_then_resolve_keeps_rejected() {
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"new Promise((res, rej) => { rej('e'); res(1); }).then(v => v, e => 'rejected:' + e)",
);
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("rejected:e"));
}
#[test]
fn thenable_resolve_after_reject_noop() {
// reject 先行置位,随后 resolve(thenable) 不得触发委托结算。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"new Promise((res, rej) => { rej('e'); res({ then(r) { r(1); } }); }).then(v => v, e => 'rejected:' + e)",
);
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("rejected:e"));
}
#[test]
fn thenable_chain_after_already_resolved_noop() {
// 双重 resolve(thenable):第二次委托不结算,结果保持第一次。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"new Promise((res) => { res({ then(r) { r(5); } }); res({ then(r) { r(9); } }); }).then(v => v)",
);
assert!(ok);
assert_eq!(val.as_int(), 5);
}
#[test]
fn subclass_then_derives_subclass_instance() {
// 子类 then 派生:class P extends Promise {},p.then 返回 P 实例(经子类构造器)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var P = class extends Promise {}; var d = new P(r => r(1)).then(v => v); d.constructor === P",
)
.unwrap();
assert!(result.as_bool(), "subclass then should derive via subclass ctor");
}
#[test]
fn subclass_catch_finally_derive_subclass() {
// catch/finally 经 this.then 调用,自动获得子类派生语义。
// 每次 eval 独立定义 P,两条断言互不干扰。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var P = class extends Promise {}; var d = new P(r => r(1)).catch(()=>{}); d.constructor === P",
)
.unwrap();
assert!(result.as_bool());
let result = eval(
&mut vm,
"var Q = class extends Promise {}; var f = new Q(r => r(1)).finally(()=>{}); f.constructor === Q",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn subclass_chain_keeps_subclass_ctor() {
// 链式 then 每次派生均保持子类构造器。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var P = class extends Promise {}; var d = new P(r => r(1)).then(v => v + 1).then(v => v); d.constructor === P",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn rewritten_prototype_constructor_used_for_derivation() {
// P.prototype.constructor 改写后,then 用改写值派生。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var P = class extends Promise {}; function Alt(exec) { exec(function(){}, function(){}); } \
P.prototype.constructor = Alt; var d = new P(r => r(1)).then(v => v); d.constructor === Alt",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn constructor_undefined_uses_intrinsic() {
// constructor 为 undefined → 退回内置 %Promise% 派生。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var q = new Promise(r => r(1)); Object.defineProperty(q, 'constructor', { value: undefined }); \
var d = q.then(v => v); d.constructor === Promise",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn constructor_non_object_throws_type_error() {
// constructor 非对象 → then 抛 TypeError。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var r = new Promise(res => res(1)); Object.defineProperty(r, 'constructor', { value: 42 }); \
try { r.then(()=>{}); 'no-throw' } catch (e) { e.name }",
)
.unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("TypeError"));
}
#[test]
fn constructor_getter_throw_preserves_original_value() {
// constructor getter 抛错 → 透传原异常对象(catch 收到同一引用)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var boom = new Error('boom'); var r = new Promise(res => res(1)); \
Object.defineProperty(r, 'constructor', { get() { throw boom; } }); \
try { r.then(()=>{}); 'no-throw' } catch (e) { e === boom }",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn intrinsic_promise_then_regression() {
// 回归:内置 promise 的 then 派生路径不变。
let mut vm = Vm::new();
let result = eval(&mut vm, "var d = new Promise(r => r(1)).then(v => v); d.constructor === Promise").unwrap();
assert!(result.as_bool());
}
#[test]
fn await_subclass_promise_resumes_with_value() {
// async function 内 await 子类 promise:值正确,额外派生仅来自 thenable
// 委托(await 机制自身的能力恒为内置,不额外调用子类构造器)。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"var ctorCalls = 0; var P = class extends Promise { constructor(exec) { ctorCalls++; super(exec); } }; \
async function f() { return await new P(r => r(1)); } \
f().then(v => [v, ctorCalls])",
);
assert!(ok);
let obj = unsafe { &*val.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2);
assert_eq!(obj.get_prop_at(0).as_int(), 1, "await value should pass through");
assert_eq!(obj.get_prop_at(1).as_int(), 2, "ctor calls: new once + thenable delegation once");
}
#[test]
fn finally_subclass_count_resolve_side() {
// finally ES2020 语义:子类 resolved 面共 7 次构造
//(new + finally-then + 语料 then + 尾部 then + PromiseResolve 能力
// + thunk-then + 委托任务 then)。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"var count = 0; var P = class extends Promise { constructor(exec) { count++; super(exec); } }; \
P.resolve().finally(() => {}).then(() => count).then(c => c)",
);
assert!(ok);
assert_eq!(val.as_int(), 7, "resolved-side finally should create 7 subclass promises");
}
#[test]
fn finally_subclass_count_reject_side() {
// 子类 rejected 面镜像:计数同为 7,拒绝原因透传。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"var count = 0; var P = class extends Promise { constructor(exec) { count++; super(exec); } }; \
P.reject('r').finally(() => {}).then(() => 'ok', e => 'caught:' + e + ':' + count).then(s => s)",
);
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("caught:r:7"));
}
#[test]
fn finally_resolved_observable_sequence() {
// resolved 面 then 可观测序列 [1,2,3,4,5]:finally 返回的 promise 被
// 下游捕获,拒绝原因经 th 链路透传。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"var seq = []; var yes = Promise.resolve(1); \
yes.then = function() { seq.push(1); return Promise.prototype.then.apply(this, arguments); }; \
var no = Promise.reject(2); \
no.then = function() { seq.push(4); return Promise.prototype.then.apply(this, arguments); }; \
yes.then(x => { seq.push(2); return x; }).finally(() => { seq.push(3); return no; }) \
.catch(e => { seq.push(5); return e; }) \
.then(e => seq.join(','))",
);
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("1,2,3,4,5"));
}
#[test]
fn finally_rejected_observable_sequence() {
// rejected 面镜像序列 [1,2,3,4,5]:重抛经 reject 角色 thunk 原值透传。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"var seq = []; var no = Promise.reject('r'); \
no.then = function() { seq.push(1); return Promise.prototype.then.apply(this, arguments); }; \
var yes = Promise.resolve(1); \
yes.then = function() { seq.push(4); return Promise.prototype.then.apply(this, arguments); }; \
no.catch(e => { seq.push(2); throw e; }).finally(() => { seq.push(3); return yes; }) \
.catch(e => { seq.push(5); return e; }) \
.then(v => seq.join(','))",
);
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("1,2,3,4,5"));
}
#[test]
fn finally_non_callable_on_finally_passed_through() {
// onFinally 不可调用时,双处理器取 onFinally 自身原值传给 then。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"var seen; var p = Promise.resolve(1); \
p.then = function(f, r) { seen = [f === 1, r === 1]; return Promise.prototype.then.call(this, f, r); }; \
p.finally(1).then(v => seen.join(','))",
);
assert!(ok);
assert_eq!(vm.lookup_str(val).as_deref(), Some("true,true"));
}
#[test]
fn then_species_override_used_for_derivation() {
// constructor 上的 @@species 覆写优先于 constructor 本身:派生走
// SpeciesConstructor 构造器且恰调用一次。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var callCount = 0; \
var SpeciesCtor = class extends Promise { constructor(a) { super(a); callCount++; } }; \
var p1 = new Promise(function() {}); \
p1.constructor = function() {}; \
p1.constructor[Symbol.species] = SpeciesCtor; \
var d = p1.then(v => v); \
[d.constructor === SpeciesCtor, callCount].join(',')",
)
.unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("true,1"));
}
#[test]
fn then_species_ctor_throw_preserves_original_value() {
// %Promise%[Symbol.species] 覆写为抛错构造器:then 透传原异常值。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var boom = new Error('boom'); \
var original = Object.getOwnPropertyDescriptor(Promise, Symbol.species); \
Object.defineProperty(Promise, Symbol.species, { value: function() { throw boom; } }); \
var thrown; \
try { new Promise(r => r(1)).then(); } catch (e) { thrown = e; } \
Object.defineProperty(Promise, Symbol.species, original); \
thrown === boom",
)
.unwrap();
assert!(result.as_bool(), "species getter throw should propagate the original value");
}
#[test]
fn finally_species_returning_promise_keeps_intrinsic() {
// 子类 @@species getter 返回 %Promise%:finally 派生留在内建,不进子类。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var FooPromise = class extends Promise { static get [Symbol.species]() { return Promise; } }; \
var p = Promise.resolve().finally(() => FooPromise.resolve()); \
[p instanceof Promise, p instanceof FooPromise].join(',')",
)
.unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("true,false"));
}
#[test]
fn static_resolve_non_object_this_throws() {
// this 非 Object 在 Type 守卫步抛 TypeError,先于同值快路径短路。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var promise = new Promise(function() {}); promise.constructor = undefined; \
var name = 'no-throw'; \
try { Promise.resolve.call(undefined, promise); } catch (e) { name = e.name; } \
name",
)
.unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("TypeError"));
}
#[test]
fn static_reject_non_object_this_throws() {
// reject 镜像守卫:this 非 Object 抛 TypeError。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var name = 'no-throw'; \
try { Promise.reject.call(null, 1); } catch (e) { name = e.name; } \
name",
)
.unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("TypeError"));
}
#[test]
fn async_gen_yield_subclass_promise_skips_species() {
// 内部 await 能力不走 species:async generator yield 子类 promise 时,
// yield 包装(await 展开)不得再经子类构造器派生。
let mut vm = Vm::new();
let (ok, val) = settled(
&mut vm,
"var ctorCalls = 0; var P = class extends Promise { constructor(exec) { ctorCalls++; super(exec); } }; \
async function* g() { yield new P(r => r(1)); } \
var it = g(); it.next().then(r => [r.value, ctorCalls])",
);
assert!(ok);
let obj = unsafe { &*val.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2);
assert_eq!(obj.get_prop_at(0).as_int(), 1, "yield value should pass through");
assert_eq!(obj.get_prop_at(1).as_int(), 1, "internal await must not derive via subclass ctor");
}