mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
test: 添加 JsValue 测试验证并修复类型冲突问题
This commit is contained in:
@@ -37,6 +37,11 @@ fn get_tag(bits: u64) -> u64 {
|
|||||||
pub struct JsValue(u64);
|
pub struct JsValue(u64);
|
||||||
|
|
||||||
impl JsValue {
|
impl JsValue {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn from_bits(bits: u64) -> Self {
|
||||||
|
Self(bits)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn int(v: i32) -> Self {
|
pub fn int(v: i32) -> Self {
|
||||||
Self(make_tag(TAG_INT) | (v as u32 as u64))
|
Self(make_tag(TAG_INT) | (v as u32 as u64))
|
||||||
}
|
}
|
||||||
@@ -44,7 +49,7 @@ impl JsValue {
|
|||||||
pub fn float(v: f64) -> Self {
|
pub fn float(v: f64) -> Self {
|
||||||
let bits = v.to_bits();
|
let bits = v.to_bits();
|
||||||
if is_nan_bits(bits) {
|
if is_nan_bits(bits) {
|
||||||
Self(QNAN_PREFIX | 1)
|
Self(QNAN_PREFIX | (7u64 << TAG_SHIFT) | 1)
|
||||||
} else {
|
} else {
|
||||||
Self(bits)
|
Self(bits)
|
||||||
}
|
}
|
||||||
@@ -69,15 +74,15 @@ impl JsValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_double(&self) -> bool {
|
pub fn is_double(&self) -> bool {
|
||||||
!is_nan_bits(self.0)
|
!is_nan_boxed(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_int(&self) -> bool {
|
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 {
|
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 {
|
pub fn is_null(&self) -> bool {
|
||||||
@@ -89,7 +94,7 @@ impl JsValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_object(&self) -> bool {
|
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 {
|
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
|
(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 {
|
impl fmt::Display for JsValue {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if self.is_int() {
|
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