mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
test: AsyncDisposableStack 测试三十组(微任务序/错误链/GC 交叉/full_reset)
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
//! AsyncDisposableStack 内置对象测试:构造器/原型形状、use 读取顺序与入栈语义、
|
||||
//! disposeAsync 状态机(微任务序/错误链/防重入/reject 而非抛)、move、GC 交叉。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
||||
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}")
|
||||
}
|
||||
}
|
||||
|
||||
/// 构造器基本形状:new 建对象、disposed=false、proto/instanceof、原型链到 Object.prototype。
|
||||
#[test]
|
||||
fn constructor_shapes() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); \
|
||||
s.disposed + '|' + (Object.getPrototypeOf(s) === AsyncDisposableStack.prototype) + '|' + \
|
||||
(s instanceof AsyncDisposableStack) + '|' + \
|
||||
(Object.getPrototypeOf(AsyncDisposableStack.prototype) === Object.prototype)",
|
||||
),
|
||||
"\"false|true|true|true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// 构造器 length/name 与 prototype 槽描述符 {f,f,f}、proto.constructor={t,f,t}、
|
||||
/// 普通调用抛 TypeError。
|
||||
#[test]
|
||||
fn constructor_identity() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var p = Object.getOwnPropertyDescriptor(AsyncDisposableStack, 'prototype'); \
|
||||
var c = Object.getOwnPropertyDescriptor(AsyncDisposableStack.prototype, 'constructor'); \
|
||||
AsyncDisposableStack.length + '|' + AsyncDisposableStack.name + '|' + \
|
||||
p.writable + '|' + p.enumerable + '|' + p.configurable + '|' + \
|
||||
c.writable + '|' + c.enumerable + '|' + c.configurable + '|' + \
|
||||
(AsyncDisposableStack.prototype.constructor === AsyncDisposableStack) + '|' + \
|
||||
(function(){ try { AsyncDisposableStack(); return 'no' } catch (e) { return e.constructor.name } }())",
|
||||
),
|
||||
"\"0|AsyncDisposableStack|false|false|false|true|false|true|true|TypeError\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// 原型形状:@@asyncDispose 别名同一函数对象、@@toStringTag 值+描述符 {f,f,t}、
|
||||
/// 方法 length/name/{t,f,t}、disposed 为访问器 getter。
|
||||
#[test]
|
||||
fn prototype_shape() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var p = AsyncDisposableStack.prototype; \
|
||||
var tag = Object.getOwnPropertyDescriptor(p, Symbol.toStringTag); \
|
||||
var use = Object.getOwnPropertyDescriptor(p, 'use'); \
|
||||
var da = Object.getOwnPropertyDescriptor(p, 'disposeAsync'); \
|
||||
var acc = Object.getOwnPropertyDescriptor(p, 'disposed'); \
|
||||
(p[Symbol.asyncDispose] === p.disposeAsync) + '|' + \
|
||||
Object.prototype.toString.call(new AsyncDisposableStack()) + '|' + \
|
||||
tag.value + '|' + tag.writable + '|' + tag.enumerable + '|' + tag.configurable + '|' + \
|
||||
use.value.length + '|' + use.value.name + '|' + use.writable + '|' + use.enumerable + '|' + use.configurable + '|' + \
|
||||
da.value.length + '|' + da.value.name + '|' + \
|
||||
(typeof acc.get === 'function') + '|' + (acc.set === undefined)",
|
||||
),
|
||||
"\"true|[object AsyncDisposableStack]|AsyncDisposableStack|false|false|true|1|use|true|false|true|0|disposeAsync|true|true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// use:@@asyncDispose 优先读取、缺失回落 @@dispose(读取顺序断言)。
|
||||
#[test]
|
||||
fn use_reads_async_dispose_then_dispose() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var order = []; \
|
||||
var o = { \
|
||||
get [Symbol.asyncDispose]() { order.push('asyncDispose'); return undefined; }, \
|
||||
get [Symbol.dispose]() { order.push('dispose'); return function() {}; } \
|
||||
}; \
|
||||
var s = new AsyncDisposableStack(); s.use(o); s.disposeAsync().then(function(){}); \
|
||||
order.join(',')",
|
||||
),
|
||||
"\"asyncDispose,dispose\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// use:@@asyncDispose getter 只读一次。
|
||||
#[test]
|
||||
fn use_reads_async_dispose_once() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var count = 0; \
|
||||
var o = { get [Symbol.asyncDispose]() { count++; return function() {}; } }; \
|
||||
var s = new AsyncDisposableStack(); s.use(o); s.disposeAsync().then(function(){}); count + ''",
|
||||
),
|
||||
"\"1\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// use:键 11 缺失(null/undefined)且键 12 也缺失 → TypeError;键 11 非 callable
|
||||
/// → TypeError 且不回落;原始值 TypeError。
|
||||
#[test]
|
||||
fn use_invalid_dispose_throws() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"function kind(fn) { try { var s = new AsyncDisposableStack(); fn(s); s.disposeAsync(); return 'no' } catch (e) { return e.constructor.name } } \
|
||||
var noSym = function(s) { s.use({}); }; \
|
||||
var nullBoth = function(s) { s.use({ [Symbol.asyncDispose]: null }); }; \
|
||||
var nonCallable11 = function(s) { s.use({ [Symbol.asyncDispose]: 42 }); }; \
|
||||
var nonCallable12 = function(s) { s.use({ [Symbol.dispose]: 42 }); }; \
|
||||
var primitive = function(s) { s.use(42); }; \
|
||||
[kind(noSym), kind(nullBoth), kind(nonCallable11), kind(nonCallable12), kind(primitive)].join(',')",
|
||||
),
|
||||
"\"TypeError,TypeError,TypeError,TypeError,TypeError\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// use(null)/use(undefined):async 栈必须入栈(返回原值、不抛;disposeAsync 后
|
||||
/// 资源求值被记录——disposed 翻转,且触发末尾 Await)。
|
||||
#[test]
|
||||
fn use_null_undefined_pushed() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); \
|
||||
var r1 = s.use(null); var r2 = s.use(undefined); \
|
||||
var first = (r1 === null) + '|' + (r2 === undefined) + '|' + s.disposed + '|'; \
|
||||
s.disposeAsync().then(function() { return first + s.disposed; })",
|
||||
),
|
||||
"\"true|true|false|true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// use:@@asyncDispose 方法被调(this=value);disposeAsync 结果 promise resolve。
|
||||
#[test]
|
||||
fn use_async_method_called() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log = []; \
|
||||
var o = { v: 7, async [Symbol.asyncDispose]() { log.push(this.v); } }; \
|
||||
var s = new AsyncDisposableStack(); s.use(o); \
|
||||
s.disposeAsync().then(function() { return log.join(','); })",
|
||||
),
|
||||
"\"7\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// use:回落 @@dispose 的同步方法返回值不被 await(计数断言),调用仍发生。
|
||||
#[test]
|
||||
fn use_sync_dispose_return_not_awaited() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log = []; \
|
||||
var o = { [Symbol.dispose]() { log.push('called'); return Promise.resolve(); } }; \
|
||||
var s = new AsyncDisposableStack(); s.use(o); \
|
||||
s.disposeAsync().then(function() { return log.join(','); })",
|
||||
),
|
||||
"\"called\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// adopt/defer:arg-style 调用约定(value 作首参、this=undefined)、返回原值/undefined、
|
||||
/// 非 callable TypeError。
|
||||
#[test]
|
||||
fn adopt_defer_semantics() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); var log = []; \
|
||||
var r1 = s.adopt('v', function(a) { 'use strict'; log.push('adopt:' + a + ':' + (this === undefined)); }); \
|
||||
var r2 = s.defer(function() { 'use strict'; log.push('defer:' + (this === undefined)); }); \
|
||||
var first = (r1 === 'v') + '|' + (r2 === undefined) + '|'; \
|
||||
s.disposeAsync().then(function() { return first + log.join(','); })",
|
||||
),
|
||||
"\"true|true|defer:true,adopt:v:true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// adopt/defer:非 callable 回调抛 TypeError。
|
||||
#[test]
|
||||
fn adopt_defer_non_callable_throws() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"function k(fn) { try { var s = new AsyncDisposableStack(); fn(s); return 'no' } catch (e) { return e.constructor.name } } \
|
||||
k(function(s) { s.adopt(1, 42); }) + ',' + k(function(s) { s.defer(null); })",
|
||||
),
|
||||
"\"TypeError,TypeError\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposeAsync:use/adopt/defer 混栈逆序执行(仿 disposes-resources-in-reverse-order)。
|
||||
#[test]
|
||||
fn dispose_async_reverse_order() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); var disposed = []; \
|
||||
var r1 = { id: 'r1', async [Symbol.asyncDispose]() { disposed.push(this.id); } }; \
|
||||
var r2 = { id: 'r2', [Symbol.dispose]() { disposed.push(this.id); } }; \
|
||||
var r3 = { id: 'r3' }; async function d3(res) { disposed.push(res.id); } \
|
||||
var r4 = { id: 'r4' }; function d4(res) { disposed.push(res.id); } \
|
||||
async function d5() { disposed.push('d5'); } \
|
||||
function d6() { disposed.push('d6'); } \
|
||||
s.use(r1); s.use(r2); s.adopt(r3, d3); s.adopt(r4, d4); s.defer(d5); s.defer(d6); \
|
||||
s.disposeAsync().then(function() { return disposed.join(','); })",
|
||||
),
|
||||
"\"d6,d5,r4,r3,r2,r1\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposeAsync:返回 promise、resolve undefined;已 disposed 后二次调用仍 resolve
|
||||
/// undefined 且不重入(计数=1)。
|
||||
#[test]
|
||||
fn dispose_async_idempotent() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); var n = 0; \
|
||||
s.defer(function() { n++; }); \
|
||||
var p1 = s.disposeAsync(); var p2 = s.disposeAsync(); \
|
||||
var r = (p1 instanceof Promise) + '|' + (p2 instanceof Promise) + '|'; \
|
||||
Promise.all([p1, p2]).then(function(v) { return r + (v[0] === undefined) + '|' + (v[1] === undefined) + '|' + n; })",
|
||||
),
|
||||
"\"true|true|true|true|1\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposeAsync:非对象/无槽/同步栈 this → promise reject(而非同步抛)。
|
||||
#[test]
|
||||
fn dispose_async_incompatible_receiver_rejects() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var da = AsyncDisposableStack.prototype.disposeAsync; \
|
||||
var syncStack = new DisposableStack(); \
|
||||
function kind(t) { \
|
||||
var p = da.call(t); \
|
||||
if (!(p instanceof Promise)) return Promise.resolve('not-promise'); \
|
||||
return p.then(function() { return 'resolved'; }, function(e) { return e.constructor.name; }); \
|
||||
} \
|
||||
Promise.all([kind(undefined), kind(42), kind({}), kind(AsyncDisposableStack.prototype), kind(syncStack)]) \
|
||||
.then(function(v) { return v.join('|'); })",
|
||||
),
|
||||
"\"TypeError|TypeError|TypeError|TypeError|TypeError\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposeAsync:sets-state-to-disposed——调用后同步置位、await 后保持。
|
||||
#[test]
|
||||
fn dispose_async_sets_state_to_disposed() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); \
|
||||
var before = s.disposed; \
|
||||
var p = s.disposeAsync(); \
|
||||
var during = s.disposed; \
|
||||
p.then(function() { return before + '|' + during + '|' + s.disposed; })",
|
||||
),
|
||||
"\"false|true|true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposeAsync:单错原样 reject(同一错误对象引用)。
|
||||
#[test]
|
||||
fn dispose_async_single_error_rejects_as_is() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var err = new Error('boom'); \
|
||||
var s = new AsyncDisposableStack(); s.defer(function() { throw err; }); \
|
||||
s.disposeAsync().then(function() { return 'resolved' }, function(e) { return String(e === err); })",
|
||||
),
|
||||
"\"true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposeAsync:三错合并为 SuppressedError 链(error=后抛、suppressed=前值嵌套)。
|
||||
#[test]
|
||||
fn dispose_async_multiple_errors_suppressed_chain() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var e1 = new Error('e1'); var e2 = new Error('e2'); var e3 = new Error('e3'); \
|
||||
var s = new AsyncDisposableStack(); \
|
||||
s.defer(function() { throw e1; }); \
|
||||
s.defer(function() { throw e2; }); \
|
||||
s.defer(function() { throw e3; }); \
|
||||
s.disposeAsync().then(function() { return 'resolved' }, function(e) { \
|
||||
return (e instanceof SuppressedError) + '|' + (e.error === e1) + '|' + \
|
||||
(e.suppressed.error === e2) + '|' + (e.suppressed.suppressed === e3); })",
|
||||
),
|
||||
"\"true|true|true|true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposeAsync:真实 async 方法返回 rejected promise → 拒绝原因合并进链。
|
||||
/// 逆序处理:后入栈的 berr 条目先抛(completion=berr),再遇 aerr 条目 →
|
||||
/// error=后抛(aerr)、suppressed=前值(berr)。
|
||||
#[test]
|
||||
fn dispose_async_rejected_async_method_merged() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var aerr = new Error('a'); var berr = new Error('b'); \
|
||||
var s = new AsyncDisposableStack(); \
|
||||
s.defer(async function() { throw aerr; }); \
|
||||
s.defer(function() { return Promise.reject(berr); }); \
|
||||
s.disposeAsync().then(function() { return 'resolved' }, function(e) { \
|
||||
return (e.error === aerr) + '|' + (e.suppressed === berr); })",
|
||||
),
|
||||
"\"true|true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// 微任务序:空栈同步 settle——dispose 恰好排在两个单跳 job 之间。
|
||||
#[test]
|
||||
fn explicit_await_skipped_when_empty() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); var seq = []; \
|
||||
Promise.all([ \
|
||||
Promise.resolve().then(function() { seq.push('job 1'); }), \
|
||||
s.disposeAsync().then(function() { seq.push('dispose'); }), \
|
||||
Promise.resolve().then(function() { seq.push('job 2'); }) \
|
||||
]).then(function() { return seq.join(','); })",
|
||||
),
|
||||
"\"job 1,dispose,job 2\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// 微任务序:单 use(null) 栈末尾单次 Await(恰一跳延迟),序仍 job1/dispose/job2。
|
||||
#[test]
|
||||
fn explicit_await_for_null() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); s.use(null); var seq = []; \
|
||||
Promise.all([ \
|
||||
Promise.resolve().then(function() { return 0; }).then(function() { seq.push('job 1'); }), \
|
||||
s.disposeAsync().then(function() { seq.push('dispose'); }), \
|
||||
Promise.resolve().then(function() { return 0; }).then(function() { seq.push('job 2'); }) \
|
||||
]).then(function() { return seq.join(','); })",
|
||||
),
|
||||
"\"job 1,dispose,job 2\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// 微任务序:单 use(undefined) 与 use(null) 同语义(末尾补 Await,两跳 job 与
|
||||
/// test262 explicit-await-for-undefined 逐字等价)。
|
||||
#[test]
|
||||
fn explicit_await_for_undefined() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); s.use(undefined); var seq = []; \
|
||||
Promise.all([ \
|
||||
Promise.resolve().then(function() { return 0; }).then(function() { seq.push('job 1'); }), \
|
||||
s.disposeAsync().then(function() { seq.push('dispose'); }), \
|
||||
Promise.resolve().then(function() { return 0; }).then(function() { seq.push('job 2'); }) \
|
||||
]).then(function() { return seq.join(','); })",
|
||||
),
|
||||
"\"job 1,dispose,job 2\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// 微任务序:wrap_sync 条目(use 回落 @@dispose)经 await 记 hasAwaited,随后
|
||||
/// async-null 条目置 needsAwait 但末尾补 await 被抑制(已 await 过)——顶层同步
|
||||
/// 阶段即调用方法,dispose 恰在 job 之间。
|
||||
#[test]
|
||||
fn explicit_await_wrap_sync_counts_as_awaited() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var seq = []; \
|
||||
var s = new AsyncDisposableStack(); \
|
||||
s.use(null); \
|
||||
s.use({ [Symbol.dispose]() { seq.push('sync-res'); } }); \
|
||||
var p = s.disposeAsync().then(function() { seq.push('dispose'); }); \
|
||||
Promise.resolve().then(function() { seq.push('job 1'); }); \
|
||||
p.then(function() { return seq.join(','); })",
|
||||
),
|
||||
"\"sync-res,job 1,dispose\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// move:entries 转移、源置 Disposed、新栈 Pending 且新栈 disposeAsync 逆序执行。
|
||||
#[test]
|
||||
fn move_transfers_entries() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); var log = []; \
|
||||
s.defer(function() { log.push('A'); }); \
|
||||
var m = s.move(); \
|
||||
var first = String(s.disposed) + '|' + String(m.disposed) + '|'; \
|
||||
m.disposeAsync().then(function() { return first + log.join(','); })",
|
||||
),
|
||||
"\"true|false|A\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// move:子类实例 move 结果 proto 固定为 AsyncDisposableStack.prototype(非子类)。
|
||||
#[test]
|
||||
fn move_subclass_proto_fixed() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"class S extends AsyncDisposableStack {} \
|
||||
var s = new S(); s.defer(function(){}); \
|
||||
var m = s.move(); \
|
||||
(m instanceof AsyncDisposableStack) + '|' + (m instanceof S) + '|' + \
|
||||
(Object.getPrototypeOf(m) === AsyncDisposableStack.prototype)",
|
||||
),
|
||||
"\"true|false|true\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// move:disposed 源栈 move 抛 ReferenceError;use/adopt/defer/move 在 disposed 后
|
||||
/// 均抛 ReferenceError。
|
||||
#[test]
|
||||
fn mutators_after_dispose_throw() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var s = new AsyncDisposableStack(); \
|
||||
var p = s.disposeAsync().then(function() { \
|
||||
function k(fn) { try { fn(); return 'no' } catch (e) { return e.constructor.name } } \
|
||||
return k(function() { s.use({ [Symbol.asyncDispose]: function(){} }); }) + ',' + \
|
||||
k(function() { s.adopt(1, function(){}); }) + ',' + \
|
||||
k(function() { s.defer(function(){}); }) + ',' + \
|
||||
k(function() { s.move(); }); \
|
||||
}); p",
|
||||
),
|
||||
"\"ReferenceError,ReferenceError,ReferenceError,ReferenceError\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// disposed getter:非对象/无槽/同步栈 this 抛 TypeError。
|
||||
#[test]
|
||||
fn disposed_getter_incompatible_throws() {
|
||||
assert_eq!(
|
||||
eval(
|
||||
"function k(t) { try { AsyncDisposableStack.prototype.disposed.call(t); return 'no' } catch (e) { return e.constructor.name } } \
|
||||
k({}) + ',' + k(42) + ',' + k(new DisposableStack())",
|
||||
),
|
||||
"\"TypeError,TypeError,TypeError\"",
|
||||
);
|
||||
}
|
||||
|
||||
/// 低阈值执行期 GC:async 栈 use 资源对象后触发移动式清扫,disposeAsync 仍按
|
||||
/// entries 正确释放(log 挂 globalThis 属性跨 run 存活)。
|
||||
#[test]
|
||||
fn gc_runtime_collection_keeps_entries() {
|
||||
let mut config = KernelConfig::minimal();
|
||||
config.set_session_gc_threshold(4096);
|
||||
let mut vm = Vm::with_kernel_core(KernelCore::new(config));
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(
|
||||
&allocator,
|
||||
"globalThis.log = []; \
|
||||
for (var i = 0; i < 200; i++) { \
|
||||
var s = new AsyncDisposableStack(); \
|
||||
s.defer(function() { globalThis.log.push('d'); }); \
|
||||
globalThis['k' + i] = s; \
|
||||
} \
|
||||
for (var i = 0; i < 200; i++) { globalThis['k' + i].disposeAsync().then(function(){}); } \
|
||||
0",
|
||||
)
|
||||
.expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
vm.run(&module).expect("run1");
|
||||
let allocator = Allocator::default();
|
||||
let program2 = oxide_parser::parse(&allocator, "globalThis.log.length + ''").expect("parse");
|
||||
let module2 = Compiler::new().compile(&program2).expect("compile");
|
||||
let result = vm.run(&module2).expect("run2");
|
||||
let text = vm.lookup_str(result).unwrap_or_default().to_string();
|
||||
assert_eq!(text, "200");
|
||||
assert!(vm.session_gc_stats().total_collections > 0, "低阈值应触发执行期 GC");
|
||||
}
|
||||
|
||||
/// promote 交叉:栈的 entries 引用 epoch 对象(释放函数),写 global 触发 promote,
|
||||
/// reset 释放 epoch 后状态盒存活:disposed 可读、move 可转移 entries。
|
||||
#[test]
|
||||
fn promote_then_reset_keeps_entries_alive() {
|
||||
let mut vm = Vm::new();
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(
|
||||
&allocator,
|
||||
"globalThis.log = []; \
|
||||
var s = new AsyncDisposableStack(); \
|
||||
s.defer(function() { globalThis.log.push('x'); }); \
|
||||
globalThis.keep = s; 0",
|
||||
)
|
||||
.expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
vm.run(&module).expect("run1");
|
||||
vm.reset();
|
||||
let allocator = Allocator::default();
|
||||
let program2 = oxide_parser::parse(
|
||||
&allocator,
|
||||
"var m = globalThis.keep.move(); \
|
||||
String(globalThis.keep.disposed) + '|' + String(m.disposed)",
|
||||
)
|
||||
.expect("parse");
|
||||
let module2 = Compiler::new().compile(&program2).expect("compile");
|
||||
let result = vm.run(&module2).expect("run2");
|
||||
let text = vm.lookup_str(result).unwrap_or_default().to_string();
|
||||
assert_eq!(text, "true|false");
|
||||
}
|
||||
|
||||
/// drop 记账:无引用的 async 栈对象被收集时释放状态盒(freed 字节增长)。
|
||||
#[test]
|
||||
fn drop_accounts_capability_bytes() {
|
||||
let mut vm = Vm::new();
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, "for (var i = 0; i < 500; i++) { new AsyncDisposableStack(); } 0")
|
||||
.expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
vm.run(&module).expect("run");
|
||||
let before = vm.session_gc_stats().total_bytes_freed;
|
||||
vm.reset();
|
||||
let after = vm.session_gc_stats().total_bytes_freed;
|
||||
assert!(after > before, "reset 应回收栈对象状态盒,before={before} after={after}");
|
||||
}
|
||||
|
||||
/// full_reset(dirty 重建):global 与 object 家族重建后 AsyncDisposableStack 仍
|
||||
/// 完整可用——全局槽、proto.constructor、原型方法、instanceof 与原型链全部存活,
|
||||
/// disposeAsync 可再次执行。
|
||||
#[test]
|
||||
fn full_reset_rebuilds_async_disposable_stack() {
|
||||
let mut vm = Vm::new();
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, "new AsyncDisposableStack().disposeAsync(); 0").expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
vm.run(&module).expect("run1");
|
||||
vm.full_reset();
|
||||
let allocator = Allocator::default();
|
||||
let program2 = oxide_parser::parse(
|
||||
&allocator,
|
||||
"var s = new AsyncDisposableStack(); var log = []; \
|
||||
s.defer(function() { log.push('A'); }); \
|
||||
var r = (s instanceof AsyncDisposableStack) + '|' + \
|
||||
(AsyncDisposableStack.prototype.constructor === AsyncDisposableStack) + '|' + \
|
||||
(AsyncDisposableStack.prototype[Symbol.asyncDispose] === AsyncDisposableStack.prototype.disposeAsync) + '|' + \
|
||||
(AsyncDisposableStack.prototype[Symbol.toStringTag] === 'AsyncDisposableStack') + '|'; \
|
||||
s.disposeAsync().then(function() { return r + log.join(','); })",
|
||||
)
|
||||
.expect("parse");
|
||||
let module2 = Compiler::new().compile(&program2).expect("compile");
|
||||
let result = vm.run(&module2).expect("run2");
|
||||
let text = match promise_settled_value(unsafe { &*result.as_js_object_ptr() }) {
|
||||
Some((true, v)) => vm.lookup_str(v).unwrap_or_default().to_string(),
|
||||
Some((false, v)) => format!("<rejected {}>", vm.lookup_str(v).unwrap_or_default()),
|
||||
None => "<pending>".to_string(),
|
||||
};
|
||||
assert_eq!(text, "true|true|true|true|A");
|
||||
}
|
||||
Reference in New Issue
Block a user