use std::path::PathBuf;
use clap::Parser;
use detcore::preemptions::strip_times_from_events_file;
use crate::CommonOpts;
use crate::cli_wrapper::*;
use crate::common::TemporaryEnvironment;
use crate::common::TemporaryEnvironmentBuilder;
use crate::schedule_trace::InspectOpts;
use crate::use_case::UseCase;
use crate::use_case::split_branches_in_file;
#[derive(Parser, Debug)]
pub struct TraceReplayOpts {
#[clap(long, env = "KEEP_TEMP_DIR")]
keep_temp_dir: bool,
#[clap(long)]
isolate_workdir: bool,
#[clap(long)]
chaos: bool,
#[clap(long)]
strip_times: bool,
#[clap(long)]
hermit_arg: Vec<String>,
#[clap(value_name = "PROGRAM")]
guest_program: PathBuf,
#[clap(long)]
split_branches: bool,
#[clap(value_name = "ARGS")]
args: Vec<String>,
}
impl UseCase for TraceReplayOpts {
fn build_temp_env(
&self,
common_args: &CommonOpts,
) -> crate::common::TemporaryEnvironmentBuilder {
TemporaryEnvironmentBuilder::new()
.persist_temp_dir(self.keep_temp_dir)
.temp_dir_path(common_args.temp_dir_path.as_ref())
.verbose(common_args.verbose)
.run_count(2)
}
fn build_first_hermit_args(
&self,
temp_env: &TemporaryEnvironment,
current_run: &crate::common::RunEnvironment,
) -> Vec<String> {
Hermit::new()
.log_level(tracing::Level::TRACE)
.log_file(current_run.log_file_path.clone())
.run(self.guest_program.clone(), self.args.clone())
.hermit_args(self.hermit_arg.clone())
.chaos(self.chaos)
.bind(temp_env.path().to_owned())
.workdir_isolate(current_run.workdir.clone(), self.isolate_workdir)
.record_preemptions_to(current_run.schedule_file.clone())
.into_args()
}
fn build_next_hermit_args(
&self,
_run_no: usize,
temp_env: &TemporaryEnvironment,
prev_run: &crate::common::RunEnvironment,
current_run: &crate::common::RunEnvironment,
) -> Vec<String> {
let replay_sched = if self.strip_times {
strip_times_from_events_file(&prev_run.schedule_file, None).unwrap()
} else {
prev_run.schedule_file.clone()
};
Hermit::new()
.log_level(tracing::Level::TRACE)
.log_file(current_run.log_file_path.clone())
.run(self.guest_program.clone(), self.args.clone())
.hermit_args(self.hermit_arg.clone())
.chaos(false) .bind(temp_env.path().to_owned())
.workdir_isolate(current_run.workdir.clone(), self.isolate_workdir)
.replay_schedule_from(replay_sched)
.record_preemptions_to(current_run.schedule_file.clone())
.into_args()
}
fn options(&self) -> crate::use_case::UseCaseOptions {
crate::use_case::UseCaseOptions {
output_stdout: true,
output_stderr: true,
verify_stdout: true,
verify_stderr: true,
verify_detlog_syscalls: true,
verify_detlog_syscall_results: true,
verify_detlog_others: !self.split_branches && !self.chaos,
verify_commits: false,
verify_exit_statuses: true,
verify_desync: true,
verify_schedules: false,
ignore_lines: if self.chaos {
vec![
"CHAOSRAND".to_string(),
"advance global time for scheduler turn".to_string(),
"inbound syscall: exit_group".to_string(),
]
} else {
Vec::new()
},
}
}
fn exec_first_run(
&self,
common_args: &CommonOpts,
temp_env: &TemporaryEnvironment,
run: &crate::common::RunEnvironment,
) -> anyhow::Result<()> {
crate::use_case::run_hermit(
self.build_first_hermit_args(temp_env, run),
common_args,
run,
)?;
if self.split_branches {
split_branches_in_file(&run.schedule_file, true)?;
}
if common_args.verbose {
println!("------------------ Recorded Schedule Summary ------------------");
InspectOpts {
sched_file: run.schedule_file.clone(),
verbose: true,
}
.run(common_args)?;
println!("---------------------------------------------------------------\n");
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::TraceReplayOpts;
use crate::use_case::UseCase;
fn trace_replay_opts(chaos: bool) -> TraceReplayOpts {
TraceReplayOpts {
keep_temp_dir: false,
isolate_workdir: false,
chaos,
strip_times: false,
hermit_arg: Vec::new(),
guest_program: PathBuf::from("/bin/true"),
split_branches: false,
args: Vec::new(),
}
}
#[test]
fn non_chaos_replay_compares_all_detlogs() {
let options = trace_replay_opts(false).options();
assert!(options.verify_detlog_syscalls);
assert!(options.verify_detlog_syscall_results);
assert!(options.verify_detlog_others);
}
#[test]
fn chaos_replay_excludes_instrumentation_only_detlogs() {
let options = trace_replay_opts(true).options();
assert!(options.verify_detlog_syscalls);
assert!(options.verify_detlog_syscall_results);
assert!(!options.verify_detlog_others);
assert!(
options
.ignore_lines
.iter()
.any(|line| line == "inbound syscall: exit_group")
);
}
}