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