dioxus/packages/native-core/tests/fuzzing.rs
2023-03-14 16:35:11 -05:00

385 lines
12 KiB
Rust

use dioxus::prelude::Props;
use dioxus_core::*;
use dioxus_native_core::prelude::*;
use dioxus_native_core_macro::partial_derive_state;
use shipyard::Component;
use std::cell::Cell;
fn random_ns() -> Option<&'static str> {
let namespace = rand::random::<u8>() % 2;
match namespace {
0 => None,
1 => Some(Box::leak(
format!("ns{}", rand::random::<usize>()).into_boxed_str(),
)),
_ => unreachable!(),
}
}
fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> {
match rand::random::<u8>() % 2 {
0 => TemplateAttribute::Static {
name: Box::leak(format!("attr{}", rand::random::<usize>()).into_boxed_str()),
value: Box::leak(format!("value{}", rand::random::<usize>()).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(
dynamic_node_types: &mut Vec<DynamicNodeType>,
template_idx: &mut usize,
attr_idx: &mut usize,
depth: usize,
) -> TemplateNode<'static> {
match rand::random::<u8>() % 4 {
0 => {
let attrs = {
let attrs: Vec<_> = (0..(rand::random::<usize>() % 10))
.map(|_| create_random_attribute(attr_idx))
.collect();
Box::leak(attrs.into_boxed_slice())
};
TemplateNode::Element {
tag: Box::leak(format!("tag{}", rand::random::<usize>()).into_boxed_str()),
namespace: random_ns(),
attrs,
children: {
if depth > 4 {
&[]
} else {
let children: Vec<_> = (0..(rand::random::<usize>() % 3))
.map(|_| {
create_random_template_node(
dynamic_node_types,
template_idx,
attr_idx,
depth + 1,
)
})
.collect();
Box::leak(children.into_boxed_slice())
}
},
}
}
1 => TemplateNode::Text {
text: Box::leak(format!("{}", rand::random::<usize>()).into_boxed_str()),
},
2 => TemplateNode::DynamicText {
id: {
let old_idx = *template_idx;
*template_idx += 1;
dynamic_node_types.push(DynamicNodeType::Text);
old_idx
},
},
3 => TemplateNode::Dynamic {
id: {
let old_idx = *template_idx;
*template_idx += 1;
dynamic_node_types.push(DynamicNodeType::Other);
old_idx
},
},
_ => unreachable!(),
}
}
fn generate_paths(
node: &TemplateNode<'static>,
current_path: &[u8],
node_paths: &mut Vec<Vec<u8>>,
attr_paths: &mut Vec<Vec<u8>>,
) {
match node {
TemplateNode::Element {
children, attrs, ..
} => {
for attr in *attrs {
match attr {
TemplateAttribute::Static { .. } => {}
TemplateAttribute::Dynamic { .. } => {
attr_paths.push(current_path.to_vec());
}
}
}
for (i, child) in children.iter().enumerate() {
let mut current_path = current_path.to_vec();
current_path.push(i as u8);
generate_paths(child, &current_path, node_paths, attr_paths);
}
}
TemplateNode::Text { .. } => {}
TemplateNode::DynamicText { .. } => {
node_paths.push(current_path.to_vec());
}
TemplateNode::Dynamic { .. } => {
node_paths.push(current_path.to_vec());
}
}
}
enum DynamicNodeType {
Text,
Other,
}
fn create_random_template(name: &'static str) -> (Template<'static>, Vec<DynamicNodeType>) {
let mut dynamic_node_type = Vec::new();
let mut template_idx = 0;
let mut attr_idx = 0;
let roots = (0..(1 + rand::random::<usize>() % 5))
.map(|_| {
create_random_template_node(&mut dynamic_node_type, &mut template_idx, &mut attr_idx, 0)
})
.collect::<Vec<_>>();
assert!(!roots.is_empty());
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, &[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::<Vec<_>>()
.into_boxed_slice(),
);
let attr_paths = Box::leak(
attr_paths
.into_iter()
.map(|v| &*Box::leak(v.into_boxed_slice()))
.collect::<Vec<_>>()
.into_boxed_slice(),
);
(
Template {
name,
roots,
node_paths,
attr_paths,
},
dynamic_node_type,
)
}
fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
let range = if depth > 3 { 1 } else { 3 };
match rand::random::<u8>() % range {
0 => DynamicNode::Placeholder(Default::default()),
1 => cx.make_node((0..(rand::random::<u8>() % 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, root: false },
"create_random_element",
)]),
dynamic_attrs: &[],
})),
2 => cx.component(
create_random_element,
DepthProps { depth, root: false },
"create_random_element",
),
_ => unreachable!(),
}
}
fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
let value = match rand::random::<u8>() % 6 {
0 => AttributeValue::Text(Box::leak(
format!("{}", rand::random::<usize>()).into_boxed_str(),
)),
1 => AttributeValue::Float(rand::random()),
2 => AttributeValue::Int(rand::random()),
3 => AttributeValue::Bool(rand::random()),
4 => cx.any_value(rand::random::<usize>()),
5 => AttributeValue::None,
// Listener(RefCell<Option<ListenerCb<'a>>>),
_ => unreachable!(),
};
Attribute {
name: Box::leak(format!("attr{}", rand::random::<usize>()).into_boxed_str()),
value,
namespace: random_ns(),
mounted_element: Default::default(),
volatile: rand::random(),
}
}
static mut TEMPLATE_COUNT: usize = 0;
#[derive(PartialEq, Props, Component)]
struct DepthProps {
depth: usize,
root: bool,
}
fn create_random_element(cx: Scope<DepthProps>) -> Element {
cx.needs_update();
let range = if cx.props.root { 2 } else { 3 };
let node = match rand::random::<usize>() % range {
0 | 1 => {
let (template, dynamic_node_types) = 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<_> = dynamic_node_types
.iter()
.map(|ty| match ty {
DynamicNodeType::Text => DynamicNode::Text(VText {
value: Box::leak(
format!("{}", rand::random::<usize>()).into_boxed_str(),
),
id: Default::default(),
}),
DynamicNodeType::Other => {
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::<Vec<_>>(),
),
};
Some(node)
}
_ => None,
};
println!("{node:#?}");
node
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Component)]
pub struct BlablaState {
count: usize,
}
#[partial_derive_state]
impl State for BlablaState {
type ParentDependencies = (Self,);
type ChildDependencies = ();
type NodeDependencies = ();
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
.with_attrs(AttributeMaskBuilder::Some(&["blabla"]))
.with_element();
fn update<'a>(
&mut self,
_: NodeView,
_: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
_: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
_: &SendAnyMap,
) -> bool {
if let Some((parent,)) = parent {
if parent.count != 0 {
self.count += 1;
}
}
true
}
fn create<'a>(
node_view: NodeView<()>,
node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
context: &SendAnyMap,
) -> Self {
let mut myself = Self::default();
myself.update(node_view, node, parent, children, context);
myself
}
}
// test for panics when creating random nodes and templates
#[test]
fn create() {
for _ in 0..100 {
let mut vdom = VirtualDom::new_with_props(
create_random_element,
DepthProps {
depth: 0,
root: true,
},
);
let mutations = vdom.rebuild();
let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]);
let mut dioxus_state = DioxusState::create(&mut rdom);
dioxus_state.apply_mutations(&mut rdom, mutations);
let ctx = SendAnyMap::new();
rdom.update_state(ctx);
}
}
// 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..10 {
let mut vdom = VirtualDom::new_with_props(
create_random_element,
DepthProps {
depth: 0,
root: true,
},
);
let mutations = vdom.rebuild();
let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]);
let mut dioxus_state = DioxusState::create(&mut rdom);
dioxus_state.apply_mutations(&mut rdom, mutations);
let ctx = SendAnyMap::new();
rdom.update_state(ctx);
for _ in 0..10 {
let mutations = vdom.render_immediate();
dioxus_state.apply_mutations(&mut rdom, mutations);
let ctx = SendAnyMap::new();
rdom.update_state(ctx);
}
}
}