You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
452 lines
13 KiB
452 lines
13 KiB
mod realizer;
|
|
mod symbol_runner;
|
|
mod util;
|
|
pub use symbol_runner::{
|
|
DelayingSymbolRunner, DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner,
|
|
SymbolRunner,
|
|
};
|
|
mod cache;
|
|
mod runnable;
|
|
use crate::loggers::Logger;
|
|
use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource};
|
|
use crate::symbols::Symbol;
|
|
use crate::{DefaultBuilder, DefaultLocator};
|
|
use crate::{ImplementationBuilder, ResourceLocator};
|
|
use async_trait::async_trait;
|
|
use cache::Cache;
|
|
use realizer::Realizer;
|
|
use runnable::Runnable;
|
|
use slog::o;
|
|
use std::error::Error;
|
|
use std::fmt::Debug;
|
|
use std::hash::Hash;
|
|
use std::rc::Rc;
|
|
use util::{Add, AddGeneric, AddResult, AddableResource, Recorder};
|
|
|
|
// Necessary for the recursive type
|
|
#[derive(Debug)]
|
|
pub struct ActualSetup<SR, L, B, Rs, As>(Cache<Realizer<SR, L, B, Self>, Rs, As>);
|
|
|
|
#[async_trait(?Send)]
|
|
impl<T: AddableResource + Debug, SR, L, B, Rs, As> Add<T> for ActualSetup<SR, L, B, Rs, As>
|
|
where
|
|
Cache<Realizer<SR, L, B, Self>, Rs, As>: Add<T>,
|
|
{
|
|
async fn add(&self, logger: &Rc<slog::Logger>, r: Rc<T>, force_run: bool) -> AddResult<T> {
|
|
self.0.add(logger, r, force_run).await
|
|
}
|
|
}
|
|
|
|
// This is for self-referential T
|
|
// FIXME: Wait for specialization
|
|
#[async_trait(?Send)]
|
|
impl<
|
|
SR: 'static + SymbolRunner,
|
|
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 ActualSetup<SR, L, B, Rs, As>
|
|
where
|
|
<B as ImplementationBuilder<T>>::Implementation: Runnable + Debug,
|
|
Self: AddGeneric<B::Prerequisites>,
|
|
T::Artifact: Clone,
|
|
// These bounds cannot be replaced by
|
|
// `Realizer<SR, L, B>: Add<T, Self>`
|
|
// because the prerequisites are Option<T>, too, and thus this would
|
|
// require AddGeneric<Option<T>> to already be implemented
|
|
{
|
|
async fn add_generic(
|
|
&self,
|
|
logger: &Rc<slog::Logger>,
|
|
r: Option<T>,
|
|
force_run: bool,
|
|
) -> AddResult<Option<T>> {
|
|
Ok(match r {
|
|
Some(r) => {
|
|
let (result, did_run) = self.add(logger, Rc::new(r), force_run).await?;
|
|
(Some(result), did_run)
|
|
}
|
|
None => (None, false),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Setup<
|
|
SR,
|
|
LOG,
|
|
L = DefaultLocator,
|
|
B = DefaultBuilder,
|
|
Rs = DefaultResources<'static, &'static str>,
|
|
As = DefaultArtifacts<'static, &'static str>,
|
|
>(LOG, Rc<SR>, Rc<ActualSetup<SR, L, B, Rs, As>>);
|
|
|
|
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 {
|
|
let runner = Rc::new(symbol_runner);
|
|
let inner = Rc::new_cyclic(|inner| {
|
|
ActualSetup(Cache::new(Realizer::new(Rc::clone(&runner), inner.clone())))
|
|
});
|
|
Self(logger, runner, inner)
|
|
}
|
|
}
|
|
|
|
impl<
|
|
SR: 'static,
|
|
LOG: 'static + Logger,
|
|
L: 'static,
|
|
B: 'static,
|
|
Rs: 'static + Eq + Hash,
|
|
As: 'static,
|
|
> Setup<SR, LOG, L, B, Rs, As>
|
|
{
|
|
pub async fn add_force<R: AddableResource>(&self, resource: R, force_run: bool) -> AddResult<R>
|
|
where
|
|
Rs: FromResource<R>,
|
|
As: FromArtifact<R> + Clone,
|
|
ActualSetup<SR, L, B, Rs, As>: Add<R>,
|
|
{
|
|
let recorder = Recorder::default();
|
|
let result = {
|
|
let log = Rc::new(slog::Logger::root(recorder.clone(), o!()));
|
|
self.2.add(&log, Rc::new(resource), force_run).await
|
|
};
|
|
self.log_result(recorder, result.as_ref().map(|(_, did_run)| *did_run));
|
|
result
|
|
}
|
|
|
|
pub async fn add<R: AddableResource>(&self, resource: R) -> AddResult<R>
|
|
where
|
|
Rs: FromResource<R>,
|
|
As: FromArtifact<R> + Clone,
|
|
R::Artifact: Clone,
|
|
ActualSetup<SR, L, B, Rs, As>: Add<R>,
|
|
{
|
|
self.add_force(resource, false).await
|
|
}
|
|
|
|
pub async fn run_symbol<S: Symbol + Debug>(
|
|
&self,
|
|
symbol: S,
|
|
force: bool,
|
|
) -> Result<bool, Box<dyn Error>>
|
|
where
|
|
SR: SymbolRunner,
|
|
{
|
|
let recorder = Recorder::default();
|
|
let result = {
|
|
let log = Rc::new(slog::Logger::root(
|
|
recorder.clone(),
|
|
o!("symbol" => format!("{symbol:?}")),
|
|
));
|
|
self.1.run_symbol(&symbol, &log, force).await
|
|
};
|
|
self.log_result(recorder, result.as_ref().copied());
|
|
result
|
|
}
|
|
|
|
fn log_result(&self, recorder: Recorder, result: Result<bool, &Box<dyn Error>>) {
|
|
let log = match result {
|
|
Ok(false) => String::new(),
|
|
Ok(true) => recorder.into_string(slog::Level::Info),
|
|
Err(e) => recorder.into_string(slog::Level::Trace),
|
|
};
|
|
if log.is_empty() {
|
|
self.0.write(3, ".");
|
|
} else {
|
|
self.0.writeln(3, &log);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::symbol_runner::TestSymbolRunner;
|
|
use crate::async_utils::run;
|
|
use crate::loggers::{Entry, 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 regex::Regex;
|
|
use std::cell::RefCell;
|
|
use std::error::Error;
|
|
use std::fmt::Debug;
|
|
use std::rc::{Rc, Weak};
|
|
|
|
#[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<()>>),
|
|
B(Rc<TestResource<&'static str>>),
|
|
C(Rc<TestResource<(&'static str, &'static str)>>),
|
|
D(Rc<TestResource<((&'static str, &'static str), &'static str)>>),
|
|
}
|
|
impl FromResource<TestResource<()>> for Resources {
|
|
fn from_resource(inner: &Rc<TestResource<()>>) -> (Self, Weak<TestResource<()>>) {
|
|
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
|
|
}
|
|
}
|
|
impl FromResource<TestResource<&'static str>> for Resources {
|
|
fn from_resource(
|
|
inner: &Rc<TestResource<&'static str>>,
|
|
) -> (Self, Weak<TestResource<&'static str>>) {
|
|
(Self::B(Rc::clone(&inner)), Rc::downgrade(&inner))
|
|
}
|
|
}
|
|
impl FromResource<TestResource<(&'static str, &'static str)>> for Resources {
|
|
fn from_resource(
|
|
inner: &Rc<TestResource<(&'static str, &'static str)>>,
|
|
) -> (Self, Weak<TestResource<(&'static str, &'static str)>>) {
|
|
(Self::C(Rc::clone(&inner)), Rc::downgrade(&inner))
|
|
}
|
|
}
|
|
impl FromResource<TestResource<((&'static str, &'static str), &'static str)>> for Resources {
|
|
fn from_resource(
|
|
inner: &Rc<TestResource<((&'static str, &'static str), &'static str)>>,
|
|
) -> (
|
|
Self,
|
|
Weak<TestResource<((&'static str, &'static str), &'static str)>>,
|
|
) {
|
|
(Self::D(Rc::clone(&inner)), Rc::downgrade(&inner))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Artifacts;
|
|
impl<V> FromArtifact<TestResource<V>> for Artifacts {
|
|
fn from_artifact(_from: ()) -> Self {
|
|
Self
|
|
}
|
|
#[allow(clippy::unused_unit)]
|
|
fn into_artifact(self) -> () {
|
|
#[allow(clippy::unused_unit)]
|
|
()
|
|
}
|
|
}
|
|
|
|
struct TestResourceLocator;
|
|
impl<T> ResourceLocator<TestResource<T>> for TestResourceLocator {
|
|
type Prerequisites = ();
|
|
fn locate(_resource: &TestResource<T>) -> (<TestResource<T> as ToArtifact>::Artifact, ()) {
|
|
((), ())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TestSymbol {
|
|
result: Result<bool, Box<str>>,
|
|
}
|
|
impl TestSymbol {
|
|
fn new(def: &str) -> Self {
|
|
let first_char = def.chars().next().unwrap();
|
|
Self {
|
|
result: if first_char == '!' {
|
|
Err(def.into())
|
|
} else {
|
|
Ok(first_char.is_uppercase())
|
|
},
|
|
}
|
|
}
|
|
}
|
|
#[async_trait(?Send)]
|
|
impl Symbol for TestSymbol {
|
|
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
|
|
self.result.clone().map_err(|s| s.to_string().into())
|
|
}
|
|
async fn execute(&self) -> Result<(), Box<dyn Error>> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct TestImplementationBuilder;
|
|
|
|
impl ImplementationBuilder<TestResource<((&'static str, &'static str), &'static str)>>
|
|
for TestImplementationBuilder
|
|
{
|
|
type Implementation = TestSymbol;
|
|
type Prerequisites = (TestResource<(&'static str, &'static str)>, TestResource<()>);
|
|
|
|
fn prerequisites(
|
|
resource: &TestResource<((&'static str, &'static str), &'static str)>,
|
|
) -> Self::Prerequisites {
|
|
(
|
|
TestResource("complex_resource", (resource.1).0), // FIXME: Only one of these can exist
|
|
TestResource((resource.1).1, ()),
|
|
)
|
|
}
|
|
fn create(
|
|
resource: &TestResource<((&'static str, &'static str), &'static str)>,
|
|
(): &(),
|
|
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
|
|
) -> Self::Implementation {
|
|
TestSymbol::new(resource.0)
|
|
}
|
|
}
|
|
|
|
impl ImplementationBuilder<TestResource<(&'static str, &'static str)>>
|
|
for TestImplementationBuilder
|
|
{
|
|
type Implementation = TestSymbol;
|
|
type Prerequisites = (TestResource<()>, TestResource<()>);
|
|
|
|
fn prerequisites(resource: &TestResource<(&'static str, &'static str)>) -> Self::Prerequisites {
|
|
(
|
|
TestResource((resource.1).0, ()),
|
|
TestResource((resource.1).1, ()),
|
|
)
|
|
}
|
|
fn create(
|
|
resource: &TestResource<(&'static str, &'static str)>,
|
|
(): &(),
|
|
_inputs: <Self::Prerequisites as ToArtifact>::Artifact,
|
|
) -> Self::Implementation {
|
|
TestSymbol::new(resource.0)
|
|
}
|
|
}
|
|
|
|
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::new(resource.0)
|
|
}
|
|
}
|
|
|
|
impl ImplementationBuilder<TestResource<()>> for TestImplementationBuilder {
|
|
type Implementation = TestSymbol;
|
|
type Prerequisites = ();
|
|
|
|
fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {}
|
|
fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation {
|
|
TestSymbol::new(resource.0)
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
fn get_setup() -> (
|
|
Rc<RefCell<usize>>,
|
|
Setup<
|
|
TestSymbolRunner,
|
|
StoringLogger,
|
|
TestResourceLocator,
|
|
TestImplementationBuilder,
|
|
Resources,
|
|
Artifacts,
|
|
>,
|
|
StoringLogger,
|
|
) {
|
|
let (count, runner) = TestSymbolRunner::new();
|
|
let logger = StoringLogger::new();
|
|
(count, Setup::new_with(runner, logger.clone()), logger)
|
|
}
|
|
|
|
#[test]
|
|
fn correctly_uses_force() {
|
|
run(async {
|
|
let (count, setup, _) = get_setup();
|
|
setup.add(TestResource("A", "b")).await.unwrap();
|
|
assert_eq!(*count.borrow(), 2);
|
|
setup.add(TestResource("A", "b")).await.unwrap();
|
|
assert_eq!(*count.borrow(), 2);
|
|
|
|
let (count, setup, _) = get_setup();
|
|
setup.add(TestResource("A", "B")).await.unwrap();
|
|
assert_eq!(*count.borrow(), 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn failing_dependencies_deadlock() {
|
|
run(async {
|
|
let (count, setup, _) = get_setup();
|
|
assert_eq!(
|
|
setup
|
|
.add(TestResource("a", (("b", "!x"), "!x")))
|
|
.await
|
|
.unwrap_err()
|
|
.to_string(),
|
|
"!x"
|
|
);
|
|
assert_eq!(*count.borrow(), 1);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn run_reached_symbol() {
|
|
run(async {
|
|
let (count, setup, log) = get_setup();
|
|
let did_run = setup
|
|
.run_symbol(TestSymbol { result: Ok(true) }, false)
|
|
.await
|
|
.unwrap();
|
|
drop(setup);
|
|
assert!(!did_run);
|
|
assert_eq!(*count.borrow(), 0);
|
|
assert_eq!(log.release(), vec![Entry(3, ".".into())]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn run_not_reached_symbol() {
|
|
run(async {
|
|
let (count, setup, log) = get_setup();
|
|
let did_run = setup
|
|
.run_symbol(TestSymbol { result: Ok(false) }, false)
|
|
.await
|
|
.unwrap();
|
|
drop(setup);
|
|
assert!(did_run);
|
|
assert_eq!(*count.borrow(), 1);
|
|
let log = log.release();
|
|
assert_eq!(log.len(), 1);
|
|
assert_eq!(log[0].0, 3);
|
|
let re = Regex::new(r"^symbol: TestSymbol \{ result: Ok\(false\) \}\n \w+ \d{1,2} \d{2}:\d{2}:\d{2}.\d{3} INFO run\n$").unwrap();
|
|
|
|
assert!(re.is_match(&log[0].1));
|
|
});
|
|
}
|
|
|
|
use super::{ActualSetup, AddGeneric, Cache, Realizer};
|
|
|
|
#[test]
|
|
fn empty_tuple_add_generic() {
|
|
let setup = Rc::new_cyclic(|inner| {
|
|
ActualSetup(Cache::<Realizer<(), (), (), _>, (), ()>::new(
|
|
Realizer::new(Rc::new(()), inner.clone()),
|
|
))
|
|
});
|
|
run(async {
|
|
assert!(setup
|
|
.add_generic(
|
|
&Rc::new(slog::Logger::root(slog::Discard, slog::o!())),
|
|
(),
|
|
false
|
|
)
|
|
.await
|
|
.is_ok());
|
|
})
|
|
}
|
|
}
|