155 lines
5.1 KiB
Rust
155 lines
5.1 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();
|
|
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);
|
|
}
|