tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Sutichaykuna

⎇ edd994fd39750e366114cf4d5a66f7092a97edfa scripts/gentest/src/main.rs Rust · 522 siq'ikuna · 17.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

Update benches to API changes edd994fd3 · msiglreith ·

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