use std::future::Future;
use std::pin::Pin;
use rustdv_sim::handle::HierarchyHandle;
use rustdv_sim::log::{Level, Logger};
use rustdv_sim::rng::Rng;
use crate::error::TestError;
use crate::objection::{ObjectionGuard, ObjectionRegistry};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Active {
Active,
Passive,
}
#[derive(Default)]
pub struct CheckSink {
errors: Vec<String>,
}
impl CheckSink {
pub fn new() -> CheckSink {
CheckSink::default()
}
pub fn error(&mut self, msg: impl Into<String>) {
let msg = msg.into();
rustdv_sim::log::error(&msg);
self.errors.push(msg);
}
pub fn is_ok(&self) -> bool {
self.errors.is_empty()
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn into_result(self) -> Result<(), String> {
if self.errors.is_empty() {
Ok(())
} else {
Err(format!("{} check failure(s): {}", self.errors.len(), self.errors.join("; ")))
}
}
}
#[derive(Clone)]
pub struct RustdvCtx {
dut: HierarchyHandle,
seed: u64,
objections: ObjectionRegistry,
logger: Logger,
}
impl RustdvCtx {
#[cfg(test)]
pub(crate) fn for_test(path: &str) -> RustdvCtx {
RustdvCtx {
dut: HierarchyHandle::null_for_test(),
seed: 1,
objections: ObjectionRegistry::new(),
logger: Logger::new(path),
}
}
pub fn new(path: &str, dut: HierarchyHandle, seed: u64) -> RustdvCtx {
RustdvCtx { dut, seed, objections: ObjectionRegistry::new(), logger: Logger::new(path) }
}
pub fn child(&self, name: &str) -> RustdvCtx {
RustdvCtx {
dut: self.dut,
seed: self.seed,
objections: self.objections.clone(),
logger: Logger::at(self.logger.rustdv_path().child(name)),
}
}
pub fn dut(&self) -> HierarchyHandle {
self.dut
}
pub fn seed(&self) -> u64 {
self.seed
}
pub fn rng(&self) -> Rng {
Rng::new(self.seed)
}
pub fn path(&self) -> &str {
self.logger.path()
}
pub fn rustdv_path(&self) -> &rustdv_sim::RustdvPath {
self.logger.rustdv_path()
}
pub fn debug(&self, msg: &str) {
self.logger.debug(msg);
}
pub fn info(&self, msg: &str) {
self.logger.info(msg);
}
pub fn warning(&self, msg: &str) {
self.logger.warning(msg);
}
pub fn error(&self, msg: &str) {
self.logger.error(msg);
}
pub fn critical(&self, msg: &str) {
self.logger.critical(msg);
}
pub fn logger(&self) -> &Logger {
&self.logger
}
pub fn set_logging_level_hier(&self, level: Level) {
rustdv_sim::log::set_level_for(self.path(), level);
}
pub fn disable_logging_hier(&self) {
rustdv_sim::log::set_level_for(self.path(), Level::Off);
}
pub fn add_file_handler_hier(&self, file: &str, append: bool) -> std::io::Result<()> {
rustdv_sim::log::add_file_for(self.path(), file, append)
}
pub fn remove_console_hier(&self) {
rustdv_sim::log::set_console_for(self.path(), false);
}
pub fn raise_objection(&self, description: &str) -> ObjectionGuard {
self.objections.raise(description)
}
pub fn objections(&self) -> &ObjectionRegistry {
&self.objections
}
pub async fn all_objections_dropped(&self) {
self.objections.wait_all_dropped().await;
}
}
pub trait Component {
fn build(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
fn connect(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
fn end_of_elaboration(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
fn start_of_simulation(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
#[allow(async_fn_in_trait)]
async fn run(&mut self, ctx: &mut RustdvCtx) -> Result<(), TestError> {
let _ = ctx;
Ok(())
}
fn extract(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
fn check(&mut self, ctx: &mut RustdvCtx, errors: &mut CheckSink) {
let _ = (ctx, errors);
}
fn report(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
fn final_phase(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
fn start(&mut self, ctx: &mut RustdvCtx) {
let _ = ctx;
}
fn comp_name() -> &'static str
where
Self: Sized,
{
let full = std::any::type_name::<Self>();
full.rsplit("::").next().unwrap_or(full)
}
fn new_comp() -> crate::factory::RustdvComp
where
Self: Sized + Default + ComponentNode + 'static,
{
crate::factory::RustdvComp::fixed(Box::new(Self::default()))
}
fn create_comp() -> crate::factory::RustdvComp
where
Self: Sized + Default + ComponentNode + 'static,
{
crate::factory::RustdvComp::overridable(Box::new(Self::default()), Self::comp_name())
}
}
pub trait DynPhases {
fn dyn_build(&mut self, ctx: &mut RustdvCtx);
fn dyn_connect(&mut self, ctx: &mut RustdvCtx);
fn dyn_end_of_elaboration(&mut self, ctx: &mut RustdvCtx);
fn dyn_start_of_simulation(&mut self, ctx: &mut RustdvCtx);
fn dyn_run<'a>(
&'a mut self,
ctx: &'a mut RustdvCtx,
) -> Pin<Box<dyn Future<Output = Result<(), TestError>> + 'a>>;
fn dyn_extract(&mut self, ctx: &mut RustdvCtx);
fn dyn_check(&mut self, ctx: &mut RustdvCtx, errors: &mut CheckSink);
fn dyn_report(&mut self, ctx: &mut RustdvCtx);
fn dyn_final(&mut self, ctx: &mut RustdvCtx);
fn dyn_start(&mut self, ctx: &mut RustdvCtx);
}
impl<T: Component> DynPhases for T {
fn dyn_build(&mut self, ctx: &mut RustdvCtx) {
Component::build(self, ctx)
}
fn dyn_run<'a>(
&'a mut self,
ctx: &'a mut RustdvCtx,
) -> Pin<Box<dyn Future<Output = Result<(), TestError>> + 'a>> {
Box::pin(Component::run(self, ctx))
}
fn dyn_connect(&mut self, ctx: &mut RustdvCtx) {
Component::connect(self, ctx)
}
fn dyn_end_of_elaboration(&mut self, ctx: &mut RustdvCtx) {
Component::end_of_elaboration(self, ctx)
}
fn dyn_start_of_simulation(&mut self, ctx: &mut RustdvCtx) {
Component::start_of_simulation(self, ctx)
}
fn dyn_extract(&mut self, ctx: &mut RustdvCtx) {
Component::extract(self, ctx)
}
fn dyn_check(&mut self, ctx: &mut RustdvCtx, errors: &mut CheckSink) {
Component::check(self, ctx, errors)
}
fn dyn_report(&mut self, ctx: &mut RustdvCtx) {
Component::report(self, ctx)
}
fn dyn_final(&mut self, ctx: &mut RustdvCtx) {
Component::final_phase(self, ctx)
}
fn dyn_start(&mut self, ctx: &mut RustdvCtx) {
Component::start(self, ctx)
}
}
pub trait ComponentNode: DynPhases {
fn node_name(&self) -> &'static str;
fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))>;
fn port_slot(&self, name: &str) -> Option<std::rc::Rc<dyn std::any::Any>> {
let _ = name;
None
}
fn port_infos(&self) -> Vec<crate::port::PortInfo> {
Vec::new()
}
fn resolve_children(&mut self, ctx: &RustdvCtx) {
let _ = ctx;
}
fn take_children(&mut self) -> Vec<(String, Box<dyn ComponentNode>)> {
Vec::new()
}
fn restore_children(&mut self, taken: Vec<(String, Box<dyn ComponentNode>)>) {
let _ = taken;
}
}
pub fn build_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
node.dyn_build(ctx);
node.resolve_children(ctx);
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
build_all(child, &mut cctx);
}
}
pub fn connect_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
connect_all(child, &mut cctx);
}
node.dyn_connect(ctx);
}
pub fn unconnected_ports(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) -> Vec<String> {
let mut out = Vec::new();
let path = ctx.path().to_string();
for info in node.port_infos() {
if info.required && !info.connected {
let owner = if path.is_empty() { String::from("(top)") } else { path.clone() };
out.push(format!("{owner}.{} ({})", info.name, info.kind));
}
}
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
out.extend(unconnected_ports(child, &mut cctx));
}
out
}
pub fn check_connections(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) -> Result<(), TestError> {
let missing = unconnected_ports(node, ctx);
if missing.is_empty() {
return Ok(());
}
let mut msg = String::from("these TLM ports were declared but never connected:");
for m in &missing {
msg.push_str("\n ");
msg.push_str(m);
}
Err(TestError::with_kind(msg, "tlm_unconnected_port"))
}
pub fn end_of_elaboration_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
node.dyn_end_of_elaboration(ctx);
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
end_of_elaboration_all(child, &mut cctx);
}
}
pub fn start_of_simulation_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
node.dyn_start_of_simulation(ctx);
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
start_of_simulation_all(child, &mut cctx);
}
}
async fn run_one<'a>(
node: &'a mut dyn ComponentNode,
ctx: &'a mut RustdvCtx,
) -> Result<(), TestError> {
let objections = ctx.objections().clone();
match rustdv_sim::first2(node.dyn_run(ctx), objections.wait_drained_event()).await {
rustdv_sim::Either::First(r) => r,
rustdv_sim::Either::Second(()) => Ok(()),
}
}
pub fn run_all<'a>(
node: &'a mut dyn ComponentNode,
ctx: &'a mut RustdvCtx,
) -> Pin<Box<dyn Future<Output = Result<(), TestError>> + 'a>> {
Box::pin(async move {
let mut taken = node.take_children();
{
let children: Vec<Pin<Box<dyn Future<Output = Result<(), TestError>> + '_>>> = node
.children_mut()
.into_iter()
.map(|(name, child)| {
let cctx = ctx.child(&name);
Box::pin(async move {
let mut cctx = cctx;
run_all(child, &mut cctx).await
}) as Pin<Box<dyn Future<Output = Result<(), TestError>> + '_>>
})
.collect();
if !children.is_empty() {
for r in rustdv_sim::join_all(children).await {
if r.is_err() {
node.restore_children(taken);
return r;
}
}
}
}
if taken.is_empty() {
return run_one(node, ctx).await;
}
let outcome = {
let mut futs: Vec<Pin<Box<dyn Future<Output = Result<(), TestError>> + '_>>> =
Vec::new();
for (name, child) in taken.iter_mut() {
let cctx = ctx.child(name);
futs.push(Box::pin(async move {
let mut cctx = cctx;
run_all(&mut **child, &mut cctx).await
}));
}
futs.push(Box::pin(run_one(node, ctx)));
let mut first_err = Ok(());
for r in rustdv_sim::join_all(futs).await {
if first_err.is_ok() {
first_err = r;
}
}
first_err
};
node.restore_children(taken);
outcome
})
}
pub fn start_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
start_all(child, &mut cctx);
}
node.dyn_start(ctx);
}
pub fn extract_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
node.dyn_extract(ctx);
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
extract_all(child, &mut cctx);
}
}
pub fn check_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx, sink: &mut CheckSink) {
node.dyn_check(ctx, sink);
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
check_all(child, &mut cctx, sink);
}
}
pub fn report_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
node.dyn_report(ctx);
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
report_all(child, &mut cctx);
}
}
pub fn final_all(node: &mut dyn ComponentNode, ctx: &mut RustdvCtx) {
node.dyn_final(ctx);
for (name, child) in node.children_mut() {
let mut cctx = ctx.child(&name);
final_all(child, &mut cctx);
}
}
pub fn run_extract_check_report(
node: &mut dyn ComponentNode,
ctx: &mut RustdvCtx,
) -> Result<(), String> {
extract_all(node, ctx);
let mut sink = CheckSink::new();
check_all(node, ctx, &mut sink);
report_all(node, ctx);
final_all(node, ctx);
sink.into_result()
}
pub async fn run_component_test<T: Component + ComponentNode>(
test: &mut T,
ctx: &mut RustdvCtx,
) -> Result<(), TestError> {
crate::config::set_in_build(true);
build_all(test, ctx);
crate::config::set_in_build(false);
connect_all(test, ctx);
check_connections(test, ctx)?;
end_of_elaboration_all(test, ctx);
start_of_simulation_all(test, ctx);
let run_result = run_all(test, ctx).await;
let post = run_extract_check_report(test, ctx).map_err(TestError::from);
run_result.and(post)
}
pub fn print_hierarchy(node: &mut dyn ComponentNode) {
fn rec(node: &mut dyn ComponentNode, path: &str) {
rustdv_sim::log::info(&format!("{path} ({})", node.node_name()));
let parent = path.to_string();
for (name, child) in node.children_mut() {
rec(child, &format!("{parent}.{name}"));
}
}
rec(node, "top");
}
#[cfg(test)]
mod tests {
use super::*;
use crate::factory::RustdvComp;
use rustdv_sim::testing::block_on;
use std::cell::RefCell;
use std::rc::Rc;
type Trace = Rc<RefCell<Vec<String>>>;
thread_local! {
static TRACE: Trace = Rc::new(RefCell::new(Vec::new()));
}
fn note(s: String) {
TRACE.with(|t| t.borrow_mut().push(s));
}
fn trace() -> Vec<String> {
TRACE.with(|t| t.borrow().clone())
}
fn reset() {
TRACE.with(|t| t.borrow_mut().clear());
}
#[derive(Default)]
struct Leaf;
impl Component for Leaf {
fn build(&mut self, ctx: &mut RustdvCtx) {
note(format!("build {}", ctx.path()));
}
fn connect(&mut self, ctx: &mut RustdvCtx) {
note(format!("connect {}", ctx.path()));
}
fn check(&mut self, ctx: &mut RustdvCtx, _e: &mut CheckSink) {
note(format!("check {}", ctx.path()));
}
async fn run(&mut self, ctx: &mut RustdvCtx) -> Result<(), TestError> {
note(format!("run {}", ctx.path()));
Ok(())
}
}
impl ComponentNode for Leaf {
fn node_name(&self) -> &'static str {
"Leaf"
}
fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
Vec::new()
}
}
#[derive(Default)]
struct Parent {
first: Option<Leaf>,
second: Option<Leaf>,
}
impl Component for Parent {
fn build(&mut self, ctx: &mut RustdvCtx) {
note(format!("build {}", ctx.path()));
self.first = Some(Leaf);
self.second = Some(Leaf);
}
fn connect(&mut self, ctx: &mut RustdvCtx) {
note(format!("connect {}", ctx.path()));
}
}
impl ComponentNode for Parent {
fn node_name(&self) -> &'static str {
"Parent"
}
fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
let mut out: Vec<(String, &mut (dyn ComponentNode + 'static))> = Vec::new();
if let Some(c) = self.first.as_mut() {
out.push((String::from("first"), c));
}
if let Some(c) = self.second.as_mut() {
out.push((String::from("second"), c));
}
out
}
}
#[test]
fn build_is_top_down_and_descends_into_what_it_created() {
reset();
let mut root = Parent::default();
let mut ctx = RustdvCtx::for_test("top");
build_all(&mut root, &mut ctx);
assert_eq!(
trace(),
vec!["build top", "build top.first", "build top.second"],
"parent first, then the children it created in its own build"
);
}
#[test]
fn connect_is_bottom_up() {
reset();
let mut root = Parent::default();
let mut ctx = RustdvCtx::for_test("top");
build_all(&mut root, &mut ctx);
reset();
connect_all(&mut root, &mut ctx);
assert_eq!(trace(), vec!["connect top.first", "connect top.second", "connect top"]);
}
#[test]
fn paths_are_derived_from_field_names() {
reset();
let mut root = Parent::default();
let mut ctx = RustdvCtx::for_test("alu_test");
build_all(&mut root, &mut ctx);
assert!(trace().contains(&String::from("build alu_test.first")));
assert!(trace().contains(&String::from("build alu_test.second")));
}
#[test]
fn an_option_child_appears_only_once_some() {
let mut root = Parent::default();
assert!(root.children_mut().is_empty(), "declared but not yet built (D6)");
let mut ctx = RustdvCtx::for_test("top");
build_all(&mut root, &mut ctx);
assert_eq!(root.children_mut().len(), 2);
}
#[test]
fn every_component_runs() {
reset();
block_on(async {
let mut root = Parent::default();
let mut ctx = RustdvCtx::for_test("top");
build_all(&mut root, &mut ctx);
reset();
run_all(&mut root, &mut ctx).await.unwrap();
});
let t = trace();
assert!(t.contains(&String::from("run top.first")));
assert!(t.contains(&String::from("run top.second")));
}
#[test]
fn check_visits_the_whole_tree() {
reset();
let mut root = Parent::default();
let mut ctx = RustdvCtx::for_test("top");
build_all(&mut root, &mut ctx);
reset();
let mut sink = CheckSink::new();
check_all(&mut root, &mut ctx, &mut sink);
assert_eq!(trace().len(), 2, "both leaves were checked");
assert!(sink.is_ok());
}
#[derive(Default)]
struct FactoryParent {
child: RustdvComp,
}
impl Component for FactoryParent {}
impl ComponentNode for FactoryParent {
fn node_name(&self) -> &'static str {
"FactoryParent"
}
fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
let mut out: Vec<(String, &mut (dyn ComponentNode + 'static))> = Vec::new();
if let Some(n) = self.child.as_node_mut() {
out.push((String::from("child"), n));
}
out
}
fn take_children(&mut self) -> Vec<(String, Box<dyn ComponentNode>)> {
let mut out = Vec::new();
if let Some(n) = self.child.take_node() {
out.push((String::from("child"), n));
}
out
}
fn restore_children(&mut self, taken: Vec<(String, Box<dyn ComponentNode>)>) {
for (_, node) in taken {
self.child.put_node(node);
}
}
}
#[test]
fn take_children_empties_the_slot_and_restore_refills_it() {
let mut p = FactoryParent { child: RustdvComp::fixed(Box::new(Leaf)) };
let taken = p.take_children();
assert_eq!(taken.len(), 1);
assert!(p.children_mut().is_empty(), "the slot is empty during the run phase");
p.restore_children(taken);
assert_eq!(p.children_mut().len(), 1, "and full again for check/report");
}
#[test]
fn children_are_restored_even_when_a_run_fails() {
#[derive(Default)]
struct Failing;
impl Component for Failing {
async fn run(&mut self, _c: &mut RustdvCtx) -> Result<(), TestError> {
Err(TestError::from(String::from("deliberate")))
}
}
impl ComponentNode for Failing {
fn node_name(&self) -> &'static str {
"Failing"
}
fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
Vec::new()
}
}
block_on(async {
let mut p = FactoryParent { child: RustdvComp::fixed(Box::new(Failing)) };
let mut ctx = RustdvCtx::for_test("top");
let outcome = run_all(&mut p, &mut ctx).await;
assert!(outcome.is_err(), "the child's run failed");
assert_eq!(p.children_mut().len(), 1, "and its child came back anyway");
});
}
#[test]
fn a_component_with_no_children_walks_cleanly() {
reset();
let mut leaf = Leaf;
let mut ctx = RustdvCtx::for_test("solo");
build_all(&mut leaf, &mut ctx);
connect_all(&mut leaf, &mut ctx);
assert_eq!(trace(), vec!["build solo", "connect solo"]);
}
}