use std::cell::RefCell; use std::error::Error; use std::fmt; use loggers::Logger; use repository::SymbolRepository; use symbols::{Symbol, SymbolRunner}; #[derive(Debug)] pub enum SymbolRunError { Symbol(Box), ExecuteDidNotReach(()), } impl Error for SymbolRunError { fn description(&self) -> &str { match self { SymbolRunError::Symbol(_) => "Symbol execution error", SymbolRunError::ExecuteDidNotReach(_) => "Target not reached after executing symbol", } } fn cause(&self) -> Option<&dyn Error> { match self { SymbolRunError::Symbol(ref e) => Some(&**e), SymbolRunError::ExecuteDidNotReach(_) => None, } } } impl fmt::Display for SymbolRunError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SymbolRunError::Symbol(ref e) => write!(f, "{}", e), SymbolRunError::ExecuteDidNotReach(_) => write!(f, "{}", self.description()), } } } pub struct InitializingSymbolRunner { logger: RefCell, } impl InitializingSymbolRunner { pub fn new(logger: L) -> Self { Self { logger: RefCell::new(logger), } } } impl SymbolRunner for InitializingSymbolRunner { fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { let mut logger = self.logger.borrow_mut(); let target_reached = symbol.target_reached()?; if target_reached { logger.write(format!("{} already reached", symbol).as_str()); } else { logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); logger.write(format!("Executing {}", symbol).as_str()); symbol.execute()?; let target_reached = symbol.target_reached()?; logger.debug( format!( "Symbol reports target_reached: {:?} (should be true)", target_reached ) .as_str(), ); if !target_reached { return Err(Box::new(SymbolRunError::ExecuteDidNotReach(()))); } } Ok(()) } } pub struct DrySymbolRunner { logger: RefCell, } impl DrySymbolRunner { pub fn new(logger: L) -> Self { Self { logger: RefCell::new(logger), } } } impl SymbolRunner for DrySymbolRunner { fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { let mut logger = self.logger.borrow_mut(); let target_reached = symbol.target_reached()?; logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); if !target_reached { logger.write(format!("Would execute {}", symbol).as_str()); } Ok(()) } } pub struct ReportingSymbolRunner<'a, R: 'a + SymbolRunner, L: Logger>(&'a R, RefCell); impl<'a, R, L> ReportingSymbolRunner<'a, R, L> where R: SymbolRunner, L: Logger, { pub fn new(symbol_runner: &'a R, logger: L) -> Self { ReportingSymbolRunner(symbol_runner, RefCell::new(logger)) } } impl<'a, R, L> SymbolRunner for ReportingSymbolRunner<'a, R, L> where R: SymbolRunner, L: Logger, { fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { let mut logger = self.1.borrow_mut(); logger.debug(format!("Running symbol {}", symbol).as_str()); let res = self.0.run_symbol(symbol); match res { Err(ref e) => { logger.write(format!("Failed on {} with {}, aborting.", symbol, e).as_str()); } Ok(_) => { logger.debug(format!("Successfully finished {}", symbol).as_str()); } } res } } use resources::Resource; use std::collections::HashSet; pub struct NonRepeatingSymbolRunner { upstream: R, done: RefCell>, } impl NonRepeatingSymbolRunner where R: SymbolRunner, { pub fn new(symbol_runner: R) -> Self { NonRepeatingSymbolRunner { upstream: symbol_runner, done: RefCell::new(HashSet::new()), } } } impl SymbolRunner for NonRepeatingSymbolRunner { fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { if let Some(resources) = symbol.provides() { let mut done = self.done.borrow_mut(); let mut has_to_run = false; for resource in resources { if !done.contains(&resource) { has_to_run = true; assert!(done.insert(resource.clone())); } } if !has_to_run { return Ok(()); } } self.upstream.run_symbol(&*symbol) } } use std::marker::PhantomData; pub struct RequirementsResolvingSymbolRunner< 'a, 's, R: 'a + SymbolRunner, G: 'a + SymbolRepository<'s>, >(R, &'a G, PhantomData>); impl<'s, 'a: 's, R, G> RequirementsResolvingSymbolRunner<'a, 's, R, G> where R: SymbolRunner, G: SymbolRepository<'s>, { pub fn new(symbol_runner: R, symbol_repo: &'a G) -> Self { RequirementsResolvingSymbolRunner(symbol_runner, symbol_repo, PhantomData) } } impl<'s, 'a: 's, R, G> SymbolRunner for RequirementsResolvingSymbolRunner<'a, 's, R, G> where R: SymbolRunner, G: SymbolRepository<'s>, { fn run_symbol(&self, symbol: &dyn Symbol) -> Result<(), Box> { for resource in symbol.get_prerequisites() { if let Some(dep) = self.1.get_symbol(&resource) { dep.as_action(self).run()?; } } self.0.run_symbol(&*symbol) } } // FIXME: Add ExpectingSymbolRunner #[cfg(test)] mod test { use std::cell::RefCell; use std::error::Error; use std::fmt; use loggers::Logger; use schema::InitializingSymbolRunner; use schema::SymbolRunner; use symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction}; #[derive(Debug, PartialEq, Clone)] enum DummySymbolError { Error(()), } impl Error for DummySymbolError { fn description(&self) -> &str { return "Description"; } } impl fmt::Display for DummySymbolError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Dummy symbol error") } } struct DummySymbol<'a> { _execute: &'a dyn Fn() -> Result<(), Box>, _target_reached: &'a dyn Fn() -> Result>, } impl<'b> Symbol for DummySymbol<'b> { fn target_reached(&self) -> Result> { (self._target_reached)() } fn execute(&self) -> Result<(), Box> { (self._execute)() } fn as_action<'a>(&'a self, runner: &'a dyn SymbolRunner) -> Box { Box::new(SymbolAction::new(runner, self)) } fn into_action<'a>(self: Box, runner: &'a dyn SymbolRunner) -> Box where Self: 'a, { Box::new(OwnedSymbolAction::new(runner, *self)) } } impl<'a> fmt::Display for DummySymbol<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Dummy symbol") } } impl<'a> DummySymbol<'a> { fn new( target_reached: &'a dyn Fn() -> Result>, execute: &'a dyn Fn() -> Result<(), Box>, ) -> DummySymbol<'a> { DummySymbol { _target_reached: target_reached, _execute: execute, } } } struct DummyLogger { log: Vec, } impl DummyLogger { fn new() -> DummyLogger { DummyLogger { log: Vec::new() } } } impl Logger for DummyLogger { fn write(&mut self, line: &str) { self.log.push(From::from(line)); } fn debug(&mut self, line: &str) { self.log.push(From::from(line)); } } #[test] fn nothing_needed_to_be_done() { let result = InitializingSymbolRunner::new(DummyLogger::new()) .run_symbol(&DummySymbol::new(&|| Ok(true), &|| Ok(()))); assert!(result.is_ok()); } #[test] fn everything_is_ok() { let first = RefCell::new(true); let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(&DummySymbol::new( &|| { let mut _first = first.borrow_mut(); Ok(if *_first { *_first = false; true } else { false }) }, &|| Ok(()), )); assert!(result.is_ok()); } #[test] fn executing_did_not_change_state() { let result = InitializingSymbolRunner::new(DummyLogger::new()) .run_symbol(&DummySymbol::new(&|| Ok(false), &|| Ok(()))); assert_eq!( result.unwrap_err().description(), "Target not reached after executing symbol" ); } #[test] fn executing_did_not_work() { let err = InitializingSymbolRunner::new(DummyLogger::new()) .run_symbol(&DummySymbol::new(&|| Ok(false), &|| { Err(Box::new(DummySymbolError::Error(()))) })) .unwrap_err(); assert_eq!(err.description(), "Description"); } }