1use anyhow::Result;
4use heck::ToUpperCamelCase;
5use proc_macro2::Span;
6use serde_json::Value;
7use std::{
8 fs::{self, ReadDir},
9 path::Path,
10};
11use syn::{parse_quote, Expr, Ident, Item, ItemMod, LitStr};
12
13const SCALE_SECTIONS: [&str; 6] = [
15 "assurances",
16 "authorizations",
17 "disputes",
18 "reports",
19 "safrole",
20 "statistics",
21];
22
23const GENERAL_SECTIONS: &[&str] = &["history", "preimages"];
25
26pub struct Registry<'s> {
28 root: &'s Path,
30
31 output: &'s Path,
33
34 registry: Vec<Item>,
36
37 tests: ItemMod,
39}
40
41impl<'s> Registry<'s> {
42 pub fn new(root: &'s Path, output: &'s Path) -> Self {
44 Self {
45 root,
46 output,
47 registry: Vec::new(),
48 tests: parse_quote!(
49 pub mod tests {}
51 ),
52 }
53 }
54
55 pub fn run(mut self) -> Result<()> {
57 self.scale()?;
58 self.general()?;
59 self.codec()?;
60 self.pvm()?;
61 self.shuffle()?;
62 self.trie()?;
63
64 let all = self.extract_all_tests();
66 let tests = self.tests;
67 let items = self.registry;
68 let registry = quote::quote! {
69 #all
70
71 #(#items)*
72
73 #tests
74 };
75
76 let out = self.output.join("registry.rs");
78 fs::write(out, registry.to_string())?;
79 Ok(())
80 }
81
82 fn extract_all_tests(&self) -> syn::Item {
84 let mut tests = Vec::new();
85 for item in &self.registry {
86 if let syn::Item::Const(syn::ItemConst { expr, .. }) = item {
87 if let syn::Expr::Array(syn::ExprArray { elems, .. }) = *expr.clone() {
88 tests.extend(elems.into_iter())
89 }
90 }
91 }
92
93 let len = tests.len();
94 let len: Expr = parse_quote!(#len);
95 parse_quote! {
96 #[doc = "The all test vectors"]
97 pub const ALL_TESTS: [crate::Test; #len] = [#(#tests),*];
98 }
99 }
100
101 fn scale(&mut self) -> Result<()> {
103 for section in SCALE_SECTIONS {
104 let path = self.root.join(section);
105 let mut tests = Vec::new();
106 for scale in crate::SCALE {
107 let path = path.join(scale);
108 let dir = fs::read_dir(&path)?;
109 tests.extend(self.process_base(section, dir, Some(scale.to_string()))?);
110 }
111 self.embed_namespace(section, tests);
112 }
113
114 Ok(())
115 }
116
117 fn general(&mut self) -> Result<()> {
119 for section in GENERAL_SECTIONS {
120 let path = self.root.join(section).join("data");
121 let dir = fs::read_dir(path)?;
122 let tests = self.process_base(section, dir, None)?;
123 self.embed_namespace(section, tests);
124 }
125
126 Ok(())
127 }
128
129 fn codec(&mut self) -> Result<()> {
131 let Some((_, tests)) = self.tests.content.as_mut() else {
132 return Err(anyhow::anyhow!("tests already initialized"));
133 };
134
135 let mut const_tests = Vec::new();
136 for entry in fs::read_dir(self.root.join("codec").join("data"))? {
137 let path = entry?.path();
138 if path.extension().unwrap_or_default() != "json" {
139 continue;
140 }
141
142 let bin = hex::encode(fs::read(path.with_extension("bin"))?);
143 let parse = move |json: Value| Ok((json.to_string(), bin));
144 let test = wrap_test(tests, &None, "codec", &path, parse)?;
145 const_tests.push(test);
146 }
147
148 self.embed_namespace("codec", const_tests);
149 Ok(())
150 }
151
152 fn pvm(&mut self) -> Result<()> {
154 let Some((_, tests)) = self.tests.content.as_mut() else {
155 return Err(anyhow::anyhow!("tests already initialized"));
156 };
157
158 let section = "pvm";
159 let dir = fs::read_dir(self.root.join(section).join("programs"))?;
160 let mut const_tests = Vec::new();
161 for entry in dir {
162 let path = entry?.path();
163 let test = self::wrap_test(tests, &None, section, &path, |json| {
164 let input = serde_json::json!({
165 "name": json["name"],
166 "initial-regs": json["pre-state"],
167 "initial-pc": json["initial-pc"],
168 "initial-regs": json["initial-regs"],
169 "initial-page-map": json["initial-page-map"],
170 "initial-memory": json["initial-memory"],
171 "initial-gas": json["initial-gas"],
172 "program": json["program"],
173
174 })
175 .to_string();
176
177 let output = serde_json::json!({
178 "expected-status": json["expected-status"],
179 "expected-regs": json["expected-regs"],
180 "expected-pc": json["expected-pc"],
181 "expected-memory": json["expected-memory"],
182 "expected-gas": json["expected-gas"],
183 })
184 .to_string();
185
186 Ok((input, output))
187 })?;
188
189 const_tests.push(test);
190 }
191
192 self.embed_namespace(section, const_tests);
193 Ok(())
194 }
195
196 fn trie(&mut self) -> Result<()> {
198 let file = self.root.join("trie").join("trie.json");
199 let Some((_, tests)) = self.tests.content.as_mut() else {
200 return Err(anyhow::anyhow!("tests already initialized"));
201 };
202
203 let test = wrap_test(
204 tests,
205 &None,
206 "trie",
207 &file,
208 |json| -> Result<(String, String)> {
209 let vectors = json
210 .as_array()
211 .ok_or_else(|| anyhow::anyhow!("invalid trie test"))?;
212
213 let mut input = Vec::new();
214 let mut output = Vec::new();
215 for vector in vectors {
216 input.push(serde_json::json!({
217 "input": vector["input"],
218 }));
219 output.push(serde_json::json!({
220 "output": vector["output"],
221 }));
222 }
223
224 Ok((
225 serde_json::to_string(&input)?,
226 serde_json::to_string(&output)?,
227 ))
228 },
229 )?;
230
231 self.embed_namespace("trie", vec![test]);
232 Ok(())
233 }
234
235 fn shuffle(&mut self) -> Result<()> {
237 let file = self.root.join("shuffle").join("shuffle_tests.json");
238 let Some((_, tests)) = self.tests.content.as_mut() else {
239 return Err(anyhow::anyhow!("tests already initialized"));
240 };
241
242 let test = wrap_test(
243 tests,
244 &None,
245 "shuffle",
246 &file,
247 |json| -> Result<(String, String)> {
248 let vectors = json
249 .as_array()
250 .ok_or_else(|| anyhow::anyhow!("invalid shuffle test"))?;
251
252 let mut input = Vec::new();
253 let mut output = Vec::new();
254 for vector in vectors {
255 input.push(serde_json::json!({
256 "input": vector["input"],
257 "entropy": vector["entropy"],
258 }));
259 output.push(serde_json::json!({
260 "output": vector["output"],
261 }));
262 }
263
264 Ok((
265 serde_json::to_string(&input)?,
266 serde_json::to_string(&output)?,
267 ))
268 },
269 )?;
270
271 self.embed_namespace("shuffle", vec![test]);
272 Ok(())
273 }
274
275 fn process_base(
276 &mut self,
277 section: &str,
278 dir: ReadDir,
279 scale: Option<String>,
280 ) -> Result<Vec<syn::Path>> {
281 let Some((_, tests)) = self.tests.content.as_mut() else {
282 return Err(anyhow::anyhow!("tests already initialized"));
283 };
284
285 let mut const_tests = Vec::new();
286 for entry in dir {
287 let path = entry?.path();
288 if path.extension().unwrap_or_default() != "json" {
289 continue;
290 }
291
292 let test = self::wrap_test(tests, &scale, section, &path, |json| {
293 let input = serde_json::json!({
294 "input": json["input"],
295 "pre_state": json["pre_state"],
296 })
297 .to_string();
298
299 let output = serde_json::json!({
300 "output": json["output"],
301 "post_state": json["post_state"],
302 })
303 .to_string();
304
305 Ok((input, output))
306 })?;
307
308 const_tests.push(test);
309 }
310
311 Ok(const_tests)
312 }
313
314 fn embed_namespace(&mut self, section: &str, tests: Vec<syn::Path>) {
316 let namespace = Ident::new(§ion.to_uppercase(), Span::call_site());
317 let tests_len = tests.len();
318 let tests_len: Expr = parse_quote!(#tests_len);
319 let doc = LitStr::new(
320 &format!("The test vectors for the {section} section"),
321 Span::call_site(),
322 );
323
324 self.registry.push(parse_quote! {
325 #[doc = #doc]
326 pub const #namespace: [crate::Test; #tests_len] = [#(#tests),*];
327 });
328 }
329}
330
331fn wrap_test<P>(
332 tests: &mut Vec<Item>,
333 scale: &Option<String>,
334 section: &str,
335 file: &Path,
336 parse: P,
337) -> Result<syn::Path>
338where
339 P: FnOnce(Value) -> Result<(String, String)>,
340{
341 let mut test = file
343 .with_extension("")
344 .file_name()
345 .ok_or_else(|| anyhow::anyhow!("invalid file name"))?
346 .to_string_lossy()
347 .replace('-', "_");
348
349 let doc = LitStr::new(
350 &format!("test vector {test} for {section}"),
351 Span::call_site(),
352 );
353
354 let json: Value = serde_json::from_slice(&fs::read(file)?)
356 .map_err(|e| anyhow::anyhow!("invalid json {file:?} : {e}"))?;
357 let (input, output) = parse(json)?;
358
359 let test_lower = LitStr::new(&test, Span::call_site());
361 test = test.to_uppercase();
362 let const_test = {
363 let mut test = format!("TEST_{}_{test}", section.to_uppercase());
364 if let Some(scale) = &scale {
365 test.push_str(&format!("_{}", scale.to_uppercase()));
366 }
367 Ident::new(&test, Span::call_site())
368 };
369 let const_input = LitStr::new(&input, Span::call_site());
370 let const_output = LitStr::new(&output, Span::call_site());
371
372 let section_caml = Ident::new(§ion.to_upper_camel_case(), Span::call_site());
374 let scale: Expr = if let Some(scale) = &scale {
375 let ident = Ident::new(&scale.to_upper_camel_case(), Span::call_site());
376 parse_quote!(Some(crate::Scale::#ident))
377 } else {
378 parse_quote!(None)
379 };
380
381 tests.push(parse_quote!(
382 #[doc = #doc]
383 pub const #const_test: crate::Test = crate::Test {
384 scale: #scale,
385 section: crate::Section::#section_caml,
386 name: #test_lower,
387 input: #const_input,
388 output: #const_output,
389 };
390 ));
391
392 Ok(parse_quote!(tests::#const_test))
393}