tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 582HTML 275Markdown 8TOML 41545 Definitions

⎇ 39cd0fcbbda7cd38cc6316e7339b1f3aed29e99f scripts/gentest/src/main.rs Rust · 482 lines · 16.0 KB History Who touched what raw

Restore and fix gentest (#145) 39cd0fcbb · François ·

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use fantoccini::{Client, ClientBuilder};
6use json;
7use log::*;
8use proc_macro2::{Span, TokenStream};
9use quote::quote;
10use syn::Ident;
11
12#[tokio::main]
13async fn main() {
14    env_logger::init();
15    // this requires being run by cargo, which is iffy
16    let root_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
17    let repo_root = root_dir.parent().and_then(Path::parent).unwrap();
18
19    let fixtures_root = repo_root.join("test_fixtures");
20    let fixtures = fs::read_dir(fixtures_root).unwrap();
21
22    info!("reading test fixtures from disk");
23    let mut fixtures: Vec<_> = fixtures
24        .into_iter()
25        .filter_map(|a| a.ok())
26        .filter(|f| f.path().is_file() && f.path().extension().map(|p| p == "html").unwrap_or(false))
27        .map(|f| {
28            let fixture_path = f.path().to_path_buf();
29            let name = fixture_path.file_stem().unwrap().to_str().unwrap().to_string();
30            (name, fixture_path)
31        })
32        .collect();
33    fixtures.sort_unstable_by_key(|f| f.0.clone());
34
35    info!("starting webdriver instance");
36    let webdriver_url = "http://localhost:4444";
37    let mut webdriver_handle = Command::new("chromedriver").arg("--port=4444").spawn().unwrap();
38
39    // this is silly, but it works
40    std::thread::sleep(std::time::Duration::from_secs(1));
41
42    let mut caps = serde_json::map::Map::new();
43    let chrome_opts = serde_json::json!({ "args": ["--headless", "--disable-gpu"] });
44    caps.insert("goog:chromeOptions".to_string(), chrome_opts.clone());
45
46    info!("spawning webdriver client and collecting test descriptions");
47    let client = ClientBuilder::native().capabilities(caps.clone()).connect(webdriver_url).await.unwrap();
48
49    let mut test_descs = vec![];
50    for (name, fixture_path) in fixtures {
51        test_descs.push(test_root_element(client.clone(), name, fixture_path).await);
52    }
53
54    info!("killing webdriver instance...");
55    webdriver_handle.kill().unwrap();
56
57    info!("generating test sources and concatenating...");
58
59    let bench_descs: Vec<_> = test_descs
60        .iter()
61        .map(|(name, description)| {
62            debug!("generating bench contents for {}", &name);
63            (name.clone(), generate_bench(description))
64        })
65        .collect();
66
67    let test_descs: Vec<_> = test_descs
68        .iter()
69        .map(|(name, description)| {
70            debug!("generating test contents for {}", &name);
71            (name.clone(), generate_test(name, description))
72        })
73        .collect();
74
75    let benchmarks: Vec<_> = test_descs
76        .iter()
77        .map(|(name, _)| {
78            let bench_mod = Ident::new(name, Span::call_site());
79            quote!(#bench_mod::compute())
80        })
81        .collect();
82
83    let test_mods = test_descs
84        .iter()
85        .map(|(name, _)| {
86            let name = Ident::new(name, Span::call_site());
87            quote!(mod #name;)
88        })
89        .fold(quote!(), |a, b| quote!(#a #b));
90
91    for (name, bench_body) in bench_descs {
92        let mut bench_filename = repo_root.join("benches").join("generated").join(&name);
93        bench_filename.set_extension("rs");
94        debug!("writing {} to disk...", &name);
95        fs::write(bench_filename, bench_body.to_string()).unwrap();
96    }
97
98    for (name, test_body) in test_descs {
99        let mut test_filename = repo_root.join("tests").join("generated").join(&name);
100        test_filename.set_extension("rs");
101        debug!("writing {} to disk...", &name);
102        fs::write(test_filename, test_body.to_string()).unwrap();
103    }
104
105    let bench_mods = quote!(
106        use criterion::{criterion_group, criterion_main, Criterion};
107
108        #test_mods
109
110        fn benchmark(c: &mut Criterion) {
111            c.bench_function("generated benchmarks", |b| {
112                b.iter(|| { #(#benchmarks;)* })
113            });
114        }
115
116        criterion_group!(benches, benchmark);
117        criterion_main!(benches);
118    );
119
120    info!("writing generated test file to disk...");
121    fs::write(repo_root.join("benches").join("generated").join("mod.rs"), bench_mods.to_string()).unwrap();
122    fs::write(repo_root.join("tests").join("generated").join("mod.rs"), test_mods.to_string()).unwrap();
123
124    info!("formatting the source directory");
125    Command::new("cargo").arg("fmt").current_dir(repo_root).status().unwrap();
126}
127
128async fn test_root_element(client: Client, name: String, fixture_path: impl AsRef<Path>) -> (String, json::JsonValue) {
129    let fixture_path = fixture_path.as_ref();
130
131    let url = format!("file://{}", fixture_path.display());
132
133    client.goto(&url).await.unwrap();
134    let description = client
135        .execute("return JSON.stringify(describeElement(document.getElementById('test-root')))", vec![])
136        .await
137        .unwrap();
138    let description_string = description.as_str().unwrap();
139    let description = json::parse(description_string).unwrap();
140    (name, description)
141}
142
143fn generate_bench(description: &json::JsonValue) -> TokenStream {
144    let node_description = generate_node("node", &description);
145
146    quote!(
147        pub fn compute() {
148            let mut taffy = taffy::Taffy::new();
149            #node_description
150            taffy.compute_layout(node, taffy::geometry::Size::undefined()).unwrap();
151        }
152    )
153}
154
155fn generate_test(name: impl AsRef<str>, description: &json::JsonValue) -> TokenStream {
156    let name = name.as_ref();
157    let name = Ident::new(name, Span::call_site());
158    let node_description = generate_node("node", &description);
159    let assertions = generate_assertions("node", &description);
160
161    quote!(
162        #[test]
163        fn #name() {
164            let mut taffy = taffy::Taffy::new();
165            #node_description
166            taffy.compute_layout(node, taffy::geometry::Size::undefined()).unwrap();
167            #assertions
168        }
169    )
170}
171
172fn generate_assertions(ident: &str, node: &json::JsonValue) -> TokenStream {
173    let layout = &node["layout"];
174
175    let read_f32 = |s: &str| layout[s].as_f32().unwrap();
176    let width = read_f32("width");
177    let height = read_f32("height");
178    let x = read_f32("x");
179    let y = read_f32("y");
180
181    let children = {
182        let mut c = Vec::new();
183        match node["children"] {
184            json::JsonValue::Array(ref value) => {
185                for i in 0..value.len() {
186                    let child = &value[i];
187                    c.push(generate_assertions(&format!("{}{}", ident, i), child));
188                }
189            }
190            _ => (),
191        };
192        c.into_iter().fold(quote!(), |a, b| quote!(#a #b))
193    };
194
195    let ident = Ident::new(ident, Span::call_site());
196
197    quote!(
198        assert_eq!(taffy.layout(#ident).unwrap().size.width, #width);
199        assert_eq!(taffy.layout(#ident).unwrap().size.height, #height);
200        assert_eq!(taffy.layout(#ident).unwrap().location.x, #x);
201        assert_eq!(taffy.layout(#ident).unwrap().location.y, #y);
202
203        #children
204    )
205}
206
207fn generate_node(ident: &str, node: &json::JsonValue) -> TokenStream {
208    let style = &node["style"];
209
210    let display = match style["display"] {
211        json::JsonValue::Short(ref value) => match value.as_ref() {
212            "none" => quote!(display: taffy::style::Display::None,),
213            _ => quote!(),
214        },
215        _ => quote!(),
216    };
217
218    let position_type = match style["position_type"] {
219        json::JsonValue::Short(ref value) => match value.as_ref() {
220            "absolute" => quote!(position_type: taffy::style::PositionType::Absolute,),
221            _ => quote!(),
222        },
223        _ => quote!(),
224    };
225
226    let direction = match style["direction"] {
227        json::JsonValue::Short(ref value) => match value.as_ref() {
228            "rtl" => quote!(direction: taffy::style::Direction::RTL,),
229            "ltr" => quote!(direction: taffy::style::Direction::LTR,),
230            _ => quote!(),
231        },
232        _ => quote!(),
233    };
234
235    let flex_direction = match style["flexDirection"] {
236        json::JsonValue::Short(ref value) => match value.as_ref() {
237            "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,),
238            "column" => quote!(flex_direction: taffy::style::FlexDirection::Column,),
239            "column-reverse" => quote!(flex_direction: taffy::style::FlexDirection::ColumnReverse,),
240            _ => quote!(),
241        },
242        _ => quote!(),
243    };
244
245    let flex_wrap = match style["flexWrap"] {
246        json::JsonValue::Short(ref value) => match value.as_ref() {
247            "wrap" => quote!(flex_wrap: taffy::style::FlexWrap::Wrap,),
248            "wrap-reverse" => quote!(flex_wrap: taffy::style::FlexWrap::WrapReverse,),
249            _ => quote!(),
250        },
251        _ => quote!(),
252    };
253
254    let overflow = match style["overflow"] {
255        json::JsonValue::Short(ref value) => match value.as_ref() {
256            "hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
257            "scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
258            _ => quote!(),
259        },
260        _ => quote!(),
261    };
262
263    let align_items = match style["alignItems"] {
264        json::JsonValue::Short(ref value) => match value.as_ref() {
265            "flex-start" => quote!(align_items: taffy::style::AlignItems::FlexStart,),
266            "flex-end" => quote!(align_items: taffy::style::AlignItems::FlexEnd,),
267            "center" => quote!(align_items: taffy::style::AlignItems::Center,),
268            "baseline" => quote!(align_items: taffy::style::AlignItems::Baseline,),
269            _ => quote!(),
270        },
271        _ => quote!(),
272    };
273
274    let align_self = match style["alignSelf"] {
275        json::JsonValue::Short(ref value) => match value.as_ref() {
276            "flex-start" => quote!(align_self: taffy::style::AlignSelf::FlexStart,),
277            "flex-end" => quote!(align_self: taffy::style::AlignSelf::FlexEnd,),
278            "center" => quote!(align_self: taffy::style::AlignSelf::Center,),
279            "baseline" => quote!(align_self: taffy::style::AlignSelf::Baseline,),
280            "stretch" => quote!(align_self: taffy::style::AlignSelf::Stretch,),
281            _ => quote!(),
282        },
283        _ => quote!(),
284    };
285
286    let align_content = match style["alignContent"] {
287        json::JsonValue::Short(ref value) => match value.as_ref() {
288            "flex-start" => quote!(align_content: taffy::style::AlignContent::FlexStart,),
289            "flex-end" => quote!(align_content: taffy::style::AlignContent::FlexEnd,),
290            "center" => quote!(align_content: taffy::style::AlignContent::Center,),
291            "space-between" => quote!(align_content: taffy::style::AlignContent::SpaceBetween,),
292            "space-around" => quote!(align_content: taffy::style::AlignContent::SpaceAround,),
293            _ => quote!(),
294        },
295        _ => quote!(),
296    };
297
298    let justify_content = match style["justifyContent"] {
299        json::JsonValue::Short(ref value) => match value.as_ref() {
300            "flex-end" => quote!(justify_content: taffy::style::JustifyContent::FlexEnd,),
301            "center" => quote!(justify_content: taffy::style::JustifyContent::Center,),
302            "space-between" => quote!(justify_content: taffy::style::JustifyContent::SpaceBetween,),
303            "space-around" => quote!(justify_content: taffy::style::JustifyContent::SpaceAround,),
304            "space-evenly" => quote!(justify_content: taffy::style::JustifyContent::SpaceEvenly,),
305            _ => quote!(),
306        },
307        _ => quote!(),
308    };
309
310    let flex_grow = match style["flexGrow"] {
311        json::JsonValue::Number(value) => {
312            let value: f32 = value.into();
313            quote!(flex_grow: #value,)
314        }
315        _ => quote!(),
316    };
317
318    let flex_shrink = match style["flexShrink"] {
319        json::JsonValue::Number(value) => {
320            let value: f32 = value.into();
321            quote!(flex_shrink: #value,)
322        }
323        _ => quote!(),
324    };
325
326    let flex_basis = match style["flexBasis"] {
327        json::JsonValue::Object(ref value) => {
328            let value = generate_dimension(value);
329            quote!(flex_basis: #value,)
330        }
331        _ => quote!(),
332    };
333
334    let size = match style["size"] {
335        json::JsonValue::Object(ref value) => {
336            let size = generate_size(value);
337            quote!(size: #size,)
338        }
339        _ => quote!(),
340    };
341
342    let min_size = match style["min_size"] {
343        json::JsonValue::Object(ref value) => {
344            let min_size = generate_size(value);
345            quote!(min_size: #min_size,)
346        }
347        _ => quote!(),
348    };
349
350    let max_size = match style["max_size"] {
351        json::JsonValue::Object(ref value) => {
352            let max_size = generate_size(value);
353            quote!(max_size: #max_size,)
354        }
355        _ => quote!(),
356    };
357
358    macro_rules! edges_quoted {
359        ($style:ident, $val:ident) => {
360            let $val = match $style[stringify!($val)] {
361                json::JsonValue::Object(ref value) => {
362                    let edges = generate_edges(value);
363                    quote!($val: #edges,)
364                },
365                _ => quote!(),
366            };
367        };
368    }
369
370    edges_quoted!(style, margin);
371    edges_quoted!(style, padding);
372    edges_quoted!(style, position);
373    edges_quoted!(style, border);
374
375    let (children_body, children) = match node["children"] {
376        json::JsonValue::Array(ref value) => {
377            if value.len() > 0 {
378                let body = value
379                    .iter()
380                    .enumerate()
381                    .map(|(i, child)| generate_node(&format!("{}{}", ident, i), child))
382                    .collect();
383                let idents = value
384                    .iter()
385                    .enumerate()
386                    .map(|(i, _)| Ident::new(&format!("{}{}", ident, i), Span::call_site()))
387                    .collect::<Vec<_>>();
388                (body, quote!(&[#(#idents),*]))
389            } else {
390                (quote!(), quote!(&[]))
391            }
392        }
393        _ => (quote!(), quote!()),
394    };
395
396    let ident = Ident::new(&format!("{}", ident), Span::call_site());
397
398    quote!(
399        #children_body
400        let #ident = taffy.new_with_children(
401        taffy::style::FlexboxLayout {
402            #display
403            #direction
404            #position_type
405            #flex_direction
406            #flex_wrap
407            #overflow
408            #align_items
409            #align_self
410            #align_content
411            #justify_content
412            #flex_grow
413            #flex_shrink
414            #flex_basis
415            #size
416            #min_size
417            #max_size
418            #margin
419            #padding
420            #position
421            #border
422            ..Default::default()
423        },
424        #children
425    ).unwrap();)
426}
427
428macro_rules! dim_quoted {
429    ($obj:ident, $dim_name:ident) => {
430        let $dim_name = match $obj.get(stringify!($dim_name)) {
431            Some(json::JsonValue::Object(ref value)) => {
432                let dim = generate_dimension(value);
433                quote!($dim_name: #dim,)
434            }
435            _ => quote!(),
436        };
437    };
438}
439
440fn generate_size(size: &json::object::Object) -> TokenStream {
441    dim_quoted!(size, width);
442    dim_quoted!(size, height);
443    quote!(
444        taffy::geometry::Size {
445            #width #height
446            ..Default::default()
447        }
448    )
449}
450
451fn generate_dimension(dimen: &json::object::Object) -> TokenStream {
452    let unit = dimen.get("unit").unwrap();
453    let value = || dimen.get("value").unwrap().as_f32().unwrap();
454
455    match unit {
456        json::JsonValue::Short(ref unit) => match unit.as_ref() {
457            "auto" => quote!(taffy::style::Dimension::Auto),
458            "points" => {
459                let value = value();
460                quote!(taffy::style::Dimension::Points(#value))
461            }
462            "percent" => {
463                let value = value();
464                quote!(taffy::style::Dimension::Percent(#value))
465            }
466            _ => unreachable!(),
467        },
468        _ => unreachable!(),
469    }
470}
471
472fn generate_edges(dimen: &json::object::Object) -> TokenStream {
473    dim_quoted!(dimen, start);
474    dim_quoted!(dimen, end);
475    dim_quoted!(dimen, top);
476    dim_quoted!(dimen, bottom);
477
478    quote!(taffy::geometry::Rect {
479        #start #end #top #bottom
480        ..Default::default()
481    })
482}