diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 8f9b71035..270e957d1 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -41,6 +41,7 @@ serde = { version = "1", features = ["derive"], optional = true } tokio = { version = "1", features = ["full"] } dioxus = { path = "../dioxus" } pretty_assertions = "1.3.0" +rand = "0.8.5" [features] default = [] diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs new file mode 100644 index 000000000..aefd947ef --- /dev/null +++ b/packages/core/tests/fuzzing.rs @@ -0,0 +1,280 @@ +use dioxus::prelude::Props; +use dioxus_core::*; +use std::cell::Cell; + +fn random_ns() -> Option<&'static str> { + let namespace = rand::random::() % 2; + match namespace { + 0 => None, + 1 => Some(Box::leak( + format!("ns{}", rand::random::()).into_boxed_str(), + )), + _ => unreachable!(), + } +} + +fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> { + match rand::random::() % 2 { + 0 => TemplateAttribute::Static { + name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), + value: Box::leak(format!("value{}", rand::random::()).into_boxed_str()), + namespace: random_ns(), + }, + 1 => TemplateAttribute::Dynamic { + id: { + let old_idx = *attr_idx; + *attr_idx += 1; + old_idx + }, + }, + _ => unreachable!(), + } +} + +fn create_random_template_node( + template_idx: &mut usize, + attr_idx: &mut usize, + depth: usize, +) -> TemplateNode<'static> { + match rand::random::() % 3 { + 0 => { + let attrs = { + let attrs: Vec<_> = (0..(rand::random::() % 10)) + .map(|_| create_random_attribute(attr_idx)) + .collect(); + Box::leak(attrs.into_boxed_slice()) + }; + TemplateNode::Element { + tag: Box::leak(format!("tag{}", rand::random::()).into_boxed_str()), + namespace: random_ns(), + attrs, + children: { + if depth > 10 { + &[] + } else { + let children: Vec<_> = (0..(rand::random::() % 3)) + .map(|_| create_random_template_node(template_idx, attr_idx, depth + 1)) + .collect(); + Box::leak(children.into_boxed_slice()) + } + }, + } + } + 1 => TemplateNode::Text { + text: Box::leak(format!("{}", rand::random::()).into_boxed_str()), + }, + 2 => TemplateNode::DynamicText { + id: { + let old_idx = *template_idx; + *template_idx += 1; + old_idx + }, + }, + 2 => TemplateNode::Dynamic { + id: { + let old_idx = *template_idx; + *template_idx += 1; + old_idx + }, + }, + _ => unreachable!(), + } +} + +fn generate_paths( + node: &TemplateNode<'static>, + current_path: &Vec, + node_paths: &mut Vec>, + attr_paths: &mut Vec>, +) { + match node { + TemplateNode::Element { children, attrs, .. } => { + for attr in *attrs { + match attr { + TemplateAttribute::Static { .. } => {} + TemplateAttribute::Dynamic { .. } => { + attr_paths.push(current_path.clone()); + } + } + } + for (i, child) in children.iter().enumerate() { + let mut current_path = current_path.clone(); + current_path.push(i as u8); + generate_paths(child, ¤t_path, node_paths, attr_paths); + } + } + TemplateNode::Text { .. } => {} + TemplateNode::DynamicText { .. } => { + node_paths.push(current_path.clone()); + } + TemplateNode::Dynamic { .. } => { + node_paths.push(current_path.clone()); + } + } +} + +fn create_random_template(name: &'static str) -> Template<'static> { + let mut template_idx = 0; + let mut attr_idx = 0; + let roots = (0..(1 + rand::random::() % 5)) + .map(|_| create_random_template_node(&mut template_idx, &mut attr_idx, 0)) + .collect::>(); + assert!(roots.len() > 0); + let roots = Box::leak(roots.into_boxed_slice()); + let mut node_paths = Vec::new(); + let mut attr_paths = Vec::new(); + for (i, root) in roots.iter().enumerate() { + generate_paths(root, &vec![i as u8], &mut node_paths, &mut attr_paths); + } + let node_paths = Box::leak( + node_paths + .into_iter() + .map(|v| &*Box::leak(v.into_boxed_slice())) + .collect::>() + .into_boxed_slice(), + ); + let attr_paths = Box::leak( + attr_paths + .into_iter() + .map(|v| &*Box::leak(v.into_boxed_slice())) + .collect::>() + .into_boxed_slice(), + ); + Template { name, roots, node_paths, attr_paths } +} + +fn create_random_dynamic_node<'a>(cx: &'a ScopeState, depth: usize) -> DynamicNode<'a> { + let range = if depth > 10 { 2 } else { 4 }; + match rand::random::() % range { + 0 => DynamicNode::Text(VText { + value: Box::leak(format!("{}", rand::random::()).into_boxed_str()), + id: Default::default(), + }), + 1 => DynamicNode::Placeholder(Default::default()), + 2 => cx.make_node((0..(rand::random::() % 5)).map(|_| VNode { + key: None, + parent: Default::default(), + template: Cell::new(Template { + name: concat!(file!(), ":", line!(), ":", column!(), ":0"), + roots: &[TemplateNode::Dynamic { id: 0 }], + node_paths: &[&[0]], + attr_paths: &[], + }), + root_ids: Default::default(), + dynamic_nodes: cx.bump().alloc([cx.component( + create_random_element, + DepthProps { depth: depth }, + "create_random_element", + )]), + dynamic_attrs: &[], + })), + 3 => cx.component( + create_random_element, + DepthProps { depth: depth }, + "create_random_element", + ), + _ => unreachable!(), + } +} + +fn create_random_dynamic_attr<'a>(cx: &'a ScopeState) -> Attribute<'a> { + let value = match rand::random::() % 6 { + 0 => AttributeValue::Text(Box::leak( + format!("{}", rand::random::()).into_boxed_str(), + )), + 1 => AttributeValue::Float(rand::random()), + 2 => AttributeValue::Int(rand::random()), + 3 => AttributeValue::Bool(rand::random()), + 4 => cx.any_value(rand::random::()), + 5 => AttributeValue::None, + // Listener(RefCell>>), + _ => unreachable!(), + }; + Attribute { + name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), + value, + namespace: random_ns(), + mounted_element: Default::default(), + volatile: rand::random(), + } +} + +#[test] +fn debugging() { + let template = create_random_template("test"); + println!("{:#?}", template) +} + +static mut TEMPLATE_COUNT: usize = 0; + +#[derive(PartialEq, Props)] +struct DepthProps { + depth: usize, +} + +fn create_random_element<'a>(cx: Scope<'a, DepthProps>) -> Element<'a> { + cx.needs_update(); + match rand::random::() % 3 { + 0 | 1 => { + // This is dynamicly created every render to trigger the pointer equality check on the template path + // In the future this check should be changed to something more obvious + let template = create_random_template(Box::leak( + format!( + "{}{}", + concat!(file!(), ":", line!(), ":", column!(), ":"), + { + unsafe { + let old = TEMPLATE_COUNT; + TEMPLATE_COUNT += 1; + old + } + } + ) + .into_boxed_str(), + )); + println!("{:#?}", template); + let node = VNode { + key: None, + parent: None, + template: Cell::new(template), + root_ids: Default::default(), + dynamic_nodes: { + let dynamic_nodes: Vec<_> = (0..template.node_paths.len()) + .map(|_| create_random_dynamic_node(cx, cx.props.depth + 1)) + .collect(); + cx.bump().alloc(dynamic_nodes) + }, + dynamic_attrs: cx.bump().alloc( + (0..template.attr_paths.len()) + .map(|_| create_random_dynamic_attr(cx)) + .collect::>(), + ), + }; + println!("{:#?}", node); + Some(node) + } + _ => None, + } +} + +// test for panics when creating random nodes and templates +#[test] +fn create() { + for _ in 0..1000 { + let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0 }); + vdom.rebuild(); + } +} + +// test for panics when diffing random nodes +// This test will change the template every render which is not very realistic, but it helps stress the system +#[test] +fn diff() { + for _ in 0..100 { + let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0 }); + vdom.rebuild(); + for _ in 0..10 { + vdom.render_immediate(); + } + } +}