1#[cfg(test)]
6mod lit_file_runner_tests {
7 use std::fs;
8 use std::path::{Path, PathBuf};
9 use std::time::Instant;
10
11 use crate::pipeline::{render_run_source_code_output, run_source_code};
12 use crate::prelude::*;
13
14 const LARGE_TEST_STACK_SIZE: usize = 16 * 1024 * 1024;
15 const SLOWEST_RUNS_TO_PRINT: usize = 10;
16
17 fn run_with_large_stack(test_name: &str, f: impl FnOnce() + Send + 'static) {
18 std::thread::Builder::new()
19 .name(test_name.to_string())
20 .stack_size(LARGE_TEST_STACK_SIZE)
21 .spawn(f)
22 .unwrap()
23 .join()
24 .unwrap();
25 }
26
27 fn extract_litex_fenced_blocks(markdown: &str) -> Vec<(usize, String)> {
31 const SKIP_MARKER: &str = "<!-- litex:skip-test -->";
32 let mut blocks: Vec<(usize, String)> = Vec::new();
33 let mut in_litex = false;
34 let mut skip_this_block = false;
35 let mut current = String::new();
36 let mut prev_non_empty_outside_block: Option<&str> = None;
37 let mut fence_open_line: usize = 0;
38
39 for (line_index_zero, line) in markdown.lines().enumerate() {
40 let line_number_1based = line_index_zero + 1;
41 let trimmed_start = line.trim_start();
42 if trimmed_start.starts_with("```") {
43 let info = trimmed_start[3..].trim();
44 if in_litex {
45 if !skip_this_block {
46 let trimmed = current.trim();
47 if !trimmed.is_empty() {
48 blocks.push((fence_open_line, trimmed.to_string()));
49 }
50 }
51 current.clear();
52 in_litex = false;
53 skip_this_block = false;
54 prev_non_empty_outside_block = None;
55 } else if info == "litex" {
56 in_litex = true;
57 fence_open_line = line_number_1based;
58 skip_this_block = prev_non_empty_outside_block == Some(SKIP_MARKER);
59 current.clear();
60 }
61 } else if in_litex {
62 if !skip_this_block {
63 if !current.is_empty() {
64 current.push('\n');
65 }
66 current.push_str(line);
67 }
68 } else {
69 let t = line.trim();
70 if !t.is_empty() {
71 prev_non_empty_outside_block = Some(t);
72 }
73 }
74 }
75 blocks
76 }
77
78 fn collect_markdown_files_under_dir_sorted(root: &Path) -> Vec<PathBuf> {
79 let mut out: Vec<PathBuf> = Vec::new();
80 if !root.is_dir() {
81 return out;
82 }
83 fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
84 let read_dir = match fs::read_dir(dir) {
85 Ok(entries) => entries,
86 Err(_) => return,
87 };
88 for entry in read_dir.flatten() {
89 let path = entry.path();
90 let Ok(file_type) = entry.file_type() else {
91 continue;
92 };
93 if file_type.is_dir() {
94 walk(&path, out);
95 } else if path.extension().is_some_and(|e| e == "md") {
96 out.push(path);
97 }
98 }
99 }
100 walk(root, &mut out);
101 out.sort();
102 out
103 }
104
105 fn litex_snippets_from_markdown_files(
106 manifest_dir: &Path,
107 md_paths: &[PathBuf],
108 ) -> Vec<(String, String, String)> {
109 let mut out: Vec<(String, String, String)> = Vec::new();
110 for md_path in md_paths {
111 let rel_label = md_path
112 .strip_prefix(manifest_dir)
113 .unwrap_or(md_path)
114 .display()
115 .to_string();
116 let md_current_path_str = md_path.to_string_lossy().into_owned();
117 let md_content = match fs::read_to_string(md_path) {
118 Ok(content) => content,
119 Err(read_error) => panic!("failed to read {:?}: {}", md_path, read_error),
120 };
121 for (block_index, (md_line, block)) in extract_litex_fenced_blocks(&md_content)
122 .into_iter()
123 .enumerate()
124 {
125 out.push((
126 format!(
127 "{} ```litex```#{} (md line {})",
128 rel_label, block_index, md_line
129 ),
130 block,
131 md_current_path_str.clone(),
132 ));
133 }
134 }
135 out
136 }
137
138 fn run_single_the_mechanics_chapter_markdown_file_impl(
139 chapter_filename: &str,
140 chapter_label: &str,
141 ) {
142 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
143 let chapter_path = manifest_dir
144 .join("The-Mechanics-of-Litex-Proof")
145 .join(chapter_filename);
146 assert!(
147 chapter_path.is_file(),
148 "{} markdown file must exist at {:?}",
149 chapter_label,
150 chapter_path
151 );
152
153 let snippets = litex_snippets_from_markdown_files(&manifest_dir, &[chapter_path.clone()]);
154 assert!(
155 !snippets.is_empty(),
156 "{} markdown file must contain ```litex``` blocks",
157 chapter_label
158 );
159
160 let mut runtime = Runtime::new_with_builtin_code();
161 runtime.new_file_path_new_env_new_name_scope(snippets[0].2.as_str());
162
163 let mut snippet_durations_ms: Vec<(String, f64)> = Vec::new();
164 let wall_start = Instant::now();
165 for (snippet_index, (label, source_code, md_path_for_run_file)) in
166 snippets.iter().enumerate()
167 {
168 if snippet_index > 0 {
169 runtime.clear_current_env_and_parse_name_scope();
170 runtime.set_current_user_lit_file_path(md_path_for_run_file.as_str());
171 }
172
173 let normalized_source = remove_windows_carriage_return(source_code);
174 let start_snippet = Instant::now();
175 let (stmt_results, runtime_error) =
176 run_source_code(normalized_source.as_str(), &mut runtime);
177 let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
178
179 let (run_succeeded, run_output) =
180 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
181
182 if !run_succeeded {
183 panic!(
184 "{} markdown litex snippet FAILED:\n{}\n>>> FAILED snippet (open .md here): {}\n",
185 chapter_label, run_output, label
186 );
187 }
188
189 snippet_durations_ms.push((label.clone(), duration_ms));
190 }
191
192 println!(
193 "--- {} markdown: {} ```litex``` block(s), all OK ({:.2} ms wall) ---",
194 chapter_label,
195 snippets.len(),
196 wall_start.elapsed().as_secs_f64() * 1000.0
197 );
198 print_slowest_run_labels(
199 format!("{} markdown snippets", chapter_label).as_str(),
200 snippet_durations_ms.as_slice(),
201 );
202 for (label, duration_ms) in snippet_durations_ms.iter() {
203 println!(" OK {:.2} ms {}", duration_ms, label);
204 }
205 }
206
207 fn run_tmp_lit_file(file_name: &str) {
208 let tmp_lit_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
209 .join("examples")
210 .join(file_name);
211
212 assert!(
213 tmp_lit_path.is_file(),
214 "examples/{} must exist at {:?}",
215 file_name,
216 tmp_lit_path
217 );
218
219 let tmp_lit_content = match fs::read_to_string(&tmp_lit_path) {
220 Ok(content) => content,
221 Err(read_error) => panic!("failed to read {:?}: {}", tmp_lit_path, read_error),
222 };
223 if tmp_lit_content.trim().is_empty() {
224 println!("examples/{} is empty; skip run", file_name);
225 return;
226 }
227
228 let path_str = match tmp_lit_path.to_str() {
229 Some(path_string) => path_string,
230 None => panic!("{:?} must be valid UTF-8", tmp_lit_path),
231 };
232
233 let mut runtime = Runtime::new_with_builtin_code();
234 runtime.new_file_path_new_env_new_name_scope(path_str);
235 let normalized_source = remove_windows_carriage_return(tmp_lit_content.as_str());
236
237 let start_time = Instant::now();
238 let (stmt_results, runtime_error) =
239 run_source_code(normalized_source.as_str(), &mut runtime);
240 let duration_ms = start_time.elapsed().as_secs_f64() * 1000.0;
241
242 let (run_succeeded, run_output) =
243 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
244
245 let status_label = if run_succeeded { "OK" } else { "FAILED" };
246 println!(
247 "{}\n=== [{}] {:?} ({:.2} ms user file only) ===\n",
248 run_output, path_str, status_label, duration_ms
249 );
250 let error_json = match &runtime_error {
251 Some(error) => display_runtime_error_json(&runtime, error, false),
252 None => run_output.clone(),
253 };
254 assert!(
255 run_succeeded,
256 "examples/{} failed.\n\n>>> Litex error JSON:\n{}\n\n=== [{}] {:?} ({:.2} ms user file only) ===",
257 file_name, error_json, path_str, status_label, duration_ms
258 );
259 }
260
261 #[test]
262 fn run_tmp0() {
263 run_tmp_lit_file("tmp.lit");
264 }
265
266 #[test]
267 fn run_tmp2() {
268 run_tmp_lit_file("tmp2.lit");
269 }
270
271 #[test]
272 fn run_tmp3() {
273 run_tmp_lit_file("tmp3.lit");
274 }
275
276 #[test]
277 fn run_tmp4() {
278 run_tmp_lit_file("tmp4.lit");
279 }
280
281 #[test]
282 fn list_set_membership_implies_equality_or() {
283 let source_code = r#"
284forall a set:
285 a = 1 or a = 2 or a = 3
286 =>:
287 a $in {1, 2, 3}
288"#;
289
290 let mut runtime = Runtime::new_with_builtin_code();
291 runtime.new_file_path_new_env_new_name_scope("list_set_membership_implies_equality_or");
292 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
293 let (run_succeeded, run_output) =
294 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
295
296 assert!(
297 run_succeeded,
298 "list_set_membership_implies_equality_or failed:\n{}",
299 run_output
300 );
301 }
302
303 #[test]
304 fn dependent_fn_param_set_uses_previous_arg() {
305 let source_code = r#"
306have f fn(n N_pos, x closed_range(1, n)) R
307f(3, 2) = f(3, 2)
308by fn as set: f
309"#;
310
311 let mut runtime = Runtime::new_with_builtin_code();
312 runtime.new_file_path_new_env_new_name_scope("dependent_fn_param_set_uses_previous_arg");
313 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
314 let (run_succeeded, run_output) =
315 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
316
317 assert!(
318 run_succeeded,
319 "dependent_fn_param_set_uses_previous_arg failed:\n{}",
320 run_output
321 );
322 }
323
324 #[test]
325 fn fn_return_set_cannot_depend_on_params() {
326 let source_code = r#"
327have f fn(n N_pos) closed_range(1, n)
328"#;
329
330 let mut runtime = Runtime::new_with_builtin_code();
331 runtime.new_file_path_new_env_new_name_scope("fn_return_set_cannot_depend_on_params");
332 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
333 let (run_succeeded, run_output) =
334 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
335
336 assert!(
337 !run_succeeded,
338 "dependent return set should fail, but succeeded:\n{}",
339 run_output
340 );
341 assert!(
342 run_output.contains("function return set cannot depend on function parameters [n]"),
343 "dependent return set failure had unexpected output:\n{}",
344 run_output
345 );
346 }
347
348 #[test]
349 fn known_equality_implies_weak_order() {
350 let source_code = r#"
351have a, b R
352know a = b
353a <= b
354a >= b
355"#;
356
357 let mut runtime = Runtime::new_with_builtin_code();
358 runtime.new_file_path_new_env_new_name_scope("known_equality_implies_weak_order");
359 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
360 let (run_succeeded, run_output) =
361 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
362
363 assert!(
364 run_succeeded,
365 "known_equality_implies_weak_order failed:\n{}",
366 run_output
367 );
368 }
369
370 #[test]
371 fn pow_with_nonnegative_base_and_positive_real_exponent_is_well_defined() {
372 let source_code = r#"
373have fn sqrt(x R: x >= 0) R = x^(1/2)
374"#;
375
376 let mut runtime = Runtime::new_with_builtin_code();
377 runtime.new_file_path_new_env_new_name_scope(
378 "pow_with_nonnegative_base_and_positive_real_exponent_is_well_defined",
379 );
380 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
381 let (run_succeeded, run_output) =
382 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
383
384 assert!(
385 run_succeeded,
386 "pow_with_nonnegative_base_and_positive_real_exponent_is_well_defined failed:\n{}",
387 run_output
388 );
389 }
390
391 #[test]
392 fn sqrt_core_builtin_rules() {
393 let source_code = r#"
394sqrt(0) = 0
395sqrt(1) = 1
396
397forall x R:
398 x >= 0
399 =>:
400 (sqrt(x))^2 = x
401
402forall x R:
403 x > 0
404 =>:
405 sqrt(x) > 0
406"#;
407
408 let mut runtime = Runtime::new_with_builtin_code();
409 runtime.new_file_path_new_env_new_name_scope("sqrt_core_builtin_rules");
410 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
411 let (run_succeeded, run_output) =
412 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
413
414 assert!(
415 run_succeeded,
416 "sqrt_core_builtin_rules failed:\n{}",
417 run_output
418 );
419 }
420
421 #[test]
422 fn weak_order_does_not_recursively_prove_equality() {
423 let source_code = r#"
424have a, b R
425know a <= b
426a = b
427"#;
428
429 let mut runtime = Runtime::new_with_builtin_code();
430 runtime
431 .new_file_path_new_env_new_name_scope("weak_order_does_not_recursively_prove_equality");
432 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
433 let (run_succeeded, run_output) =
434 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
435
436 assert!(
437 !run_succeeded,
438 "recursive equality/order proof should fail, but succeeded:\n{}",
439 run_output
440 );
441 }
442
443 #[test]
444 fn hidden_file_path_output_omits_source_fields() {
445 let source_code = "x = 1";
446 let path = "/private/tmp/litex-hidden-source-test.lit";
447
448 let mut runtime = Runtime::new_with_builtin_code();
449 runtime.new_file_path_new_env_new_name_scope(path);
450 runtime.module_manager.hide_file_paths_in_output = true;
451 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
452 let (run_succeeded, run_output) =
453 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
454
455 assert!(!run_succeeded);
456 assert!(!run_output.contains("\"source\""));
457 assert!(!run_output.contains(path));
458 assert!(run_output.contains("\"line\": 1"));
459 }
460
461 #[test]
462 fn show_file_path_output_keeps_source_fields() {
463 let source_code = "x = 1";
464 let path = "/private/tmp/litex-show-source-test.lit";
465
466 let mut runtime = Runtime::new_with_builtin_code();
467 runtime.new_file_path_new_env_new_name_scope(path);
468 runtime.module_manager.hide_file_paths_in_output = false;
469 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
470 let (run_succeeded, run_output) =
471 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
472
473 assert!(!run_succeeded);
474 assert!(run_output.contains("\"source\""));
475 assert!(run_output.contains(path));
476 }
477
478 #[test]
479 fn hidden_file_path_run_file_output_omits_run_file_path() {
480 let run_file_path = std::env::temp_dir().join("litex-hidden-run-file-test.lit");
481 fs::write(&run_file_path, "1 = 1\n").unwrap();
482 let run_file_path_string = run_file_path.to_string_lossy().into_owned();
483 let source_code = format!("run_file \"{}\"", run_file_path_string);
484
485 let mut runtime = Runtime::new_with_builtin_code();
486 runtime.new_file_path_new_env_new_name_scope("repl");
487 runtime.module_manager.hide_file_paths_in_output = true;
488 let (stmt_results, runtime_error) = run_source_code(source_code.as_str(), &mut runtime);
489 let (run_succeeded, run_output) =
490 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
491
492 let _ = fs::remove_file(&run_file_path);
493 assert!(run_succeeded, "run_file failed:\n{}", run_output);
494 assert!(run_output.contains("\"stmt\": \"run_file\""));
495 assert!(!run_output.contains(run_file_path_string.as_str()));
496 assert!(!run_output.contains("\"source\""));
497 }
498
499 #[test]
500 fn citation_verified_by_type_reflects_cited_stmt_kind() {
501 let source_code = r#"
502abstract_prop p(x)
503know forall x R:
504 $p(x)
505$p(2)
506let a R:
507 a = 1
508a = 1
509prop q(x R):
510 x = 1
511$q(1)
512"#;
513
514 let mut runtime = Runtime::new_with_builtin_code();
515 runtime.new_file_path_new_env_new_name_scope(
516 "citation_verified_by_type_reflects_cited_stmt_kind",
517 );
518 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
519 let (run_succeeded, run_output) =
520 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
521
522 assert!(
523 run_succeeded,
524 "citation_verified_by_type_reflects_cited_stmt_kind failed:\n{}",
525 run_output
526 );
527 assert!(run_output.contains("\"type\": \"cite forall fact\""));
528 assert!(run_output.contains("\"type\": \"cite atomic fact\""));
529 assert!(run_output.contains("\"type\": \"cite prop def\""));
530 }
531
532 #[test]
533 fn run_file_from_path() {
534 run_with_large_stack("run_file_from_path_large_stack", run_file_from_path_impl);
535 }
536
537 #[test]
538 fn run_file_in_std_from_folder_name() {
539 run_with_large_stack(
540 "run_file_in_std_from_folder_name_large_stack",
541 run_file_in_std_from_folder_name_impl,
542 );
543 }
544
545 fn run_file_in_std_from_folder_name_impl() {
546 let source_code = "run_file trigonometry\n\nsin(0) = 0\ncos(0) = 1";
547
548 let mut runtime = Runtime::new_with_builtin_code();
549 runtime.new_file_path_new_env_new_name_scope("repl");
550 let (stmt_results, runtime_error) = run_source_code(source_code, &mut runtime);
551 let (run_succeeded, run_output) =
552 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
553
554 assert!(run_succeeded, "run_file in std failed:\n{}", run_output);
555 assert!(run_output.contains("\"type\": \"RunFileInStd\""));
556 assert!(run_output.contains("\"stmt\": \"run_file trigonometry\""));
557 assert!(run_output.contains("\"stmt\": \"sin(0) = 0\""));
558 assert!(run_output.contains("\"stmt\": \"cos(0) = 1\""));
559 }
560
561 fn run_file_from_path_impl() {
562 let path: String = "./examples/strong_induc.lit".to_string();
563 let file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path);
564 assert!(
565 file_path.is_absolute(),
566 "path must be an absolute path: {:?}",
567 file_path
568 );
569 assert!(
570 file_path.is_file(),
571 "path must point to a file: {:?}",
572 file_path
573 );
574
575 let source_code = match fs::read_to_string(&file_path) {
576 Ok(content) => content,
577 Err(read_error) => panic!("failed to read {:?}: {}", file_path, read_error),
578 };
579 let path_str = match file_path.to_str() {
580 Some(path_string) => path_string,
581 None => panic!("{:?} must be valid UTF-8", file_path),
582 };
583
584 let mut runtime = Runtime::new_with_builtin_code();
585 runtime.new_file_path_new_env_new_name_scope(path_str);
586 let normalized_source = remove_windows_carriage_return(source_code.as_str());
587
588 let start_time = Instant::now();
589 let (stmt_results, runtime_error) =
590 run_source_code(normalized_source.as_str(), &mut runtime);
591 let duration_ms = start_time.elapsed().as_secs_f64() * 1000.0;
592
593 let (run_succeeded, run_output) =
594 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
595 let status_label = if run_succeeded { "OK" } else { "FAILED" };
596 println!(
597 "{}\n=== [{}] {:?} ({:.2} ms user file only) ===\n",
598 run_output, path_str, status_label, duration_ms
599 );
600 let error_json = match &runtime_error {
601 Some(error) => display_runtime_error_json(&runtime, error, false),
602 None => run_output.clone(),
603 };
604 assert!(
605 run_succeeded,
606 "Litex file failed: {}\n\n>>> Litex error JSON:\n{}\n\n=== [{}] {:?} ({:.2} ms user file only) ===",
607 path_str, error_json, path_str, status_label, duration_ms
608 );
609 }
610
611 #[test]
612 fn run_the_mechanics_markdown_files() {
613 run_with_large_stack(
614 "run_the_mechanics_markdown_files_large_stack",
615 run_the_mechanics_markdown_files_impl,
616 );
617 }
618
619 #[test]
620 fn run_the_mechanics_chapter_1_markdown_file() {
621 run_with_large_stack(
622 "run_the_mechanics_chapter_1_markdown_file_large_stack",
623 run_the_mechanics_chapter_1_markdown_file_impl,
624 );
625 }
626
627 fn run_the_mechanics_chapter_1_markdown_file_impl() {
628 run_single_the_mechanics_chapter_markdown_file_impl(
629 "Chapter_1_Proofs_By_Calculation.md",
630 "Chapter 1",
631 );
632 }
633
634 #[test]
635 fn run_the_mechanics_chapter_2_markdown_file() {
636 run_with_large_stack(
637 "run_the_mechanics_chapter_2_markdown_file_large_stack",
638 run_the_mechanics_chapter_2_markdown_file_impl,
639 );
640 }
641
642 fn run_the_mechanics_chapter_2_markdown_file_impl() {
643 run_single_the_mechanics_chapter_markdown_file_impl(
644 "Chapter_2_Proofs_With_Structure.md",
645 "Chapter 2",
646 );
647 }
648
649 #[test]
650 fn run_the_mechanics_chapter_3_markdown_file() {
651 run_with_large_stack(
652 "run_the_mechanics_chapter_3_markdown_file_large_stack",
653 run_the_mechanics_chapter_3_markdown_file_impl,
654 );
655 }
656
657 fn run_the_mechanics_chapter_3_markdown_file_impl() {
658 run_single_the_mechanics_chapter_markdown_file_impl(
659 "Chapter_3_Parity_And_Divisibility.md",
660 "Chapter 3",
661 );
662 }
663
664 #[test]
665 fn run_the_mechanics_chapter_4_markdown_file() {
666 run_with_large_stack(
667 "run_the_mechanics_chapter_4_markdown_file_large_stack",
668 run_the_mechanics_chapter_4_markdown_file_impl,
669 );
670 }
671
672 fn run_the_mechanics_chapter_4_markdown_file_impl() {
673 run_single_the_mechanics_chapter_markdown_file_impl(
674 "Chapter_4_Proofs_With_Structure_II.md",
675 "Chapter 4",
676 );
677 }
678
679 #[test]
680 fn run_the_mechanics_chapter_5_markdown_file() {
681 run_with_large_stack(
682 "run_the_mechanics_chapter_5_markdown_file_large_stack",
683 run_the_mechanics_chapter_5_markdown_file_impl,
684 );
685 }
686
687 fn run_the_mechanics_chapter_5_markdown_file_impl() {
688 run_single_the_mechanics_chapter_markdown_file_impl("Chapter_5_Logic.md", "Chapter 5");
689 }
690
691 #[test]
692 fn run_the_mechanics_chapter_7_markdown_file() {
693 run_with_large_stack(
694 "run_the_mechanics_chapter_7_markdown_file_large_stack",
695 run_the_mechanics_chapter_7_markdown_file_impl,
696 );
697 }
698
699 fn run_the_mechanics_chapter_7_markdown_file_impl() {
700 run_single_the_mechanics_chapter_markdown_file_impl(
701 "Chapter_7_Number_Theory.md",
702 "Chapter 7",
703 );
704 }
705
706 #[test]
707 fn run_the_mechanics_chapter_9_markdown_file() {
708 run_with_large_stack(
709 "run_the_mechanics_chapter_9_markdown_file_large_stack",
710 run_the_mechanics_chapter_9_markdown_file_impl,
711 );
712 }
713
714 fn run_the_mechanics_chapter_9_markdown_file_impl() {
715 run_single_the_mechanics_chapter_markdown_file_impl("Chapter_9_Sets.md", "Chapter 9");
716 }
717
718 #[test]
719 fn run_the_mechanics_chapter_10_markdown_file() {
720 run_with_large_stack(
721 "run_the_mechanics_chapter_10_markdown_file_large_stack",
722 run_the_mechanics_chapter_10_markdown_file_impl,
723 );
724 }
725
726 fn run_the_mechanics_chapter_10_markdown_file_impl() {
727 run_single_the_mechanics_chapter_markdown_file_impl(
728 "Chapter_10_Relations.md",
729 "Chapter 10",
730 );
731 }
732
733 fn run_the_mechanics_markdown_files_impl() {
734 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
735 let mechanics_dir = manifest_dir.join("The-Mechanics-of-Litex-Proof");
736 assert!(
737 mechanics_dir.is_dir(),
738 "The-Mechanics-of-Litex-Proof must exist at {:?}",
739 mechanics_dir
740 );
741
742 let md_paths = collect_markdown_files_under_dir_sorted(&mechanics_dir);
743 assert!(
744 !md_paths.is_empty(),
745 "The-Mechanics-of-Litex-Proof must contain markdown files"
746 );
747
748 let mut snippets_by_file: Vec<Vec<(String, String, String)>> = Vec::new();
749 let mut total_snippet_count: usize = 0;
750 for md_path in md_paths.iter() {
751 let snippets = litex_snippets_from_markdown_files(&manifest_dir, &[md_path.clone()]);
752 total_snippet_count += snippets.len();
753 snippets_by_file.push(snippets);
754 }
755 assert!(
756 total_snippet_count > 0,
757 "The-Mechanics-of-Litex-Proof markdown files must contain ```litex``` blocks"
758 );
759
760 let mut runtime = Runtime::new_with_builtin_code();
761
762 let mut snippet_durations_ms: Vec<(String, f64)> = Vec::new();
763 let mut failed_labels: Vec<String> = Vec::new();
764 let wall_start = Instant::now();
765 let mut file_count_with_snippets: usize = 0;
766 let mut snippet_count_run: usize = 0;
767 for snippets in snippets_by_file.iter() {
768 if snippets.is_empty() {
769 continue;
770 }
771
772 file_count_with_snippets += 1;
773
774 for (label, source_code, md_path_for_run_file) in snippets.iter() {
775 if snippet_count_run == 0 {
776 runtime.new_file_path_new_env_new_name_scope(md_path_for_run_file.as_str());
777 } else {
778 runtime.clear_current_env_and_parse_name_scope();
779 runtime.set_current_user_lit_file_path(md_path_for_run_file.as_str());
780 }
781 snippet_count_run += 1;
782
783 let normalized_source = remove_windows_carriage_return(source_code);
784 let start_snippet = Instant::now();
785 let run_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
786 run_source_code(normalized_source.as_str(), &mut runtime)
787 }));
788 let (stmt_results, runtime_error) = match run_result {
789 Ok(result) => result,
790 Err(panic_payload) => {
791 let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
792 let panic_message =
793 if let Some(message) = panic_payload.downcast_ref::<&str>() {
794 message.to_string()
795 } else if let Some(message) = panic_payload.downcast_ref::<String>() {
796 message.clone()
797 } else {
798 "non-string panic payload".to_string()
799 };
800 println!(
801 "=== [PANICKED] The-Mechanics-of-Litex-Proof markdown snippet ({:.2} ms) ===\n{}\n>>> PANICKED snippet (open .md here): {}\n",
802 duration_ms, panic_message, label
803 );
804 failed_labels.push(label.clone());
805 break;
806 }
807 };
808 let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
809
810 let (run_succeeded, run_output) =
811 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
812
813 if !run_succeeded {
814 let status_label = if run_output.contains("\"error_type\": \"UnknownError\"") {
815 "UNKNOWN"
816 } else {
817 "FAILED"
818 };
819 println!(
820 "=== [{}] The-Mechanics-of-Litex-Proof markdown snippet ({:.2} ms) ===\n{}\n>>> {} snippet (open .md here): {}\n",
821 status_label, duration_ms, run_output, status_label, label
822 );
823 failed_labels.push(label.clone());
824 break;
825 }
826 snippet_durations_ms.push((label.clone(), duration_ms));
827 }
828 }
829
830 let status_text = if failed_labels.is_empty() {
831 "all OK"
832 } else {
833 "completed with failures"
834 };
835 println!(
836 "--- The-Mechanics-of-Litex-Proof markdown: {} ```litex``` block(s) in {} markdown file(s), {} ({:.2} ms wall) ---",
837 total_snippet_count,
838 file_count_with_snippets,
839 status_text,
840 wall_start.elapsed().as_secs_f64() * 1000.0
841 );
842 for (label, duration_ms) in snippet_durations_ms.iter() {
843 println!(" OK {:.2} ms {}", duration_ms, label);
844 }
845
846 if !failed_labels.is_empty() {
847 println!("--- The-Mechanics-of-Litex-Proof markdown failed snippets ---");
848 for label in failed_labels.iter() {
849 println!("{}", label);
850 }
851 }
852 assert!(
853 failed_labels.is_empty(),
854 "The-Mechanics-of-Litex-Proof markdown has {} failing snippet(s); see output above",
855 failed_labels.len()
856 );
857 }
858
859 fn collect_lit_files_recursive_under(manifest_dir: &Path, subdir: &str) -> Vec<PathBuf> {
862 let dir_path = manifest_dir.join(subdir);
863 if !dir_path.is_dir() {
864 println!("--- {} {:?}: directory missing; skip ---", subdir, dir_path);
865 return Vec::new();
866 }
867 fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
868 let read_directory = match fs::read_dir(dir) {
869 Ok(entries) => entries,
870 Err(read_error) => panic!("failed to read {:?}: {}", dir, read_error),
871 };
872 for directory_entry_result in read_directory {
873 let directory_entry = match directory_entry_result {
874 Ok(entry) => entry,
875 Err(read_error) => panic!("failed to read directory entry: {}", read_error),
876 };
877 let path = directory_entry.path();
878 let Ok(file_type) = directory_entry.file_type() else {
879 continue;
880 };
881 if file_type.is_dir() {
882 walk(&path, out);
883 } else if path.extension().is_some_and(|ext| ext == "lit") {
884 out.push(path);
885 }
886 }
887 }
888 let mut lit_file_paths = Vec::new();
889 walk(&dir_path, &mut lit_file_paths);
890 lit_file_paths.sort();
891 lit_file_paths
892 }
893
894 fn print_run_examples_timing_summary(
896 builtin_duration_ms: f64,
897 examples_ran: bool,
898 example_runs_ms: &[(String, f64)],
899 examples_phase_wall_ms: f64,
900 doc_runs_ms: &[(String, f64)],
901 docs_phase_wall_ms: f64,
902 ) {
903 let examples_sum_ms: f64 = example_runs_ms.iter().map(|(_, ms)| *ms).sum();
904 let docs_sum_ms: f64 = doc_runs_ms.iter().map(|(_, ms)| *ms).sum();
905 println!("--- timing (summary) ---");
906 println!(" builtin init (once): {:.2} ms", builtin_duration_ms);
907 if examples_ran {
908 println!(
909 " phase 1 (examples/*.lit + docs/Manual ```litex```): sum of runs: {:.2} ms | wall: {:.2} ms",
910 examples_sum_ms, examples_phase_wall_ms
911 );
912 }
913 println!(
914 " remaining markdown ```litex``` snippets (README + docs excluding docs/Manual; see phase 1): sum of runs: {:.2} ms | wall: {:.2} ms",
915 docs_sum_ms, docs_phase_wall_ms
916 );
917 println!(
918 "--- phase timing: phase1 {:.2} ms | docs {:.2} ms ---",
919 examples_phase_wall_ms, docs_phase_wall_ms
920 );
921
922 let mut all_runs_ms: Vec<(String, f64)> = Vec::new();
923 for (label, duration_ms) in example_runs_ms.iter() {
924 all_runs_ms.push((format!("phase 1: {}", label), *duration_ms));
925 }
926 for (label, duration_ms) in doc_runs_ms.iter() {
927 all_runs_ms.push((format!("docs: {}", label), *duration_ms));
928 }
929 print_slowest_run_labels("all examples/docs runs", all_runs_ms.as_slice());
930 }
931
932 fn print_slowest_run_labels(title: &str, run_durations_ms: &[(String, f64)]) {
933 if run_durations_ms.is_empty() {
934 return;
935 }
936
937 let mut sorted_runs = run_durations_ms.to_vec();
938 sorted_runs.sort_by(|left, right| {
939 right
940 .1
941 .partial_cmp(&left.1)
942 .unwrap_or(std::cmp::Ordering::Equal)
943 });
944
945 let count_to_print = SLOWEST_RUNS_TO_PRINT.min(sorted_runs.len());
946 println!(
947 "--- slowest {}: top {} of {} ---",
948 title,
949 count_to_print,
950 sorted_runs.len()
951 );
952 for (index, (label, duration_ms)) in sorted_runs.iter().take(count_to_print).enumerate() {
953 println!(" {:>2}. {:.2} ms {}", index + 1, duration_ms, label);
954 }
955 }
956
957 #[test]
958 fn run_examples() {
959 run_with_large_stack("run_examples_large_stack", run_examples_impl);
960 }
961
962 #[test]
963 fn run_all() {
964 run_with_large_stack("run_all_large_stack", run_all_impl);
965 }
966
967 #[test]
972 fn run_math500_tmp() {
973 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
974 let local_tmp_path = manifest_dir.join("tmp").join("math500_work.lit");
975 let sibling_tmp_path = match manifest_dir.parent() {
976 Some(parent) => parent
977 .join("MATH-500-litex")
978 .join("tmp")
979 .join("math500_work.lit"),
980 None => PathBuf::new(),
981 };
982
983 let math500_tmp_path = if local_tmp_path.is_file() {
984 local_tmp_path
985 } else if sibling_tmp_path.is_file() {
986 sibling_tmp_path
987 } else {
988 println!("--- run_math500_tmp: skip (missing file) ---");
989 println!(" checked: {}", local_tmp_path.display());
990 if !sibling_tmp_path.as_os_str().is_empty() {
991 println!(" checked: {}", sibling_tmp_path.display());
992 }
993 return;
994 };
995 println!(
996 "--- run_math500_tmp: using {} ---",
997 math500_tmp_path.display()
998 );
999
1000 let source_code = match fs::read_to_string(&math500_tmp_path) {
1001 Ok(content) => content,
1002 Err(read_error) => panic!("failed to read {:?}: {}", math500_tmp_path, read_error),
1003 };
1004
1005 #[derive(Clone)]
1006 struct Snippet {
1007 label: String,
1008 source: String,
1009 }
1010
1011 let mut snippets: Vec<Snippet> = Vec::new();
1014 let mut current_lines: Vec<String> = Vec::new();
1015 let mut current_label: Option<String> = None;
1016
1017 for line in source_code.lines() {
1018 if line.starts_with("# test/") {
1019 if let Some(label) = current_label.take() {
1020 let body = current_lines.join("\n");
1021 if !body.trim().is_empty() {
1022 snippets.push(Snippet {
1023 label,
1024 source: body,
1025 });
1026 }
1027 }
1028 current_lines.clear();
1029 current_label = Some(line.trim().to_string());
1030 }
1031 if current_label.is_some() {
1032 current_lines.push(line.to_string());
1033 }
1034 }
1035 if let Some(label) = current_label.take() {
1036 let body = current_lines.join("\n");
1037 if !body.trim().is_empty() {
1038 snippets.push(Snippet {
1039 label,
1040 source: body,
1041 });
1042 }
1043 }
1044 if snippets.is_empty() {
1045 snippets.push(Snippet {
1047 label: "math500_work.lit (whole file)".to_string(),
1048 source: source_code,
1049 });
1050 }
1051
1052 let path_for_runtime = match math500_tmp_path.to_str() {
1053 Some(path_string) => path_string,
1054 None => panic!("{:?} must be valid UTF-8", math500_tmp_path),
1055 };
1056
1057 let mut runtime = Runtime::new_with_builtin_code();
1058 runtime.new_file_path_new_env_new_name_scope(path_for_runtime);
1059
1060 let mut durations_ms: Vec<(String, f64)> = Vec::new();
1061 for (snippet_index, snippet) in snippets.iter().enumerate() {
1062 if snippet_index > 0 {
1063 runtime.clear_current_env_and_parse_name_scope();
1064 runtime.set_current_user_lit_file_path(path_for_runtime);
1065 }
1066
1067 let normalized_source = remove_windows_carriage_return(snippet.source.as_str());
1068 let start_time = Instant::now();
1069 let (stmt_results, runtime_error) =
1070 run_source_code(normalized_source.as_str(), &mut runtime);
1071 let duration_ms = start_time.elapsed().as_secs_f64() * 1000.0;
1072
1073 let (run_succeeded, run_output) =
1074 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1075
1076 durations_ms.push((snippet.label.clone(), duration_ms));
1077
1078 if !run_succeeded {
1079 print_slowest_run_labels(
1080 "math500 snippets before failure",
1081 durations_ms.as_slice(),
1082 );
1083 panic!(
1084 "math500 snippet FAILED:\n{}\n>>> FAILED snippet: {}\n",
1085 run_output, snippet.label
1086 );
1087 }
1088 }
1089
1090 print_slowest_run_labels("math500 snippets", durations_ms.as_slice());
1091 for (label, duration_ms) in durations_ms.iter() {
1092 println!(" OK {:.2} ms {}", duration_ms, label);
1093 }
1094 }
1095
1096 #[test]
1097 fn run_math500_litex_simple() {
1098 run_with_large_stack(
1099 "run_math500_litex_simple_large_stack",
1100 run_math500_litex_simple_impl,
1101 );
1102 }
1103
1104 #[test]
1105 fn run_math500_litex_all() {
1106 run_with_large_stack(
1107 "run_math500_litex_all_large_stack",
1108 run_math500_litex_all_impl,
1109 );
1110 }
1111
1112 fn run_math500_litex_simple_impl() {
1113 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1114 let completed_dir = manifest_dir
1115 .join("MATH-500-litex")
1116 .join("math-500")
1117 .join("finished");
1118 assert!(
1119 completed_dir.is_dir(),
1120 "MATH-500-litex/math-500/finished must exist at {:?}",
1121 completed_dir
1122 );
1123 run_math500_litex_lit_dir(&completed_dir);
1124 }
1125
1126 fn run_math500_litex_all_impl() {
1127 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1128 let lit_dir = manifest_dir
1129 .join("MATH-500-litex")
1130 .join("math-500")
1131 .join("unfinished");
1132 assert!(
1133 lit_dir.is_dir(),
1134 "MATH-500-litex/math-500/unfinished must exist at {:?}",
1135 lit_dir
1136 );
1137 run_math500_litex_lit_dir(&lit_dir);
1138 }
1139
1140 fn run_math500_litex_lit_dir(base_dir: &Path) {
1141 fn collect_lit_files(dir: &Path, out: &mut Vec<PathBuf>) {
1142 let read_directory = fs::read_dir(dir)
1143 .unwrap_or_else(|read_error| panic!("failed to read {:?}: {}", dir, read_error));
1144 for directory_entry_result in read_directory {
1145 let directory_entry = directory_entry_result.unwrap_or_else(|read_error| {
1146 panic!("failed to read directory entry: {}", read_error)
1147 });
1148 let path = directory_entry.path();
1149 let file_type = directory_entry
1150 .file_type()
1151 .unwrap_or_else(|file_type_error| {
1152 panic!(
1153 "failed to read file type for {:?}: {}",
1154 path, file_type_error
1155 )
1156 });
1157 if file_type.is_dir() {
1158 collect_lit_files(path.as_path(), out);
1159 } else if path.extension().is_some_and(|ext| ext == "lit") {
1160 out.push(path);
1161 }
1162 }
1163 }
1164
1165 let mut lit_paths: Vec<PathBuf> = Vec::new();
1166 collect_lit_files(base_dir, &mut lit_paths);
1167 lit_paths.sort();
1168
1169 assert!(
1170 !lit_paths.is_empty(),
1171 "{:?} must contain at least one .lit file",
1172 base_dir
1173 );
1174
1175 let base_dir_str = base_dir.to_string_lossy().to_string();
1176
1177 let builtin_start = Instant::now();
1178 let mut runtime = Runtime::new_with_builtin_code();
1179 let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1180 runtime.new_file_path_new_env_new_name_scope(base_dir_str.as_str());
1181
1182 let run_wall_start = Instant::now();
1183 let mut total_count: usize = 0;
1184 let mut failed_labels: Vec<String> = Vec::new();
1185 let mut total_solution_duration_ms: f64 = 0.0;
1186
1187 for lit_path in lit_paths.iter() {
1188 if total_count > 0 {
1189 runtime.clear_current_env_and_parse_name_scope();
1190 }
1191
1192 let lit_path_str = lit_path.to_string_lossy().to_string();
1193 runtime.set_current_user_lit_file_path(lit_path_str.as_str());
1194
1195 let relative_label = match lit_path.strip_prefix(base_dir) {
1196 Ok(relative_path) => relative_path.to_string_lossy().to_string(),
1197 Err(_) => lit_path_str.clone(),
1198 };
1199
1200 let litex_code = fs::read_to_string(lit_path).unwrap_or_else(|read_error| {
1201 panic!("failed to read {:?}: {}", lit_path, read_error)
1202 });
1203 let litex_code = litex_code.trim();
1204 if litex_code.is_empty() {
1205 println!("--- [SKIP] empty .lit file: {} ---", relative_label);
1206 continue;
1207 }
1208
1209 let normalized_source = remove_windows_carriage_return(litex_code);
1210
1211 let start_time_for_one_solution = Instant::now();
1212 let (stmt_results, runtime_error) =
1213 run_source_code(normalized_source.as_str(), &mut runtime);
1214 let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1215 total_solution_duration_ms += duration_ms;
1216
1217 let (run_succeeded, run_output) =
1218 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1219
1220 total_count += 1;
1221 if !run_succeeded {
1222 let label = relative_label.clone();
1223 println!(
1224 "=== [FAILED] math500-litex simple at .lit file {} ({:.2} ms) ===\n{}\n",
1225 relative_label, duration_ms, run_output
1226 );
1227 failed_labels.push(label);
1228 }
1229 }
1230
1231 let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1232 println!("--- math500-litex simple timing (summary) ---");
1233 println!(" builtin init (once): {:.2} ms", builtin_duration_ms);
1234 println!(
1235 " snippets: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1236 total_count, total_solution_duration_ms, run_wall_ms
1237 );
1238
1239 if failed_labels.is_empty() {
1240 println!("--- math500-litex simple: all snippets OK ---");
1241 return;
1242 }
1243
1244 println!("--- math500-litex simple failed unique_id ---");
1245 for label in failed_labels.iter() {
1246 println!("{}", label);
1247 }
1248 panic!(
1249 "math500-litex simple failed for {} of {} item(s)",
1250 failed_labels.len(),
1251 total_count
1252 );
1253 }
1254
1255 fn run_all_impl() {
1256 run_examples_impl();
1257 run_the_mechanics_markdown_files_impl();
1258 }
1259
1260 #[test]
1261 fn run_the_mechanics_of_litex_proof() {
1262 run_with_large_stack(
1263 "run_the_mechanics_of_litex_proof_large_stack",
1264 run_the_mechanics_of_litex_proof_impl,
1265 );
1266 }
1267
1268 fn run_the_mechanics_of_litex_proof_impl() {
1269 run_the_mechanics_markdown_files_impl();
1270 }
1271
1272 fn run_examples_impl() {
1273 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1274 let lit_file_paths = collect_lit_files_recursive_under(&manifest_dir, "examples");
1275
1276 let manual_md_dir = manifest_dir.join("docs").join("Manual");
1277 let manual_md_paths = collect_markdown_files_under_dir_sorted(&manual_md_dir);
1278 let manual_snippets = litex_snippets_from_markdown_files(&manifest_dir, &manual_md_paths);
1279
1280 #[derive(Clone)]
1281 struct Phase1Item {
1282 report_label: String,
1283 source: String,
1284 path_for_runtime: String,
1285 }
1286
1287 let mut phase1_items: Vec<Phase1Item> = Vec::new();
1288 for lit_file_path in lit_file_paths.iter() {
1289 let lit_file_path_str = match lit_file_path.to_str() {
1290 Some(path_string) => path_string,
1291 None => panic!("{:?} must be valid UTF-8", lit_file_path),
1292 };
1293 let file_label_for_report = match lit_file_path.strip_prefix(&manifest_dir) {
1294 Ok(rel) => rel.display().to_string(),
1295 Err(_) => match lit_file_path.file_name() {
1296 Some(os_file_name) => match os_file_name.to_str() {
1297 Some(name_string) => String::from(name_string),
1298 None => format!("{:?}", lit_file_path),
1299 },
1300 None => format!("{:?}", lit_file_path),
1301 },
1302 };
1303 let source_code = match fs::read_to_string(lit_file_path) {
1304 Ok(content) => content,
1305 Err(read_error) => panic!("failed to read {:?}: {}", lit_file_path, read_error),
1306 };
1307 phase1_items.push(Phase1Item {
1308 report_label: file_label_for_report,
1309 source: source_code,
1310 path_for_runtime: lit_file_path_str.to_string(),
1311 });
1312 }
1313 for (label, block, md_path_str) in manual_snippets.iter() {
1314 phase1_items.push(Phase1Item {
1315 report_label: label.clone(),
1316 source: block.clone(),
1317 path_for_runtime: md_path_str.clone(),
1318 });
1319 }
1320
1321 let builtin_start = Instant::now();
1322 let mut runtime = Runtime::new_with_builtin_code();
1323 let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1324
1325 let mut file_label_and_duration_ms_list: Vec<(String, f64)> = Vec::new();
1326 let mut every_file_run_ok = true;
1327 let mut examples_ran = false;
1328 let mut examples_phase_wall_ms: f64 = 0.0;
1329
1330 if phase1_items.is_empty() {
1331 println!("--- phase 1: no examples/*.lit and no docs/Manual ```litex``` snippets ---");
1332 } else {
1333 examples_ran = true;
1334 let examples_wall_start = Instant::now();
1335 let first_path = phase1_items[0].path_for_runtime.as_str();
1336 runtime.new_file_path_new_env_new_name_scope(first_path);
1337
1338 for (item_index, item) in phase1_items.iter().enumerate() {
1339 if item_index > 0 {
1340 runtime.clear_current_env_and_parse_name_scope();
1341 runtime.set_current_user_lit_file_path(item.path_for_runtime.as_str());
1342 }
1343
1344 let normalized_source = remove_windows_carriage_return(item.source.as_str());
1345
1346 let start_time_for_one_file = Instant::now();
1347 let (stmt_results, runtime_error) =
1348 run_source_code(normalized_source.as_str(), &mut runtime);
1349 let duration_ms_for_one_file =
1350 start_time_for_one_file.elapsed().as_secs_f64() * 1000.0;
1351
1352 let (run_succeeded, run_output) =
1353 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1354
1355 if !run_succeeded {
1356 every_file_run_ok = false;
1357 file_label_and_duration_ms_list
1358 .push((item.report_label.clone(), duration_ms_for_one_file));
1359 print_slowest_run_labels(
1360 "phase 1 runs before failure",
1361 file_label_and_duration_ms_list.as_slice(),
1362 );
1363 println!(
1364 "=== [{}] {} ===\n{}\n>>> FAILED snippet (open .md here): {}\n",
1365 "FAILED", item.report_label, run_output, item.report_label
1366 );
1367 break;
1368 }
1369
1370 file_label_and_duration_ms_list
1371 .push((item.report_label.clone(), duration_ms_for_one_file));
1372 }
1373 examples_phase_wall_ms = examples_wall_start.elapsed().as_secs_f64() * 1000.0;
1374 }
1375
1376 if every_file_run_ok && examples_ran {
1377 println!(
1378 "--- phase 1: {} run(s) (examples/*.lit + docs/Manual ```litex```), all OK ---",
1379 file_label_and_duration_ms_list.len()
1380 );
1381 print_slowest_run_labels("phase 1 runs", file_label_and_duration_ms_list.as_slice());
1382 for (file_label, duration_ms) in file_label_and_duration_ms_list.iter() {
1383 println!(" {} {:.2} ms", file_label, duration_ms);
1384 }
1385 }
1386
1387 assert!(
1388 every_file_run_ok,
1389 "examples or docs/Manual litex snippet failed; see output above"
1390 );
1391
1392 let docs_dir = manifest_dir.join("docs");
1393 if !docs_dir.is_dir() {
1394 println!(
1395 "--- docs folder missing at {:?}; skip markdown litex blocks ---",
1396 docs_dir
1397 );
1398 print_run_examples_timing_summary(
1399 builtin_duration_ms,
1400 examples_ran,
1401 file_label_and_duration_ms_list.as_slice(),
1402 examples_phase_wall_ms,
1403 &[],
1404 0.0,
1405 );
1406 return;
1407 }
1408
1409 let mut md_paths_all: Vec<PathBuf> = Vec::new();
1410 let readme_path = manifest_dir.join("README.md");
1411 if readme_path.is_file() {
1412 md_paths_all.push(readme_path);
1413 }
1414 md_paths_all.extend(collect_markdown_files_under_dir_sorted(&docs_dir));
1415 md_paths_all.sort();
1416 let manual_prefix = manifest_dir.join("docs").join("Manual");
1417 let md_paths: Vec<PathBuf> = md_paths_all
1418 .into_iter()
1419 .filter(|p| !p.starts_with(&manual_prefix))
1420 .collect();
1421
1422 let mut doc_snippets: Vec<(String, String, String)> = Vec::new();
1424 for md_path in md_paths.iter() {
1425 let rel_label = md_path
1426 .strip_prefix(&manifest_dir)
1427 .unwrap_or(md_path)
1428 .display()
1429 .to_string();
1430 let md_current_path_str = md_path.to_string_lossy().into_owned();
1431 let md_content = match fs::read_to_string(md_path) {
1432 Ok(content) => content,
1433 Err(read_error) => panic!("failed to read {:?}: {}", md_path, read_error),
1434 };
1435 for (block_index, (md_line, block)) in extract_litex_fenced_blocks(&md_content)
1436 .into_iter()
1437 .enumerate()
1438 {
1439 doc_snippets.push((
1440 format!(
1441 "{} ```litex```#{} (md line {})",
1442 rel_label, block_index, md_line
1443 ),
1444 block,
1445 md_current_path_str.clone(),
1446 ));
1447 }
1448 }
1449
1450 if doc_snippets.is_empty() {
1451 println!("--- remaining markdown: no ```litex``` fenced blocks ---");
1452 print_run_examples_timing_summary(
1453 builtin_duration_ms,
1454 examples_ran,
1455 file_label_and_duration_ms_list.as_slice(),
1456 examples_phase_wall_ms,
1457 &[],
1458 0.0,
1459 );
1460 return;
1461 }
1462
1463 if !examples_ran {
1464 runtime.new_file_path_new_env_new_name_scope("remaining markdown ```litex``` snippets");
1465 }
1466
1467 println!(
1468 "--- remaining markdown: {} ```litex``` block(s) in {} markdown file(s) ---",
1469 doc_snippets.len(),
1470 md_paths.len()
1471 );
1472
1473 let docs_wall_start = Instant::now();
1474 let mut doc_durations_ms: Vec<(String, f64)> = Vec::new();
1475 for (snippet_index, (label, source_code, md_path_for_run_file)) in
1476 doc_snippets.iter().enumerate()
1477 {
1478 if examples_ran || snippet_index > 0 {
1479 runtime.clear_current_env_and_parse_name_scope();
1480 }
1481 runtime.set_current_user_lit_file_path(md_path_for_run_file.as_str());
1482
1483 let normalized_source = remove_windows_carriage_return(source_code);
1484 let start_snippet = Instant::now();
1485 let (stmt_results, runtime_error) =
1486 run_source_code(normalized_source.as_str(), &mut runtime);
1487 let duration_ms = start_snippet.elapsed().as_secs_f64() * 1000.0;
1488
1489 let (run_succeeded, run_output) =
1490 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1491
1492 doc_durations_ms.push((label.clone(), duration_ms));
1493
1494 if !run_succeeded {
1495 print_slowest_run_labels(
1496 "remaining markdown snippets before failure",
1497 doc_durations_ms.as_slice(),
1498 );
1499 panic!(
1500 "docs litex snippet FAILED:\n{}\n>>> FAILED snippet (open .md here): {}\n",
1501 run_output, label
1502 );
1503 }
1504 }
1505 let docs_phase_wall_ms = docs_wall_start.elapsed().as_secs_f64() * 1000.0;
1506
1507 print_slowest_run_labels("remaining markdown snippets", doc_durations_ms.as_slice());
1508 for (label, duration_ms) in doc_durations_ms.iter() {
1509 println!(" OK {:.2} ms {}", duration_ms, label);
1510 }
1511 print_run_examples_timing_summary(
1512 builtin_duration_ms,
1513 examples_ran,
1514 file_label_and_duration_ms_list.as_slice(),
1515 examples_phase_wall_ms,
1516 doc_durations_ms.as_slice(),
1517 docs_phase_wall_ms,
1518 );
1519 }
1520
1521 #[test]
1522 fn run_gsm8k_solutions() {
1523 run_with_large_stack("run_gsm8k_solutions_large_stack", run_gsm8k_solutions_impl);
1524 }
1525
1526 fn run_gsm8k_solutions_impl() {
1527 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1528 let jsonl_paths = vec![
1529 manifest_dir
1530 .join("scripts")
1531 .join("gsm8k-litex")
1532 .join("train.jsonl"),
1533 manifest_dir
1534 .join("scripts")
1535 .join("gsm8k-litex")
1536 .join("test.jsonl"),
1537 ];
1538
1539 for jsonl_path in jsonl_paths.iter() {
1540 if !jsonl_path.is_file() {
1541 println!(
1542 "--- gsm8k jsonl file missing at {:?}; skip gsm8k solutions ---",
1543 jsonl_path
1544 );
1545 return;
1546 }
1547 }
1548
1549 let builtin_start = Instant::now();
1550 let mut runtime = Runtime::new_with_builtin_code();
1551 let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1552
1553 let run_wall_start = Instant::now();
1554 let mut total_count: usize = 0;
1555 let mut failed_labels: Vec<String> = Vec::new();
1556 let mut total_solution_duration_ms: f64 = 0.0;
1557
1558 for jsonl_path in jsonl_paths.iter() {
1559 run_gsm8k_jsonl_file(
1560 jsonl_path,
1561 &mut runtime,
1562 &mut total_count,
1563 &mut failed_labels,
1564 &mut total_solution_duration_ms,
1565 );
1566 }
1567
1568 let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1569 println!("--- gsm8k timing (summary) ---");
1570 println!(" builtin init (once): {:.2} ms", builtin_duration_ms);
1571 println!(
1572 " solutions: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1573 total_count, total_solution_duration_ms, run_wall_ms
1574 );
1575
1576 if failed_labels.is_empty() {
1577 println!("--- gsm8k: all train/test solutions OK ---");
1578 return;
1579 }
1580
1581 println!("--- gsm8k failed titles ---");
1582 for label in failed_labels.iter() {
1583 println!("{}", label);
1584 }
1585 panic!(
1586 "gsm8k solution run failed for {} of {} item(s)",
1587 failed_labels.len(),
1588 total_count
1589 );
1590 }
1591
1592 fn run_gsm8k_jsonl_file(
1593 jsonl_path: &Path,
1594 runtime: &mut Runtime,
1595 total_count: &mut usize,
1596 failed_labels: &mut Vec<String>,
1597 total_solution_duration_ms: &mut f64,
1598 ) {
1599 let jsonl_path_str = match jsonl_path.to_str() {
1600 Some(path_string) => path_string.to_string(),
1601 None => panic!("{:?} must be valid UTF-8", jsonl_path),
1602 };
1603
1604 let jsonl_content = match fs::read_to_string(&jsonl_path) {
1605 Ok(content) => content,
1606 Err(read_error) => panic!("failed to read {:?}: {}", jsonl_path, read_error),
1607 };
1608
1609 if *total_count == 0 {
1610 runtime.new_file_path_new_env_new_name_scope(jsonl_path_str.as_str());
1611 } else {
1612 runtime.clear_current_env_and_parse_name_scope();
1613 runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1614 }
1615
1616 for (line_index, line) in jsonl_content.lines().enumerate() {
1617 if line.trim().is_empty() {
1618 continue;
1619 }
1620
1621 if *total_count > 0 || line_index > 0 {
1622 runtime.clear_current_env_and_parse_name_scope();
1623 runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1624 }
1625
1626 let title = jsonl_string_field(line, "title").unwrap_or_else(|error_message| {
1627 panic!(
1628 "failed to parse title in {:?} line {}: {}",
1629 jsonl_path,
1630 line_index + 1,
1631 error_message
1632 )
1633 });
1634 let solution = jsonl_string_field(line, "solution").unwrap_or_else(|error_message| {
1635 panic!(
1636 "failed to parse solution in {:?} line {} ({}): {}",
1637 jsonl_path,
1638 line_index + 1,
1639 title,
1640 error_message
1641 )
1642 });
1643 let normalized_source = remove_windows_carriage_return(solution.as_str());
1644
1645 let start_time_for_one_solution = Instant::now();
1646 let (stmt_results, runtime_error) =
1647 run_source_code(normalized_source.as_str(), runtime);
1648 let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1649 *total_solution_duration_ms += duration_ms;
1650
1651 let (run_succeeded, run_output) =
1652 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1653
1654 *total_count += 1;
1655 if !run_succeeded {
1656 let label = format!(
1657 "{}:{}",
1658 jsonl_path
1659 .file_name()
1660 .and_then(|file_name| file_name.to_str())
1661 .unwrap_or("gsm8k jsonl"),
1662 title
1663 );
1664 println!(
1665 "=== [FAILED] {} at jsonl line {} ({:.2} ms) ===\n{}\n",
1666 label,
1667 line_index + 1,
1668 duration_ms,
1669 run_output
1670 );
1671 failed_labels.push(label);
1672 }
1673
1674 if *total_count % 1000 == 0 {
1675 println!(
1676 "--- gsm8k progress: {} solution(s), {} failure(s) ---",
1677 total_count,
1678 failed_labels.len()
1679 );
1680 }
1681 }
1682 }
1683
1684 #[test]
1685 fn run_metamathqa_litex_solutions() {
1686 run_with_large_stack(
1687 "run_metamathqa_litex_solutions_large_stack",
1688 run_metamathqa_litex_solutions_impl,
1689 );
1690 }
1691
1692 #[test]
1693 fn run_minif2f_litex_completed() {
1694 run_with_large_stack(
1695 "run_minif2f_litex_completed_large_stack",
1696 run_minif2f_litex_completed_impl,
1697 );
1698 }
1699
1700 fn run_minif2f_litex_completed_impl() {
1701 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1702 let jsonl_path = manifest_dir
1703 .parent()
1704 .unwrap_or(manifest_dir.as_path())
1705 .join("MiniF2F-litex")
1706 .join("litex_completed.jsonl");
1707 assert!(
1708 jsonl_path.is_file(),
1709 "MiniF2F-litex/litex_completed.jsonl must exist at {:?}",
1710 jsonl_path
1711 );
1712
1713 let jsonl_path_str = match jsonl_path.to_str() {
1714 Some(path_string) => path_string.to_string(),
1715 None => panic!("{:?} must be valid UTF-8", jsonl_path),
1716 };
1717 let jsonl_content = match fs::read_to_string(&jsonl_path) {
1718 Ok(content) => content,
1719 Err(read_error) => panic!("failed to read {:?}: {}", jsonl_path, read_error),
1720 };
1721
1722 let builtin_start = Instant::now();
1723 let mut runtime = Runtime::new_with_builtin_code();
1724 let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1725 runtime.new_file_path_new_env_new_name_scope(jsonl_path_str.as_str());
1726
1727 let run_wall_start = Instant::now();
1728 let mut total_count: usize = 0;
1729 let mut failed_labels: Vec<String> = Vec::new();
1730 let mut total_solution_duration_ms: f64 = 0.0;
1731
1732 for (line_index, line) in jsonl_content.lines().enumerate() {
1733 if line.trim().is_empty() {
1734 continue;
1735 }
1736 if total_count > 0 {
1737 runtime.clear_current_env_and_parse_name_scope();
1738 runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1739 }
1740
1741 let name = jsonl_string_field(line, "name").unwrap_or_else(|error_message| {
1742 panic!(
1743 "failed to parse name in {:?} line {}: {}",
1744 jsonl_path,
1745 line_index + 1,
1746 error_message
1747 )
1748 });
1749 let litex_code =
1750 jsonl_string_field(line, "litex_code").unwrap_or_else(|error_message| {
1751 panic!(
1752 "failed to parse litex_code in {:?} line {} ({}): {}",
1753 jsonl_path,
1754 line_index + 1,
1755 name,
1756 error_message
1757 )
1758 });
1759
1760 let normalized_source = remove_windows_carriage_return(litex_code.as_str());
1761 let start_time_for_one_solution = Instant::now();
1762 let (stmt_results, runtime_error) =
1763 run_source_code(normalized_source.as_str(), &mut runtime);
1764 let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1765 total_solution_duration_ms += duration_ms;
1766
1767 let (run_succeeded, run_output) =
1768 render_run_source_code_output(&runtime, &stmt_results, &runtime_error, false);
1769
1770 total_count += 1;
1771 if !run_succeeded {
1772 let label = format!("{}:{}", line_index + 1, name);
1773 println!(
1774 "=== [FAILED] MiniF2F-litex at jsonl line {} ({:.2} ms): {} ===\n{}\n",
1775 line_index + 1,
1776 duration_ms,
1777 name,
1778 run_output
1779 );
1780 failed_labels.push(label);
1781 }
1782 }
1783
1784 let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1785 println!("--- MiniF2F-litex timing (summary) ---");
1786 println!(" builtin init (once): {:.2} ms", builtin_duration_ms);
1787 println!(
1788 " completed snippets: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1789 total_count, total_solution_duration_ms, run_wall_ms
1790 );
1791
1792 if failed_labels.is_empty() {
1793 println!("--- MiniF2F-litex: all completed snippets OK ---");
1794 return;
1795 }
1796
1797 println!("--- MiniF2F-litex failed names ---");
1798 for label in failed_labels.iter() {
1799 println!("{}", label);
1800 }
1801 panic!(
1802 "MiniF2F-litex completed snippet run failed for {} of {} item(s)",
1803 failed_labels.len(),
1804 total_count
1805 );
1806 }
1807
1808 fn run_metamathqa_litex_solutions_impl() {
1809 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1810 let jsonl_path = manifest_dir
1811 .join("scripts")
1812 .join("MetaMathQA-litex")
1813 .join("MetaMathQA.jsonl");
1814 assert!(
1815 jsonl_path.is_file(),
1816 "MetaMathQA-litex jsonl file must exist at {:?}",
1817 jsonl_path
1818 );
1819
1820 let builtin_start = Instant::now();
1821 let mut runtime = Runtime::new_with_builtin_code();
1822 let builtin_duration_ms = builtin_start.elapsed().as_secs_f64() * 1000.0;
1823
1824 let run_wall_start = Instant::now();
1825 let mut total_count: usize = 0;
1826 let mut failed_labels: Vec<String> = Vec::new();
1827 let mut total_solution_duration_ms: f64 = 0.0;
1828
1829 run_metamathqa_jsonl_file(
1830 &jsonl_path,
1831 &mut runtime,
1832 &mut total_count,
1833 &mut failed_labels,
1834 &mut total_solution_duration_ms,
1835 );
1836
1837 let run_wall_ms = run_wall_start.elapsed().as_secs_f64() * 1000.0;
1838 println!("--- MetaMathQA-litex timing (summary) ---");
1839 println!(" builtin init (once): {:.2} ms", builtin_duration_ms);
1840 println!(
1841 " solutions: {} run(s), sum of runs: {:.2} ms | wall: {:.2} ms",
1842 total_count, total_solution_duration_ms, run_wall_ms
1843 );
1844
1845 if failed_labels.is_empty() {
1846 println!("--- MetaMathQA-litex: all solutions OK ---");
1847 return;
1848 }
1849
1850 println!("--- MetaMathQA-litex failed titles ---");
1851 for label in failed_labels.iter() {
1852 println!("{}", label);
1853 }
1854 panic!(
1855 "MetaMathQA-litex solution run failed for {} of {} item(s)",
1856 failed_labels.len(),
1857 total_count
1858 );
1859 }
1860
1861 fn run_metamathqa_jsonl_file(
1862 jsonl_path: &Path,
1863 runtime: &mut Runtime,
1864 total_count: &mut usize,
1865 failed_labels: &mut Vec<String>,
1866 total_solution_duration_ms: &mut f64,
1867 ) {
1868 let jsonl_path_str = match jsonl_path.to_str() {
1869 Some(path_string) => path_string.to_string(),
1870 None => panic!("{:?} must be valid UTF-8", jsonl_path),
1871 };
1872
1873 let jsonl_content = match fs::read_to_string(jsonl_path) {
1874 Ok(content) => content,
1875 Err(read_error) => panic!("failed to read {:?}: {}", jsonl_path, read_error),
1876 };
1877
1878 runtime.new_file_path_new_env_new_name_scope(jsonl_path_str.as_str());
1879
1880 for (line_index, line) in jsonl_content.lines().enumerate() {
1881 if line.trim().is_empty() {
1882 continue;
1883 }
1884
1885 if line_index > 0 {
1886 runtime.clear_current_env_and_parse_name_scope();
1887 runtime.set_current_user_lit_file_path(jsonl_path_str.as_str());
1888 }
1889
1890 let title = jsonl_string_field(line, "title").unwrap_or_else(|error_message| {
1891 panic!(
1892 "failed to parse title in {:?} line {}: {}",
1893 jsonl_path,
1894 line_index + 1,
1895 error_message
1896 )
1897 });
1898 let solution = jsonl_string_field(line, "solution").unwrap_or_else(|error_message| {
1899 panic!(
1900 "failed to parse solution in {:?} line {} ({}): {}",
1901 jsonl_path,
1902 line_index + 1,
1903 title,
1904 error_message
1905 )
1906 });
1907 let normalized_source = remove_windows_carriage_return(solution.as_str());
1908
1909 let start_time_for_one_solution = Instant::now();
1910 let (stmt_results, runtime_error) =
1911 run_source_code(normalized_source.as_str(), runtime);
1912 let duration_ms = start_time_for_one_solution.elapsed().as_secs_f64() * 1000.0;
1913 *total_solution_duration_ms += duration_ms;
1914
1915 let (run_succeeded, run_output) =
1916 render_run_source_code_output(runtime, &stmt_results, &runtime_error, false);
1917
1918 *total_count += 1;
1919 if !run_succeeded {
1920 let label = format!("{}:{}", line_index + 1, title);
1921 println!(
1922 "=== [FAILED] MetaMathQA-litex at jsonl line {} ({:.2} ms): {} ===\n{}\n",
1923 line_index + 1,
1924 duration_ms,
1925 title,
1926 run_output
1927 );
1928 failed_labels.push(label);
1929 }
1930
1931 if *total_count % 100 == 0 {
1932 println!(
1933 "--- MetaMathQA-litex progress: {} solution(s), {} failure(s) ---",
1934 total_count,
1935 failed_labels.len()
1936 );
1937 }
1938 }
1939 }
1940
1941 fn jsonl_string_field(line: &str, key: &str) -> Result<String, String> {
1942 let field_name = format!("\"{}\"", key);
1943 let field_start = line
1944 .find(field_name.as_str())
1945 .ok_or_else(|| format!("missing JSON field `{}`", key))?;
1946 let after_field_name = field_start + field_name.len();
1947 let colon_offset = line[after_field_name..]
1948 .find(':')
1949 .ok_or_else(|| format!("missing `:` after JSON field `{}`", key))?;
1950 let mut value_start = after_field_name + colon_offset + 1;
1951 while value_start < line.len() && line.as_bytes()[value_start].is_ascii_whitespace() {
1952 value_start += 1;
1953 }
1954 parse_json_string_at(line, value_start)
1955 }
1956
1957 fn parse_json_string_at(line: &str, start_index: usize) -> Result<String, String> {
1958 if start_index >= line.len() || line.as_bytes()[start_index] != b'"' {
1959 return Err("JSON field value must be a string".to_string());
1960 }
1961
1962 let mut result = String::new();
1963 let mut chars = line[start_index + 1..].chars();
1964 while let Some(ch) = chars.next() {
1965 if ch == '"' {
1966 return Ok(result);
1967 }
1968 if ch != '\\' {
1969 result.push(ch);
1970 continue;
1971 }
1972
1973 let escaped = chars
1974 .next()
1975 .ok_or_else(|| "unfinished JSON escape".to_string())?;
1976 match escaped {
1977 '"' => result.push('"'),
1978 '\\' => result.push('\\'),
1979 '/' => result.push('/'),
1980 'b' => result.push('\u{0008}'),
1981 'f' => result.push('\u{000c}'),
1982 'n' => result.push('\n'),
1983 'r' => result.push('\r'),
1984 't' => result.push('\t'),
1985 'u' => {
1986 let mut hex = String::new();
1987 for _ in 0..4 {
1988 hex.push(
1989 chars
1990 .next()
1991 .ok_or_else(|| "unfinished JSON unicode escape".to_string())?,
1992 );
1993 }
1994 let code = u32::from_str_radix(hex.as_str(), 16)
1995 .map_err(|_| format!("invalid JSON unicode escape: {}", hex))?;
1996 let decoded = char::from_u32(code)
1997 .ok_or_else(|| format!("invalid JSON unicode code point: {}", hex))?;
1998 result.push(decoded);
1999 }
2000 other => return Err(format!("unknown JSON escape: \\{}", other)),
2001 }
2002 }
2003
2004 Err("unterminated JSON string".to_string())
2005 }
2006}