diff options
author | Aleksey Kladov <[email protected]> | 2018-07-30 14:16:58 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-07-30 14:16:58 +0100 |
commit | 3b6a6f6673041cf9ee315c00f9b0e24e2c067091 (patch) | |
tree | 001e556601e9dd37556338f877759466846c7af0 /tools/src/bin/main.rs | |
parent | d39198490f878a9ae395af1cf923fb7375de4548 (diff) |
Add render test functionality
Diffstat (limited to 'tools/src/bin/main.rs')
-rw-r--r-- | tools/src/bin/main.rs | 180 |
1 files changed, 0 insertions, 180 deletions
diff --git a/tools/src/bin/main.rs b/tools/src/bin/main.rs deleted file mode 100644 index f4f6e82ae..000000000 --- a/tools/src/bin/main.rs +++ /dev/null | |||
@@ -1,180 +0,0 @@ | |||
1 | extern crate clap; | ||
2 | #[macro_use] | ||
3 | extern crate failure; | ||
4 | extern crate itertools; | ||
5 | extern crate ron; | ||
6 | extern crate tera; | ||
7 | extern crate walkdir; | ||
8 | |||
9 | use clap::{App, Arg, SubCommand}; | ||
10 | use itertools::Itertools; | ||
11 | use std::{collections::HashSet, fs, path::Path}; | ||
12 | |||
13 | type Result<T> = ::std::result::Result<T, failure::Error>; | ||
14 | |||
15 | const GRAMMAR_DIR: &str = "./src/parser/grammar"; | ||
16 | const INLINE_TESTS_DIR: &str = "tests/data/parser/inline"; | ||
17 | const GRAMMAR: &str = "./src/grammar.ron"; | ||
18 | const SYNTAX_KINDS: &str = "./src/syntax_kinds/generated.rs"; | ||
19 | const SYNTAX_KINDS_TEMPLATE: &str = "./src/syntax_kinds/generated.rs.tera"; | ||
20 | |||
21 | fn main() -> Result<()> { | ||
22 | let matches = App::new("tasks") | ||
23 | .setting(clap::AppSettings::SubcommandRequiredElseHelp) | ||
24 | .arg( | ||
25 | Arg::with_name("verify") | ||
26 | .long("--verify") | ||
27 | .help("Verify that generated code is up-to-date") | ||
28 | .global(true), | ||
29 | ) | ||
30 | .subcommand(SubCommand::with_name("gen-kinds")) | ||
31 | .subcommand(SubCommand::with_name("gen-tests")) | ||
32 | .get_matches(); | ||
33 | match matches.subcommand() { | ||
34 | (name, Some(matches)) => run_gen_command(name, matches.is_present("verify"))?, | ||
35 | _ => unreachable!(), | ||
36 | } | ||
37 | Ok(()) | ||
38 | } | ||
39 | |||
40 | fn run_gen_command(name: &str, verify: bool) -> Result<()> { | ||
41 | match name { | ||
42 | "gen-kinds" => update(Path::new(SYNTAX_KINDS), &get_kinds()?, verify), | ||
43 | "gen-tests" => gen_tests(verify), | ||
44 | _ => unreachable!(), | ||
45 | } | ||
46 | } | ||
47 | |||
48 | fn update(path: &Path, contents: &str, verify: bool) -> Result<()> { | ||
49 | match fs::read_to_string(path) { | ||
50 | Ok(ref old_contents) if old_contents == contents => { | ||
51 | return Ok(()); | ||
52 | } | ||
53 | _ => (), | ||
54 | } | ||
55 | if verify { | ||
56 | bail!("`{}` is not up-to-date", path.display()); | ||
57 | } | ||
58 | eprintln!("updating {}", path.display()); | ||
59 | fs::write(path, contents)?; | ||
60 | Ok(()) | ||
61 | } | ||
62 | |||
63 | fn get_kinds() -> Result<String> { | ||
64 | let grammar = grammar()?; | ||
65 | let template = fs::read_to_string(SYNTAX_KINDS_TEMPLATE)?; | ||
66 | let ret = tera::Tera::one_off(&template, &grammar, false) | ||
67 | .map_err(|e| format_err!("template error: {}", e))?; | ||
68 | Ok(ret) | ||
69 | } | ||
70 | |||
71 | fn grammar() -> Result<ron::value::Value> { | ||
72 | let text = fs::read_to_string(GRAMMAR)?; | ||
73 | let ret = ron::de::from_str(&text)?; | ||
74 | Ok(ret) | ||
75 | } | ||
76 | |||
77 | fn gen_tests(verify: bool) -> Result<()> { | ||
78 | let tests = tests_from_dir(Path::new(GRAMMAR_DIR))?; | ||
79 | |||
80 | let inline_tests_dir = Path::new(INLINE_TESTS_DIR); | ||
81 | if !inline_tests_dir.is_dir() { | ||
82 | fs::create_dir_all(inline_tests_dir)?; | ||
83 | } | ||
84 | let existing = existing_tests(inline_tests_dir)?; | ||
85 | |||
86 | for t in existing.difference(&tests) { | ||
87 | panic!("Test is deleted: {}\n{}", t.name, t.text); | ||
88 | } | ||
89 | |||
90 | let new_tests = tests.difference(&existing); | ||
91 | for (i, t) in new_tests.enumerate() { | ||
92 | let name = format!("{:04}_{}.rs", existing.len() + i + 1, t.name); | ||
93 | let path = inline_tests_dir.join(name); | ||
94 | update(&path, &t.text, verify)?; | ||
95 | } | ||
96 | Ok(()) | ||
97 | } | ||
98 | |||
99 | #[derive(Debug, Eq)] | ||
100 | struct Test { | ||
101 | name: String, | ||
102 | text: String, | ||
103 | } | ||
104 | |||
105 | impl PartialEq for Test { | ||
106 | fn eq(&self, other: &Test) -> bool { | ||
107 | self.name.eq(&other.name) | ||
108 | } | ||
109 | } | ||
110 | |||
111 | impl ::std::hash::Hash for Test { | ||
112 | fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) { | ||
113 | self.name.hash(state) | ||
114 | } | ||
115 | } | ||
116 | |||
117 | fn tests_from_dir(dir: &Path) -> Result<HashSet<Test>> { | ||
118 | let mut res = HashSet::new(); | ||
119 | for entry in ::walkdir::WalkDir::new(dir) { | ||
120 | let entry = entry.unwrap(); | ||
121 | if !entry.file_type().is_file() { | ||
122 | continue; | ||
123 | } | ||
124 | if entry.path().extension().unwrap_or_default() != "rs" { | ||
125 | continue; | ||
126 | } | ||
127 | let text = fs::read_to_string(entry.path())?; | ||
128 | |||
129 | for test in collect_tests(&text) { | ||
130 | if let Some(old_test) = res.replace(test) { | ||
131 | bail!("Duplicate test: {}", old_test.name) | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | Ok(res) | ||
136 | } | ||
137 | |||
138 | fn collect_tests(s: &str) -> Vec<Test> { | ||
139 | let mut res = vec![]; | ||
140 | let prefix = "// "; | ||
141 | let comment_blocks = s | ||
142 | .lines() | ||
143 | .map(str::trim_left) | ||
144 | .group_by(|line| line.starts_with(prefix)); | ||
145 | |||
146 | 'outer: for (is_comment, block) in comment_blocks.into_iter() { | ||
147 | if !is_comment { | ||
148 | continue; | ||
149 | } | ||
150 | let mut block = block.map(|line| &line[prefix.len()..]); | ||
151 | |||
152 | let name = loop { | ||
153 | match block.next() { | ||
154 | Some(line) if line.starts_with("test ") => break line["test ".len()..].to_string(), | ||
155 | Some(_) => (), | ||
156 | None => continue 'outer, | ||
157 | } | ||
158 | }; | ||
159 | let text: String = itertools::join(block.chain(::std::iter::once("")), "\n"); | ||
160 | assert!(!text.trim().is_empty() && text.ends_with("\n")); | ||
161 | res.push(Test { name, text }) | ||
162 | } | ||
163 | res | ||
164 | } | ||
165 | |||
166 | fn existing_tests(dir: &Path) -> Result<HashSet<Test>> { | ||
167 | let mut res = HashSet::new(); | ||
168 | for file in fs::read_dir(dir)? { | ||
169 | let file = file?; | ||
170 | let path = file.path(); | ||
171 | if path.extension().unwrap_or_default() != "rs" { | ||
172 | continue; | ||
173 | } | ||
174 | let name = path.file_name().unwrap().to_str().unwrap(); | ||
175 | let name = name["0000_".len()..name.len() - 3].to_string(); | ||
176 | let text = fs::read_to_string(&path)?; | ||
177 | res.insert(Test { name, text }); | ||
178 | } | ||
179 | Ok(res) | ||
180 | } | ||