From 66ad14159d51bbe6d66f3618f92a347361824ac1 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 15 Sep 2026 18:04:15 +0000 Subject: [PATCH] =?UTF-8?q?test:=20=E5=80=BC/=E5=BD=A2=E7=8A=B6/=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E9=94=AE/=E5=86=85=E5=AD=98=E5=86=85=E8=81=94?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=BF=81=E5=85=A5=E5=85=84=E5=BC=9F=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_types/src/mem.rs | 27 +-- crates/oxide_types/src/mem/tests.rs | 24 +++ crates/oxide_types/src/private_key.rs | 79 +------- crates/oxide_types/src/private_key/tests.rs | 76 ++++++++ crates/oxide_types/src/shape.rs | 85 +-------- crates/oxide_types/src/shape/tests.rs | 82 +++++++++ crates/oxide_types/src/value.rs | 189 +------------------- crates/oxide_types/src/value/tests.rs | 186 +++++++++++++++++++ 8 files changed, 372 insertions(+), 376 deletions(-) create mode 100644 crates/oxide_types/src/mem/tests.rs create mode 100644 crates/oxide_types/src/private_key/tests.rs create mode 100644 crates/oxide_types/src/shape/tests.rs create mode 100644 crates/oxide_types/src/value/tests.rs diff --git a/crates/oxide_types/src/mem.rs b/crates/oxide_types/src/mem.rs index a6c22a46..86e05cb9 100644 --- a/crates/oxide_types/src/mem.rs +++ b/crates/oxide_types/src/mem.rs @@ -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::())); - } - - #[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::())); - assert!(!epoch.is_epoch_ptr((&stack_value as *const i32).cast::())); - assert!(!epoch.is_epoch_ptr(std::ptr::null())); - } -} +mod tests; diff --git a/crates/oxide_types/src/mem/tests.rs b/crates/oxide_types/src/mem/tests.rs new file mode 100644 index 00000000..fc1a9a7c --- /dev/null +++ b/crates/oxide_types/src/mem/tests.rs @@ -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::())); +} + +#[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::())); + assert!(!epoch.is_epoch_ptr((&stack_value as *const i32).cast::())); + assert!(!epoch.is_epoch_ptr(std::ptr::null())); +} diff --git a/crates/oxide_types/src/private_key.rs b/crates/oxide_types/src/private_key.rs index 648e86f7..118ffeff 100644 --- a/crates/oxide_types/src/private_key.rs +++ b/crates/oxide_types/src/private_key.rs @@ -115,81 +115,4 @@ pub const fn well_known_symbol_id_from_key(key: u32) -> Option { } #[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; diff --git a/crates/oxide_types/src/private_key/tests.rs b/crates/oxide_types/src/private_key/tests.rs new file mode 100644 index 00000000..5b04a384 --- /dev/null +++ b/crates/oxide_types/src/private_key/tests.rs @@ -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); +} diff --git a/crates/oxide_types/src/shape.rs b/crates/oxide_types/src/shape.rs index bbb41e68..7b5d6739 100644 --- a/crates/oxide_types/src/shape.rs +++ b/crates/oxide_types/src/shape.rs @@ -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; diff --git a/crates/oxide_types/src/shape/tests.rs b/crates/oxide_types/src/shape/tests.rs new file mode 100644 index 00000000..82a329bb --- /dev/null +++ b/crates/oxide_types/src/shape/tests.rs @@ -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)); +} diff --git a/crates/oxide_types/src/value.rs b/crates/oxide_types/src/value.rs index f856afdd..3dd14cd6 100644 --- a/crates/oxide_types/src/value.rs +++ b/crates/oxide_types/src/value.rs @@ -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::(), |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::(), |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::(), |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::(), |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::(), |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; diff --git a/crates/oxide_types/src/value/tests.rs b/crates/oxide_types/src/value/tests.rs new file mode 100644 index 00000000..15e864b6 --- /dev/null +++ b/crates/oxide_types/src/value/tests.rs @@ -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::(), |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::(), |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::(), |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::(), |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::(), |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)); + } +}