#![warn(
unreachable_pub,
missing_docs,
missing_copy_implementations,
trivial_numeric_casts,
unused_extern_crates,
rust_2018_idioms
)]
#![allow(unused_imports, dead_code)]
mod error;
pub mod artifact;
pub mod model;
mod cache;
mod jar;
pub mod cli;
pub mod datachef;
mod util;
pub mod tester;
pub mod event;
pub mod step_runner;
pub mod test_util;
use artifact::model_checker_stdout::ModelCheckerStdout;
use artifact::TlaFileSuite;
pub use datachef::Recipe;
pub use error::{Error, TestError};
pub use event::{ActionHandler, Event, EventRunner, EventStream, StateHandler};
use model::checker::{Apalache, ModelChecker, ModelCheckerRuntime, Tlc};
use model::language::Tla;
use serde::de::DeserializeOwned;
pub use step_runner::StepRunner;
use crate::artifact::{Artifact, ArtifactCreator};
use std::collections::BTreeMap;
use std::env;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use tempfile::tempdir;
use once_cell::sync::Lazy;
use std::sync::Mutex;
static FILE_SYSTEM_MUTEX: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
pub struct TestReport {
test_name_to_trace_execution_result: BTreeMap<String, Vec<Result<(), TestError>>>,
}
impl TestReport {
pub fn no_test_failed(&self) -> bool {
!self
.test_name_to_trace_execution_result
.values()
.flatten()
.any(Result::is_err)
}
pub fn result_of_test(&self, name: &str) -> Option<&Vec<Result<(), TestError>>> {
self.test_name_to_trace_execution_result.get(name)
}
pub fn all(
&self,
) -> std::collections::btree_map::Values<
'_,
std::string::String,
Vec<Result<(), error::TestError>>,
> {
self.test_name_to_trace_execution_result.values()
}
pub fn flat(&self) -> Vec<&Result<(), TestError>> {
self.all().flatten().collect()
}
}
#[derive(Clone, Debug)]
pub struct ModelatorRuntime {
pub model_checker_runtime: ModelCheckerRuntime,
pub dir: PathBuf,
}
impl Default for ModelatorRuntime {
fn default() -> Self {
Self {
model_checker_runtime: ModelCheckerRuntime::default(),
dir: directories::ProjectDirs::from("systems", "Informal", "modelator")
.expect("there is no valid home directory")
.data_dir()
.into(), }
}
}
impl ModelatorRuntime {
pub fn model_checker_runtime(mut self, model_checker_runtime: ModelCheckerRuntime) -> Self {
self.model_checker_runtime = model_checker_runtime;
self
}
pub fn dir(mut self, dir: impl AsRef<Path>) -> Self {
self.dir = dir.as_ref().to_path_buf();
self
}
pub(crate) fn setup(&self) -> Result<(), Error> {
if let Err(e) = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.try_init()
{
tracing::trace!(
"modelator attempted to init the tracing_subscriber: {:?}",
e
);
}
self.ensure_dependencies_exist_on_filesystem()?;
Ok(())
}
fn ensure_dependencies_exist_on_filesystem(&self) -> Result<(), Error> {
let _guard = FILE_SYSTEM_MUTEX.lock();
if !self.dir.as_path().is_dir() {
std::fs::create_dir_all(&self.dir)?;
}
jar::download_jars_if_necessary(&self.dir)?;
tracing::trace!("modelator setup completed");
Ok(())
}
pub fn traces<P: AsRef<Path>>(
&self,
tla_tests_file_path: P,
tla_config_file_path: P,
) -> Result<BTreeMap<String, Result<Vec<artifact::JsonTrace>, Error>>, Error> {
let mut res: BTreeMap<String, Result<Vec<artifact::JsonTrace>, Error>> = BTreeMap::new();
self.setup()?;
let file_suite =
TlaFileSuite::from_tla_and_config_paths(tla_tests_file_path, tla_config_file_path)?;
let tests = Tla::generate_tests(&file_suite)?;
#[allow(clippy::needless_collect)]
let trace_results = (&tests)
.into_par_iter()
.map(|test| match self.model_checker_runtime.model_checker {
ModelChecker::Tlc => Tlc::test(&test.file_suite, self),
ModelChecker::Apalache => Apalache::test(&test.file_suite, self),
})
.collect::<Vec<_>>();
for (i, trace_result) in trace_results.into_iter().enumerate() {
let test_name = tests[i].name.clone();
let (traces, _) = trace_result?;
let jsons: Result<Vec<artifact::JsonTrace>, Error> = traces
.into_iter()
.map(Tla::tla_trace_to_json_trace)
.collect();
res.insert(test_name, jsons);
}
Ok(res)
}
pub fn run_tla_steps<P, System, Step>(
&self,
tla_tests_file_path: P,
tla_config_file_path: P,
system: &mut System,
) -> Result<TestReport, Error>
where
P: AsRef<Path>,
System: StepRunner<Step> + Debug + Clone,
Step: DeserializeOwned + Debug + Clone,
{
Ok(TestReport {
test_name_to_trace_execution_result: {
let mut ret = BTreeMap::new();
let traces_for_tests = self.traces(tla_tests_file_path, tla_config_file_path)?;
for (test_name, traces) in traces_for_tests {
let traces = traces?;
let results: Vec<Result<(), TestError>> =
traces.into_iter().map(|it| system.run(it)).collect();
ret.insert(test_name, results);
}
ret
},
})
}
#[allow(clippy::needless_doctest_main)]
pub fn run_tla_events<P, System>(
&self,
tla_tests_file_path: P,
tla_config_file_path: P,
system: &mut System,
runner: &mut event::EventRunner<System>,
) -> Result<TestReport, Error>
where
P: AsRef<Path>,
System: Debug + Default,
{
Ok(TestReport {
test_name_to_trace_execution_result: {
let mut ret = BTreeMap::new();
let traces_for_tests = self.traces(tla_tests_file_path, tla_config_file_path)?;
for (test_name, traces) in traces_for_tests {
let traces = traces?;
let results: Vec<Result<(), TestError>> = traces
.iter()
.map(|trace| {
let events: EventStream = trace.clone().into();
runner
.run(system, &mut events.into_iter())
.map_err(|op| match op {
TestError::UnhandledTest { system, .. } => {
TestError::UnhandledTest {
test: trace.to_string(),
system,
}
}
TestError::FailedTest {
message,
location,
system,
..
} => TestError::FailedTest {
test: trace.to_string(),
message,
location,
system,
},
TestError::Modelator(_) => op,
})
})
.collect();
ret.insert(test_name, results);
}
ret
},
})
}
}