151 lines
4.9 KiB
Rust
151 lines
4.9 KiB
Rust
//! Remote-only regression coverage. Do not execute locally.
|
|
use endor_core::{
|
|
ChangeKey, ContextNativeFunction, ExecutionLimits, NativeCallable, NativeContext, NativeResult,
|
|
ObjectRef, Value, Vm, VmError,
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
struct Allocate(bool);
|
|
impl NativeCallable for Allocate {
|
|
fn invoke(&self, cx: &mut NativeContext<'_>, _args: &[Value]) -> Result<NativeResult, VmError> {
|
|
let value = cx.new_object()?;
|
|
cx.set_attr(value, "label", Value::Text("scoped".into()))?;
|
|
let _temporary = cx.new_object()?;
|
|
if self.0 {
|
|
return Err(VmError::InvalidArgument("intentional failure"));
|
|
}
|
|
Ok(NativeResult::Return(Value::Object(value)))
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn scoped_allocations_return_one_root_and_clean_up_errors() {
|
|
let mut vm = Vm::new().unwrap();
|
|
let function = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"allocate",
|
|
Allocate(false),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
let value = vm.call(function, &[]).unwrap();
|
|
let Value::Object(object) = value else {
|
|
panic!("expected object")
|
|
};
|
|
assert_eq!(vm.ref_count(object).unwrap(), 1);
|
|
assert_eq!(vm.object_count(), 2);
|
|
vm.release_value(value).unwrap();
|
|
vm.replace_payload(
|
|
function,
|
|
ContextNativeFunction::new("fail", Allocate(true), vec![]),
|
|
)
|
|
.unwrap();
|
|
assert!(vm.call(function, &[]).is_err());
|
|
assert_eq!(vm.object_count(), 1);
|
|
vm.release(function).unwrap();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Delegate(ObjectRef);
|
|
impl NativeCallable for Delegate {
|
|
fn invoke(&self, _cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
|
|
Ok(NativeResult::Invoke {
|
|
function: self.0,
|
|
arguments: args.to_vec(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn native_delegation_shares_budget_and_cleans_owned_arguments() {
|
|
let mut vm = Vm::new().unwrap();
|
|
let target = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"target",
|
|
Allocate(false),
|
|
vec![],
|
|
))
|
|
.unwrap();
|
|
let delegate = vm
|
|
.allocate(ContextNativeFunction::new(
|
|
"delegate",
|
|
Delegate(target),
|
|
vec![target],
|
|
))
|
|
.unwrap();
|
|
let arg = vm.new_object().unwrap();
|
|
assert!(
|
|
vm.call_with_limits(
|
|
delegate,
|
|
&[Value::Object(arg)],
|
|
ExecutionLimits {
|
|
max_frames: 1,
|
|
max_instructions: 1
|
|
}
|
|
)
|
|
.is_err()
|
|
);
|
|
assert_eq!(vm.ref_count(arg).unwrap(), 1);
|
|
assert_eq!(vm.ref_count(target).unwrap(), 2);
|
|
let value = vm.call(delegate, &[]).unwrap();
|
|
vm.release_value(value).unwrap();
|
|
vm.release(delegate).unwrap();
|
|
vm.release(target).unwrap();
|
|
vm.release(arg).unwrap();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn weak_references_expire_without_retaining_reused_slots() {
|
|
let mut vm = Vm::new().unwrap();
|
|
let object = vm.new_object().unwrap();
|
|
let weak = vm.downgrade(object).unwrap();
|
|
let upgraded = vm.upgrade(weak).unwrap().unwrap();
|
|
vm.release(upgraded).unwrap();
|
|
vm.release(object).unwrap();
|
|
let replacement = vm.new_object().unwrap();
|
|
assert!(vm.upgrade(weak).unwrap().is_none());
|
|
let mut foreign = Vm::new().unwrap();
|
|
assert_eq!(foreign.upgrade(weak), Err(VmError::ForeignHandle));
|
|
vm.release(replacement).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn changes_coalesce_cover_containers_and_do_not_keep_sources_alive() {
|
|
let mut vm = Vm::new().unwrap();
|
|
let object = vm.new_object().unwrap();
|
|
vm.set_attr(object, "value", Value::Bool(false)).unwrap();
|
|
vm.set_attr(object, "value", Value::Bool(true)).unwrap();
|
|
let version = vm.change_version(object).unwrap();
|
|
let changes = vm.take_changes();
|
|
assert!(!changes.rescan_required);
|
|
assert_eq!(changes.changes.len(), 1);
|
|
assert_eq!(changes.changes[0].key, ChangeKey::Attribute("value".into()));
|
|
assert_eq!(changes.changes[0].version, version);
|
|
let cell = vm.new_cell(None).unwrap();
|
|
vm.cell_set(cell, Value::Object(object)).unwrap();
|
|
vm.cell_clear(cell).unwrap();
|
|
assert_eq!(vm.take_changes().changes[0].key, ChangeKey::Contents);
|
|
vm.set_attr(object, "self", Value::Object(object)).unwrap();
|
|
vm.release(object).unwrap();
|
|
assert_eq!(vm.collect_cycles(), 1);
|
|
vm.release(cell).unwrap();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() {
|
|
let mut vm = Vm::new().unwrap();
|
|
let object = vm.new_object().unwrap();
|
|
for index in 0..4100 {
|
|
vm.set_attr(object, index.to_string(), Value::Nil).unwrap();
|
|
}
|
|
let changes = vm.take_changes();
|
|
assert!(changes.rescan_required);
|
|
assert!(changes.changes.is_empty());
|
|
vm.set_attr(object, "after", Value::Nil).unwrap();
|
|
assert_eq!(vm.take_changes().changes.len(), 1);
|
|
vm.release(object).unwrap();
|
|
}
|