use crate::{
common::{
error::{err, inv_arg, oe_inv_arg, Result},
log::{tee_file::TeeFileConfiguration, LoglevelFilter},
types::PluginType,
util::friendly_enumerate,
},
host::configuration::*,
};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
mod host_call;
pub use host_call::HostCall;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PluginReproduction {
pub name: String,
pub executable: PathBuf,
pub script: Option<PathBuf>,
#[serde(flatten)]
pub functional: PluginProcessFunctionalConfiguration,
}
#[derive(Debug)]
pub struct PluginModification {
pub name: String,
pub verbosity: Option<LoglevelFilter>,
pub tee_files: Vec<TeeFileConfiguration>,
pub stdout_mode: Option<StreamCaptureMode>,
pub stderr_mode: Option<StreamCaptureMode>,
pub accept_timeout: Option<Timeout>,
pub shutdown_timeout: Option<Timeout>,
}
impl PluginModification {
pub fn apply(self, to: &mut Vec<PluginProcessConfiguration>) -> Result<()> {
for plugin_config in &mut to.iter_mut() {
if plugin_config.name == self.name {
if let Some(verbosity) = self.verbosity {
plugin_config.nonfunctional.verbosity = verbosity;
}
plugin_config
.nonfunctional
.tee_files
.extend(self.tee_files.iter().cloned());
if let Some(stdout_mode) = &self.stdout_mode {
plugin_config.nonfunctional.stdout_mode = stdout_mode.clone();
}
if let Some(stderr_mode) = &self.stderr_mode {
plugin_config.nonfunctional.stderr_mode = stderr_mode.clone();
}
if let Some(accept_timeout) = &self.accept_timeout {
plugin_config.nonfunctional.accept_timeout = *accept_timeout;
}
if let Some(shutdown_timeout) = &self.shutdown_timeout {
plugin_config.nonfunctional.shutdown_timeout = *shutdown_timeout;
}
return Ok(());
}
}
inv_arg(format!(
"There is no plugin named {}. The available plugins are {}.",
self.name,
friendly_enumerate(to.iter().map(|x| &x.name[..]), Some("or"))
))
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Reproduction {
pub seed: u64,
pub plugins: Vec<PluginReproduction>,
pub host_calls: Vec<HostCall>,
pub hostname: String,
pub username: String,
pub workdir: PathBuf,
}
impl Reproduction {
pub fn new_logger(config: &SimulatorConfiguration) -> Result<Reproduction> {
Ok(Reproduction {
seed: config.seed.value,
host_calls: vec![],
plugins: config
.plugins
.iter()
.map(|x| x.get_reproduction(config.reproduction_path_style.ok_or_else(oe_inv_arg("cannot create reproduction logger for simulator configuration with reproduction explicitly disabled"))?))
.collect::<Result<Vec<PluginReproduction>>>()?,
hostname: whoami::hostname(),
username: whoami::username(),
workdir: std::env::current_dir()?,
})
}
pub fn record(&mut self, host_call: HostCall) {
self.host_calls.push(host_call);
}
pub fn to_run(
&self,
config: &mut SimulatorConfiguration,
modifications: impl IntoIterator<Item = PluginModification>,
exact: bool,
) -> Result<Vec<HostCall>> {
if exact {
config.seed.value = self.seed;
}
let mut plugins = self
.plugins
.iter()
.map(|x| PluginProcessConfiguration {
name: x.name.clone(),
specification: PluginProcessSpecification::new(
&x.executable,
x.script.clone(),
PluginType::Operator,
),
functional: x.functional.clone(),
nonfunctional: PluginProcessNonfunctionalConfiguration::default(),
})
.collect::<Vec<PluginProcessConfiguration>>();
let plugin_count = plugins.len();
if plugin_count < 2 {
err("reproduction file corrupted: less than two plugins specified")?;
}
plugins[0].specification.typ = PluginType::Frontend;
plugins[plugin_count - 1].specification.typ = PluginType::Backend;
for m in modifications {
m.apply(&mut plugins)?;
}
config.plugins = plugins
.into_iter()
.map(|plugin| Box::new(plugin) as Box<dyn PluginConfiguration>)
.collect();
Ok(self.host_calls.clone())
}
pub fn from_file(file: impl AsRef<Path>) -> Result<Reproduction> {
Ok(serde_yaml::from_reader(&mut std::fs::File::open(
file.as_ref(),
)?)?)
}
pub fn to_file(&self, file: impl AsRef<Path>) -> Result<()> {
serde_yaml::to_writer(&mut std::fs::File::create(file.as_ref())?, self)?;
Ok(())
}
}