mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
test: 值/形状/私有键/内存内联测试迁入兄弟测试文件
This commit is contained in:
@@ -156,29 +156,4 @@ impl Default for Epoch {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::object::JsObject;
|
||||
use crate::shape::EMPTY_SHAPE_ID;
|
||||
use crate::value::JsValue;
|
||||
|
||||
#[test]
|
||||
fn is_epoch_ptr_returns_true_for_epoch_object() {
|
||||
let epoch = Epoch::new();
|
||||
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||
let ptr = epoch.alloc(obj);
|
||||
|
||||
assert!(epoch.is_epoch_ptr(ptr.cast::<u8>()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_epoch_ptr_returns_false_for_heap_and_stack_pointers() {
|
||||
let epoch = Epoch::new();
|
||||
let persistent = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let stack_value = 7i32;
|
||||
|
||||
assert!(!epoch.is_epoch_ptr(persistent.as_ptr().cast::<u8>()));
|
||||
assert!(!epoch.is_epoch_ptr((&stack_value as *const i32).cast::<u8>()));
|
||||
assert!(!epoch.is_epoch_ptr(std::ptr::null()));
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use super::*;
|
||||
use crate::object::JsObject;
|
||||
use crate::shape::EMPTY_SHAPE_ID;
|
||||
use crate::value::JsValue;
|
||||
|
||||
#[test]
|
||||
fn is_epoch_ptr_returns_true_for_epoch_object() {
|
||||
let epoch = Epoch::new();
|
||||
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||
let ptr = epoch.alloc(obj);
|
||||
|
||||
assert!(epoch.is_epoch_ptr(ptr.cast::<u8>()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_epoch_ptr_returns_false_for_heap_and_stack_pointers() {
|
||||
let epoch = Epoch::new();
|
||||
let persistent = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let stack_value = 7i32;
|
||||
|
||||
assert!(!epoch.is_epoch_ptr(persistent.as_ptr().cast::<u8>()));
|
||||
assert!(!epoch.is_epoch_ptr((&stack_value as *const i32).cast::<u8>()));
|
||||
assert!(!epoch.is_epoch_ptr(std::ptr::null()));
|
||||
}
|
||||
@@ -115,81 +115,4 @@ pub const fn well_known_symbol_id_from_key(key: u32) -> Option<u32> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn private_name_ids_use_high_band() {
|
||||
let id = make_private_name_id(7);
|
||||
assert!(is_private_name_key(id));
|
||||
assert_ne!(id, u32::MAX);
|
||||
assert!(!is_private_name_key(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_keys_roundtrip() {
|
||||
let key = make_symbol_key(0);
|
||||
assert!(is_symbol_key(key));
|
||||
assert!(is_private_name_key(key));
|
||||
assert_eq!(symbol_index_from_key(key), 0);
|
||||
assert!(well_known_symbol_id_from_key(key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_keys_distinct_per_index() {
|
||||
let a = make_symbol_key(1);
|
||||
let b = make_symbol_key(2);
|
||||
assert_ne!(a, b);
|
||||
assert!(is_symbol_key(a));
|
||||
assert!(is_symbol_key(b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_keys_avoid_private_band() {
|
||||
let id = make_private_name_id(12);
|
||||
let key = make_symbol_key(12);
|
||||
assert!(!is_symbol_key(id));
|
||||
assert!(is_symbol_key(key));
|
||||
assert_ne!(id, key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn well_known_keys_reserve_low_slots() {
|
||||
for id in 0..WELL_KNOWN_SYMBOL_COUNT {
|
||||
let key = make_well_known_symbol_key(id);
|
||||
assert!(is_symbol_key(key));
|
||||
assert_eq!(well_known_symbol_id_from_key(key), Some(id));
|
||||
}
|
||||
let user_key = make_well_known_symbol_key(WELL_KNOWN_SYMBOL_COUNT);
|
||||
assert!(well_known_symbol_id_from_key(user_key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_index_mask_prevents_wrap() {
|
||||
let key = make_symbol_key(SYMBOL_INDEX_MASK);
|
||||
assert_eq!(symbol_index_from_key(key), SYMBOL_INDEX_MASK);
|
||||
assert!(is_symbol_key(key));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_keys_stay_in_own_band() {
|
||||
assert_eq!(make_int_key(0), INT_KEY_BASE);
|
||||
assert_eq!(int_key_value(INT_KEY_BASE), 0);
|
||||
assert_eq!(int_key_value(make_int_key(123)), 123);
|
||||
assert!(is_int_key(make_int_key(0)));
|
||||
assert!(!is_int_key(0));
|
||||
assert!(!is_int_key(PRIVATE_NAME_BASE));
|
||||
assert!(!is_private_name_key(make_int_key(0)));
|
||||
assert!(!is_symbol_key(make_int_key(0)));
|
||||
assert_ne!(make_int_key(5), make_private_name_id(5));
|
||||
assert_ne!(make_int_key(5), make_symbol_key(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_key_roundtrip_max() {
|
||||
let key = make_int_key(INT_KEY_COUNT - 1);
|
||||
assert!(is_int_key(key));
|
||||
assert!(key < PRIVATE_NAME_BASE);
|
||||
assert_eq!(int_key_value(key), INT_KEY_COUNT - 1);
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn private_name_ids_use_high_band() {
|
||||
let id = make_private_name_id(7);
|
||||
assert!(is_private_name_key(id));
|
||||
assert_ne!(id, u32::MAX);
|
||||
assert!(!is_private_name_key(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_keys_roundtrip() {
|
||||
let key = make_symbol_key(0);
|
||||
assert!(is_symbol_key(key));
|
||||
assert!(is_private_name_key(key));
|
||||
assert_eq!(symbol_index_from_key(key), 0);
|
||||
assert!(well_known_symbol_id_from_key(key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_keys_distinct_per_index() {
|
||||
let a = make_symbol_key(1);
|
||||
let b = make_symbol_key(2);
|
||||
assert_ne!(a, b);
|
||||
assert!(is_symbol_key(a));
|
||||
assert!(is_symbol_key(b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_keys_avoid_private_band() {
|
||||
let id = make_private_name_id(12);
|
||||
let key = make_symbol_key(12);
|
||||
assert!(!is_symbol_key(id));
|
||||
assert!(is_symbol_key(key));
|
||||
assert_ne!(id, key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn well_known_keys_reserve_low_slots() {
|
||||
for id in 0..WELL_KNOWN_SYMBOL_COUNT {
|
||||
let key = make_well_known_symbol_key(id);
|
||||
assert!(is_symbol_key(key));
|
||||
assert_eq!(well_known_symbol_id_from_key(key), Some(id));
|
||||
}
|
||||
let user_key = make_well_known_symbol_key(WELL_KNOWN_SYMBOL_COUNT);
|
||||
assert!(well_known_symbol_id_from_key(user_key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_index_mask_prevents_wrap() {
|
||||
let key = make_symbol_key(SYMBOL_INDEX_MASK);
|
||||
assert_eq!(symbol_index_from_key(key), SYMBOL_INDEX_MASK);
|
||||
assert!(is_symbol_key(key));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_keys_stay_in_own_band() {
|
||||
assert_eq!(make_int_key(0), INT_KEY_BASE);
|
||||
assert_eq!(int_key_value(INT_KEY_BASE), 0);
|
||||
assert_eq!(int_key_value(make_int_key(123)), 123);
|
||||
assert!(is_int_key(make_int_key(0)));
|
||||
assert!(!is_int_key(0));
|
||||
assert!(!is_int_key(PRIVATE_NAME_BASE));
|
||||
assert!(!is_private_name_key(make_int_key(0)));
|
||||
assert!(!is_symbol_key(make_int_key(0)));
|
||||
assert_ne!(make_int_key(5), make_private_name_id(5));
|
||||
assert_ne!(make_int_key(5), make_symbol_key(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_key_roundtrip_max() {
|
||||
let key = make_int_key(INT_KEY_COUNT - 1);
|
||||
assert!(is_int_key(key));
|
||||
assert!(key < PRIVATE_NAME_BASE);
|
||||
assert_eq!(int_key_value(key), INT_KEY_COUNT - 1);
|
||||
}
|
||||
@@ -185,87 +185,4 @@ pub fn shape_prop_count(shape_id: ShapeId) -> u32 {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_shape_exists() {
|
||||
let s = get_shape(EMPTY_SHAPE_ID);
|
||||
assert!(s.is_some());
|
||||
let s = s.unwrap();
|
||||
assert_eq!(s.id, EMPTY_SHAPE_ID);
|
||||
assert!(s.parent.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_shape_creates_new_id() {
|
||||
let key: StringIndex = 1_000_000;
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, key);
|
||||
assert!(s1 > EMPTY_SHAPE_ID);
|
||||
let shape = get_shape(s1).unwrap();
|
||||
assert_eq!(shape.property_name, key);
|
||||
assert_eq!(shape.parent, Some(EMPTY_SHAPE_ID));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_cons_returns_same_id() {
|
||||
let key: StringIndex = 1_000_001;
|
||||
let a = make_shape(EMPTY_SHAPE_ID, key);
|
||||
let b = make_shape(EMPTY_SHAPE_ID, key);
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_props_different_ids() {
|
||||
let a = make_shape(EMPTY_SHAPE_ID, 1_000_002);
|
||||
let b = make_shape(EMPTY_SHAPE_ID, 1_000_003);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_of_three() {
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, 1_000_010);
|
||||
let s2 = make_shape(s1, 1_000_020);
|
||||
let s3 = make_shape(s2, 1_000_030);
|
||||
|
||||
assert_eq!(shape_prop_count(s3), 3);
|
||||
|
||||
assert_eq!(lookup_position(s3, 1_000_030), Some(2));
|
||||
assert_eq!(lookup_position(s3, 1_000_020), Some(1));
|
||||
assert_eq!(lookup_position(s3, 1_000_010), Some(0));
|
||||
assert_eq!(lookup_position(s3, 99), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_branches_share_ancestor() {
|
||||
let base = make_shape(EMPTY_SHAPE_ID, 1_000_040);
|
||||
let branch_a = make_shape(base, 1_000_050);
|
||||
let branch_b = make_shape(base, 1_000_060);
|
||||
assert_ne!(branch_a, branch_b);
|
||||
let a_shape = get_shape(branch_a).unwrap();
|
||||
let b_shape = get_shape(branch_b).unwrap();
|
||||
assert_eq!(a_shape.parent, Some(base));
|
||||
assert_eq!(b_shape.parent, Some(base));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_same_structure_different_names() {
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, 1_000_070);
|
||||
let s2 = make_shape(s1, 1_000_080);
|
||||
let s3 = make_shape(EMPTY_SHAPE_ID, 1_000_090);
|
||||
let s4 = make_shape(s3, 1_000_100);
|
||||
assert_ne!(s1, s3);
|
||||
assert_ne!(s2, s4);
|
||||
assert_eq!(lookup_position(s2, 1_000_070), Some(0));
|
||||
assert_eq!(lookup_position(s4, 1_000_090), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_property_works() {
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, 1_000_110);
|
||||
let s2 = make_shape(s1, 1_000_120);
|
||||
assert!(has_property(s2, 1_000_110));
|
||||
assert!(has_property(s2, 1_000_120));
|
||||
assert!(!has_property(s2, 99));
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_shape_exists() {
|
||||
let s = get_shape(EMPTY_SHAPE_ID);
|
||||
assert!(s.is_some());
|
||||
let s = s.unwrap();
|
||||
assert_eq!(s.id, EMPTY_SHAPE_ID);
|
||||
assert!(s.parent.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_shape_creates_new_id() {
|
||||
let key: StringIndex = 1_000_000;
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, key);
|
||||
assert!(s1 > EMPTY_SHAPE_ID);
|
||||
let shape = get_shape(s1).unwrap();
|
||||
assert_eq!(shape.property_name, key);
|
||||
assert_eq!(shape.parent, Some(EMPTY_SHAPE_ID));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_cons_returns_same_id() {
|
||||
let key: StringIndex = 1_000_001;
|
||||
let a = make_shape(EMPTY_SHAPE_ID, key);
|
||||
let b = make_shape(EMPTY_SHAPE_ID, key);
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_props_different_ids() {
|
||||
let a = make_shape(EMPTY_SHAPE_ID, 1_000_002);
|
||||
let b = make_shape(EMPTY_SHAPE_ID, 1_000_003);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_of_three() {
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, 1_000_010);
|
||||
let s2 = make_shape(s1, 1_000_020);
|
||||
let s3 = make_shape(s2, 1_000_030);
|
||||
|
||||
assert_eq!(shape_prop_count(s3), 3);
|
||||
|
||||
assert_eq!(lookup_position(s3, 1_000_030), Some(2));
|
||||
assert_eq!(lookup_position(s3, 1_000_020), Some(1));
|
||||
assert_eq!(lookup_position(s3, 1_000_010), Some(0));
|
||||
assert_eq!(lookup_position(s3, 99), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_branches_share_ancestor() {
|
||||
let base = make_shape(EMPTY_SHAPE_ID, 1_000_040);
|
||||
let branch_a = make_shape(base, 1_000_050);
|
||||
let branch_b = make_shape(base, 1_000_060);
|
||||
assert_ne!(branch_a, branch_b);
|
||||
let a_shape = get_shape(branch_a).unwrap();
|
||||
let b_shape = get_shape(branch_b).unwrap();
|
||||
assert_eq!(a_shape.parent, Some(base));
|
||||
assert_eq!(b_shape.parent, Some(base));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_same_structure_different_names() {
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, 1_000_070);
|
||||
let s2 = make_shape(s1, 1_000_080);
|
||||
let s3 = make_shape(EMPTY_SHAPE_ID, 1_000_090);
|
||||
let s4 = make_shape(s3, 1_000_100);
|
||||
assert_ne!(s1, s3);
|
||||
assert_ne!(s2, s4);
|
||||
assert_eq!(lookup_position(s2, 1_000_070), Some(0));
|
||||
assert_eq!(lookup_position(s4, 1_000_090), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_property_works() {
|
||||
let s1 = make_shape(EMPTY_SHAPE_ID, 1_000_110);
|
||||
let s2 = make_shape(s1, 1_000_120);
|
||||
assert!(has_property(s2, 1_000_110));
|
||||
assert!(has_property(s2, 1_000_120));
|
||||
assert!(!has_property(s2, 99));
|
||||
}
|
||||
@@ -476,191 +476,4 @@ impl fmt::Debug for JsValue {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{JsType, JsValue};
|
||||
use num_bigint::BigInt;
|
||||
use proptest::prelude::*;
|
||||
use proptest::test_runner::TestRunner;
|
||||
|
||||
#[test]
|
||||
fn int_roundtrip_prop() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&(i32::MIN..=i32::MAX), |v| {
|
||||
let val = JsValue::int(v);
|
||||
assert!(val.is_int());
|
||||
assert_eq!(val.as_int(), v);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_roundtrip_prop() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<f64>(), |v| {
|
||||
let val = JsValue::float(v);
|
||||
assert!(val.is_double());
|
||||
if !v.is_nan() {
|
||||
assert_eq!(val.as_double(), v);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_roundtrip_prop() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<bool>(), |v| {
|
||||
let val = JsValue::bool(v);
|
||||
assert!(val.is_bool());
|
||||
assert_eq!(val.as_bool(), v);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_u64_type_safety() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<u64>(), |bits| {
|
||||
let val = JsValue::from_bits(bits);
|
||||
let matched = [
|
||||
val.is_double(),
|
||||
val.is_int(),
|
||||
val.is_bool(),
|
||||
val.is_null(),
|
||||
val.is_undefined(),
|
||||
val.is_object(),
|
||||
val.is_string(),
|
||||
val.is_symbol(),
|
||||
val.is_bigint(),
|
||||
];
|
||||
let count = matched.iter().filter(|&&x| x).count();
|
||||
assert_eq!(count, 1, "bits={bits:#018x} matched {count} types");
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_type_matches_tag_checks() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<u64>(), |bits| {
|
||||
let val = JsValue::from_bits(bits);
|
||||
let t = val.js_type();
|
||||
// 随机位模式上 js_type 与 9 个 is_* 检查 1:1 对拍。
|
||||
let expected = if val.is_double() {
|
||||
JsType::Double
|
||||
} else if val.is_int() {
|
||||
JsType::Int
|
||||
} else if val.is_bool() {
|
||||
JsType::Bool
|
||||
} else if val.is_null() {
|
||||
JsType::Null
|
||||
} else if val.is_undefined() {
|
||||
JsType::Undefined
|
||||
} else if val.is_object() {
|
||||
JsType::Object
|
||||
} else if val.is_string() {
|
||||
JsType::String
|
||||
} else if val.is_symbol() {
|
||||
JsType::Symbol
|
||||
} else {
|
||||
JsType::BigInt
|
||||
};
|
||||
assert_eq!(t, expected, "bits={bits:#018x} js_type={t:?}");
|
||||
assert_eq!(t.is_number(), val.is_int() || val.is_double(), "bits={bits:#018x}");
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_identity() {
|
||||
assert_eq!(JsValue::null(), JsValue::null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_identity() {
|
||||
assert_eq!(JsValue::undefined(), JsValue::undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nullish_matches_only_null_and_undefined() {
|
||||
assert!(JsValue::null().is_nullish());
|
||||
assert!(JsValue::undefined().is_nullish());
|
||||
assert!(!JsValue::int(0).is_nullish());
|
||||
assert!(!JsValue::bool(false).is_nullish());
|
||||
assert!(!JsValue::float(f64::NAN).is_nullish());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalization_idempotent() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<f64>(), |v| {
|
||||
let a = JsValue::float(v);
|
||||
let b = JsValue::float(a.as_double());
|
||||
assert_eq!(a, b);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_ptr_roundtrip() {
|
||||
use crate::object::JsString;
|
||||
let s = Box::new(JsString::new("test".to_string()));
|
||||
let ptr: *const JsString = &*s;
|
||||
let val = JsValue::string(ptr);
|
||||
assert!(val.is_string());
|
||||
assert_eq!(val.as_string_ptr(), ptr);
|
||||
assert_eq!(unsafe { (*val.as_string_ptr()).as_str() }, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_pointer_equality() {
|
||||
use crate::object::JsString;
|
||||
let a = Box::new(JsString::new("x".to_string()));
|
||||
let b = Box::new(JsString::new("x".to_string()));
|
||||
let va = JsValue::string(&*a);
|
||||
let vb = JsValue::string(&*b);
|
||||
// 内容相同但分配不同 → NOT ==(指针同一性)。
|
||||
// 语义内容相等在 coercion 层处理,不在 PartialEq。
|
||||
assert_ne!(va, vb);
|
||||
assert_eq!(va, JsValue::string(&*a));
|
||||
assert_eq!(unsafe { (*va.as_string_ptr()).as_str() }, unsafe { (*vb.as_string_ptr()).as_str() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_pointer_roundtrip() {
|
||||
let boxed = Box::new(BigInt::from(123));
|
||||
let ptr: *const BigInt = &*boxed;
|
||||
let val = JsValue::bigint(ptr);
|
||||
assert!(val.is_bigint());
|
||||
assert_eq!(val.as_bigint_ptr(), ptr);
|
||||
assert_eq!(unsafe { &*val.as_bigint_ptr() }, &BigInt::from(123));
|
||||
assert!(!val.is_double());
|
||||
assert!(!val.is_int());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_value_equality_by_value() {
|
||||
let a = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7))));
|
||||
let b = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7))));
|
||||
let c = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(8))));
|
||||
assert_eq!(a, b);
|
||||
assert_ne!(a, c);
|
||||
unsafe {
|
||||
drop(Box::from_raw(a.as_bigint_ptr() as *mut BigInt));
|
||||
drop(Box::from_raw(b.as_bigint_ptr() as *mut BigInt));
|
||||
drop(Box::from_raw(c.as_bigint_ptr() as *mut BigInt));
|
||||
}
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
use super::{JsType, JsValue};
|
||||
use num_bigint::BigInt;
|
||||
use proptest::prelude::*;
|
||||
use proptest::test_runner::TestRunner;
|
||||
|
||||
#[test]
|
||||
fn int_roundtrip_prop() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&(i32::MIN..=i32::MAX), |v| {
|
||||
let val = JsValue::int(v);
|
||||
assert!(val.is_int());
|
||||
assert_eq!(val.as_int(), v);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_roundtrip_prop() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<f64>(), |v| {
|
||||
let val = JsValue::float(v);
|
||||
assert!(val.is_double());
|
||||
if !v.is_nan() {
|
||||
assert_eq!(val.as_double(), v);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_roundtrip_prop() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<bool>(), |v| {
|
||||
let val = JsValue::bool(v);
|
||||
assert!(val.is_bool());
|
||||
assert_eq!(val.as_bool(), v);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_u64_type_safety() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<u64>(), |bits| {
|
||||
let val = JsValue::from_bits(bits);
|
||||
let matched = [
|
||||
val.is_double(),
|
||||
val.is_int(),
|
||||
val.is_bool(),
|
||||
val.is_null(),
|
||||
val.is_undefined(),
|
||||
val.is_object(),
|
||||
val.is_string(),
|
||||
val.is_symbol(),
|
||||
val.is_bigint(),
|
||||
];
|
||||
let count = matched.iter().filter(|&&x| x).count();
|
||||
assert_eq!(count, 1, "bits={bits:#018x} matched {count} types");
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_type_matches_tag_checks() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<u64>(), |bits| {
|
||||
let val = JsValue::from_bits(bits);
|
||||
let t = val.js_type();
|
||||
// 随机位模式上 js_type 与 9 个 is_* 检查 1:1 对拍。
|
||||
let expected = if val.is_double() {
|
||||
JsType::Double
|
||||
} else if val.is_int() {
|
||||
JsType::Int
|
||||
} else if val.is_bool() {
|
||||
JsType::Bool
|
||||
} else if val.is_null() {
|
||||
JsType::Null
|
||||
} else if val.is_undefined() {
|
||||
JsType::Undefined
|
||||
} else if val.is_object() {
|
||||
JsType::Object
|
||||
} else if val.is_string() {
|
||||
JsType::String
|
||||
} else if val.is_symbol() {
|
||||
JsType::Symbol
|
||||
} else {
|
||||
JsType::BigInt
|
||||
};
|
||||
assert_eq!(t, expected, "bits={bits:#018x} js_type={t:?}");
|
||||
assert_eq!(t.is_number(), val.is_int() || val.is_double(), "bits={bits:#018x}");
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_identity() {
|
||||
assert_eq!(JsValue::null(), JsValue::null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_identity() {
|
||||
assert_eq!(JsValue::undefined(), JsValue::undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nullish_matches_only_null_and_undefined() {
|
||||
assert!(JsValue::null().is_nullish());
|
||||
assert!(JsValue::undefined().is_nullish());
|
||||
assert!(!JsValue::int(0).is_nullish());
|
||||
assert!(!JsValue::bool(false).is_nullish());
|
||||
assert!(!JsValue::float(f64::NAN).is_nullish());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalization_idempotent() {
|
||||
let mut runner = TestRunner::default();
|
||||
runner
|
||||
.run(&any::<f64>(), |v| {
|
||||
let a = JsValue::float(v);
|
||||
let b = JsValue::float(a.as_double());
|
||||
assert_eq!(a, b);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_ptr_roundtrip() {
|
||||
use crate::object::JsString;
|
||||
let s = Box::new(JsString::new("test".to_string()));
|
||||
let ptr: *const JsString = &*s;
|
||||
let val = JsValue::string(ptr);
|
||||
assert!(val.is_string());
|
||||
assert_eq!(val.as_string_ptr(), ptr);
|
||||
assert_eq!(unsafe { (*val.as_string_ptr()).as_str() }, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_pointer_equality() {
|
||||
use crate::object::JsString;
|
||||
let a = Box::new(JsString::new("x".to_string()));
|
||||
let b = Box::new(JsString::new("x".to_string()));
|
||||
let va = JsValue::string(&*a);
|
||||
let vb = JsValue::string(&*b);
|
||||
// 内容相同但分配不同 → NOT ==(指针同一性)。
|
||||
// 语义内容相等在 coercion 层处理,不在 PartialEq。
|
||||
assert_ne!(va, vb);
|
||||
assert_eq!(va, JsValue::string(&*a));
|
||||
assert_eq!(unsafe { (*va.as_string_ptr()).as_str() }, unsafe { (*vb.as_string_ptr()).as_str() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_pointer_roundtrip() {
|
||||
let boxed = Box::new(BigInt::from(123));
|
||||
let ptr: *const BigInt = &*boxed;
|
||||
let val = JsValue::bigint(ptr);
|
||||
assert!(val.is_bigint());
|
||||
assert_eq!(val.as_bigint_ptr(), ptr);
|
||||
assert_eq!(unsafe { &*val.as_bigint_ptr() }, &BigInt::from(123));
|
||||
assert!(!val.is_double());
|
||||
assert!(!val.is_int());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_value_equality_by_value() {
|
||||
let a = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7))));
|
||||
let b = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(7))));
|
||||
let c = JsValue::bigint(Box::into_raw(Box::new(BigInt::from(8))));
|
||||
assert_eq!(a, b);
|
||||
assert_ne!(a, c);
|
||||
unsafe {
|
||||
drop(Box::from_raw(a.as_bigint_ptr() as *mut BigInt));
|
||||
drop(Box::from_raw(b.as_bigint_ptr() as *mut BigInt));
|
||||
drop(Box::from_raw(c.as_bigint_ptr() as *mut BigInt));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user