use crate::test_tools::{NALInput, OutputExpectation, OutputExpectationError};
use anyhow::Result;
use nar_dev_utils::if_return;
use narsese::api::FloatPrecision;
use navm::{cmd::Cmd, output::Output, vm::VmRuntime};
use std::{ops::ControlFlow, path::Path, time::Duration};
pub trait VmOutputCache {
fn put(&mut self, output: Output) -> Result<()>;
fn for_each<T>(&self, f: impl FnMut(&Output) -> ControlFlow<T>) -> Result<Option<T>>;
}
pub fn put_nal(
vm: &mut impl VmRuntime,
input: NALInput,
output_cache: &mut impl VmOutputCache,
enabled_user_input: bool,
nal_root_path: &Path,
precision_epoch: FloatPrecision,
) -> Result<()> {
use NALInput::*;
match input {
Put(cmd) => vm.input_cmd(cmd),
Sleep(duration) => nal_sleep(duration),
Await(expectation) => nal_await(vm, output_cache, expectation, precision_epoch),
ExpectContains(expectation) => {
nal_expect_contains(vm, output_cache, expectation, precision_epoch)
}
ExpectCycle(max_cycles, step_cycles, step_duration, expectation) => nal_expect_cycle(
max_cycles,
vm,
step_cycles,
step_duration,
output_cache,
expectation,
precision_epoch,
),
SaveOutputs(path_str) => nal_save_outputs(output_cache, nal_root_path, path_str),
Terminate {
if_not_user,
result,
} => nal_terminate(if_not_user, enabled_user_input, vm, result),
}
}
fn nal_sleep(duration: Duration) -> Result<()> {
std::thread::sleep(duration);
Ok(())
}
fn nal_await(
vm: &mut impl VmRuntime,
output_cache: &mut impl VmOutputCache,
expectation: OutputExpectation,
precision_epoch: f64,
) -> Result<()> {
loop {
let output = match vm.fetch_output() {
Ok(output) => {
output_cache.put(output.clone())?;
output
}
Err(e) => {
println!("尝试拉取输出出错:{e}");
continue;
}
};
if expectation.matches(&output, precision_epoch) {
break Ok(());
}
}
}
fn nal_expect_contains(
vm: &mut impl VmRuntime,
output_cache: &mut impl VmOutputCache,
expectation: OutputExpectation,
precision_epoch: f64,
) -> Result<()> {
while let Some(output) = vm.try_fetch_output()? {
output_cache.put(output)?;
}
let result =
output_cache.for_each(
|output| match expectation.matches(output, precision_epoch) {
true => ControlFlow::Break(true),
false => ControlFlow::Continue(()),
},
)?;
match result {
Some(true) => Ok(()),
_ => Err(OutputExpectationError::ExpectedNotExists(expectation).into()),
}
}
fn nal_expect_cycle(
max_cycles: usize,
vm: &mut impl VmRuntime,
step_cycles: usize,
step_duration: Option<Duration>,
output_cache: &mut impl VmOutputCache,
expectation: OutputExpectation,
precision_epoch: f64,
) -> Result<()> {
let mut cycles = 0;
while cycles < max_cycles {
vm.input_cmd(Cmd::CYC(step_cycles))?;
cycles += step_cycles;
if let Some(duration) = step_duration {
std::thread::sleep(duration);
}
while let Some(output) = vm.try_fetch_output()? {
output_cache.put(output)?;
}
let result =
output_cache.for_each(
|output| match expectation.matches(output, precision_epoch) {
true => ControlFlow::Break(true),
false => ControlFlow::Continue(()),
},
)?;
if let Some(true) = result {
let message = format!("expect-cycle({cycles}): {expectation}");
let output = Output::INFO { message };
output_cache.put(output)?;
return Ok(());
}
}
Err(OutputExpectationError::ExpectedNotExists(expectation).into())
}
fn nal_save_outputs(
output_cache: &mut impl VmOutputCache,
nal_root_path: &Path,
path_str: String,
) -> Result<()> {
let file_str = collect_outputs_to_json(output_cache)?;
let path = nal_root_path.join(path_str.trim());
std::fs::write(path, file_str)?;
Ok(())
}
fn collect_outputs_to_json(output_cache: &mut impl VmOutputCache) -> Result<String> {
let mut file_str = "[".to_string();
output_cache.for_each(|output| {
file_str += "\n\t";
file_str += &output.to_json_string();
file_str.push(',');
ControlFlow::<()>::Continue(())
})?;
file_str.pop();
file_str += "\n]";
Ok(file_str)
}
fn nal_terminate(
if_not_user: bool,
enabled_user_input: bool,
vm: &mut impl VmRuntime,
result: std::result::Result<(), String>,
) -> Result<()> {
if_return! { if_not_user && enabled_user_input => Ok(()) }
vm.terminate()?;
result.map_err(|e| anyhow::anyhow!("{e}"))
}