use std::sync::Arc; use objects::{ CodeLayout, EndorCode, EndorCodeObject, EndorFunctionObject, Instruction, ObjectStore, Opcode, Value, VmError, }; #[test] fn dictionary_text_and_managed_string_keys_share_entries_and_keep_first_identity() { let mut store = ObjectStore::new().unwrap(); let dict = store.new_dict().unwrap(); for text in ["", "camera", "δΈ­ζ–‡πŸ™‚"] { let key = store.new_string(text).unwrap(); store .dict_set(dict, Value::Object(key), Value::Bool(false)) .unwrap(); store .dict_set(dict, Value::Text(text.into()), Value::Bool(true)) .unwrap(); assert_eq!( store.dict_get_name(dict, text).unwrap(), Some(Value::Bool(true)) ); assert_eq!( store.dict_items(dict).unwrap().last().unwrap().0, Value::Object(key) ); assert_eq!( store.dict_get(dict, &Value::Object(key)).unwrap(), Some(Value::Bool(true)) ); store.release(key).unwrap(); assert!(store.dict_delete(dict, &Value::Text(text.into())).unwrap()); assert_eq!(store.dict_get_name(dict, text).unwrap(), None); } store.release(dict).unwrap(); assert_eq!(store.object_count(), 0); } #[test] fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() { let mut store = ObjectStore::new().unwrap(); let mut other = ObjectStore::new().unwrap(); let dictionary = store.new_dict().unwrap(); let cell = store.new_cell(Some(Value::Object(dictionary))).unwrap(); let foreign = other.new_object().unwrap(); let address = store.object(dictionary).unwrap() as *const _; let key = Value::Text("cell".into()); store .dict_set(dictionary, key.clone(), Value::Object(cell)) .unwrap(); assert_eq!(store.object(dictionary).unwrap() as *const _, address); assert_eq!( store.dict_set(dictionary, key.clone(), Value::Object(foreign)), Err(VmError::ForeignHandle) ); assert_eq!( store.dict_get(dictionary, &key).unwrap(), Some(Value::Object(cell)) ); assert_eq!(store.ref_count(cell).unwrap(), 2); store.release(dictionary).unwrap(); store.release(cell).unwrap(); assert_eq!(store.collect_cycles(), 2); assert_eq!(store.object_count(), 0); assert!(matches!(store.object(cell), Err(VmError::InvalidHandle))); other.release(foreign).unwrap(); } #[test] fn standalone_function_factories_validate_and_own_shared_executable_data() { let mut store = ObjectStore::new().unwrap(); let constant = store.new_string("kept by code").unwrap(); let globals = store.new_dict().unwrap(); let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), Instruction::simple(Opcode::ReturnValue), ], vec![Value::Object(constant)], CodeLayout::default(), ) .unwrap(); let code = store.new_code(code).unwrap(); let before = store.object_count(); assert_eq!( store.new_function("bad", code, globals, vec![Value::Nil], vec![]), Err(VmError::InvalidArgument( "function defaults or closure do not match code" )) ); assert_eq!(store.object_count(), before); assert_eq!(store.ref_count(code).unwrap(), 1); let first = store .new_function("first", code, globals, vec![], vec![]) .unwrap(); let second = store .new_function("second", code, globals, vec![], vec![]) .unwrap(); let first_code = store .payload::(first, "function") .unwrap() .code(); let second_code = store .payload::(second, "function") .unwrap() .code(); let first_executable = store .payload::(first_code, "code") .unwrap() .shared_code(); let second_executable = store .payload::(second_code, "code") .unwrap() .shared_code(); assert!(Arc::ptr_eq(&first_executable, &second_executable)); drop(first_executable); drop(second_executable); store.release(constant).unwrap(); store.release(code).unwrap(); store.release(globals).unwrap(); assert_eq!(store.str(constant).unwrap(), "kept by code"); store.release(first).unwrap(); assert_eq!(store.ref_count(code).unwrap(), 1); store.release(second).unwrap(); assert_eq!(store.object_count(), 0); } #[test] fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_results() { let mut store = objects::ObjectStore::new().unwrap(); let roots = store.bootstrap_types().unwrap(); let record = store.new_object().unwrap(); let dict = store.new_dict().unwrap(); let cell = store .new_cell(Some(objects::Value::Number(1_i64.into()))) .unwrap(); let scope = store.begin_read_only(); assert!( store .set_attr(record, "value", objects::Value::Nil) .is_err() ); assert!(store.attach_type(record, roots.object).is_err()); assert!( store .dict_set( dict, objects::Value::Text("key".into()), objects::Value::Nil ) .is_err() ); assert!( store .cell_set(cell, objects::Value::Number(2_i64.into())) .is_err() ); assert_eq!( store.cell_get(cell).unwrap(), objects::Value::Number(1_i64.into()) ); let fresh = store.new_object().unwrap(); store.set_attr(fresh, "value", objects::Value::Nil).unwrap(); assert!(!store.assign_from(record, fresh).unwrap()); let nested = store.begin_read_only(); assert!( store .set_attr(fresh, "value", objects::Value::Bool(true)) .is_err() ); store.end_read_only(nested).unwrap(); store .set_attr(fresh, "value", objects::Value::Bool(true)) .unwrap(); let reads = store.end_read_only(scope).unwrap(); assert!(reads.contains(&cell)); store .set_attr(record, "value", objects::Value::Nil) .unwrap(); for object in [record, dict, cell, fresh, roots.object, roots.type_type] { store.release(object).unwrap(); } store.collect_cycles(); assert_eq!(store.object_count(), 0); } #[test] fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies() { let mut store = ObjectStore::new().unwrap(); let first = store.new_object().unwrap(); let second = store.new_object().unwrap(); assert!(store.begin_read_tracking().is_err()); let scope = store.begin_read_only(); let outer = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); let nested = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); store.object(second).unwrap(); assert!(store.end_read_tracking(outer).is_err()); assert_eq!( store.end_read_tracking(nested).unwrap(), vec![first, second] ); assert_eq!(store.end_read_tracking(outer).unwrap(), vec![first, second]); let repeated = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); assert_eq!(store.end_read_tracking(repeated).unwrap(), vec![first]); // Ending the call discards an unfinished region, as on callback failure. store.begin_read_tracking().unwrap(); assert_eq!(store.end_read_only(scope).unwrap(), vec![first, second]); assert!(store.end_read_tracking(1).is_err()); assert!(!store.is_read_only()); store.release(first).unwrap(); store.release(second).unwrap(); }