Adrian Heine
4 years ago
44 changed files with 2081 additions and 1242 deletions
-
5Cargo.toml
-
87src/async_utils.rs
-
200src/builder.rs
-
124src/command_runner.rs
-
19src/lib.rs
-
2src/locator.rs
-
125src/loggers.rs
-
43src/resources/mod.rs
-
274src/schema.rs
-
396src/setup.rs
-
191src/setup/core.rs
-
10src/setup/mod.rs
-
193src/setup/runnable.rs
-
300src/setup/setup.rs
-
318src/setup/symbol_runner.rs
-
9src/setup/util.rs
-
61src/symbols/acme/cert.rs
-
6src/symbols/concat.rs
-
27src/symbols/cron.rs
-
6src/symbols/dir.rs
-
6src/symbols/file.rs
-
124src/symbols/git/checkout.rs
-
2src/symbols/git/mod.rs
-
43src/symbols/git/submodules.rs
-
43src/symbols/mariadb/database.rs
-
43src/symbols/mariadb/dump.rs
-
24src/symbols/mariadb/user.rs
-
6src/symbols/mod.rs
-
53src/symbols/npm.rs
-
18src/symbols/owner.rs
-
100src/symbols/postgresql/database.rs
-
69src/symbols/saved_directory.rs
-
18src/symbols/systemd/reload.rs
-
61src/symbols/systemd/user_service.rs
-
7src/symbols/systemd/user_session.rs
-
49src/symbols/tls/csr.rs
-
52src/symbols/tls/key.rs
-
34src/symbols/user.rs
-
57src/symbols/wordpress/plugin.rs
-
28src/symbols/wordpress/translation.rs
-
1src/templates/nginx/server.rs
-
2src/to_artifact.rs
-
57tests/file.rs
-
30tests/setup.rs
@ -0,0 +1,87 @@ |
|||||
|
use std::{
|
||||
|
future::Future,
|
||||
|
pin::Pin,
|
||||
|
sync::{Arc, Mutex},
|
||||
|
task::{Context, Poll, Waker},
|
||||
|
thread,
|
||||
|
time::Duration,
|
||||
|
};
|
||||
|
|
||||
|
pub use async_trait::async_trait;
|
||||
|
|
||||
|
pub fn run<F: Future>(future: F) -> F::Output {
|
||||
|
tokio::runtime::Runtime::new().unwrap().block_on(future)
|
||||
|
}
|
||||
|
pub use tokio::try_join;
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
pub struct TimerFuture {
|
||||
|
state: Arc<Mutex<State>>,
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
enum State {
|
||||
|
NotStarted(Duration),
|
||||
|
Running(Waker),
|
||||
|
Completed,
|
||||
|
}
|
||||
|
|
||||
|
impl Future for TimerFuture {
|
||||
|
type Output = ();
|
||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
|
let mut state = self.state.lock().unwrap();
|
||||
|
if let State::Completed = *state {
|
||||
|
return Poll::Ready(());
|
||||
|
}
|
||||
|
|
||||
|
if let State::NotStarted(duration) = *state {
|
||||
|
let thread_state = self.state.clone();
|
||||
|
thread::spawn(move || {
|
||||
|
thread::sleep(duration);
|
||||
|
let mut state = thread_state.lock().unwrap();
|
||||
|
let waker = if let State::Running(waker) = &*state {
|
||||
|
Some(waker.clone())
|
||||
|
} else {
|
||||
|
None
|
||||
|
};
|
||||
|
*state = State::Completed;
|
||||
|
if let Some(w) = waker {
|
||||
|
w.wake()
|
||||
|
}
|
||||
|
});
|
||||
|
}
|
||||
|
|
||||
|
*state = State::Running(cx.waker().clone());
|
||||
|
Poll::Pending
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
pub fn sleep(duration: Duration) -> impl Future<Output = ()> {
|
||||
|
TimerFuture {
|
||||
|
state: Arc::new(Mutex::new(State::NotStarted(duration))),
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod test {
|
||||
|
use crate::async_utils::{run, sleep};
|
||||
|
use futures::future::FutureExt;
|
||||
|
use std::time::{Duration, Instant};
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_sleep() {
|
||||
|
run(async {
|
||||
|
let start = Instant::now();
|
||||
|
let sleep = sleep(Duration::from_millis(100)).fuse();
|
||||
|
let ok = async {}.fuse();
|
||||
|
futures::pin_mut!(sleep, ok);
|
||||
|
loop {
|
||||
|
futures::select! {
|
||||
|
_ = sleep => {},
|
||||
|
_ = ok => assert!((Instant::now() - start).as_millis() < 100),
|
||||
|
complete => break,
|
||||
|
}
|
||||
|
}
|
||||
|
})
|
||||
|
}
|
||||
|
}
|
@ -1,274 +0,0 @@ |
|||||
use crate::loggers::Logger;
|
|
||||
use crate::symbols::Symbol;
|
|
||||
use std::cell::RefCell;
|
|
||||
use std::error::Error;
|
|
||||
use std::fmt;
|
|
||||
use std::fmt::Debug;
|
|
||||
|
|
||||
pub trait SymbolRunner {
|
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>>;
|
|
||||
}
|
|
||||
|
|
||||
impl<R: SymbolRunner + ?Sized> SymbolRunner for Box<R> {
|
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
||||
(**self).run_symbol(symbol, force)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
#[derive(Debug)]
|
|
||||
pub enum SymbolRunError {
|
|
||||
Symbol(Box<dyn Error>),
|
|
||||
ExecuteDidNotReach(()),
|
|
||||
}
|
|
||||
|
|
||||
impl Error for SymbolRunError {
|
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
|
||||
match self {
|
|
||||
Self::Symbol(ref e) => Some(&**e),
|
|
||||
Self::ExecuteDidNotReach(_) => None,
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl fmt::Display for SymbolRunError {
|
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||
match self {
|
|
||||
Self::Symbol(ref e) => write!(f, "{}", e),
|
|
||||
Self::ExecuteDidNotReach(_) => write!(f, "Target not reached after executing symbol"),
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
pub struct InitializingSymbolRunner<L: Logger> {
|
|
||||
logger: RefCell<L>,
|
|
||||
}
|
|
||||
|
|
||||
impl<L: Logger> InitializingSymbolRunner<L> {
|
|
||||
pub fn new(logger: L) -> Self {
|
|
||||
Self {
|
|
||||
logger: RefCell::new(logger),
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
fn exec_symbol<S: Symbol + Debug>(&self, symbol: &S) -> Result<(), Box<dyn Error>> {
|
|
||||
let mut logger = self.logger.borrow_mut();
|
|
||||
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 {
|
|
||||
Ok(())
|
|
||||
} else {
|
|
||||
Err(Box::new(SymbolRunError::ExecuteDidNotReach(())))
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl<L: Logger> SymbolRunner for InitializingSymbolRunner<L> {
|
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
||||
let mut logger = self.logger.borrow_mut();
|
|
||||
let executed = if force {
|
|
||||
logger.debug("Forcing symbol execution");
|
|
||||
drop(logger);
|
|
||||
self.exec_symbol(symbol)?;
|
|
||||
true
|
|
||||
} else {
|
|
||||
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());
|
|
||||
drop(logger);
|
|
||||
self.exec_symbol(symbol)?;
|
|
||||
}
|
|
||||
!target_reached
|
|
||||
};
|
|
||||
Ok(executed)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
pub struct DrySymbolRunner<L: Logger> {
|
|
||||
logger: RefCell<L>,
|
|
||||
}
|
|
||||
|
|
||||
impl<L: Logger> DrySymbolRunner<L> {
|
|
||||
pub fn new(logger: L) -> Self {
|
|
||||
Self {
|
|
||||
logger: RefCell::new(logger),
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl<L: Logger> SymbolRunner for DrySymbolRunner<L> {
|
|
||||
fn run_symbol<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
||||
let mut logger = self.logger.borrow_mut();
|
|
||||
let would_execute = if force {
|
|
||||
logger.write(format!("Would force-execute {:?}", symbol).as_str());
|
|
||||
true
|
|
||||
} else {
|
|
||||
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());
|
|
||||
}
|
|
||||
!target_reached
|
|
||||
};
|
|
||||
Ok(would_execute)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
pub struct ReportingSymbolRunner<'a, R, L>(&'a R, RefCell<L>);
|
|
||||
|
|
||||
impl<'a, R, L> ReportingSymbolRunner<'a, R, L> {
|
|
||||
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<S: Symbol + Debug>(&self, symbol: &S, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
||||
let mut logger = self.1.borrow_mut();
|
|
||||
logger.debug(format!("Running symbol {:?}", symbol).as_str());
|
|
||||
let res = self.0.run_symbol(symbol, force);
|
|
||||
if let Err(ref e) = res {
|
|
||||
logger.write(format!("Failed on {:?} with {}, aborting.", symbol, e).as_str())
|
|
||||
} else {
|
|
||||
logger.debug(format!("Successfully finished {:?}", symbol).as_str())
|
|
||||
}
|
|
||||
res
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
#[cfg(test)]
|
|
||||
mod test {
|
|
||||
use std::cell::RefCell;
|
|
||||
use std::error::Error;
|
|
||||
use std::fmt;
|
|
||||
|
|
||||
use crate::loggers::Logger;
|
|
||||
use crate::schema::InitializingSymbolRunner;
|
|
||||
use crate::schema::SymbolRunner;
|
|
||||
use crate::symbols::Symbol;
|
|
||||
|
|
||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||
enum DummySymbolError {
|
|
||||
Error(()),
|
|
||||
}
|
|
||||
|
|
||||
impl Error for DummySymbolError {}
|
|
||||
|
|
||||
impl fmt::Display for DummySymbolError {
|
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||
write!(f, "Dummy symbol error")
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
#[derive(Debug)]
|
|
||||
struct DummySymbol<T, E> {
|
|
||||
_target_reached: RefCell<T>,
|
|
||||
_execute: RefCell<E>,
|
|
||||
}
|
|
||||
|
|
||||
impl<
|
|
||||
E: Iterator<Item = Result<(), Box<dyn Error>>>,
|
|
||||
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
|
|
||||
> Symbol for DummySymbol<T, E>
|
|
||||
{
|
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
|
||||
self._target_reached.borrow_mut().next().unwrap()
|
|
||||
}
|
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
|
||||
self._execute.borrow_mut().next().unwrap()
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl<
|
|
||||
E: Iterator<Item = Result<(), Box<dyn Error>>>,
|
|
||||
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
|
|
||||
> DummySymbol<T, E>
|
|
||||
{
|
|
||||
fn new<
|
|
||||
IE: IntoIterator<IntoIter = E, Item = Result<(), Box<dyn Error>>>,
|
|
||||
IT: IntoIterator<IntoIter = T, Item = Result<bool, Box<dyn Error>>>,
|
|
||||
>(
|
|
||||
target_reached: IT,
|
|
||||
execute: IE,
|
|
||||
) -> Self {
|
|
||||
Self {
|
|
||||
_target_reached: RefCell::new(target_reached.into_iter()),
|
|
||||
_execute: RefCell::new(execute.into_iter()),
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
struct DummyLogger {
|
|
||||
log: Vec<String>,
|
|
||||
}
|
|
||||
|
|
||||
impl DummyLogger {
|
|
||||
fn new() -> DummyLogger {
|
|
||||
DummyLogger { log: Vec::new() }
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl Logger for DummyLogger {
|
|
||||
fn write(&mut self, line: &str) {
|
|
||||
self.log.push(line.into());
|
|
||||
}
|
|
||||
fn debug(&mut self, line: &str) {
|
|
||||
self.log.push(line.into());
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
#[test]
|
|
||||
fn nothing_needed_to_be_done() {
|
|
||||
let result = InitializingSymbolRunner::new(DummyLogger::new())
|
|
||||
.run_symbol(&DummySymbol::new(vec![Ok(true)], vec![Ok(())]), false);
|
|
||||
assert!(result.is_ok());
|
|
||||
}
|
|
||||
|
|
||||
#[test]
|
|
||||
fn everything_is_ok() {
|
|
||||
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(
|
|
||||
&DummySymbol::new(vec![Ok(true), Ok(false)], vec![Ok(())]),
|
|
||||
false,
|
|
||||
);
|
|
||||
assert!(result.is_ok());
|
|
||||
}
|
|
||||
|
|
||||
#[test]
|
|
||||
fn executing_did_not_change_state() {
|
|
||||
let result = InitializingSymbolRunner::new(DummyLogger::new()).run_symbol(
|
|
||||
&DummySymbol::new(vec![Ok(false), Ok(false)], vec![Ok(())]),
|
|
||||
false,
|
|
||||
);
|
|
||||
assert_eq!(
|
|
||||
result.unwrap_err().to_string(),
|
|
||||
"Target not reached after executing symbol"
|
|
||||
);
|
|
||||
}
|
|
||||
|
|
||||
#[test]
|
|
||||
fn executing_did_not_work() {
|
|
||||
let err = InitializingSymbolRunner::new(DummyLogger::new())
|
|
||||
.run_symbol(
|
|
||||
&DummySymbol::new(
|
|
||||
vec![Ok(false)],
|
|
||||
vec![Err(Box::new(DummySymbolError::Error(())) as Box<dyn Error>)],
|
|
||||
),
|
|
||||
false,
|
|
||||
)
|
|
||||
.unwrap_err();
|
|
||||
assert_eq!(err.to_string(), "Dummy symbol error");
|
|
||||
}
|
|
||||
}
|
|
@ -1,396 +0,0 @@ |
|||||
use crate::resources::{BorrowResource, DefaultResources, Resource};
|
|
||||
use crate::schema::SymbolRunner;
|
|
||||
use crate::symbols::Symbol;
|
|
||||
use crate::to_artifact::ToArtifact;
|
|
||||
use crate::{DefaultBuilder, DefaultLocator, ResourceLocator, SymbolBuilder};
|
|
||||
use std::collections::HashSet;
|
|
||||
use std::error::Error;
|
|
||||
use std::fmt::Debug;
|
|
||||
use std::hash::Hash;
|
|
||||
use std::marker::PhantomData;
|
|
||||
|
|
||||
pub trait CanHandle<X: ToArtifact> {
|
|
||||
fn handle(&mut self, x: X) -> Result<(X::Artifact, bool), Box<dyn Error>>;
|
|
||||
}
|
|
||||
|
|
||||
macro_rules! can_handle {
|
|
||||
( $($name:ident)* ) => (
|
|
||||
#[allow(non_snake_case)]
|
|
||||
impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, $($name: Resource,)*>
|
|
||||
CanHandle<($($name,)*)>
|
|
||||
for Setup<_SR, _L, _R, _B>
|
|
||||
where
|
|
||||
$(
|
|
||||
_B: SymbolBuilder<$name>,
|
|
||||
<_B as SymbolBuilder<$name>>::Symbol: Runnable + Debug,
|
|
||||
_L: ResourceLocator<$name>,
|
|
||||
_R: From<$name> + BorrowResource<$name>,
|
|
||||
Self: CanHandle<<_B as SymbolBuilder<$name>>::Prerequisites> +
|
|
||||
CanHandle<<_L as ResourceLocator<$name>>::Prerequisites>
|
|
||||
),*
|
|
||||
{
|
|
||||
fn handle(&mut self, ($($name,)*): ($($name,)*)) -> Result<(($($name::Artifact,)*), bool), Box<dyn Error>>
|
|
||||
{
|
|
||||
$(let $name = self.add($name)?;)*
|
|
||||
Ok((($($name.0,)*), false $(|| $name.1)*))
|
|
||||
}
|
|
||||
}
|
|
||||
);
|
|
||||
}
|
|
||||
|
|
||||
for_each_tuple!(can_handle);
|
|
||||
|
|
||||
// This is for self-referential T
|
|
||||
// FIXME: Wait for specialization
|
|
||||
impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle<Option<T>>
|
|
||||
for Setup<_SR, _L, _R, _B>
|
|
||||
where
|
|
||||
_B: SymbolBuilder<T>,
|
|
||||
<_B as SymbolBuilder<T>>::Symbol: Runnable + Debug,
|
|
||||
_L: ResourceLocator<T, Prerequisites = Option<T>>,
|
|
||||
_R: From<T> + BorrowResource<T>,
|
|
||||
Self: CanHandle<<_B as SymbolBuilder<T>>::Prerequisites>,
|
|
||||
{
|
|
||||
fn handle(
|
|
||||
&mut self,
|
|
||||
r: Option<T>,
|
|
||||
) -> Result<(<Option<T> as ToArtifact>::Artifact, bool), Box<dyn Error>> {
|
|
||||
Ok(match r {
|
|
||||
Some(r) => {
|
|
||||
let (result, did_run) = self.add(r)?;
|
|
||||
(Some(result), did_run)
|
|
||||
}
|
|
||||
None => (None, false),
|
|
||||
})
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl<_SR: SymbolRunner, _L, _R: Hash + Eq, _B, T: Resource> CanHandle<T> for Setup<_SR, _L, _R, _B>
|
|
||||
where
|
|
||||
_B: SymbolBuilder<T>,
|
|
||||
<_B as SymbolBuilder<T>>::Symbol: Runnable + Debug,
|
|
||||
_L: ResourceLocator<T>,
|
|
||||
_R: From<T> + Debug + BorrowResource<T>,
|
|
||||
Self: CanHandle<<_B as SymbolBuilder<T>>::Prerequisites>
|
|
||||
+ CanHandle<<_L as ResourceLocator<T>>::Prerequisites>,
|
|
||||
{
|
|
||||
fn handle(&mut self, r: T) -> Result<(<T as ToArtifact>::Artifact, bool), Box<dyn Error>> {
|
|
||||
self.add(r)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
pub struct Setup<
|
|
||||
SR,
|
|
||||
L = DefaultLocator,
|
|
||||
R = DefaultResources<'static, &'static str>,
|
|
||||
B = DefaultBuilder,
|
|
||||
> {
|
|
||||
symbol_runner: SR,
|
|
||||
resources: HashSet<R>,
|
|
||||
phantom: PhantomData<(L, B)>,
|
|
||||
}
|
|
||||
|
|
||||
// https://github.com/rust-lang/rust/issues/27336
|
|
||||
impl<SR> Setup<SR> {
|
|
||||
pub fn new(symbol_runner: SR) -> Self {
|
|
||||
Self {
|
|
||||
symbol_runner,
|
|
||||
resources: Default::default(),
|
|
||||
phantom: Default::default(),
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl<SR, L, R: Hash + Eq, B> Setup<SR, L, R, B> {
|
|
||||
pub fn new_with(symbol_runner: SR) -> Self {
|
|
||||
Self {
|
|
||||
symbol_runner,
|
|
||||
resources: Default::default(),
|
|
||||
phantom: Default::default(),
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
pub trait Runnable {
|
|
||||
fn run<R: SymbolRunner>(&self, runner: &R, force: bool) -> Result<bool, Box<dyn Error>>;
|
|
||||
}
|
|
||||
|
|
||||
impl<S: Symbol + Debug> Runnable for S {
|
|
||||
fn run<R: SymbolRunner>(&self, runner: &R, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
||||
runner.run_symbol(self, force)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
macro_rules! runnable_for_tuple {
|
|
||||
( $($name:ident)* ) => (
|
|
||||
#[allow(non_snake_case)]
|
|
||||
impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) {
|
|
||||
#[allow(unused)]
|
|
||||
fn run<_R: SymbolRunner>(&self, runner: &_R, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
||||
let ($($name,)*) = self;
|
|
||||
let mut result = false;
|
|
||||
$(result = runner.run_symbol($name, force || result)? || result;)*
|
|
||||
Ok(result)
|
|
||||
}
|
|
||||
}
|
|
||||
);
|
|
||||
}
|
|
||||
|
|
||||
for_each_tuple!(runnable_for_tuple);
|
|
||||
|
|
||||
impl<SR: SymbolRunner, L, Rs: Hash + Eq, B> Setup<SR, L, Rs, B> {
|
|
||||
pub fn add<R: Resource>(&mut self, resource: R) -> Result<(R::Artifact, bool), Box<dyn Error>>
|
|
||||
where
|
|
||||
B: SymbolBuilder<R>,
|
|
||||
<B as SymbolBuilder<R>>::Symbol: Runnable + Debug,
|
|
||||
L: ResourceLocator<R>,
|
|
||||
Rs: From<R> + BorrowResource<R>,
|
|
||||
Self: CanHandle<B::Prerequisites> + CanHandle<<L as ResourceLocator<R>>::Prerequisites>,
|
|
||||
{
|
|
||||
self.add_force(resource, false)
|
|
||||
}
|
|
||||
|
|
||||
pub fn add_force<R: Resource>(
|
|
||||
&mut self,
|
|
||||
resource: R,
|
|
||||
force_run: bool,
|
|
||||
) -> Result<(R::Artifact, bool), Box<dyn Error>>
|
|
||||
where
|
|
||||
B: SymbolBuilder<R>,
|
|
||||
<B as SymbolBuilder<R>>::Symbol: Runnable + Debug,
|
|
||||
L: ResourceLocator<R>,
|
|
||||
Rs: From<R> + BorrowResource<R>,
|
|
||||
Self: CanHandle<B::Prerequisites> + CanHandle<<L as ResourceLocator<R>>::Prerequisites>,
|
|
||||
{
|
|
||||
let (target, target_prereqs) = L::locate(&resource);
|
|
||||
let storable_resource = Rs::from(resource);
|
|
||||
let did_run = if self.resources.get(&storable_resource).is_some() {
|
|
||||
assert!(
|
|
||||
!force_run,
|
|
||||
"Forcing to run an already-added resource is a logical error"
|
|
||||
);
|
|
||||
false
|
|
||||
} else {
|
|
||||
let (_, target_prereqs_did_run) = self.handle(target_prereqs)?;
|
|
||||
let (symbol, prereqs_did_run) =
|
|
||||
self.get_symbol(storable_resource.borrow_resource().unwrap(), &target)?;
|
|
||||
self.resources.insert(storable_resource);
|
|
||||
self.run_symbol(
|
|
||||
symbol,
|
|
||||
force_run || target_prereqs_did_run || prereqs_did_run,
|
|
||||
)?
|
|
||||
};
|
|
||||
Ok((target, did_run))
|
|
||||
}
|
|
||||
|
|
||||
fn get_symbol<R: Resource>(
|
|
||||
&mut self,
|
|
||||
resource: &R,
|
|
||||
target: &R::Artifact,
|
|
||||
) -> Result<(<B as SymbolBuilder<R>>::Symbol, bool), Box<dyn Error>>
|
|
||||
where
|
|
||||
B: SymbolBuilder<R>,
|
|
||||
Self: CanHandle<B::Prerequisites>,
|
|
||||
{
|
|
||||
let (prereqs, prereqs_did_run) = self.handle(B::prerequisites(resource))?;
|
|
||||
Ok((B::create(resource, target, prereqs), prereqs_did_run))
|
|
||||
}
|
|
||||
|
|
||||
pub fn run_symbol<S: Runnable>(&self, symbol: S, force: bool) -> Result<bool, Box<dyn Error>> {
|
|
||||
symbol.run(&self.symbol_runner, force)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
#[cfg(test)]
|
|
||||
mod test {
|
|
||||
use crate::resources::{BorrowResource, Resource};
|
|
||||
use crate::schema::SymbolRunner;
|
|
||||
use crate::symbols::Symbol;
|
|
||||
use crate::to_artifact::ToArtifact;
|
|
||||
use crate::{ResourceLocator, Setup, SymbolBuilder};
|
|
||||
use std::cell::RefCell;
|
|
||||
use std::error::Error;
|
|
||||
use std::fmt::Debug;
|
|
||||
use std::rc::Rc;
|
|
||||
|
|
||||
struct TestSymbolRunner {
|
|
||||
count: Rc<RefCell<usize>>,
|
|
||||
}
|
|
||||
|
|
||||
impl SymbolRunner for TestSymbolRunner {
|
|
||||
fn run_symbol<S: Symbol + Debug>(
|
|
||||
&self,
|
|
||||
symbol: &S,
|
|
||||
force: bool,
|
|
||||
) -> Result<bool, Box<dyn Error>> {
|
|
||||
let run = force || !symbol.target_reached()?;
|
|
||||
if run {
|
|
||||
*self.count.borrow_mut() += 1;
|
|
||||
}
|
|
||||
Ok(run)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||
struct TestResource<T>(&'static str, T);
|
|
||||
impl<T> Resource for TestResource<T> {
|
|
||||
type Artifact = ();
|
|
||||
}
|
|
||||
|
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
|
||||
enum Resources {
|
|
||||
A(TestResource<&'static str>),
|
|
||||
B(TestResource<()>),
|
|
||||
}
|
|
||||
impl From<TestResource<&'static str>> for Resources {
|
|
||||
fn from(from: TestResource<&'static str>) -> Self {
|
|
||||
Self::A(from)
|
|
||||
}
|
|
||||
}
|
|
||||
impl From<TestResource<()>> for Resources {
|
|
||||
fn from(from: TestResource<()>) -> Self {
|
|
||||
Self::B(from)
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
impl BorrowResource<TestResource<&'static str>> for Resources {
|
|
||||
fn borrow_resource(&self) -> Option<&TestResource<&'static str>> {
|
|
||||
match self {
|
|
||||
Self::A(a) => Some(a),
|
|
||||
_ => None,
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
impl BorrowResource<TestResource<()>> for Resources {
|
|
||||
fn borrow_resource(&self) -> Option<&TestResource<()>> {
|
|
||||
match self {
|
|
||||
Self::B(b) => Some(b),
|
|
||||
_ => None,
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
struct TestResourceLocator;
|
|
||||
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
|
|
||||
type Prerequisites = ();
|
|
||||
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
|
|
||||
((), ())
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
struct TestSymbolBuilder;
|
|
||||
impl SymbolBuilder<TestResource<&'static str>> for TestSymbolBuilder {
|
|
||||
type Symbol = TestSymbol;
|
|
||||
type Prerequisites = TestResource<()>;
|
|
||||
|
|
||||
fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites {
|
|
||||
TestResource(resource.1, ())
|
|
||||
}
|
|
||||
fn create(
|
|
||||
resource: &TestResource<&'static str>,
|
|
||||
(): &(),
|
|
||||
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
|
|
||||
) -> Self::Symbol {
|
|
||||
TestSymbol {
|
|
||||
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
impl SymbolBuilder<TestResource<()>> for TestSymbolBuilder {
|
|
||||
type Symbol = TestSymbol;
|
|
||||
type Prerequisites = ();
|
|
||||
|
|
||||
fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {}
|
|
||||
fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Symbol {
|
|
||||
TestSymbol {
|
|
||||
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
|
||||
}
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
#[derive(Debug)]
|
|
||||
struct TestSymbol {
|
|
||||
reached: bool,
|
|
||||
}
|
|
||||
impl Symbol for TestSymbol {
|
|
||||
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
|
||||
Ok(self.reached)
|
|
||||
}
|
|
||||
fn execute(&self) -> Result<(), Box<dyn Error>> {
|
|
||||
Ok(())
|
|
||||
}
|
|
||||
}
|
|
||||
|
|
||||
fn get_setup() -> (
|
|
||||
Rc<RefCell<usize>>,
|
|
||||
Setup<TestSymbolRunner, TestResourceLocator, Resources, TestSymbolBuilder>,
|
|
||||
) {
|
|
||||
let count = Rc::new(RefCell::new(0));
|
|
||||
let runner = TestSymbolRunner {
|
|
||||
count: Rc::clone(&count),
|
|
||||
};
|
|
||||
(count, Setup::new_with(runner))
|
|
||||
}
|
|
||||
|
|
||||
#[test]
|
|
||||
fn correctly_uses_force() {
|
|
||||
let (count, mut setup) = get_setup();
|
|
||||
setup.add(TestResource("A", "b")).unwrap();
|
|
||||
assert_eq!(*count.borrow(), 2);
|
|
||||
setup.add(TestResource("A", "b")).unwrap();
|
|
||||
assert_eq!(*count.borrow(), 2);
|
|
||||
|
|
||||
let (count, mut setup) = get_setup();
|
|
||||
setup.add(TestResource("A", "B")).unwrap();
|
|
||||
assert_eq!(*count.borrow(), 0);
|
|
||||
}
|
|
||||
|
|
||||
#[test]
|
|
||||
fn correctly_handles_symbol_tuples() {
|
|
||||
let (count, setup) = get_setup();
|
|
||||
setup
|
|
||||
.run_symbol(
|
|
||||
(TestSymbol { reached: false }, TestSymbol { reached: false }),
|
|
||||
false,
|
|
||||
)
|
|
||||
.unwrap();
|
|
||||
assert_eq!(*count.borrow(), 2);
|
|
||||
|
|
||||
let (count, setup) = get_setup();
|
|
||||
setup
|
|
||||
.run_symbol(
|
|
||||
(TestSymbol { reached: true }, TestSymbol { reached: false }),
|
|
||||
false,
|
|
||||
)
|
|
||||
.unwrap();
|
|
||||
assert_eq!(*count.borrow(), 1);
|
|
||||
|
|
||||
// An unreached symbol forces all further symbols
|
|
||||
let (count, setup) = get_setup();
|
|
||||
setup
|
|
||||
.run_symbol(
|
|
||||
(TestSymbol { reached: false }, TestSymbol { reached: true }),
|
|
||||
false,
|
|
||||
)
|
|
||||
.unwrap();
|
|
||||
assert_eq!(*count.borrow(), 2);
|
|
||||
|
|
||||
let (count, setup) = get_setup();
|
|
||||
setup
|
|
||||
.run_symbol(
|
|
||||
(TestSymbol { reached: true }, TestSymbol { reached: true }),
|
|
||||
false,
|
|
||||
)
|
|
||||
.unwrap();
|
|
||||
assert_eq!(*count.borrow(), 0);
|
|
||||
|
|
||||
let (count, setup) = get_setup();
|
|
||||
setup
|
|
||||
.run_symbol(
|
|
||||
(TestSymbol { reached: true }, TestSymbol { reached: true }),
|
|
||||
true,
|
|
||||
)
|
|
||||
.unwrap();
|
|
||||
assert_eq!(*count.borrow(), 2);
|
|
||||
}
|
|
||||
}
|
|
@ -0,0 +1,191 @@ |
|||||
|
use super::runnable::Runnable;
|
||||
|
use super::util::{AddResult, AddableResource};
|
||||
|
use super::Setup;
|
||||
|
use super::SymbolRunner;
|
||||
|
use crate::async_utils::try_join;
|
||||
|
use crate::loggers::{Logger, StoringLogger};
|
||||
|
use crate::resources::{FromArtifact, FromResource};
|
||||
|
use crate::symbols::Symbol;
|
||||
|
use crate::to_artifact::ToArtifact;
|
||||
|
use crate::{ImplementationBuilder, ResourceLocator};
|
||||
|
use async_trait::async_trait;
|
||||
|
use std::error::Error;
|
||||
|
use std::fmt::Debug;
|
||||
|
use std::hash::Hash;
|
||||
|
use std::marker::PhantomData;
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
pub trait AddGeneric<X: ToArtifact> {
|
||||
|
async fn add_generic(&self, x: X) -> AddResult<X>;
|
||||
|
}
|
||||
|
|
||||
|
macro_rules! add_generic {
|
||||
|
( $($name:ident)* ) => (
|
||||
|
#[async_trait(?Send)]
|
||||
|
#[allow(non_snake_case)]
|
||||
|
impl<SR: 'static, _L: 'static, _B: 'static, LOG: 'static + Logger, Rs: 'static + Hash + Eq, As: 'static + Clone, $($name: AddableResource,)*>
|
||||
|
AddGeneric<($($name,)*)> for Setup<SR, LOG, _L, _B, Rs, As>
|
||||
|
where
|
||||
|
$(
|
||||
|
RegularSetupCore<SR, _L, _B>: SetupCore<$name, Self>,
|
||||
|
As: FromArtifact<$name>,
|
||||
|
Rs: FromResource<$name>,
|
||||
|
$name::Artifact: Clone |
||||
|
),*
|
||||
|
{
|
||||
|
#[allow(unused)]
|
||||
|
async fn add_generic(&self, ($($name,)*): ($($name,)*)) -> Result<(($($name::Artifact,)*), bool), Box<dyn Error>>
|
||||
|
{
|
||||
|
let x: Result<_, Box<dyn Error>> = try_join!($(self.add_async($name, false),)*);
|
||||
|
let ($($name,)*) = x?;
|
||||
|
Ok((($($name.0,)*), false $(|| $name.1)*))
|
||||
|
}
|
||||
|
}
|
||||
|
);
|
||||
|
}
|
||||
|
|
||||
|
for_each_tuple!(add_generic);
|
||||
|
|
||||
|
// This is for self-referential T
|
||||
|
// FIXME: Wait for specialization
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<
|
||||
|
SR: 'static + SymbolRunner,
|
||||
|
LOG: 'static + Logger,
|
||||
|
T: AddableResource,
|
||||
|
Rs: 'static + Hash + Eq + FromResource<T>,
|
||||
|
As: 'static + FromArtifact<T> + Clone,
|
||||
|
L: 'static + ResourceLocator<T, Prerequisites = Option<T>>,
|
||||
|
B: 'static + ImplementationBuilder<T>,
|
||||
|
> AddGeneric<Option<T>> for Setup<SR, LOG, L, B, Rs, As>
|
||||
|
where
|
||||
|
<B as ImplementationBuilder<T>>::Implementation: Runnable + Debug,
|
||||
|
Self: AddGeneric<B::Prerequisites>,
|
||||
|
T::Artifact: Clone,
|
||||
|
{
|
||||
|
async fn add_generic(&self, r: Option<T>) -> AddResult<Option<T>> {
|
||||
|
Ok(match r {
|
||||
|
Some(r) => {
|
||||
|
let (result, did_run) = self.add_async(r, false).await?;
|
||||
|
(Some(result), did_run)
|
||||
|
}
|
||||
|
None => (None, false),
|
||||
|
})
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<
|
||||
|
LOG: 'static + Logger,
|
||||
|
T: AddableResource,
|
||||
|
Rs: 'static + Hash + Eq + FromResource<T>,
|
||||
|
As: 'static + FromArtifact<T> + Clone,
|
||||
|
SR: 'static,
|
||||
|
L: 'static,
|
||||
|
B: 'static,
|
||||
|
> AddGeneric<T> for Setup<SR, LOG, L, B, Rs, As>
|
||||
|
where
|
||||
|
T::Artifact: Clone,
|
||||
|
RegularSetupCore<SR, L, B>: 'static + SetupCore<T, Self>,
|
||||
|
{
|
||||
|
async fn add_generic(&self, r: T) -> AddResult<T> {
|
||||
|
self.add_async(r, false).await
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
pub trait SetupCore<R: AddableResource, S> {
|
||||
|
async fn add<LOG: Logger, RR: AsRef<R>>(
|
||||
|
&self,
|
||||
|
setup: &S,
|
||||
|
parent_logger: &LOG,
|
||||
|
resource: RR,
|
||||
|
force_run: bool,
|
||||
|
) -> AddResult<R>;
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
pub struct RegularSetupCore<SR, L, B> {
|
||||
|
symbol_runner: SR,
|
||||
|
phantom: PhantomData<(L, B)>,
|
||||
|
}
|
||||
|
|
||||
|
impl<SR, L, B> RegularSetupCore<SR, L, B> {
|
||||
|
pub fn new(symbol_runner: SR) -> Self {
|
||||
|
Self {
|
||||
|
symbol_runner,
|
||||
|
phantom: PhantomData::default(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<SR: SymbolRunner, L, B, R: AddableResource, S> SetupCore<R, S> for RegularSetupCore<SR, L, B>
|
||||
|
where
|
||||
|
B: ImplementationBuilder<R>,
|
||||
|
<B as ImplementationBuilder<R>>::Implementation: Runnable + Debug,
|
||||
|
L: ResourceLocator<R>,
|
||||
|
S: AddGeneric<B::Prerequisites> + AddGeneric<<L as ResourceLocator<R>>::Prerequisites>,
|
||||
|
{
|
||||
|
async fn add<LOG: Logger, RR: AsRef<R>>(
|
||||
|
&self,
|
||||
|
setup: &S,
|
||||
|
parent_logger: &LOG,
|
||||
|
resource: RR,
|
||||
|
force_run: bool,
|
||||
|
) -> AddResult<R> {
|
||||
|
let resource = resource.as_ref();
|
||||
|
let logger = StoringLogger::new();
|
||||
|
logger.write(4, format!("Adding {:?} ... ", resource));
|
||||
|
let result = {
|
||||
|
logger.trace(format!(" (force_run is {})", force_run));
|
||||
|
let (location, location_prereqs) = L::locate(resource);
|
||||
|
logger.trace(format!("Adding location prereqs for {:?}", resource));
|
||||
|
let (_, location_prereqs_did_run) = setup.add_generic(location_prereqs).await?;
|
||||
|
logger.trace(format!(
|
||||
|
"Location prereqs for {:?} did_run: {}",
|
||||
|
resource, location_prereqs_did_run
|
||||
|
));
|
||||
|
logger.trace(format!("Adding implementation prereqs for {:?}", resource));
|
||||
|
let (prereqs, prereqs_did_run) = setup.add_generic(B::prerequisites(resource)).await?;
|
||||
|
logger.trace(format!(
|
||||
|
"Implementation prereqs for {:?} did_run: {}",
|
||||
|
resource, prereqs_did_run
|
||||
|
));
|
||||
|
logger.trace(format!("Running implementation for {:?}", resource));
|
||||
|
let implementation = B::create(resource, &location, prereqs);
|
||||
|
let did_run = implementation
|
||||
|
.run(
|
||||
|
&self.symbol_runner,
|
||||
|
&logger,
|
||||
|
force_run || location_prereqs_did_run || prereqs_did_run,
|
||||
|
)
|
||||
|
.await?;
|
||||
|
Ok((location, did_run))
|
||||
|
};
|
||||
|
logger.write(4, "done.");
|
||||
|
let max_level = if result.is_err() { 5 } else { 3 };
|
||||
|
if parent_logger.put(logger.release().into_iter().filter(|e| e.0 <= max_level)) == 0 {
|
||||
|
parent_logger.write(3, ".");
|
||||
|
}
|
||||
|
result
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<SR: SymbolRunner, L, B> SymbolRunner for RegularSetupCore<SR, L, B> {
|
||||
|
async fn run_symbol<S: Symbol + Debug, LOG: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
parent_logger: &LOG,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||
|
let logger = StoringLogger::new();
|
||||
|
logger.debug(format!("Directly running {:?} ...", symbol));
|
||||
|
let result = self.symbol_runner.run_symbol(symbol, &logger, force).await;
|
||||
|
logger.debug("done.");
|
||||
|
let max_level = if result.is_err() { 5 } else { 3 };
|
||||
|
parent_logger.put(logger.release().into_iter().filter(|e| e.0 <= max_level));
|
||||
|
result
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,10 @@ |
|||||
|
mod core;
|
||||
|
mod util;
|
||||
|
pub use util::{AddResult, AddableResource};
|
||||
|
mod symbol_runner;
|
||||
|
pub use symbol_runner::{
|
||||
|
DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner,
|
||||
|
};
|
||||
|
mod runnable;
|
||||
|
mod setup;
|
||||
|
pub use setup::Setup;
|
@ -0,0 +1,193 @@ |
|||||
|
use super::SymbolRunner;
|
||||
|
use crate::loggers::Logger;
|
||||
|
use crate::symbols::Symbol;
|
||||
|
use async_trait::async_trait;
|
||||
|
use std::error::Error;
|
||||
|
use std::fmt::Debug;
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
pub trait Runnable {
|
||||
|
async fn run<R: SymbolRunner, L: Logger>(
|
||||
|
&self,
|
||||
|
runner: &R,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>>;
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<S> Runnable for S
|
||||
|
where
|
||||
|
Self: Symbol + Debug,
|
||||
|
{
|
||||
|
async fn run<R: SymbolRunner, L: Logger>(
|
||||
|
&self,
|
||||
|
runner: &R,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||
|
runner.run_symbol(self, logger, force).await
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
macro_rules! runnable_for_tuple {
|
||||
|
( $($name:ident)* ) => (
|
||||
|
#[async_trait(?Send)]
|
||||
|
#[allow(non_snake_case)]
|
||||
|
impl<$($name: Symbol + Debug,)*> Runnable for ($($name,)*) {
|
||||
|
#[allow(unused)]
|
||||
|
async fn run<_R: SymbolRunner, _L: Logger>(&self, runner: &_R, logger: &_L, force: bool) -> Result<bool, Box<dyn Error>> {
|
||||
|
let ($($name,)*) = self;
|
||||
|
let mut result = false;
|
||||
|
$(result = runner.run_symbol($name, logger, force || result).await? || result;)*
|
||||
|
Ok(result)
|
||||
|
}
|
||||
|
}
|
||||
|
);
|
||||
|
}
|
||||
|
|
||||
|
for_each_tuple!(runnable_for_tuple);
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod test {
|
||||
|
use super::Runnable;
|
||||
|
use crate::async_utils::run;
|
||||
|
use crate::loggers::{Logger, StoringLogger};
|
||||
|
use crate::symbols::Symbol;
|
||||
|
use crate::SymbolRunner;
|
||||
|
use async_trait::async_trait;
|
||||
|
use std::cell::RefCell;
|
||||
|
use std::error::Error;
|
||||
|
use std::fmt::Debug;
|
||||
|
use std::rc::Rc;
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
struct DummySymbol<T, E> {
|
||||
|
_target_reached: RefCell<T>,
|
||||
|
_execute: RefCell<E>,
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<
|
||||
|
E: Iterator<Item = Result<(), Box<dyn Error>>>,
|
||||
|
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
|
||||
|
> Symbol for DummySymbol<T, E>
|
||||
|
{
|
||||
|
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
|
self._target_reached.borrow_mut().next().unwrap()
|
||||
|
}
|
||||
|
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
|
self._execute.borrow_mut().next().unwrap()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<
|
||||
|
E: Iterator<Item = Result<(), Box<dyn Error>>>,
|
||||
|
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
|
||||
|
> DummySymbol<T, E>
|
||||
|
{
|
||||
|
fn new<
|
||||
|
IE: IntoIterator<IntoIter = E, Item = Result<(), Box<dyn Error>>>,
|
||||
|
IT: IntoIterator<IntoIter = T, Item = Result<bool, Box<dyn Error>>>,
|
||||
|
>(
|
||||
|
target_reached: IT,
|
||||
|
execute: IE,
|
||||
|
) -> Self {
|
||||
|
Self {
|
||||
|
_target_reached: RefCell::new(target_reached.into_iter()),
|
||||
|
_execute: RefCell::new(execute.into_iter()),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
struct TestSymbolRunner {
|
||||
|
count: Rc<RefCell<usize>>,
|
||||
|
}
|
||||
|
|
||||
|
fn get_runner() -> (Rc<RefCell<usize>>, TestSymbolRunner) {
|
||||
|
let count = Rc::new(RefCell::new(0));
|
||||
|
let runner = TestSymbolRunner {
|
||||
|
count: Rc::clone(&count),
|
||||
|
};
|
||||
|
(count, runner)
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl SymbolRunner for TestSymbolRunner {
|
||||
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||
|
let run = force || !symbol.target_reached().await?;
|
||||
|
if run {
|
||||
|
*self.count.borrow_mut() += 1;
|
||||
|
}
|
||||
|
Ok(run)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
fn run_symbol(
|
||||
|
runnable: impl Runnable,
|
||||
|
force: bool,
|
||||
|
) -> (Rc<RefCell<usize>>, Result<bool, Box<dyn Error>>) {
|
||||
|
let (count, runner) = get_runner();
|
||||
|
let res = run(runnable.run(&runner, &StoringLogger::new(), force));
|
||||
|
(count, res)
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn correctly_handles_symbol_tuples() {
|
||||
|
let (count, res) = run_symbol(
|
||||
|
(
|
||||
|
DummySymbol::new(vec![Ok(false)], vec![Ok(())]),
|
||||
|
DummySymbol::new(vec![Ok(false)], vec![Ok(())]),
|
||||
|
),
|
||||
|
false,
|
||||
|
);
|
||||
|
res.unwrap();
|
||||
|
assert_eq!(*count.borrow(), 2);
|
||||
|
|
||||
|
let (count, res) = run_symbol(
|
||||
|
(
|
||||
|
DummySymbol::new(vec![Ok(true)], vec![Ok(())]),
|
||||
|
DummySymbol::new(vec![Ok(false)], vec![Ok(())]),
|
||||
|
),
|
||||
|
false,
|
||||
|
);
|
||||
|
res.unwrap();
|
||||
|
assert_eq!(*count.borrow(), 1);
|
||||
|
|
||||
|
// An unreached symbol forces all further symbols
|
||||
|
let (count, res) = run_symbol(
|
||||
|
(
|
||||
|
DummySymbol::new(vec![Ok(false)], vec![Ok(())]),
|
||||
|
DummySymbol::new(vec![Ok(true)], vec![Ok(())]),
|
||||
|
),
|
||||
|
false,
|
||||
|
);
|
||||
|
res.unwrap();
|
||||
|
assert_eq!(*count.borrow(), 2);
|
||||
|
|
||||
|
let (count, res) = run_symbol(
|
||||
|
(
|
||||
|
DummySymbol::new(vec![Ok(true)], vec![Ok(())]),
|
||||
|
DummySymbol::new(vec![Ok(true)], vec![Ok(())]),
|
||||
|
),
|
||||
|
false,
|
||||
|
);
|
||||
|
res.unwrap();
|
||||
|
assert_eq!(*count.borrow(), 0);
|
||||
|
|
||||
|
let (count, res) = run_symbol(
|
||||
|
(
|
||||
|
DummySymbol::new(vec![Ok(true)], vec![Ok(())]),
|
||||
|
DummySymbol::new(vec![Ok(true)], vec![Ok(())]),
|
||||
|
),
|
||||
|
true,
|
||||
|
);
|
||||
|
res.unwrap();
|
||||
|
assert_eq!(*count.borrow(), 2);
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,300 @@ |
|||||
|
use super::core::{RegularSetupCore, SetupCore};
|
||||
|
use super::runnable::Runnable;
|
||||
|
use super::util::{AddResult, AddableResource};
|
||||
|
use super::SymbolRunner;
|
||||
|
use crate::async_utils::run;
|
||||
|
use crate::loggers::Logger;
|
||||
|
use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource};
|
||||
|
use crate::{DefaultBuilder, DefaultLocator};
|
||||
|
use futures::future::FutureExt;
|
||||
|
use futures::future::Shared;
|
||||
|
use std::cell::{RefCell, RefMut};
|
||||
|
use std::collections::HashMap;
|
||||
|
use std::error::Error;
|
||||
|
use std::future::Future;
|
||||
|
use std::hash::Hash;
|
||||
|
use std::pin::Pin;
|
||||
|
use std::rc::Rc;
|
||||
|
|
||||
|
type Cache<Rs, As> = HashMap<Rs, Shared<Pin<Box<dyn Future<Output = (As, bool)>>>>>;
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
struct SetupInner<CORE, LOG, Rs, As> {
|
||||
|
core: CORE,
|
||||
|
logger: LOG,
|
||||
|
resources: RefCell<Cache<Rs, As>>,
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
pub struct Setup<
|
||||
|
SR,
|
||||
|
LOG,
|
||||
|
L = DefaultLocator,
|
||||
|
B = DefaultBuilder,
|
||||
|
Rs = DefaultResources<'static, &'static str>,
|
||||
|
As = DefaultArtifacts<'static, &'static str>,
|
||||
|
>(Rc<SetupInner<RegularSetupCore<SR, L, B>, LOG, Rs, As>>);
|
||||
|
|
||||
|
// https://github.com/rust-lang/rust/issues/27336
|
||||
|
impl<SR, LOG> Setup<SR, LOG> {
|
||||
|
pub fn new(symbol_runner: SR, logger: LOG) -> Self {
|
||||
|
Self::new_with(symbol_runner, logger)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<L, B, As, SR, LOG, Rs: Hash + Eq> Setup<SR, LOG, L, B, Rs, As> {
|
||||
|
pub fn new_with(symbol_runner: SR, logger: LOG) -> Self {
|
||||
|
Self(Rc::new(SetupInner {
|
||||
|
core: RegularSetupCore::new(symbol_runner),
|
||||
|
logger,
|
||||
|
resources: RefCell::default(),
|
||||
|
}))
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<
|
||||
|
L: 'static,
|
||||
|
B: 'static,
|
||||
|
SR: 'static,
|
||||
|
LOG: 'static + Logger,
|
||||
|
Rs: Hash + Eq + 'static,
|
||||
|
As: 'static,
|
||||
|
> Setup<SR, LOG, L, B, Rs, As>
|
||||
|
{
|
||||
|
fn borrow_resources(&self) -> RefMut<'_, Cache<Rs, As>> {
|
||||
|
self.0.resources.borrow_mut()
|
||||
|
}
|
||||
|
|
||||
|
pub(super) async fn add_async<R: AddableResource>(
|
||||
|
&self,
|
||||
|
resource: R,
|
||||
|
force_run: bool,
|
||||
|
) -> AddResult<R>
|
||||
|
where
|
||||
|
Rs: FromResource<R>,
|
||||
|
As: FromArtifact<R> + Clone,
|
||||
|
R::Artifact: Clone,
|
||||
|
RegularSetupCore<SR, L, B>: SetupCore<R, Self>,
|
||||
|
{
|
||||
|
let (storable_resource, weak_resource) = Rs::from_resource(resource);
|
||||
|
let mut resources = self.borrow_resources();
|
||||
|
if let Some(future) = resources.remove(&storable_resource) {
|
||||
|
assert!(
|
||||
|
!force_run,
|
||||
|
"Forcing to run an already-added resource is a logical error"
|
||||
|
);
|
||||
|
resources.insert(storable_resource, future.clone());
|
||||
|
drop(resources);
|
||||
|
Ok(future.await)
|
||||
|
} else {
|
||||
|
let inner_weak = Rc::downgrade(&self.0);
|
||||
|
let future = Box::pin(async move {
|
||||
|
let this = Self(inner_weak.upgrade().expect("Dangling!"));
|
||||
|
let resource = weak_resource.upgrade().expect("Dangling!");
|
||||
|
// Need to convert Box<Error> to String for Clone for Shared
|
||||
|
this
|
||||
|
.0
|
||||
|
.core
|
||||
|
.add(&this, &this.0.logger, resource, force_run)
|
||||
|
.await
|
||||
|
.map(|(t, did_run)| (As::from_artifact(t), did_run))
|
||||
|
.map_err(|e| e.to_string())
|
||||
|
})
|
||||
|
.shared();
|
||||
|
let future_clone = future.clone();
|
||||
|
resources.insert(
|
||||
|
storable_resource,
|
||||
|
(Box::pin(async move { future_clone.await.unwrap() })
|
||||
|
as Pin<Box<dyn Future<Output = (As, bool)>>>)
|
||||
|
.shared(),
|
||||
|
);
|
||||
|
drop(resources);
|
||||
|
future.await.map_err(|e| e.into())
|
||||
|
}
|
||||
|
.map(|(t, did_run)| (t.into_artifact(), did_run))
|
||||
|
}
|
||||
|
|
||||
|
//
|
||||
|
// Legacy
|
||||
|
//
|
||||
|
pub fn add<R: AddableResource>(&self, resource: R) -> AddResult<R>
|
||||
|
where
|
||||
|
RegularSetupCore<SR, L, B>: SetupCore<R, Self>,
|
||||
|
Rs: FromResource<R>,
|
||||
|
As: FromArtifact<R> + Clone,
|
||||
|
R::Artifact: Clone,
|
||||
|
{
|
||||
|
run(self.add_async(resource, false))
|
||||
|
}
|
||||
|
|
||||
|
pub fn add_force<R: AddableResource>(&self, resource: R, force_run: bool) -> AddResult<R>
|
||||
|
where
|
||||
|
RegularSetupCore<SR, L, B>: SetupCore<R, Self>,
|
||||
|
Rs: FromResource<R>,
|
||||
|
As: FromArtifact<R> + Clone,
|
||||
|
R::Artifact: Clone,
|
||||
|
{
|
||||
|
run(self.add_async(resource, force_run))
|
||||
|
}
|
||||
|
|
||||
|
pub fn run_symbol<S: Runnable>(&self, symbol: S, force: bool) -> Result<bool, Box<dyn Error>>
|
||||
|
where
|
||||
|
RegularSetupCore<SR, L, B>: SymbolRunner,
|
||||
|
{
|
||||
|
run(symbol.run(&self.0.core, &self.0.logger, force))
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod test {
|
||||
|
use super::SymbolRunner;
|
||||
|
use crate::loggers::{Logger, StoringLogger};
|
||||
|
use crate::resources::{FromArtifact, FromResource, Resource};
|
||||
|
use crate::symbols::Symbol;
|
||||
|
use crate::to_artifact::ToArtifact;
|
||||
|
use crate::{ImplementationBuilder, ResourceLocator, Setup};
|
||||
|
use async_trait::async_trait;
|
||||
|
use std::cell::RefCell;
|
||||
|
use std::error::Error;
|
||||
|
use std::fmt::Debug;
|
||||
|
use std::rc::{Rc, Weak};
|
||||
|
|
||||
|
struct TestSymbolRunner {
|
||||
|
count: Rc<RefCell<usize>>,
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl SymbolRunner for TestSymbolRunner {
|
||||
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||
|
let run = force || !symbol.target_reached().await?;
|
||||
|
if run {
|
||||
|
*self.count.borrow_mut() += 1;
|
||||
|
}
|
||||
|
Ok(run)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
struct TestResource<T>(&'static str, T);
|
||||
|
impl<T> Resource for TestResource<T> {
|
||||
|
type Artifact = ();
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
|
enum Resources {
|
||||
|
A(Rc<TestResource<&'static str>>),
|
||||
|
B(Rc<TestResource<()>>),
|
||||
|
}
|
||||
|
impl FromResource<TestResource<&'static str>> for Resources {
|
||||
|
fn from_resource(from: TestResource<&'static str>) -> (Self, Weak<TestResource<&'static str>>) {
|
||||
|
let inner = Rc::new(from);
|
||||
|
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
|
||||
|
}
|
||||
|
}
|
||||
|
impl FromResource<TestResource<()>> for Resources {
|
||||
|
fn from_resource(from: TestResource<()>) -> (Self, Weak<TestResource<()>>) {
|
||||
|
let inner = Rc::new(from);
|
||||
|
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Clone)]
|
||||
|
struct Artifacts;
|
||||
|
impl<V> FromArtifact<TestResource<V>> for Artifacts {
|
||||
|
fn from_artifact(from: ()) -> Self {
|
||||
|
Self
|
||||
|
}
|
||||
|
fn into_artifact(self) -> () {
|
||||
|
()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
struct TestResourceLocator;
|
||||
|
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
|
||||
|
type Prerequisites = ();
|
||||
|
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
|
||||
|
((), ())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
struct TestImplementationBuilder;
|
||||
|
impl ImplementationBuilder<TestResource<&'static str>> for TestImplementationBuilder {
|
||||
|
type Implementation = TestSymbol;
|
||||
|
type Prerequisites = TestResource<()>;
|
||||
|
|
||||
|
fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites {
|
||||
|
TestResource(resource.1, ())
|
||||
|
}
|
||||
|
fn create(
|
||||
|
resource: &TestResource<&'static str>,
|
||||
|
(): &(),
|
||||
|
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
|
||||
|
) -> Self::Implementation {
|
||||
|
TestSymbol {
|
||||
|
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
impl ImplementationBuilder<TestResource<()>> for TestImplementationBuilder {
|
||||
|
type Implementation = TestSymbol;
|
||||
|
type Prerequisites = ();
|
||||
|
|
||||
|
fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {}
|
||||
|
fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation {
|
||||
|
TestSymbol {
|
||||
|
reached: resource.0.chars().next().unwrap().is_uppercase(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
struct TestSymbol {
|
||||
|
reached: bool,
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl Symbol for TestSymbol {
|
||||
|
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
|
Ok(self.reached)
|
||||
|
}
|
||||
|
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
fn get_setup() -> (
|
||||
|
Rc<RefCell<usize>>,
|
||||
|
Setup<
|
||||
|
TestSymbolRunner,
|
||||
|
StoringLogger,
|
||||
|
TestResourceLocator,
|
||||
|
TestImplementationBuilder,
|
||||
|
Resources,
|
||||
|
Artifacts,
|
||||
|
>,
|
||||
|
) {
|
||||
|
let count = Rc::new(RefCell::new(0));
|
||||
|
let runner = TestSymbolRunner {
|
||||
|
count: Rc::clone(&count),
|
||||
|
};
|
||||
|
(count, Setup::new_with(runner, StoringLogger::new()))
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn correctly_uses_force() {
|
||||
|
let (count, setup) = get_setup();
|
||||
|
setup.add(TestResource("A", "b")).unwrap();
|
||||
|
assert_eq!(*count.borrow(), 2);
|
||||
|
setup.add(TestResource("A", "b")).unwrap();
|
||||
|
assert_eq!(*count.borrow(), 2);
|
||||
|
|
||||
|
let (count, setup) = get_setup();
|
||||
|
setup.add(TestResource("A", "B")).unwrap();
|
||||
|
assert_eq!(*count.borrow(), 0);
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,318 @@ |
|||||
|
use crate::loggers::Logger;
|
||||
|
use crate::symbols::Symbol;
|
||||
|
use async_trait::async_trait;
|
||||
|
use std::error::Error;
|
||||
|
use std::fmt;
|
||||
|
use std::fmt::Debug;
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
pub trait SymbolRunner {
|
||||
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>>;
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
pub enum SymbolRunError {
|
||||
|
Symbol(Box<dyn Error>),
|
||||
|
ExecuteDidNotReach(()),
|
||||
|
}
|
||||
|
|
||||
|
impl Error for SymbolRunError {
|
||||
|
fn cause(&self) -> Option<&dyn Error> {
|
||||
|
match self {
|
||||
|
Self::Symbol(ref e) => Some(&**e),
|
||||
|
Self::ExecuteDidNotReach(_) => None,
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl fmt::Display for SymbolRunError {
|
||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
match self {
|
||||
|
Self::Symbol(ref e) => write!(f, "{}", e),
|
||||
|
Self::ExecuteDidNotReach(_) => write!(f, "Target not reached after executing symbol"),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Clone, Debug, Default)]
|
||||
|
pub struct InitializingSymbolRunner;
|
||||
|
|
||||
|
impl InitializingSymbolRunner {
|
||||
|
pub fn new() -> Self {
|
||||
|
Self
|
||||
|
}
|
||||
|
|
||||
|
async fn exec_symbol<S: Symbol + Debug, L: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
logger: &L,
|
||||
|
) -> Result<(), Box<dyn Error>> {
|
||||
|
logger.info(format!("Executing {:?}", symbol));
|
||||
|
symbol.execute().await?;
|
||||
|
let target_reached = symbol.target_reached().await?;
|
||||
|
logger.trace(format!(
|
||||
|
"Symbol reports target_reached: {:?} (should be true)",
|
||||
|
target_reached
|
||||
|
));
|
||||
|
if target_reached {
|
||||
|
Ok(())
|
||||
|
} else {
|
||||
|
Err(Box::new(SymbolRunError::ExecuteDidNotReach(())))
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl SymbolRunner for InitializingSymbolRunner {
|
||||
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||
|
let executed = if force {
|
||||
|
logger.debug("Forcing symbol execution");
|
||||
|
self.exec_symbol(symbol, logger).await?;
|
||||
|
true
|
||||
|
} else {
|
||||
|
let target_reached = symbol.target_reached().await?;
|
||||
|
if target_reached {
|
||||
|
logger.debug(format!("{:?} already reached", symbol));
|
||||
|
} else {
|
||||
|
logger.trace(format!(
|
||||
|
"Symbol reports target_reached: {:?}",
|
||||
|
target_reached
|
||||
|
));
|
||||
|
self.exec_symbol(symbol, logger).await?;
|
||||
|
}
|
||||
|
!target_reached
|
||||
|
};
|
||||
|
Ok(executed)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Clone, Debug, Default)]
|
||||
|
pub struct DrySymbolRunner;
|
||||
|
|
||||
|
impl DrySymbolRunner {
|
||||
|
pub fn new() -> Self {
|
||||
|
Self
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl SymbolRunner for DrySymbolRunner {
|
||||
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||
|
let would_execute = if force {
|
||||
|
logger.info(format!("Would force-execute {:?}", symbol));
|
||||
|
true
|
||||
|
} else {
|
||||
|
let target_reached = symbol.target_reached().await?;
|
||||
|
logger.debug(format!(
|
||||
|
"Symbol reports target_reached: {:?}",
|
||||
|
target_reached
|
||||
|
));
|
||||
|
if !target_reached {
|
||||
|
logger.info(format!("Would execute {:?}", symbol));
|
||||
|
}
|
||||
|
!target_reached
|
||||
|
};
|
||||
|
Ok(would_execute)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Clone, Debug)]
|
||||
|
pub struct ReportingSymbolRunner<R>(R);
|
||||
|
|
||||
|
impl<R> ReportingSymbolRunner<R> {
|
||||
|
pub fn new(symbol_runner: R) -> Self {
|
||||
|
Self(symbol_runner)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<R> SymbolRunner for ReportingSymbolRunner<R>
|
||||
|
where
|
||||
|
R: SymbolRunner,
|
||||
|
{
|
||||
|
async fn run_symbol<S: Symbol + Debug, L: Logger>(
|
||||
|
&self,
|
||||
|
symbol: &S,
|
||||
|
logger: &L,
|
||||
|
force: bool,
|
||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||
|
logger.debug(format!("Running symbol {:?}", symbol));
|
||||
|
let res = self.0.run_symbol(symbol, logger, force).await;
|
||||
|
if let Err(ref e) = res {
|
||||
|
logger.info(format!("Failed on {:?} with {}, aborting.", symbol, e))
|
||||
|
} else {
|
||||
|
logger.debug(format!("Successfully finished {:?}", symbol))
|
||||
|
}
|
||||
|
res
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod test {
|
||||
|
use super::{DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner, SymbolRunner};
|
||||
|
use crate::async_utils::sleep;
|
||||
|
use crate::async_utils::{run, try_join};
|
||||
|
use crate::loggers::StoringLogger;
|
||||
|
use crate::symbols::Symbol;
|
||||
|
use async_trait::async_trait;
|
||||
|
use std::cell::RefCell;
|
||||
|
use std::error::Error;
|
||||
|
use std::fmt;
|
||||
|
use std::fmt::Debug;
|
||||
|
use std::time::Duration;
|
||||
|
|
||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||
|
enum DummySymbolError {
|
||||
|
Error(()),
|
||||
|
}
|
||||
|
|
||||
|
impl Error for DummySymbolError {}
|
||||
|
|
||||
|
impl fmt::Display for DummySymbolError {
|
||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
write!(f, "Dummy symbol error")
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
struct DummySymbol<T, E> {
|
||||
|
_target_reached: RefCell<T>,
|
||||
|
_execute: RefCell<E>,
|
||||
|
}
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl<
|
||||
|
E: Iterator<Item = Result<(), Box<dyn Error>>>,
|
||||
|
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
|
||||
|
> Symbol for DummySymbol<T, E>
|
||||
|
{
|
||||
|
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
|
self._target_reached.borrow_mut().next().unwrap()
|
||||
|
}
|
||||
|
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
|
self._execute.borrow_mut().next().unwrap()
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<
|
||||
|
E: Iterator<Item = Result<(), Box<dyn Error>>>,
|
||||
|
T: Iterator<Item = Result<bool, Box<dyn Error>>>,
|
||||
|
> DummySymbol<T, E>
|
||||
|
{
|
||||
|
fn new<
|
||||
|
IE: IntoIterator<IntoIter = E, Item = Result<(), Box<dyn Error>>>,
|
||||
|
IT: IntoIterator<IntoIter = T, Item = Result<bool, Box<dyn Error>>>,
|
||||
|
>(
|
||||
|
target_reached: IT,
|
||||
|
execute: IE,
|
||||
|
) -> Self {
|
||||
|
Self {
|
||||
|
_target_reached: RefCell::new(target_reached.into_iter()),
|
||||
|
_execute: RefCell::new(execute.into_iter()),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
fn run_symbol<S: Symbol + Debug>(s: S) -> Result<bool, Box<dyn Error>> {
|
||||
|
run(InitializingSymbolRunner::new().run_symbol(&s, &StoringLogger::new(), false))
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn nothing_needed_to_be_done() {
|
||||
|
let result = run_symbol(DummySymbol::new(vec![Ok(true)], vec![Ok(())]));
|
||||
|
assert!(result.is_ok());
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn everything_is_ok() {
|
||||
|
let result = run_symbol(DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]));
|
||||
|
assert!(result.is_ok());
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn executing_did_not_change_state() {
|
||||
|
let result = run_symbol(DummySymbol::new(vec![Ok(false), Ok(false)], vec![Ok(())]));
|
||||
|
assert_eq!(
|
||||
|
result.unwrap_err().to_string(),
|
||||
|
"Target not reached after executing symbol"
|
||||
|
);
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn executing_did_not_work() {
|
||||
|
let result = run_symbol(DummySymbol::new(
|
||||
|
vec![Ok(false)],
|
||||
|
vec![Err(Box::new(DummySymbolError::Error(())) as Box<dyn Error>)],
|
||||
|
));
|
||||
|
assert_eq!(result.unwrap_err().to_string(), "Dummy symbol error");
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug)]
|
||||
|
struct SleeperSymbol;
|
||||
|
|
||||
|
#[async_trait(?Send)]
|
||||
|
impl Symbol for SleeperSymbol {
|
||||
|
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
||||
|
sleep(Duration::from_millis(0)).await;
|
||||
|
Ok(true)
|
||||
|
}
|
||||
|
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
||||
|
unimplemented!();
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn actually_support_parallel_execution() {
|
||||
|
run(async {
|
||||
|
let s1 = SleeperSymbol;
|
||||
|
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
||||
|
|
||||
|
let l1 = StoringLogger::new();
|
||||
|
let l2 = StoringLogger::new();
|
||||
|
let runner1 = InitializingSymbolRunner::new();
|
||||
|
let result = try_join!(
|
||||
|
runner1.run_symbol(&s1, &l1, false),
|
||||
|
runner1.run_symbol(&s2, &l2, false),
|
||||
|
)
|
||||
|
.unwrap();
|
||||
|
assert_eq!(result, (false, true));
|
||||
|
|
||||
|
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
||||
|
let l1 = StoringLogger::new();
|
||||
|
let l2 = StoringLogger::new();
|
||||
|
let runner2 = DrySymbolRunner::new();
|
||||
|
let result = try_join!(
|
||||
|
runner2.run_symbol(&s1, &l1, false),
|
||||
|
runner2.run_symbol(&s2, &l2, false),
|
||||
|
)
|
||||
|
.unwrap();
|
||||
|
assert_eq!(result, (false, true));
|
||||
|
|
||||
|
let s2 = DummySymbol::new(vec![Ok(false), Ok(true)], vec![Ok(())]);
|
||||
|
let l1 = StoringLogger::new();
|
||||
|
let l2 = StoringLogger::new();
|
||||
|
let runner3 = ReportingSymbolRunner::new(runner1);
|
||||
|
let result = try_join!(
|
||||
|
runner3.run_symbol(&s1, &l1, false),
|
||||
|
runner3.run_symbol(&s2, &l2, false),
|
||||
|
)
|
||||
|
.unwrap();
|
||||
|
assert_eq!(result, (false, true));
|
||||
|
});
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,9 @@ |
|||||
|
use crate::resources::Resource;
|
||||
|
use crate::to_artifact::ToArtifact;
|
||||
|
use std::error::Error;
|
||||
|
use std::fmt::Debug;
|
||||
|
|
||||
|
pub trait AddableResource: 'static + Resource + Debug {}
|
||||
|
impl<R> AddableResource for R where R: 'static + Resource + Debug {}
|
||||
|
|
||||
|
pub type AddResult<R> = Result<(<R as ToArtifact>::Artifact, bool), Box<dyn Error>>;
|
@ -1,4 +1,4 @@ |
|||||
mod checkout;
|
mod checkout;
|
||||
//pub mod submodules;
|
|
||||
|
pub mod submodules;
|
||||
|
|
||||
pub use checkout::Checkout;
|
pub use checkout::Checkout;
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue