Files
endorphin-aster-vm/crates/objects/tests/store.rs

186 lines
6.4 KiB
Rust

use std::sync::Arc;
use objects::{
CodeLayout, EndorCode, EndorCodeObject, EndorFunctionObject, Instruction, ObjectStore, Opcode,
Value, VmError,
};
#[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::<EndorFunctionObject>(first, "function")
.unwrap()
.code();
let second_code = store
.payload::<EndorFunctionObject>(second, "function")
.unwrap()
.code();
let first_executable = store
.payload::<EndorCodeObject>(first_code, "code")
.unwrap()
.shared_code();
let second_executable = store
.payload::<EndorCodeObject>(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();
}