This repository has been archived on 2026-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OxideJS/crates/oxide_vm/tests/async_tests.rs
T
pjh123 a8378694be fix: for-of/for-in 提前退出时按规范顺序完成 IteratorClose
return/break/continue 逃出 for-of 时此前完全不执行 IteratorClose:
BREAK/CONTINUE/RETURN 指令现在经 ext 字携带词法逃出的迭代器层数
(for-of + for-in 各 16 位),运行时在全部 finally 穿越之后、真正
跳转/返回之前按 LIFO 关闭——与规范 §13.7.5.4(循环体完成值已含
内部 try-finally 处理,IteratorClose 在后)顺序一致。

- emit 词法:LabelCtx 增加 for-of/for-in 深度计数,loop/switch/label
  打开时快照,break/continue/return 按当前深度减目标深度计算逃出层数
  (switch 内 break 以 switch 打开点为基准,目标循环自身不计入)
- 指令编码:brk/cont/ret 三参构造,lower 跳转路径补发 ext 字
- 运行时:dispatch_break/continue/return 读取逃出计数,无 finally 时
  直接关闭后跳转/返回,有 finally 时随 Completion 悬挂由 TRY_FINALLY_END
  在穿越完成后消费;生成器 .return()/.throw() 注入经 complete_generator_return
  全关快照栈迭代器;return() 抛错经 unwind 替代完成值并关闭剩余迭代器
- 顺带:for-in 逃出残留(无 return() 语义,仅弹栈);for-await-of 的
  异步迭代器不做同步关闭(return() promise 须 await,走异步机制)
- 新增 15 项回归测试:return/labeled break/continue/switch/while/finally
  顺序/抛错优先/生成器注入/for-in/for-await-of 嵌套
2026-08-16 09:31:39 +08:00

239 lines
8.5 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.
//! async/await 运行时测试:异步函数调用返回 promise、await 恢复、reject 进 catch、
//! 多 await 顺序、嵌套 async、微任务执行序。
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_vm::promise::promise_settled_value;
use oxide_vm::vm::Vm;
/// 执行源码并格式化顶层结果;Promise 结果取其 drain 后的 settled 值。
fn eval(source: &str) -> String {
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, source) {
Ok(p) => p,
Err(e) => return format!("parse error: {}", e[0].message),
};
let module = match Compiler::new().compile(&program) {
Ok(m) => m,
Err(e) => return format!("compile error: {e}"),
};
let mut vm = Vm::new();
match vm.run(&module) {
Ok(result) => format_value(&vm, result),
Err(e) => format!("vm error: {e}"),
}
}
fn format_value(vm: &Vm, val: oxide_vm::JsValue) -> String {
if val.is_string() {
format!("\"{}\"", vm.lookup_str(val).unwrap_or_default())
} else if val.is_object() {
let obj = unsafe { &*val.as_js_object_ptr() };
if obj.is_promise_obj() {
match promise_settled_value(obj) {
Some((true, v)) => format_value(vm, v),
Some((false, v)) => format!("<rejected {}>", format_value(vm, v)),
None => "<pending>".to_string(),
}
} else if obj.is_array() {
"[array]".to_string()
} else {
"[object]".to_string()
}
} else {
format!("{val}")
}
}
#[test]
fn async_returns_immediate_value() {
assert_eq!(eval("async function f(){ return 42 } f()"), "42");
}
#[test]
fn async_await_primitive() {
assert_eq!(eval("async function f(){ var v = await 10; return v*2 } f()"), "20");
}
#[test]
fn async_await_resolved_promise() {
assert_eq!(eval("async function f(){ var v = await Promise.resolve(5); return v+1 } f()"), "6");
}
#[test]
fn async_await_reject_into_catch() {
assert_eq!(
eval("async function f(){ try { await Promise.reject('e') } catch(e) { return 'caught:'+e } } f()"),
"\"caught:e\""
);
}
#[test]
fn async_multiple_awaits_in_order() {
assert_eq!(eval("async function f(){ var x = await 1; x += await 2; return x } f()"), "3");
}
#[test]
fn async_await_return_promise() {
assert_eq!(eval("async function f(){ return await Promise.resolve(7) } f()"), "7");
}
#[test]
fn async_nested_calls() {
assert_eq!(eval("async function g(){ return 2 } async function f(){ return await g() + 3 } f()"), "5");
}
#[test]
fn async_arrow_in_then() {
assert_eq!(eval("Promise.resolve().then(async ()=>{ return 9 }).then(v=>v)"), "9");
}
#[test]
fn async_execution_order_suspends_immediately() {
// await 挂起 body;当前同步代码先跑完,body 恢复在微任务 drain 阶段。
assert_eq!(
eval("var order=[]; async function f(){ order.push(1); await 0; order.push(2) } f(); order.push(0); Promise.resolve().then(function(){ return order.join(',') })"),
"\"1,0,2\""
);
}
#[test]
fn async_throw_rejects_promise() {
assert_eq!(eval("async function f(){ throw 'boom' } f()"), "<rejected \"boom\">");
}
#[test]
fn async_is_not_constructor() {
assert!(
eval("new (async function(){})").starts_with("vm error")
|| eval("new (async function(){})").contains("not a constructor")
);
}
#[test]
fn async_constructor_name() {
assert_eq!(eval("async function f(){}; f.constructor.name"), "\"AsyncFunction\"");
}
#[test]
fn async_class_method() {
assert_eq!(eval("class A { async m(){ return await 7 } } new A().m()"), "7");
}
#[test]
fn async_gen_rejected_yield_queued_next_gets_done() {
// P6:yield 值被拒 → 生成器 completed,unwrap 前排队的 next 得 {done:true},
// 不得错误继续执行后续 yield。
assert_eq!(
eval(
"async function* g() { yield Promise.reject('e1'); yield 'never'; } \
async function run() { const it = g(); const p1 = it.next(); const p2 = it.next(); \
let r1, r2; try { await p1; r1 = 'p1-ok'; } catch (e) { r1 = 'p1-rej:' + e; } \
try { const v = await p2; r2 = 'p2:' + v.value + ':' + v.done; } catch (e) { r2 = 'p2-rej:' + e; } \
return r1 + '|' + r2; } run()"
),
"\"p1-rej:e1|p2:undefined:true\""
);
}
#[test]
fn async_gen_normal_yield_awaits_value() {
// yield 一个 resolved promise:unwrap 完成后让出展开值;next(arg) 作为 yield 表达式值。
assert_eq!(
eval(
"async function* g() { var v = yield Promise.resolve(9); yield v; } \
async function run() { const it = g(); const a = (await it.next()).value; \
const b = (await it.next(100)).value; return a + ',' + b; } run()"
),
"\"9,100\""
);
}
#[test]
fn async_gen_yield_then_next_continues() {
// 正常 yield 值(非 promise)后 next 继续执行到完成。
assert_eq!(
eval(
"async function* g() { yield 1; yield 2; } \
async function run() { const it = g(); const a = (await it.next()).value; \
const b = (await it.next()).value; const c = (await it.next()).done; \
return a + ',' + b + ',' + c; } run()"
),
"\"1,2,true\""
);
}
#[test]
fn for_await_of_break_closes_iterator() {
// 数组解构的 DONE 结果不得覆盖外层 for-await-of 迭代器的结果:break 提前
// 退出时 CLOSE 按自身条目判 done,须调用 return() 关闭迭代器。
assert_eq!(
eval(
"let closed=false;\
const iter={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){closed=true;return Promise.resolve({done:true})}}}};\
(async()=>{ for await (const [a,b] of iter) { break; } })();\
Promise.resolve().then(()=>closed)"
),
"true"
);
}
#[test]
fn for_await_of_natural_done_skips_return() {
// for-await-of 迭代自然结束(next 返回 done:true)时不调用 return()。
assert_eq!(
eval(
"let closed=false;\
const iter={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:true})},return(){closed=true;return Promise.resolve({done:true})}}}};\
(async()=>{ for await (const x of iter) { } })();\
Promise.resolve().then(()=>closed)"
),
"false"
);
}
#[test]
fn for_await_of_nested_sync_for_of_break_close_respective() {
// for-await-of 内嵌同步 for-of:内层 break 只关内层同步迭代器,外层异步
// 迭代器保持打开(条目配对,互不覆盖结果)。
assert_eq!(
eval(
"let log=[];\
const sync={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('s');return{}}}}};\
const asyncIter={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('a');return Promise.resolve({done:true})}}}};\
(async()=>{ for await (const x of asyncIter) { for (const y of sync) { break; } break; } })();\
Promise.resolve().then(()=>log.join(','))"
),
"\"s,a\""
);
}
#[test]
fn for_await_of_body_throw_closes_and_original_error_wins() {
// for-await-of 循环体抛错:先调 return() 关闭迭代器,原错误优先传播给 catch。
assert_eq!(
eval(
"let log=[];\
const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('r');return Promise.resolve({done:true})}}}};\
(async()=>{ try { for await (const x of it) { throw 'orig'; } } catch(e) { log.push('c:'+e); } return log.join(','); })()"
),
"\"r,c:orig\""
);
}
#[test]
fn for_await_of_return_escape_defers_async_close() {
// return 逃出 for-await-of:异步迭代器 return() 的 promise 须 await 后结算,
// 走异步关闭机制(后续阶段);阶段 1 同步逃出路径不得同步调用异步 return()。
// 断言循环体已执行且没有同步副作用泄露。
assert_eq!(
eval(
"let log=[];\
const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('close');return Promise.resolve({done:true})}}}};\
(async()=>{ for await (const x of it) { log.push('body'); return 1; } })();\
Promise.resolve().then(()=>log.join(','))"
),
"\"body\""
);
}