test: 添加 JsValue 测试验证并修复类型冲突问题
This commit is contained in:
@@ -37,6 +37,11 @@ fn get_tag(bits: u64) -> u64 {
|
||||
pub struct JsValue(u64);
|
||||
|
||||
impl JsValue {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn from_bits(bits: u64) -> Self {
|
||||
Self(bits)
|
||||
}
|
||||
|
||||
pub fn int(v: i32) -> Self {
|
||||
Self(make_tag(TAG_INT) | (v as u32 as u64))
|
||||
}
|
||||
@@ -44,7 +49,7 @@ impl JsValue {
|
||||
pub fn float(v: f64) -> Self {
|
||||
let bits = v.to_bits();
|
||||
if is_nan_bits(bits) {
|
||||
Self(QNAN_PREFIX | 1)
|
||||
Self(QNAN_PREFIX | (7u64 << TAG_SHIFT) | 1)
|
||||
} else {
|
||||
Self(bits)
|
||||
}
|
||||
@@ -69,15 +74,15 @@ impl JsValue {
|
||||
}
|
||||
|
||||
pub fn is_double(&self) -> bool {
|
||||
!is_nan_bits(self.0)
|
||||
!is_nan_boxed(self.0)
|
||||
}
|
||||
|
||||
pub fn is_int(&self) -> bool {
|
||||
get_tag(self.0) == TAG_INT
|
||||
is_nan_boxed(self.0) && get_tag(self.0) == TAG_INT
|
||||
}
|
||||
|
||||
pub fn is_bool(&self) -> bool {
|
||||
get_tag(self.0) == TAG_BOOL
|
||||
is_nan_boxed(self.0) && get_tag(self.0) == TAG_BOOL
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
@@ -89,7 +94,7 @@ impl JsValue {
|
||||
}
|
||||
|
||||
pub fn is_object(&self) -> bool {
|
||||
get_tag(self.0) == TAG_OBJECT
|
||||
is_nan_boxed(self.0) && get_tag(self.0) == TAG_OBJECT
|
||||
}
|
||||
|
||||
pub fn as_double(&self) -> f64 {
|
||||
@@ -117,6 +122,10 @@ fn is_nan_bits(bits: u64) -> bool {
|
||||
(bits & EXP_MASK) == EXP_MASK && (bits & MANTISSA_MASK) != 0
|
||||
}
|
||||
|
||||
fn is_nan_boxed(bits: u64) -> bool {
|
||||
(0xFFF8..=0xFFFC).contains(&((bits >> 48) as u16))
|
||||
}
|
||||
|
||||
impl fmt::Display for JsValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_int() {
|
||||
@@ -172,3 +181,95 @@ impl fmt::Debug for JsValue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::JsValue;
|
||||
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(),
|
||||
];
|
||||
let count = matched.iter().filter(|&&x| x).count();
|
||||
assert_eq!(count, 1, "bits={bits:#018x} matched {count} types");
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_identity() {
|
||||
assert_eq!(JsValue::null(), JsValue::null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_identity() {
|
||||
assert_eq!(JsValue::undefined(), JsValue::undefined());
|
||||
}
|
||||
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
use oxide_vm::JsValue;
|
||||
|
||||
#[test]
|
||||
fn int_roundtrip() {
|
||||
for v in [0, 1, -1, 42, i32::MIN, i32::MAX] {
|
||||
let val = JsValue::int(v);
|
||||
assert!(val.is_int(), "int({v}) should be int");
|
||||
assert_eq!(val.as_int(), v, "int({v}) roundtrip failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_roundtrip() {
|
||||
for v in [0.0, -0.0, 1.0, -1.0, 42.5, f64::MIN, f64::MAX] {
|
||||
let val = JsValue::float(v);
|
||||
assert!(val.is_double(), "float({v}) should be double");
|
||||
assert_eq!(val.as_double(), v, "float({v}) roundtrip failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_infinity() {
|
||||
let val = JsValue::float(f64::INFINITY);
|
||||
assert!(val.is_double());
|
||||
assert!(val.as_double().is_infinite());
|
||||
assert!(val.as_double().is_sign_positive());
|
||||
|
||||
let val = JsValue::float(f64::NEG_INFINITY);
|
||||
assert!(val.is_double());
|
||||
assert!(val.as_double().is_infinite());
|
||||
assert!(val.as_double().is_sign_negative());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_nan_canonicalization() {
|
||||
let val = JsValue::float(f64::NAN);
|
||||
assert!(val.is_double(), "NaN should be classified as double");
|
||||
let d = val.as_double();
|
||||
assert!(d.is_nan(), "NaN value should still be NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_roundtrip() {
|
||||
let t = JsValue::bool(true);
|
||||
assert!(t.is_bool());
|
||||
assert!(t.as_bool());
|
||||
|
||||
let f = JsValue::bool(false);
|
||||
assert!(f.is_bool());
|
||||
assert!(!f.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_type_check() {
|
||||
let val = JsValue::null();
|
||||
assert!(val.is_null());
|
||||
assert!(!val.is_int());
|
||||
assert!(!val.is_double());
|
||||
assert!(!val.is_bool());
|
||||
assert!(!val.is_undefined());
|
||||
assert!(!val.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_type_check() {
|
||||
let val = JsValue::undefined();
|
||||
assert!(val.is_undefined());
|
||||
assert!(!val.is_int());
|
||||
assert!(!val.is_double());
|
||||
assert!(!val.is_bool());
|
||||
assert!(!val.is_null());
|
||||
assert!(!val.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_equals_null() {
|
||||
assert_eq!(JsValue::null(), JsValue::null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_not_equals_undefined() {
|
||||
assert_ne!(JsValue::null(), JsValue::undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_pointer_roundtrip() {
|
||||
let x = 42u8;
|
||||
let val = JsValue::object(&x as *const u8);
|
||||
assert!(val.is_object());
|
||||
let ptr = val.as_ptr();
|
||||
assert_eq!(ptr, &x as *const u8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_int() {
|
||||
assert_eq!(format!("{}", JsValue::int(42)), "42");
|
||||
assert_eq!(format!("{}", JsValue::int(-1)), "-1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_double() {
|
||||
let val = JsValue::float(42.5);
|
||||
assert!(format!("{}", val).contains("42.5"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_bool() {
|
||||
assert_eq!(format!("{}", JsValue::bool(true)), "true");
|
||||
assert_eq!(format!("{}", JsValue::bool(false)), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_null() {
|
||||
assert_eq!(format!("{}", JsValue::null()), "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_undefined() {
|
||||
assert_eq!(format!("{}", JsValue::undefined()), "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_nan() {
|
||||
assert_eq!(format!("{}", JsValue::float(f64::NAN)), "NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_infinity() {
|
||||
assert_eq!(format!("{}", JsValue::float(f64::INFINITY)), "Infinity");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_int() {
|
||||
assert_eq!(format!("{:?}", JsValue::int(42)), "JsValue(Int(42))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_double() {
|
||||
let val = JsValue::float(42.5);
|
||||
assert!(format!("{:?}", val).starts_with("JsValue(Double("));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_bool() {
|
||||
assert_eq!(format!("{:?}", JsValue::bool(true)), "JsValue(Bool(true))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_null() {
|
||||
assert_eq!(format!("{:?}", JsValue::null()), "JsValue(Null)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_undefined() {
|
||||
assert_eq!(format!("{:?}", JsValue::undefined()), "JsValue(Undefined)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_object() {
|
||||
let x = 0u8;
|
||||
let val = JsValue::object(&x as *const u8);
|
||||
assert!(format!("{:?}", val).starts_with("JsValue(Object("));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_semantics() {
|
||||
let a = JsValue::int(42);
|
||||
let b = a;
|
||||
assert_eq!(a, b);
|
||||
assert_eq!(a.as_int(), 42);
|
||||
assert_eq!(b.as_int(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_exclusivity_int() {
|
||||
let val = JsValue::int(1);
|
||||
assert!(val.is_int());
|
||||
assert!(!val.is_double());
|
||||
assert!(!val.is_bool());
|
||||
assert!(!val.is_null());
|
||||
assert!(!val.is_undefined());
|
||||
assert!(!val.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_exclusivity_double() {
|
||||
let val = JsValue::float(0.0);
|
||||
assert!(val.is_double());
|
||||
assert!(!val.is_int());
|
||||
assert!(!val.is_bool());
|
||||
assert!(!val.is_null());
|
||||
assert!(!val.is_undefined());
|
||||
assert!(!val.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_exclusivity_object() {
|
||||
let x = 0u8;
|
||||
let val = JsValue::object(&x as *const u8);
|
||||
assert!(val.is_object());
|
||||
assert!(!val.is_int());
|
||||
assert!(!val.is_double());
|
||||
assert!(!val.is_bool());
|
||||
assert!(!val.is_null());
|
||||
assert!(!val.is_undefined());
|
||||
}
|
||||
Reference in New Issue
Block a user