1use std::path::PathBuf;
25use std::process::Command;
26
27use serde::{Deserialize, Serialize};
28use xlsynth_prover::prover::SolverChoice;
29use xlsynth_prover::prover::types::AssertionSemantics;
30use xlsynth_prover::prover::types::EquivParallelism;
31use xlsynth_prover::prover::types::QuickCheckAssertionSemantics;
32
33fn add_flag(cmd: &mut Command, name: &str, value: &str) {
34 cmd.arg(format!("--{}", name)).arg(value);
35}
36
37fn add_bool(cmd: &mut Command, name: &str, value: Option<bool>) {
38 if let Some(v) = value {
39 add_flag(cmd, name, if v { "true" } else { "false" });
40 }
41}
42
43fn resolve_driver_exe() -> std::path::PathBuf {
44 if let Ok(p) = std::env::var("CARGO_BIN_EXE_xlsynth-driver") {
45 return std::path::PathBuf::from(p);
46 }
47 let cur = std::env::current_exe().expect("resolve current exe");
48 if let Some(deps_dir) = cur.parent() {
49 if let Some(debug_dir) = deps_dir.parent() {
50 let candidate = debug_dir.join("xlsynth-driver");
51 if candidate.exists() {
52 return candidate;
53 }
54 }
55 }
56 cur
57}
58
59#[derive(Clone, Debug, Default, Serialize, Deserialize)]
60pub struct IrEquivConfig {
61 pub lhs_ir_file: PathBuf,
62 pub rhs_ir_file: PathBuf,
63 pub top: Option<String>,
65 pub lhs_ir_top: Option<String>,
66 pub rhs_ir_top: Option<String>,
67
68 pub solver: Option<SolverChoice>,
69 pub flatten_aggregates: Option<bool>,
70 pub drop_params: Option<Vec<String>>, pub parallelism_strategy: Option<EquivParallelism>,
72 pub assertion_semantics: Option<AssertionSemantics>,
73 pub lhs_fixed_implicit_activation: Option<bool>,
74 pub rhs_fixed_implicit_activation: Option<bool>,
75 pub assert_label_filter: Option<String>,
77
78 pub json: Option<bool>,
79}
80
81#[derive(Clone, Debug, Default, Serialize, Deserialize)]
82pub struct DslxEquivConfig {
83 pub lhs_dslx_file: PathBuf,
84 pub rhs_dslx_file: PathBuf,
85
86 pub dslx_top: Option<String>,
88 pub lhs_dslx_top: Option<String>,
89 pub rhs_dslx_top: Option<String>,
90
91 pub dslx_path: Option<Vec<PathBuf>>, pub dslx_stdlib_path: Option<PathBuf>,
94
95 pub solver: Option<SolverChoice>,
97 pub flatten_aggregates: Option<bool>,
98 pub drop_params: Option<Vec<String>>, pub parallelism_strategy: Option<EquivParallelism>,
100 pub assertion_semantics: Option<AssertionSemantics>,
101 pub lhs_fixed_implicit_activation: Option<bool>,
102 pub rhs_fixed_implicit_activation: Option<bool>,
103 pub assume_enum_in_bound: Option<bool>,
104 pub type_inference_v2: Option<bool>, pub assert_label_filter: Option<String>,
107
108 pub lhs_uf: Option<Vec<String>>, pub rhs_uf: Option<Vec<String>>, pub json: Option<bool>,
114}
115
116#[derive(Clone, Debug, Default, Serialize, Deserialize)]
117pub struct ProveQuickcheckConfig {
118 pub dslx_input_file: PathBuf,
119 pub dslx_path: Option<Vec<PathBuf>>,
120 pub dslx_stdlib_path: Option<PathBuf>,
121 pub test_filter: Option<String>,
122 pub solver: Option<SolverChoice>,
123 pub assertion_semantics: Option<QuickCheckAssertionSemantics>,
124 pub uf: Option<Vec<String>>,
127 pub assert_label_filter: Option<String>,
129 pub json: Option<bool>,
130}
131
132#[derive(Clone, Debug, Default, Serialize, Deserialize)]
133pub struct DslxFnProveAssertionsConfig {
134 pub dslx_input_file: PathBuf,
135 pub dslx_top: String,
136 pub dslx_path: Option<Vec<PathBuf>>,
137 pub dslx_stdlib_path: Option<PathBuf>,
138 pub solver: Option<SolverChoice>,
139 pub assert_label_filter: Option<String>,
141 pub assume_enum_in_bound: Option<bool>,
142 pub uf: Option<Vec<String>>,
145}
146
147pub trait ToDriverCommand {
148 fn to_command(&self) -> Command;
149}
150
151impl ToDriverCommand for IrEquivConfig {
152 fn to_command(&self) -> Command {
153 let exe = resolve_driver_exe();
154 let mut cmd = Command::new(exe);
155 cmd.arg("ir-equiv");
156 cmd.arg(self.lhs_ir_file.as_os_str());
157 cmd.arg(self.rhs_ir_file.as_os_str());
158 if let Some(top) = &self.top {
159 add_flag(&mut cmd, "top", top);
160 }
161 if let Some(lhs_top) = &self.lhs_ir_top {
162 add_flag(&mut cmd, "lhs_ir_top", lhs_top);
163 }
164 if let Some(rhs_top) = &self.rhs_ir_top {
165 add_flag(&mut cmd, "rhs_ir_top", rhs_top);
166 }
167 if let Some(solver) = &self.solver {
168 add_flag(&mut cmd, "solver", &solver.to_string());
169 }
170 add_bool(&mut cmd, "flatten_aggregates", self.flatten_aggregates);
171 if let Some(drop) = &self.drop_params {
172 if !drop.is_empty() {
173 add_flag(&mut cmd, "drop_params", &drop.join(","));
174 }
175 }
176 if let Some(strategy) = &self.parallelism_strategy {
177 add_flag(&mut cmd, "parallelism-strategy", &strategy.to_string());
178 }
179 if let Some(sem) = &self.assertion_semantics {
180 add_flag(&mut cmd, "assertion-semantics", &sem.to_string());
181 }
182 if let Some(pat) = &self.assert_label_filter {
183 add_flag(&mut cmd, "assert-label-filter", pat);
184 }
185 add_bool(
186 &mut cmd,
187 "lhs_fixed_implicit_activation",
188 self.lhs_fixed_implicit_activation,
189 );
190 add_bool(
191 &mut cmd,
192 "rhs_fixed_implicit_activation",
193 self.rhs_fixed_implicit_activation,
194 );
195 add_bool(&mut cmd, "json", self.json);
196 cmd
197 }
198}
199
200impl ToDriverCommand for DslxEquivConfig {
201 fn to_command(&self) -> Command {
202 let exe = resolve_driver_exe();
203 let mut cmd = Command::new(exe);
204 cmd.arg("dslx-equiv");
205 cmd.arg(self.lhs_dslx_file.as_os_str());
206 cmd.arg(self.rhs_dslx_file.as_os_str());
207
208 if let Some(top) = &self.dslx_top {
209 add_flag(&mut cmd, "dslx_top", top);
210 }
211 if let Some(lhs_top) = &self.lhs_dslx_top {
212 add_flag(&mut cmd, "lhs_dslx_top", lhs_top);
213 }
214 if let Some(rhs_top) = &self.rhs_dslx_top {
215 add_flag(&mut cmd, "rhs_dslx_top", rhs_top);
216 }
217 if let Some(stdlib) = &self.dslx_stdlib_path {
218 add_flag(&mut cmd, "dslx_stdlib_path", &stdlib.display().to_string());
219 }
220 if let Some(paths) = &self.dslx_path {
221 if !paths.is_empty() {
222 let joined = paths
223 .iter()
224 .map(|p| p.display().to_string())
225 .collect::<Vec<_>>()
226 .join(";");
227 add_flag(&mut cmd, "dslx_path", &joined);
228 }
229 }
230
231 if let Some(solver) = &self.solver {
232 add_flag(&mut cmd, "solver", &solver.to_string());
233 }
234 add_bool(&mut cmd, "flatten_aggregates", self.flatten_aggregates);
235 if let Some(drop) = &self.drop_params {
236 if !drop.is_empty() {
237 add_flag(&mut cmd, "drop_params", &drop.join(","));
238 }
239 }
240 if let Some(strategy) = &self.parallelism_strategy {
241 add_flag(&mut cmd, "parallelism-strategy", &strategy.to_string());
242 }
243 if let Some(sem) = &self.assertion_semantics {
244 add_flag(&mut cmd, "assertion-semantics", &sem.to_string());
245 }
246 if let Some(pat) = &self.assert_label_filter {
247 add_flag(&mut cmd, "assert-label-filter", pat);
248 }
249 add_bool(
250 &mut cmd,
251 "lhs_fixed_implicit_activation",
252 self.lhs_fixed_implicit_activation,
253 );
254 add_bool(
255 &mut cmd,
256 "rhs_fixed_implicit_activation",
257 self.rhs_fixed_implicit_activation,
258 );
259 add_bool(&mut cmd, "assume-enum-in-bound", self.assume_enum_in_bound);
260 add_bool(&mut cmd, "type_inference_v2", self.type_inference_v2);
261
262 if let Some(list) = &self.lhs_uf {
263 for entry in list {
264 add_flag(&mut cmd, "lhs_uf", entry);
265 }
266 }
267 if let Some(list) = &self.rhs_uf {
268 for entry in list {
269 add_flag(&mut cmd, "rhs_uf", entry);
270 }
271 }
272
273 add_bool(&mut cmd, "json", self.json);
274 cmd
275 }
276}
277
278impl ToDriverCommand for ProveQuickcheckConfig {
279 fn to_command(&self) -> Command {
280 let exe = resolve_driver_exe();
281 let mut cmd = Command::new(exe);
282 cmd.arg("prove-quickcheck");
283 add_flag(
284 &mut cmd,
285 "dslx_input_file",
286 &self.dslx_input_file.display().to_string(),
287 );
288 if let Some(paths) = &self.dslx_path {
289 if !paths.is_empty() {
290 let joined = paths
291 .iter()
292 .map(|p| p.display().to_string())
293 .collect::<Vec<_>>()
294 .join(";");
295 add_flag(&mut cmd, "dslx_path", &joined);
296 }
297 }
298 if let Some(stdlib) = &self.dslx_stdlib_path {
299 add_flag(&mut cmd, "dslx_stdlib_path", &stdlib.display().to_string());
300 }
301 if let Some(filter) = &self.test_filter {
302 add_flag(&mut cmd, "test_filter", filter);
303 }
304 if let Some(solver) = &self.solver {
305 add_flag(&mut cmd, "solver", &solver.to_string());
306 }
307 if let Some(sem) = &self.assertion_semantics {
308 add_flag(&mut cmd, "assertion-semantics", &sem.to_string());
309 }
310 if let Some(list) = &self.uf {
311 for entry in list {
312 add_flag(&mut cmd, "uf", entry);
313 }
314 }
315 if let Some(pat) = &self.assert_label_filter {
316 add_flag(&mut cmd, "assert-label-filter", pat);
317 }
318 add_bool(&mut cmd, "json", self.json);
319 cmd
320 }
321}
322
323impl ToDriverCommand for DslxFnProveAssertionsConfig {
324 fn to_command(&self) -> Command {
325 let exe = resolve_driver_exe();
326 let mut cmd = Command::new(exe);
327 cmd.arg("dslx-fn-prove-assertions");
328 add_flag(
329 &mut cmd,
330 "dslx_input_file",
331 &self.dslx_input_file.display().to_string(),
332 );
333 add_flag(&mut cmd, "dslx_top", &self.dslx_top);
334 if let Some(paths) = &self.dslx_path {
335 if !paths.is_empty() {
336 let joined = paths
337 .iter()
338 .map(|p| p.display().to_string())
339 .collect::<Vec<_>>()
340 .join(";");
341 add_flag(&mut cmd, "dslx_path", &joined);
342 }
343 }
344 if let Some(stdlib) = &self.dslx_stdlib_path {
345 add_flag(&mut cmd, "dslx_stdlib_path", &stdlib.display().to_string());
346 }
347 if let Some(solver) = &self.solver {
348 add_flag(&mut cmd, "solver", &solver.to_string());
349 }
350 if let Some(pat) = &self.assert_label_filter {
351 add_flag(&mut cmd, "assert-label-filter", pat);
352 }
353 add_bool(&mut cmd, "assume-enum-in-bound", self.assume_enum_in_bound);
354 if let Some(list) = &self.uf {
355 for entry in list {
356 add_flag(&mut cmd, "uf", entry);
357 }
358 }
359 cmd
360 }
361}
362
363#[cfg(feature = "enable-fake-task")]
367#[derive(Clone, Debug, Default, Serialize, Deserialize)]
368pub struct FakeTaskConfig {
369 pub delay_ms: Option<u32>,
375 pub success: bool,
377 pub stdout_len: u16,
379 pub stderr_len: u16,
381}
382
383#[cfg(feature = "enable-fake-task")]
384impl ToDriverCommand for FakeTaskConfig {
385 fn to_command(&self) -> Command {
386 let script = r#"
389delay_ms="${FAKE_DELAY_MS:-}"
390# If delay_ms is empty, sleep indefinitely (never complete).
391if [ -z "$delay_ms" ]; then
392 # Sleep forever to simulate a non-terminating task; will be killed on timeout/cancel.
393 tail -f /dev/null
394fi
395# Otherwise, sleep for the requested milliseconds.
396secs=$(printf "%s" "$delay_ms" | awk '{ printf("%.3f", $1/1000.0) }')
397sleep "$secs"
398# Emit stdout noise
399ol=${FAKE_STDOUT_LEN:-0}
400if [ "$ol" -gt 0 ]; then head -c "$ol" < /dev/zero | tr '\0' 'X'; echo; fi
401# Emit stderr noise
402el=${FAKE_STDERR_LEN:-0}
403if [ "$el" -gt 0 ]; then (head -c "$el" < /dev/zero | tr '\0' 'Y'; echo) >&2; fi
404# Parse --output_json DEST from arguments
405out=""
406while [ $# -gt 0 ]; do
407 if [ "$1" = "--output_json" ]; then
408 shift
409 out="$1"
410 break
411 fi
412 shift
413done
414if [ -n "$out" ]; then
415 if [ "${FAKE_SUCCESS:-1}" -eq 1 ]; then
416 printf '{"success":true}\n' > "$out"
417 else
418 printf '{"success":false}\n' > "$out"
419 fi
420fi
421"#;
422 let mut cmd = Command::new("/bin/sh");
423 cmd.arg("-c").arg(script).arg("sh");
424 if let Some(ms) = self.delay_ms {
425 cmd.env("FAKE_DELAY_MS", ms.to_string());
426 }
427 cmd.env("FAKE_SUCCESS", if self.success { "1" } else { "0" });
428 cmd.env("FAKE_STDOUT_LEN", self.stdout_len.to_string());
429 cmd.env("FAKE_STDERR_LEN", self.stderr_len.to_string());
430 cmd
431 }
432}
433
434#[derive(Clone, Debug, Serialize, Deserialize)]
439#[serde(tag = "kind", rename_all = "kebab-case")]
440pub enum ProverTask {
441 #[serde(rename = "ir-equiv")]
442 IrEquiv {
443 #[serde(flatten)]
444 config: IrEquivConfig,
445 },
446 #[serde(rename = "dslx-equiv")]
447 DslxEquiv {
448 #[serde(flatten)]
449 config: DslxEquivConfig,
450 },
451 #[serde(rename = "prove-quickcheck")]
452 ProveQuickcheck {
453 #[serde(flatten)]
454 config: ProveQuickcheckConfig,
455 },
456 #[serde(rename = "dslx-fn-prove-assertions")]
457 DslxFnProveAssertions {
458 #[serde(flatten)]
459 config: DslxFnProveAssertionsConfig,
460 },
461 #[cfg(feature = "enable-fake-task")]
462 #[serde(rename = "fake")]
463 Fake {
464 #[serde(flatten)]
465 config: FakeTaskConfig,
466 },
467}
468
469impl ToDriverCommand for ProverTask {
473 fn to_command(&self) -> Command {
474 match self {
475 ProverTask::IrEquiv { config, .. } => config.to_command(),
476 ProverTask::DslxEquiv { config, .. } => config.to_command(),
477 ProverTask::ProveQuickcheck { config, .. } => config.to_command(),
478 ProverTask::DslxFnProveAssertions { config, .. } => config.to_command(),
479 #[cfg(feature = "enable-fake-task")]
480 ProverTask::Fake { config, .. } => config.to_command(),
481 }
482 }
483}
484
485#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
486#[serde(rename_all = "kebab-case")]
487pub enum GroupKind {
488 All,
489 Any,
490 First,
491}
492
493#[derive(Clone, Debug, Serialize, Deserialize)]
494#[serde(untagged)]
495pub enum ProverPlan {
496 Task {
497 #[serde(flatten)]
498 task: ProverTask,
499 #[serde(default, skip_serializing_if = "Option::is_none")]
501 timeout_ms: Option<u64>,
502 #[serde(default, skip_serializing_if = "Option::is_none")]
504 task_id: Option<String>,
505 },
506 Group {
507 kind: GroupKind,
508 tasks: Vec<ProverPlan>,
509 #[serde(default)]
513 keep_running_till_finish: bool,
514 },
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 fn args_of(cmd: Command) -> Vec<String> {
522 cmd.get_args()
523 .map(|s| s.to_string_lossy().into_owned())
524 .collect()
525 }
526
527 fn head_of(plan: &ProverPlan) -> String {
528 match plan {
529 ProverPlan::Task { task, .. } => args_of(task.to_command())[0].clone(),
530 ProverPlan::Group { kind, .. } => match kind {
531 GroupKind::All => "all".to_string(),
532 GroupKind::Any => "any".to_string(),
533 GroupKind::First => "first".to_string(),
534 },
535 }
536 }
537
538 #[test]
539 fn test_ir_equiv_to_command_args() {
540 let cfg = IrEquivConfig {
541 lhs_ir_file: "lhs.ir".into(),
542 rhs_ir_file: "rhs.ir".into(),
543 top: Some("main".to_string()),
544 solver: Some(SolverChoice::Toolchain),
545 parallelism_strategy: Some(EquivParallelism::OutputBits),
546 assertion_semantics: Some(AssertionSemantics::Same),
547 flatten_aggregates: Some(true),
548 drop_params: Some(vec!["p0".into(), "p1".into()]),
549 json: Some(true),
550 ..Default::default()
551 };
552 let got = args_of(cfg.to_command());
553 assert_eq!(
554 got,
555 vec![
556 "ir-equiv",
557 "lhs.ir",
558 "rhs.ir",
559 "--top",
560 "main",
561 "--solver",
562 "toolchain",
563 "--flatten_aggregates",
564 "true",
565 "--drop_params",
566 "p0,p1",
567 "--parallelism-strategy",
568 "output-bits",
569 "--assertion-semantics",
570 "same",
571 "--json",
572 "true",
573 ]
574 );
575 }
576
577 #[test]
578 fn test_dslx_equiv_to_command_args() {
579 let cfg = DslxEquivConfig {
580 lhs_dslx_file: "lhs.x".into(),
581 rhs_dslx_file: "rhs.x".into(),
582 dslx_top: Some("foo".into()),
583 dslx_path: Some(vec!["a".into(), "b".into()]),
584 dslx_stdlib_path: Some("stdlib".into()),
585 solver: Some(SolverChoice::Toolchain),
586 assertion_semantics: Some(AssertionSemantics::Assume),
587 parallelism_strategy: Some(EquivParallelism::SingleThreaded),
588 flatten_aggregates: Some(false),
589 lhs_fixed_implicit_activation: Some(true),
590 rhs_fixed_implicit_activation: Some(false),
591 assume_enum_in_bound: Some(true),
592 json: Some(true),
593 ..Default::default()
594 };
595 let got = args_of(cfg.to_command());
596 assert_eq!(
597 got,
598 vec![
599 "dslx-equiv",
600 "lhs.x",
601 "rhs.x",
602 "--dslx_top",
603 "foo",
604 "--dslx_stdlib_path",
605 "stdlib",
606 "--dslx_path",
607 "a;b",
608 "--solver",
609 "toolchain",
610 "--flatten_aggregates",
611 "false",
612 "--parallelism-strategy",
613 "single-threaded",
614 "--assertion-semantics",
615 "assume",
616 "--lhs_fixed_implicit_activation",
617 "true",
618 "--rhs_fixed_implicit_activation",
619 "false",
620 "--assume-enum-in-bound",
621 "true",
622 "--json",
623 "true",
624 ]
625 );
626 }
627
628 #[test]
629 fn test_prove_quickcheck_to_command_args() {
630 let cfg = ProveQuickcheckConfig {
631 dslx_input_file: "qc.x".into(),
632 dslx_path: Some(vec!["a".into(), "b".into()]),
633 dslx_stdlib_path: Some("stdlib".into()),
634 test_filter: Some(".*mytest".into()),
635 solver: Some(SolverChoice::Toolchain),
636 assertion_semantics: Some(QuickCheckAssertionSemantics::Assume),
637 json: Some(true),
638 ..Default::default()
639 };
640 let got = args_of(cfg.to_command());
641 assert_eq!(
642 got,
643 vec![
644 "prove-quickcheck",
645 "--dslx_input_file",
646 "qc.x",
647 "--dslx_path",
648 "a;b",
649 "--dslx_stdlib_path",
650 "stdlib",
651 "--test_filter",
652 ".*mytest",
653 "--solver",
654 "toolchain",
655 "--assertion-semantics",
656 "assume",
657 "--json",
658 "true",
659 ]
660 );
661 }
662
663 #[test]
664 fn test_dslx_fn_prove_assertions_to_command_args() {
665 let cfg = DslxFnProveAssertionsConfig {
666 dslx_input_file: "assertions.x".into(),
667 dslx_top: "main".into(),
668 dslx_path: Some(vec!["a".into(), "b".into()]),
669 dslx_stdlib_path: Some("stdlib".into()),
670 solver: Some(SolverChoice::Bitwuzla),
671 assert_label_filter: Some("red|blue".into()),
672 assume_enum_in_bound: Some(false),
673 uf: Some(vec!["helper:F".into()]),
674 };
675 let got = args_of(cfg.to_command());
676 assert_eq!(
677 got,
678 vec![
679 "dslx-fn-prove-assertions",
680 "--dslx_input_file",
681 "assertions.x",
682 "--dslx_top",
683 "main",
684 "--dslx_path",
685 "a;b",
686 "--dslx_stdlib_path",
687 "stdlib",
688 "--solver",
689 "bitwuzla",
690 "--assert-label-filter",
691 "red|blue",
692 "--assume-enum-in-bound",
693 "false",
694 "--uf",
695 "helper:F",
696 ]
697 );
698 }
699
700 #[test]
701 fn test_serde_ir_equiv_parse() {
702 let json = r#"{
703 "lhs_ir_file": "lhs.ir",
704 "rhs_ir_file": "rhs.ir",
705 "top": "main",
706 "solver": "toolchain",
707 "parallelism_strategy": "input-bit-split",
708 "assertion_semantics": "never",
709 "json": true
710 }"#;
711 let cfg: IrEquivConfig = serde_json::from_str(json).unwrap();
712 assert_eq!(cfg.solver, Some(SolverChoice::Toolchain));
713 assert_eq!(
714 cfg.parallelism_strategy,
715 Some(EquivParallelism::InputBitSplit)
716 );
717 assert_eq!(cfg.assertion_semantics, Some(AssertionSemantics::Never));
718 assert_eq!(cfg.json, Some(true));
719 }
720
721 #[test]
722 fn test_prover_task_json_roundtrip() {
723 let json = r#"[
724 {
725 "kind": "ir-equiv",
726 "lhs_ir_file": "lhs.ir",
727 "rhs_ir_file": "rhs.ir",
728 "top": "main",
729 "solver": "toolchain",
730 "json": true
731 },
732 {
733 "kind": "dslx-equiv",
734 "lhs_dslx_file": "lhs.x",
735 "rhs_dslx_file": "rhs.x",
736 "dslx_top": "foo",
737 "solver": "toolchain",
738 "json": true
739 },
740 {
741 "kind": "prove-quickcheck",
742 "dslx_input_file": "qc.x",
743 "assertion_semantics": "assume",
744 "solver": "toolchain",
745 "json": true
746 },
747 {
748 "kind": "dslx-fn-prove-assertions",
749 "dslx_input_file": "assertions.x",
750 "dslx_top": "main",
751 "solver": "bitwuzla"
752 }
753 ]"#;
754 let tasks: Vec<ProverTask> = serde_json::from_str(json).unwrap();
755 assert_eq!(tasks.len(), 4);
756 let head: Vec<String> = args_of(tasks[0].to_command());
758 assert_eq!(head[0], "ir-equiv");
759 let head: Vec<String> = args_of(tasks[1].to_command());
760 assert_eq!(head[0], "dslx-equiv");
761 let head: Vec<String> = args_of(tasks[2].to_command());
762 assert_eq!(head[0], "prove-quickcheck");
763 let head: Vec<String> = args_of(tasks[3].to_command());
764 assert_eq!(head[0], "dslx-fn-prove-assertions");
765 }
766
767 #[test]
772 fn test_prover_plan_all_deserialize_and_heads() {
773 let json = r#"{
774 "kind": "all",
775 "tasks": [
776 { "kind": "ir-equiv", "lhs_ir_file": "lhs.ir", "rhs_ir_file": "rhs.ir" },
777 { "kind": "dslx-equiv", "lhs_dslx_file": "lhs.x", "rhs_dslx_file": "rhs.x", "dslx_top": "foo" },
778 { "kind": "prove-quickcheck", "dslx_input_file": "qc.x" },
779 { "kind": "dslx-fn-prove-assertions", "dslx_input_file": "assertions.x", "dslx_top": "main" }
780 ]
781 }"#;
782 let plan: ProverPlan = serde_json::from_str(json).unwrap();
783 match plan {
784 ProverPlan::Group {
785 kind: GroupKind::All,
786 tasks,
787 keep_running_till_finish,
788 } => {
789 assert_eq!(tasks.len(), 4);
790 let heads: Vec<String> = tasks.iter().map(head_of).collect();
791 assert_eq!(
792 heads,
793 vec![
794 "ir-equiv",
795 "dslx-equiv",
796 "prove-quickcheck",
797 "dslx-fn-prove-assertions"
798 ]
799 );
800 assert!(!keep_running_till_finish);
801 }
802 _ => panic!("expected ProverPlan::Group(All)"),
803 }
804 }
805
806 #[test]
807 fn test_prover_plan_any_deserialize_and_heads() {
808 let json = r#"{
809 "kind": "any",
810 "tasks": [
811 { "kind": "ir-equiv", "lhs_ir_file": "lhs.ir", "rhs_ir_file": "rhs.ir" },
812 { "kind": "prove-quickcheck", "dslx_input_file": "qc.x" }
813 ],
814 "keep_running_till_finish": true
815 }"#;
816 let plan: ProverPlan = serde_json::from_str(json).unwrap();
817 match plan {
818 ProverPlan::Group {
819 kind: GroupKind::Any,
820 tasks,
821 keep_running_till_finish,
822 } => {
823 assert_eq!(tasks.len(), 2);
824 let heads: Vec<String> = tasks.iter().map(head_of).collect();
825 assert_eq!(heads, vec!["ir-equiv", "prove-quickcheck"]);
826 assert!(keep_running_till_finish);
827 }
828 _ => panic!("expected ProverPlan::Group(Any)"),
829 }
830 }
831
832 #[test]
833 fn test_prover_plan_first_deserialize_and_heads() {
834 let json = r#"{
835 "kind": "first",
836 "tasks": [
837 { "kind": "ir-equiv", "lhs_ir_file": "lhs.ir", "rhs_ir_file": "rhs.ir" },
838 { "kind": "dslx-equiv", "lhs_dslx_file": "lhs.x", "rhs_dslx_file": "rhs.x", "dslx_top": "foo" }
839 ]
840 }"#;
841 let plan: ProverPlan = serde_json::from_str(json).unwrap();
842 match plan {
843 ProverPlan::Group {
844 kind: GroupKind::First,
845 tasks,
846 ..
847 } => {
848 assert_eq!(tasks.len(), 2);
849 let heads: Vec<String> = tasks.iter().map(head_of).collect();
850 assert_eq!(heads, vec!["ir-equiv", "dslx-equiv"]);
851 }
852 _ => panic!("expected ProverPlan::Group(First)"),
853 }
854 }
855
856 #[test]
857 fn test_prover_plan_all_serialize_shape() {
858 let plan = ProverPlan::Group {
859 kind: GroupKind::All,
860 tasks: vec![ProverPlan::Task {
861 task: ProverTask::IrEquiv {
862 config: IrEquivConfig {
863 lhs_ir_file: "lhs.ir".into(),
864 rhs_ir_file: "rhs.ir".into(),
865 ..Default::default()
866 },
867 },
868 timeout_ms: None,
869 task_id: None,
870 }],
871 keep_running_till_finish: false,
872 };
873 let v = serde_json::to_value(&plan).unwrap();
874 assert_eq!(v["kind"], "all");
875 assert!(v["tasks"].is_array());
876 assert_eq!(v["tasks"][0]["kind"], "ir-equiv");
877 assert_eq!(v["tasks"][0]["lhs_ir_file"], "lhs.ir");
878 assert_eq!(v["tasks"][0]["rhs_ir_file"], "rhs.ir");
879 }
880
881 #[test]
882 fn test_prover_plan_task_with_metadata_serde() {
883 let plan = ProverPlan::Task {
886 task: ProverTask::IrEquiv {
887 config: IrEquivConfig {
888 lhs_ir_file: "lhs.ir".into(),
889 rhs_ir_file: "rhs.ir".into(),
890 ..Default::default()
891 },
892 },
893 timeout_ms: Some(1234),
894 task_id: Some("task-xyz".to_string()),
895 };
896 let v = serde_json::to_value(&plan).unwrap();
897 assert_eq!(v["kind"], "ir-equiv");
898 assert_eq!(v["lhs_ir_file"], "lhs.ir");
899 assert_eq!(v["rhs_ir_file"], "rhs.ir");
900 assert_eq!(v["timeout_ms"], 1234);
901 assert_eq!(v["task_id"], "task-xyz");
902
903 let parsed: ProverPlan = serde_json::from_value(v).unwrap();
905 match parsed {
906 ProverPlan::Task {
907 task: ProverTask::IrEquiv { .. },
908 timeout_ms,
909 task_id,
910 } => {
911 assert_eq!(timeout_ms, Some(1234));
912 assert_eq!(task_id.as_deref(), Some("task-xyz"));
913 }
914 _ => panic!("expected ProverPlan::Task::IrEquiv"),
915 }
916 }
917
918 #[test]
919 fn test_prover_plan_task_parse_metadata_from_json() {
920 let json = r#"{
921 "kind": "ir-equiv",
922 "lhs_ir_file": "lhs.ir",
923 "rhs_ir_file": "rhs.ir",
924 "timeout_ms": 5000,
925 "task_id": "tid-123"
926 }"#;
927 let plan: ProverPlan = serde_json::from_str(json).unwrap();
928 match plan {
929 ProverPlan::Task {
930 task: ProverTask::IrEquiv { .. },
931 timeout_ms,
932 task_id,
933 } => {
934 assert_eq!(timeout_ms, Some(5000));
935 assert_eq!(task_id.as_deref(), Some("tid-123"));
936 }
937 _ => panic!("expected ProverPlan::Task::IrEquiv"),
938 }
939 }
940
941 #[test]
942 fn test_prover_plan_task_serialize_and_parse_behavior() {
943 let plan = ProverPlan::Task {
944 task: ProverTask::IrEquiv {
945 config: IrEquivConfig {
946 lhs_ir_file: "lhs.ir".into(),
947 rhs_ir_file: "rhs.ir".into(),
948 ..Default::default()
949 },
950 },
951 timeout_ms: None,
952 task_id: None,
953 };
954 let v = serde_json::to_value(&plan).unwrap();
955 assert_eq!(v["kind"], "ir-equiv");
957 let task: ProverTask = serde_json::from_value(v.clone()).unwrap();
959 match task {
960 ProverTask::IrEquiv { .. } => {}
961 _ => panic!("expected ProverTask::IrEquiv"),
962 }
963 let plan2: ProverPlan = serde_json::from_value(v.clone()).unwrap();
965 match plan2 {
966 ProverPlan::Task {
967 task: ProverTask::IrEquiv { .. },
968 ..
969 } => {}
970 _ => panic!("expected ProverPlan::Task::IrEquiv"),
971 }
972 }
973}