Experiment with futures

This commit is contained in:
Adrian Heine 2020-08-16 11:08:22 +02:00
parent 907fbf95db
commit 2d3e3688fa
44 changed files with 2081 additions and 1242 deletions

191
src/setup/core.rs Normal file
View file

@ -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
}
}

10
src/setup/mod.rs Normal file
View file

@ -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;

193
src/setup/runnable.rs Normal file
View file

@ -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);
}
}

300
src/setup/setup.rs Normal file
View file

@ -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);
}
}

318
src/setup/symbol_runner.rs Normal file
View file

@ -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));
});
}
}

9
src/setup/util.rs Normal file
View file

@ -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>>;