use std::cell::RefCell;
use std::env;
use std::fmt;
use std::io::{self, BufWriter, Write as _};
use std::pin::pin;
use std::process::ExitCode;
use rusty_rtos_demo_core::runner::{Runner, Shared};
use rusty_rtos_demo_core::{
Scenario, Step, Verdict, abortdelay, apisweep, blockq, blocktim, countsem, death, dynamic,
eventgroups, genqtest, intqueue, intsem, mbamp, messagebuffer, pollq, pollq_async, pollq_typed,
qoverwrite, qpeek, qset, qsetpoll, recmutex, sbint, semtest, step_limit_for, streambuffer,
tasknotify, timerdemo,
};
const DEFAULT_MAX_TICKS: u64 = 2000;
struct Stderr {
out: BufWriter<io::Stderr>,
failed: bool,
}
impl Stderr {
fn new() -> Self {
Self {
out: BufWriter::with_capacity(
if std::env::var_os("KAIROS_TRACE_UNBUFFERED").is_some() {
1
} else {
1 << 20
},
io::stderr(),
),
failed: false,
}
}
fn flush(&mut self) -> io::Result<()> {
self.out.flush()
}
}
impl fmt::Write for Stderr {
#[inline(always)]
fn write_str(&mut self, s: &str) -> fmt::Result {
match self.out.write_all(s.as_bytes()) {
Ok(()) => Ok(()),
Err(_) => {
self.failed = true;
Err(fmt::Error)
}
}
}
}
fn run_stepping(runner: &mut Runner<Stderr>, step_limit: u64) -> Verdict {
let mut steps: u64 = 0;
let mut pass = false;
let mut runaway = false;
let mut last_lines = 0;
let stdout = io::stdout();
let mut out = BufWriter::with_capacity(1 << 20, stdout.lock());
loop {
if steps >= step_limit {
runaway = true;
break;
}
steps = steps.wrapping_add(1);
let step = runner.step_once();
let k = runner.kernel();
let lines = k.trace().lines();
if lines != last_lines {
last_lines = lines;
let _ = writeln!(
out,
"{lines} {} {} {} {}",
k.port().exits(),
k.port().nesting(),
k.scheduler_suspended(),
k.pended_ticks()
);
}
if let Step::Finish(verdict) = step {
pass = verdict;
break;
}
}
let _ = out.flush();
runner.verdict(steps, pass, runaway)
}
fn usage() -> ExitCode {
let mut err = io::stderr();
let _ = writeln!(err, "usage: kairos-sim <scenario> [max_ticks]");
let names: Vec<&str> = Scenario::all().iter().map(|s| s.name()).collect();
let _ = writeln!(err, "scenarios: {}", names.join(", "));
ExitCode::from(2)
}
fn main() -> ExitCode {
let args: Vec<String> = env::args().skip(1).collect();
let Some(name) = args.first() else {
return usage();
};
let Some(scenario) = Scenario::from_name(name) else {
let mut err = io::stderr();
let _ = writeln!(err, "unknown scenario {name}");
return usage();
};
let max_ticks = args
.get(1)
.and_then(|t| t.parse::<u64>().ok())
.unwrap_or(DEFAULT_MAX_TICKS);
let debug_exits = env::var_os("KAIROS_TRACE_EXITS").is_some();
let sink = rusty_rtos_demo_core::LineTrace::new(Stderr::new()).with_exit_column(debug_exits);
let kernel = match Runner::kernel_with_sink(sink) {
Ok(k) => k,
Err(e) => {
let mut err = io::stderr();
let _ = writeln!(err, "kernel refused the geometry: {e:?}");
return ExitCode::from(3);
}
};
let shared = RefCell::new(Shared::default());
let limit = step_limit_for(max_ticks);
let verdict = if scenario == Scenario::PollQAsync {
let (producer, consumer) = match pollq_async::tasks(&kernel, &shared) {
Ok(pair) => pair,
Err(e) => {
let mut err = io::stderr();
let _ = writeln!(err, "scenario {name} could not start: {e:?}");
return ExitCode::from(3);
}
};
let mut producer = pin!(producer);
let mut consumer = pin!(consumer);
let mut runner = Runner::new(&kernel, &shared);
if let Err(e) =
pollq_async::start(&mut runner, max_ticks, producer.as_mut(), consumer.as_mut())
{
let mut err = io::stderr();
let _ = writeln!(err, "scenario {name} could not start: {e:?}");
return ExitCode::from(3);
}
let verdict = if env::var_os("KAIROS_SIM_EXITS").is_some() {
run_stepping(&mut runner, limit)
} else {
runner.run(limit)
};
let _ = runner.finish(scenario.name(), &verdict);
verdict
} else {
let mut runner = Runner::new(&kernel, &shared);
let started = match scenario {
Scenario::Dynamic => dynamic::start(&mut runner, max_ticks),
Scenario::PollQ => pollq::start(&mut runner, max_ticks),
Scenario::BlockQ => blockq::start(&mut runner, max_ticks),
Scenario::SemTest => semtest::start(&mut runner, max_ticks),
Scenario::CountSem => countsem::start(&mut runner, max_ticks),
Scenario::RecMutex => recmutex::start(&mut runner, max_ticks),
Scenario::BlockTim => blocktim::start(&mut runner, max_ticks),
Scenario::AbortDelay => abortdelay::start(&mut runner, max_ticks),
Scenario::Death => death::start(&mut runner, max_ticks),
Scenario::QPeek => qpeek::start(&mut runner, max_ticks),
Scenario::GenQTest => genqtest::start(&mut runner, max_ticks),
Scenario::QOverwrite => qoverwrite::start(&mut runner, max_ticks),
Scenario::QSetPoll => qsetpoll::start(&mut runner, max_ticks),
Scenario::IntSem => intsem::start(&mut runner, max_ticks),
Scenario::SbInt => sbint::start(&mut runner, max_ticks),
Scenario::StreamBuffer => streambuffer::start(&mut runner, max_ticks),
Scenario::MessageBuffer => messagebuffer::start(&mut runner, max_ticks),
Scenario::IntQueue => intqueue::start(&mut runner, max_ticks),
Scenario::QueueSet => qset::start(&mut runner, max_ticks),
Scenario::ApiSweep => apisweep::start(&mut runner, max_ticks),
Scenario::TaskNotify => tasknotify::start(&mut runner, max_ticks),
Scenario::TimerDemo => timerdemo::start(&mut runner, max_ticks),
Scenario::EventGroups => eventgroups::start(&mut runner, max_ticks),
Scenario::MbAmp => mbamp::start(&mut runner, max_ticks),
Scenario::PollQTyped => pollq_typed::start(&mut runner, max_ticks),
Scenario::PollQAsync => Ok(()),
};
if let Err(e) = started {
let mut err = io::stderr();
let _ = writeln!(err, "scenario {name} could not start: {e:?}");
return ExitCode::from(3);
}
let verdict = if env::var_os("KAIROS_SIM_EXITS").is_some() {
run_stepping(&mut runner, limit)
} else {
runner.run(limit)
};
let _ = runner.finish(scenario.name(), &verdict);
verdict
};
let mut sink = Runner::<Stderr>::into_writer(kernel);
let _ = sink.flush();
if verdict.runaway {
let mut err = io::stderr();
let _ = writeln!(err, "the scenario did not finish within {limit} steps");
}
if verdict.pass {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}