⎇ 2e0d1bd2f08d3e477e564acfbed30c9caa2e0440
scripts/gentest/src/main.rs
Historia
Quién tocó qué
crudo
Run Clippy CI on the entire workspace (#234)
1use std::fs;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use fantoccini::{Client, ClientBuilder};
6
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();
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 if let json::JsonValue::Array(ref value) = node["children"] {
187 for (idx, child) in value.iter().enumerate() {
188 c.push(generate_assertions(&format!("{}{}", ident, idx), child));
189 }
190 };
191 c.into_iter().fold(quote!(), |a, b| quote!(#a #b))
192 };
193
194 let ident = Ident::new(ident, Span::call_site());
195
196 quote!(
197 assert_eq!(taffy.layout(#ident).unwrap().size.width, #width);
198 assert_eq!(taffy.layout(#ident).unwrap().size.height, #height);
199 assert_eq!(taffy.layout(#ident).unwrap().location.x, #x);
200 assert_eq!(taffy.layout(#ident).unwrap().location.y, #y);
201
202 #children
203 )
204}
205
206fn generate_node(ident: &str, node: &json::JsonValue) -> TokenStream {
207 let style = &node["style"];
208
209 let display = match style["display"] {
210 json::JsonValue::Short(ref value) => match value.as_ref() {
211 "none" => quote!(display: taffy::style::Display::None,),
212 _ => quote!(),
213 },
214 _ => quote!(),
215 };
216
217 let position_type = match style["position_type"] {
218 json::JsonValue::Short(ref value) => match value.as_ref() {
219 "absolute" => quote!(position_type: taffy::style::PositionType::Absolute,),
220 _ => quote!(),
221 },
222 _ => quote!(),
223 };
224
225 let direction = match style["direction"] {
226 json::JsonValue::Short(ref value) => match value.as_ref() {
227 "rtl" => quote!(direction: taffy::style::Direction::RTL,),
228 "ltr" => quote!(direction: taffy::style::Direction::LTR,),
229 _ => quote!(),
230 },
231 _ => quote!(),
232 };
233
234 let flex_direction = match style["flexDirection"] {
235 json::JsonValue::Short(ref value) => match value.as_ref() {
236 "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,),
237 "column" => quote!(flex_direction: taffy::style::FlexDirection::Column,),
238 "column-reverse" => quote!(flex_direction: taffy::style::FlexDirection::ColumnReverse,),
239 _ => quote!(),
240 },
241 _ => quote!(),
242 };
243
244 let flex_wrap = match style["flexWrap"] {
245 json::JsonValue::Short(ref value) => match value.as_ref() {
246 "wrap" => quote!(flex_wrap: taffy::style::FlexWrap::Wrap,),
247 "wrap-reverse" => quote!(flex_wrap: taffy::style::FlexWrap::WrapReverse,),
248 _ => quote!(),
249 },
250 _ => quote!(),
251 };
252
253 let overflow = match style["overflow"] {
254 json::JsonValue::Short(ref value) => match value.as_ref() {
255 "hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
256 "scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
257 _ => quote!(),
258 },
259 _ => quote!(),
260 };
261
262 let align_items = match style["alignItems"] {
263 json::JsonValue::Short(ref value) => match value.as_ref() {
264 "flex-start" => quote!(align_items: taffy::style::AlignItems::FlexStart,),
265 "flex-end" => quote!(align_items: taffy::style::AlignItems::FlexEnd,),
266 "center" => quote!(align_items: taffy::style::AlignItems::Center,),
267 "baseline" => quote!(align_items: taffy::style::AlignItems::Baseline,),
268 _ => quote!(),
269 },
270 _ => quote!(),
271 };
272
273 let align_self = match style["alignSelf"] {
274 json::JsonValue::Short(ref value) => match value.as_ref() {
275 "flex-start" => quote!(align_self: taffy::style::AlignSelf::FlexStart,),
276 "flex-end" => quote!(align_self: taffy::style::AlignSelf::FlexEnd,),
277 "center" => quote!(align_self: taffy::style::AlignSelf::Center,),
278 "baseline" => quote!(align_self: taffy::style::AlignSelf::Baseline,),
279 "stretch" => quote!(align_self: taffy::style::AlignSelf::Stretch,),
280 _ => quote!(),
281 },
282 _ => quote!(),
283 };
284
285 let align_content = match style["alignContent"] {
286 json::JsonValue::Short(ref value) => match value.as_ref() {
287 "flex-start" => quote!(align_content: taffy::style::AlignContent::FlexStart,),
288 "flex-end" => quote!(align_content: taffy::style::AlignContent::FlexEnd,),
289 "center" => quote!(align_content: taffy::style::AlignContent::Center,),
290 "space-between" => quote!(align_content: taffy::style::AlignContent::SpaceBetween,),
291 "space-around" => quote!(align_content: taffy::style::AlignContent::SpaceAround,),
292 "space-evenly" => quote!(align_content: taffy::style::AlignContent::SpaceEvenly,),
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.is_empty() {
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(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 // TODO: Only add children if they exist
426 ).unwrap();)
427}
428
429macro_rules! dim_quoted {
430 ($obj:ident, $dim_name:ident) => {
431 let $dim_name = match $obj.get(stringify!($dim_name)) {
432 Some(json::JsonValue::Object(ref value)) => {
433 let dim = generate_dimension(value);
434 quote!($dim_name: #dim,)
435 }
436 _ => quote!(),
437 };
438 };
439}
440
441fn generate_size(size: &json::object::Object) -> TokenStream {
442 dim_quoted!(size, width);
443 dim_quoted!(size, height);
444 quote!(
445 taffy::geometry::Size {
446 #width #height
447 ..Default::default()
448 }
449 )
450}
451
452fn generate_dimension(dimen: &json::object::Object) -> TokenStream {
453 let unit = dimen.get("unit").unwrap();
454 let value = || dimen.get("value").unwrap().as_f32().unwrap();
455
456 match unit {
457 json::JsonValue::Short(ref unit) => match unit.as_ref() {
458 "auto" => quote!(taffy::style::Dimension::Auto),
459 "points" => {
460 let value = value();
461 quote!(taffy::style::Dimension::Points(#value))
462 }
463 "percent" => {
464 let value = value();
465 quote!(taffy::style::Dimension::Percent(#value))
466 }
467 _ => unreachable!(),
468 },
469 _ => unreachable!(),
470 }
471}
472
473fn generate_edges(dimen: &json::object::Object) -> TokenStream {
474 dim_quoted!(dimen, start);
475 dim_quoted!(dimen, end);
476 dim_quoted!(dimen, top);
477 dim_quoted!(dimen, bottom);
478
479 quote!(taffy::geometry::Rect {
480 #start #end #top #bottom
481 ..Default::default()
482 })
483}