//! 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 { 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 { 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(); }