218 lines
7.5 KiB
Rust
218 lines
7.5 KiB
Rust
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::<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();
|
|
}
|