tendermint_testgen/
apalache.rs1use std::{fmt::Write as _, io};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{command::*, tester::TestEnv};
6
7#[derive(Deserialize, Clone, Debug)]
8pub struct ApalacheTestBatch {
9 pub description: String,
10 pub model: String,
11 pub length: Option<u64>,
12 pub timeout: Option<u64>,
13 pub tests: Vec<String>,
14}
15
16#[derive(Serialize, Deserialize, Clone, Debug)]
17pub struct ApalacheTestCase {
18 pub model: String,
19 pub test: String,
20 pub length: Option<u64>,
21 pub timeout: Option<u64>,
22}
23
24pub enum ApalacheRun {
25 Counterexample(CommandRun),
27 NoCounterexample(CommandRun),
29 Deadlock(CommandRun),
31 ModelError(CommandRun),
33 Unknown(CommandRun),
35 Timeout(CommandRun),
37}
38
39impl ApalacheRun {
40 pub fn message(&self) -> &str {
41 match self {
42 ApalacheRun::Counterexample(_) => "Apalache has generated a counterexample",
43 ApalacheRun::NoCounterexample(_) => "Apalache failed to generate a counterexample; consider increasing the length bound, or changing your test",
44 ApalacheRun::Deadlock(_) => "Apalache has found a deadlock; please inspect your model and test",
45 ApalacheRun::ModelError(_) => "Apalache failed to process the model; please check it",
46 ApalacheRun::Unknown(_) => "Apalache has generated an unknown outcome; please contact Apalache developers",
47 ApalacheRun::Timeout(_) => "Apalache failed to generate a counterexample within given time; consider increasing the timeout, or changing your test",
48 }
49 }
50}
51
52pub fn run_apalache_test(dir: &str, test: ApalacheTestCase) -> io::Result<ApalacheRun> {
53 let inv = format!("{}Inv", test.test);
54
55 let mutation_failed = || {
57 io::Error::new(
58 io::ErrorKind::InvalidInput,
59 "failed to mutate the model and add invariant",
60 )
61 };
62 let env = TestEnv::new(dir).ok_or_else(mutation_failed)?;
63 let model = env.read_file(&test.model).unwrap();
64 let mut new_model = String::new();
65 for line in model.lines() {
66 if line.starts_with(&inv) {
67 new_model.clear();
69 break;
70 }
71 if line.starts_with("======") {
72 let _ = writeln!(new_model, "{} == ~{}", inv, test.test);
73 }
74 new_model += line;
75 new_model += "\n";
76 }
77 if !new_model.is_empty() {
78 env.write_file(&test.model, &new_model)
79 .ok_or_else(mutation_failed)?;
80 }
81
82 let mut cmd = Command::new();
84 if let Some(timeout) = test.timeout {
85 cmd.program("timeout");
86 cmd.arg(&timeout.to_string());
87 cmd.arg("apalache-mc");
88 } else {
89 cmd.program("apalache-mc");
90 }
91 cmd.arg("check");
92 cmd.arg_from_parts(vec!["--inv=", &inv]);
93 cmd.arg("--init=InitTest");
94 cmd.arg("--next=NextTest");
95 if let Some(length) = test.length {
96 cmd.arg_from_parts(vec!["--length=", &length.to_string()]);
97 }
98 cmd.arg(&test.model);
99 if !dir.is_empty() {
100 cmd.current_dir(dir);
101 }
102 match cmd.spawn() {
103 Ok(run) => {
104 if run.status.success() {
105 if run.stdout.contains("The outcome is: NoError") {
106 Ok(ApalacheRun::NoCounterexample(run))
107 } else if run.stdout.contains("The outcome is: Error") {
108 Ok(ApalacheRun::Counterexample(run))
109 } else if run.stdout.contains("The outcome is: Deadlock") {
110 Ok(ApalacheRun::Deadlock(run))
111 } else {
112 Ok(ApalacheRun::Unknown(run))
113 }
114 } else if let Some(code) = run.status.code() {
115 match code {
116 99 => Ok(ApalacheRun::ModelError(run)),
117 124 => Ok(ApalacheRun::Timeout(run)),
118 _ => Ok(ApalacheRun::Unknown(run)),
119 }
120 } else {
121 Ok(ApalacheRun::Timeout(run))
122 }
123 },
124 Err(e) => Err(e),
125 }
126}