23 Commits
4938a51ad7
...
ac1c06dd31
34 changed files with 1096 additions and 896 deletions
-
7Cargo.toml
-
24src/async_utils.rs
-
74src/build.rs
-
23src/builder.rs
-
64src/command_runner.rs
-
1src/lib.rs
-
6src/locator.rs
-
72src/loggers.rs
-
7src/resources/mod.rs
-
88src/setup/cache.rs
-
204src/setup/core.rs
-
388src/setup/mod.rs
-
74src/setup/realizer.rs
-
21src/setup/runnable.rs
-
338src/setup/setup.rs
-
75src/setup/symbol_runner.rs
-
125src/setup/util.rs
-
17src/symbols/git/checkout.rs
-
2src/symbols/mariadb/database.rs
-
2src/symbols/mariadb/dump.rs
-
2src/symbols/mariadb/user.rs
-
2src/symbols/npm.rs
-
4src/symbols/postgresql/database.rs
-
2src/symbols/saved_directory.rs
-
19src/symbols/systemd/user_service.rs
-
12src/symbols/tls/key.rs
-
2src/symbols/wordpress/plugin.rs
-
6src/symbols/wordpress/translation.rs
-
52src/templates/nginx/mod.rs
-
70src/templates/nginx/server.rs
-
22src/templates/php.rs
-
2src/to_artifact.rs
-
4tests/file.rs
-
111tests/setup.rs
@ -1,57 +1,53 @@ |
|||
use std::env;
|
|||
use std::fs::{read_dir, File};
|
|||
use std::error::Error;
|
|||
use std::fs::{read as read_file, File};
|
|||
use std::io::ErrorKind::NotFound;
|
|||
use std::io::Read;
|
|||
use std::io::Write;
|
|||
use std::path::{Path, PathBuf};
|
|||
use std::path::Path;
|
|||
|
|||
fn get_const_name<P: Clone + Into<PathBuf>>(p: &P) -> String {
|
|||
let mut file_name_without_extension = p.clone().into();
|
|||
file_name_without_extension.set_extension("");
|
|||
String::from(
|
|||
file_name_without_extension
|
|||
.file_name()
|
|||
.unwrap()
|
|||
.to_string_lossy(),
|
|||
)
|
|||
.to_uppercase()
|
|||
pub fn create_static_output(
|
|||
source_path: &Path,
|
|||
mut target: impl Write,
|
|||
) -> Result<(), Box<dyn Error>> {
|
|||
let const_name = source_path
|
|||
.file_stem()
|
|||
.ok_or("Not a filename")?
|
|||
.to_str()
|
|||
.ok_or("Filename is not valid unicode")?
|
|||
.to_uppercase();
|
|||
let content = String::from_utf8(read_file(source_path)?)?;
|
|||
let fence = content.chars().filter(|&c| c == '#').collect::<String>() + "#";
|
|||
|
|||
writeln!(
|
|||
target,
|
|||
"pub const {const_name}: &str = r{fence}\"{content}\"{fence};"
|
|||
)?;
|
|||
Ok(())
|
|||
}
|
|||
|
|||
pub fn create_static_output_files(source_dir: &str) {
|
|||
let out_dir = env::var("OUT_DIR").unwrap();
|
|||
let dest_path = Path::new(&out_dir).join("static_files.rs");
|
|||
let mut f = File::create(&dest_path).unwrap();
|
|||
match read_dir(source_dir) {
|
|||
pub fn create_static_output_files(
|
|||
source_dir: &Path,
|
|||
dest_path: &Path,
|
|||
) -> Result<(), Box<dyn Error>> {
|
|||
let mut f = File::create(dest_path)?;
|
|||
match source_dir.read_dir() {
|
|||
Ok(dir_content) => {
|
|||
for maybe_dir_entry in dir_content {
|
|||
let file_path = maybe_dir_entry.unwrap().path();
|
|||
let mut buffer = String::new();
|
|||
File::open(file_path.clone())
|
|||
.unwrap()
|
|||
.read_to_string(&mut buffer)
|
|||
.unwrap();
|
|||
let fence = buffer.chars().filter(|c| *c == '#').collect::<String>() + "#";
|
|||
f.write_all(
|
|||
format!(
|
|||
"pub const {}: &str = r{1}\"{2}\"{1};\n",
|
|||
get_const_name(&file_path),
|
|||
fence,
|
|||
buffer
|
|||
)
|
|||
.as_bytes(),
|
|||
)
|
|||
.unwrap();
|
|||
create_static_output(&maybe_dir_entry?.path(), &mut f)?;
|
|||
}
|
|||
}
|
|||
Err(err) => {
|
|||
if err.kind() != NotFound {
|
|||
panic!("Unexpected error: {}", err)
|
|||
return Err(format!("Unexpected error: {err}").into());
|
|||
}
|
|||
}
|
|||
}
|
|||
Ok(())
|
|||
}
|
|||
|
|||
#[allow(unused)]
|
|||
fn main() {
|
|||
create_static_output_files("static_files");
|
|||
pub fn main() -> Result<(), Box<dyn Error>> {
|
|||
create_static_output_files(
|
|||
Path::new("static_files"),
|
|||
&(Path::new(&env::var("OUT_DIR")?).join("static_files.rs")),
|
|||
)
|
|||
}
|
@ -0,0 +1,88 @@ |
|||
use super::{Add, AddResult, AddableResource};
|
|||
use crate::async_utils::sleep;
|
|||
use crate::resources::{FromArtifact, FromResource};
|
|||
use async_trait::async_trait;
|
|||
use futures_util::future::{FutureExt, Shared};
|
|||
use slog::{trace, Logger};
|
|||
use std::cell::RefCell;
|
|||
use std::collections::HashMap;
|
|||
use std::fmt::Debug;
|
|||
use std::future::Future;
|
|||
use std::hash::Hash;
|
|||
use std::pin::Pin;
|
|||
use std::rc::Rc;
|
|||
use std::time::Duration;
|
|||
|
|||
type ResourceCache<Rs, As> = HashMap<Rs, Shared<Pin<Box<dyn Future<Output = (As, bool)>>>>>;
|
|||
|
|||
#[derive(Debug)]
|
|||
pub struct Cache<I, Rs, As> {
|
|||
resources: RefCell<ResourceCache<Rs, As>>,
|
|||
inner: Rc<I>,
|
|||
}
|
|||
|
|||
impl<I, Rs, As> Cache<I, Rs, As> {
|
|||
pub fn new(inner: I) -> Self {
|
|||
Self {
|
|||
resources: RefCell::default(),
|
|||
inner: Rc::new(inner),
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
#[async_trait(?Send)]
|
|||
impl<R: AddableResource + Debug, I, Rs, As> Add<R> for Cache<I, Rs, As>
|
|||
where
|
|||
Rs: Hash + Eq + 'static + FromResource<R>,
|
|||
As: 'static + FromArtifact<R> + Clone,
|
|||
I: 'static + Add<R>,
|
|||
{
|
|||
// FIXME: https://github.com/rust-lang/rust-clippy/issues/6353
|
|||
#[allow(clippy::await_holding_refcell_ref)]
|
|||
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R> {
|
|||
let (storable_resource, weak_resource) = Rs::from_resource(&resource);
|
|||
let mut resources = self.resources.borrow_mut();
|
|||
let result = 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);
|
|||
trace!(logger, "Resource already added");
|
|||
Ok(future.await)
|
|||
} else {
|
|||
let inner_weak = Rc::downgrade(&self.inner);
|
|||
let logger_weak = Rc::downgrade(logger);
|
|||
let future = Box::pin(async move {
|
|||
let inner = inner_weak.upgrade().expect("Dangling!");
|
|||
let logger = logger_weak.upgrade().expect("Dangling!");
|
|||
let resource = weak_resource.upgrade().expect("Dangling!");
|
|||
let result = inner.add(&logger, Rc::clone(&resource), force_run).await;
|
|||
|
|||
// Need to convert Box<Error> to String for Clone for Shared
|
|||
result
|
|||
.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 {
|
|||
let result = future_clone.await;
|
|||
if result.is_err() {
|
|||
// Step back to give the initial caller time to handle the error before unwrapping
|
|||
sleep(Duration::from_millis(0)).await;
|
|||
}
|
|||
result.unwrap()
|
|||
}) as Pin<Box<dyn Future<Output = (As, bool)>>>)
|
|||
.shared(),
|
|||
);
|
|||
drop(resources);
|
|||
let result = future.await;
|
|||
result.map_err(std::convert::Into::into)
|
|||
};
|
|||
result.map(|(t, did_run)| (t.into_artifact(), did_run))
|
|||
}
|
|||
}
|
@ -1,204 +0,0 @@ |
|||
use super::runnable::Runnable;
|
|||
use super::setup::Setup;
|
|||
use super::util::{AddableResource, InternalAddResult};
|
|||
use super::SymbolRunner;
|
|||
use crate::async_utils::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) -> InternalAddResult<X>;
|
|||
}
|
|||
|
|||
macro_rules! add_generic {
|
|||
( $($name:ident)* ) => (
|
|||
#[async_trait(?Send)]
|
|||
#[allow(non_snake_case)]
|
|||
impl<SR: 'static, _L: 'static, _B: 'static, Rs: 'static + Hash + Eq, As: 'static + Clone, $($name: AddableResource,)*>
|
|||
AddGeneric<($($name,)*)> for Setup<SR, _L, _B, Rs, As>
|
|||
where
|
|||
$(
|
|||
RegularSetupCore<SR, _L, _B>: SetupCore<$name, Self>,
|
|||
As: FromArtifact<$name>,
|
|||
Rs: FromResource<$name>,
|
|||
$name::Artifact: Clone |
|||
),*
|
|||
{
|
|||
#[allow(unused, clippy::shadow_unrelated)]
|
|||
async fn add_generic(&self, ($($name,)*): ($($name,)*)) -> Result<(StoringLogger, ($($name::Artifact,)*), bool), (StoringLogger, Box<dyn Error>)>
|
|||
{
|
|||
let ($($name,)*) = join!($(self.add($name, false),)*);
|
|||
let logger = StoringLogger::default();
|
|||
let mut did_run_any = false;
|
|||
$(
|
|||
let (log, artifact, did_run) = $name?;
|
|||
logger.put(log.release());
|
|||
did_run_any = did_run_any || did_run;
|
|||
let $name = artifact;
|
|||
)*
|
|||
Ok((logger, ($($name,)*), did_run_any))
|
|||
}
|
|||
}
|
|||
);
|
|||
}
|
|||
|
|||
for_each_tuple!(add_generic);
|
|||
|
|||
// 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 Setup<SR, 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>) -> InternalAddResult<Option<T>> {
|
|||
Ok(match r {
|
|||
Some(r) => {
|
|||
let (logger, result, did_run) = self.add(r, false).await?;
|
|||
(logger, Some(result), did_run)
|
|||
}
|
|||
None => (StoringLogger::default(), None, false),
|
|||
})
|
|||
}
|
|||
}
|
|||
|
|||
#[async_trait(?Send)]
|
|||
impl<
|
|||
T: AddableResource,
|
|||
Rs: 'static + Hash + Eq + FromResource<T>,
|
|||
As: 'static + FromArtifact<T> + Clone,
|
|||
SR: 'static,
|
|||
L: 'static,
|
|||
B: 'static,
|
|||
> AddGeneric<T> for Setup<SR, L, B, Rs, As>
|
|||
where
|
|||
T::Artifact: Clone,
|
|||
RegularSetupCore<SR, L, B>: 'static + SetupCore<T, Self>,
|
|||
{
|
|||
async fn add_generic(&self, r: T) -> InternalAddResult<T> {
|
|||
self.add(r, false).await
|
|||
}
|
|||
}
|
|||
|
|||
#[async_trait(?Send)]
|
|||
pub trait SetupCore<R: AddableResource, S> {
|
|||
async fn add<RR: AsRef<R>>(
|
|||
&self,
|
|||
setup: &S,
|
|||
resource: RR,
|
|||
force_run: bool,
|
|||
) -> InternalAddResult<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<RR: AsRef<R>>(
|
|||
&self,
|
|||
setup: &S,
|
|||
resource: RR,
|
|||
force_run: bool,
|
|||
) -> InternalAddResult<R> {
|
|||
let resource = resource.as_ref();
|
|||
let logger = StoringLogger::new();
|
|||
logger.write(4, format!("Adding {:?} ... ", resource));
|
|||
logger.write(4, format!("(force_run is {})", force_run));
|
|||
let (location, location_prereqs) = L::locate(resource);
|
|||
logger.trace(format!("Adding location prereqs for {:?}", resource));
|
|||
let result = setup.add_generic(location_prereqs).await;
|
|||
if let Err((log, e)) = result {
|
|||
logger.put(log.release());
|
|||
return Err((logger, e));
|
|||
}
|
|||
let (location_prereq_logger, _, location_prereqs_did_run) = result.unwrap();
|
|||
logger.put(location_prereq_logger.release());
|
|||
logger.trace(format!(
|
|||
"Location prereqs for {:?} did_run: {}",
|
|||
resource, location_prereqs_did_run
|
|||
));
|
|||
logger.trace(format!("Adding implementation prereqs for {:?}", resource));
|
|||
let result = setup.add_generic(B::prerequisites(resource)).await;
|
|||
if let Err((log, e)) = result {
|
|||
logger.put(log.release());
|
|||
return Err((logger, e));
|
|||
}
|
|||
let (impl_prereq_logger, prereqs, prereqs_did_run) = result.unwrap();
|
|||
logger.put(impl_prereq_logger.release());
|
|||
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_result = implementation
|
|||
.run(
|
|||
&self.symbol_runner,
|
|||
&logger,
|
|||
force_run || location_prereqs_did_run || prereqs_did_run,
|
|||
)
|
|||
.await;
|
|||
match did_run_result {
|
|||
Ok(did_run) => {
|
|||
logger.write(4, "done.");
|
|||
Ok((logger, location, did_run))
|
|||
}
|
|||
Err(e) => Err((logger, e)),
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
#[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.write(4, format!("Directly running {:?} ...", symbol));
|
|||
let result = self.symbol_runner.run_symbol(symbol, &logger, force).await;
|
|||
logger.write(4, "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
|
|||
}
|
|||
}
|
@ -1,12 +1,388 @@ |
|||
mod core;
|
|||
mod util;
|
|||
pub use util::{AddResult, AddableResource};
|
|||
mod realizer;
|
|||
mod symbol_runner;
|
|||
mod util;
|
|||
pub use symbol_runner::{
|
|||
DelayingSymbolRunner, DrySymbolRunner, InitializingSymbolRunner, ReportingSymbolRunner,
|
|||
SymbolRunner,
|
|||
};
|
|||
mod cache;
|
|||
mod runnable;
|
|||
#[allow(clippy::module_inception)]
|
|||
mod setup;
|
|||
pub use setup::SetupFacade as Setup;
|
|||
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::SymbolRunner;
|
|||
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 slog::{info, Logger};
|
|||
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>(
|
|||
&self,
|
|||
symbol: &S,
|
|||
logger: &Logger,
|
|||
force: bool,
|
|||
) -> Result<bool, Box<dyn Error>> {
|
|||
info!(logger, "run");
|
|||
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(
|
|||
inner: &Rc<TestResource<&'static str>>,
|
|||
) -> (Self, Weak<TestResource<&'static str>>) {
|
|||
(Self::A(Rc::clone(&inner)), Rc::downgrade(&inner))
|
|||
}
|
|||
}
|
|||
impl FromResource<TestResource<()>> for Resources {
|
|||
fn from_resource(inner: &Rc<TestResource<()>>) -> (Self, Weak<TestResource<()>>) {
|
|||
(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
|
|||
}
|
|||
#[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, ()) {
|
|||
((), ())
|
|||
}
|
|||
}
|
|||
|
|||
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(())
|
|||
}
|
|||
}
|
|||
|
|||
#[allow(clippy::type_complexity)]
|
|||
fn get_setup() -> (
|
|||
Rc<RefCell<usize>>,
|
|||
Setup<
|
|||
TestSymbolRunner,
|
|||
StoringLogger,
|
|||
TestResourceLocator,
|
|||
TestImplementationBuilder,
|
|||
Resources,
|
|||
Artifacts,
|
|||
>,
|
|||
StoringLogger,
|
|||
) {
|
|||
let count = Rc::new(RefCell::new(0));
|
|||
let runner = TestSymbolRunner {
|
|||
count: Rc::clone(&count),
|
|||
};
|
|||
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 run_reached_symbol() {
|
|||
run(async {
|
|||
let (count, setup, log) = get_setup();
|
|||
let did_run = setup
|
|||
.run_symbol(TestSymbol { reached: 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 { reached: 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 \{ reached: 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());
|
|||
})
|
|||
}
|
|||
}
|
@ -0,0 +1,74 @@ |
|||
use super::{Add, AddGeneric, AddResult, AddableResource, Runnable, SymbolRunner};
|
|||
use crate::{ImplementationBuilder, ResourceLocator};
|
|||
use async_trait::async_trait;
|
|||
use slog::{debug, o, trace, Logger};
|
|||
use std::fmt::Debug;
|
|||
use std::marker::PhantomData;
|
|||
use std::rc::{Rc, Weak};
|
|||
|
|||
#[derive(Debug)]
|
|||
pub struct Realizer<SR, L, B, S> {
|
|||
symbol_runner: Rc<SR>,
|
|||
outer: Weak<S>,
|
|||
phantom: PhantomData<(L, B)>,
|
|||
}
|
|||
|
|||
impl<SR, L, B, S> Realizer<SR, L, B, S> {
|
|||
pub fn new(symbol_runner: Rc<SR>, outer: Weak<S>) -> Self {
|
|||
Self {
|
|||
symbol_runner,
|
|||
outer,
|
|||
phantom: PhantomData::default(),
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
#[async_trait(?Send)]
|
|||
impl<R, SR, L, B, S> Add<R> for Realizer<SR, L, B, S>
|
|||
where
|
|||
R: AddableResource,
|
|||
SR: SymbolRunner,
|
|||
L: ResourceLocator<R>,
|
|||
B: ImplementationBuilder<R>,
|
|||
<B as ImplementationBuilder<R>>::Implementation: Runnable + Debug,
|
|||
S: AddGeneric<B::Prerequisites> + AddGeneric<<L as ResourceLocator<R>>::Prerequisites>,
|
|||
{
|
|||
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R> {
|
|||
let setup = self.outer.upgrade().unwrap();
|
|||
let logger = Rc::new(logger.new(o!("resource" => format!("{resource:?}"))));
|
|||
trace!(logger, "(force_run is {})", force_run);
|
|||
let (location, location_prereqs) = L::locate(&resource);
|
|||
trace!(logger, "Adding location prereqs ...");
|
|||
let (_, location_prereqs_did_run) = (*setup)
|
|||
.add_generic(&logger, location_prereqs, false)
|
|||
.await?;
|
|||
trace!(
|
|||
logger,
|
|||
"Location prereqs did_run: {}",
|
|||
location_prereqs_did_run
|
|||
);
|
|||
|
|||
trace!(logger, "Adding implementation prereqs ...");
|
|||
let (prereqs, prereqs_did_run) = (*setup)
|
|||
.add_generic(&logger, B::prerequisites(&resource), false)
|
|||
.await?;
|
|||
trace!(
|
|||
logger,
|
|||
"Implementation prereqs did_run: {}",
|
|||
prereqs_did_run
|
|||
);
|
|||
|
|||
trace!(logger, "Running implementation ...");
|
|||
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?;
|
|||
debug!(logger, "done.");
|
|||
|
|||
Ok((location, did_run))
|
|||
}
|
|||
}
|
@ -1,338 +0,0 @@ |
|||
use super::core::{RegularSetupCore, SetupCore};
|
|||
use super::runnable::Runnable;
|
|||
use super::util::{AddResult, AddableResource, InternalAddResult};
|
|||
use super::SymbolRunner;
|
|||
use crate::async_utils::sleep;
|
|||
use crate::loggers::{Logger, StoringLogger};
|
|||
use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource};
|
|||
use crate::{DefaultBuilder, DefaultLocator};
|
|||
use futures_util::future::{FutureExt, 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;
|
|||
use std::time::Duration;
|
|||
|
|||
type Cache<Rs, As> = HashMap<Rs, Shared<Pin<Box<dyn Future<Output = (As, bool)>>>>>;
|
|||
|
|||
#[derive(Debug)]
|
|||
struct SetupInner<CORE, Rs, As> {
|
|||
core: CORE,
|
|||
resources: RefCell<Cache<Rs, As>>,
|
|||
}
|
|||
|
|||
#[derive(Debug)]
|
|||
pub struct Setup<SR, L, B, Rs, As>(Rc<SetupInner<RegularSetupCore<SR, L, B>, Rs, As>>);
|
|||
|
|||
impl<L: 'static, B: 'static, SR: 'static, Rs: Hash + Eq + 'static, As: 'static>
|
|||
Setup<SR, L, B, Rs, As>
|
|||
{
|
|||
fn borrow_resources(&self) -> RefMut<'_, Cache<Rs, As>> {
|
|||
self.0.resources.borrow_mut()
|
|||
}
|
|||
|
|||
// FIXME: https://github.com/rust-lang/rust-clippy/issues/6353
|
|||
#[allow(clippy::await_holding_refcell_ref)]
|
|||
pub async fn add<R: AddableResource>(&self, resource: R, force_run: bool) -> InternalAddResult<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();
|
|||
let result = 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);
|
|||
let logger = StoringLogger::default();
|
|||
logger.trace(format!(
|
|||
"{:?} already added",
|
|||
weak_resource.upgrade().expect("Dangling!")
|
|||
));
|
|||
let (t, did_run) = future.await;
|
|||
Ok((logger, t, did_run))
|
|||
} 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
|
|||
let result = this.0.core.add(&this, resource, force_run).await;
|
|||
|
|||
result
|
|||
.map(|(logger, t, did_run)| (logger, As::from_artifact(t), did_run))
|
|||
.map_err(|(logger, e)| (logger, e.to_string()))
|
|||
})
|
|||
.shared();
|
|||
let future_clone = future.clone();
|
|||
resources.insert(
|
|||
storable_resource,
|
|||
(Box::pin(async move {
|
|||
let result = future_clone.await;
|
|||
if result.is_err() {
|
|||
// Step back to give the initial caller time to handle the error before unwrapping
|
|||
sleep(Duration::from_millis(0)).await;
|
|||
}
|
|||
result.map(|(_, t, did_run)| (t, did_run)).unwrap()
|
|||
}) as Pin<Box<dyn Future<Output = (As, bool)>>>)
|
|||
.shared(),
|
|||
);
|
|||
drop(resources);
|
|||
let result = future.await;
|
|||
result.map_err(|(logger, e)| (logger, e.into()))
|
|||
};
|
|||
result.map(|(logger, t, did_run)| (logger, t.into_artifact(), did_run))
|
|||
}
|
|||
}
|
|||
|
|||
#[derive(Debug)]
|
|||
pub struct SetupFacade<
|
|||
SR,
|
|||
LOG,
|
|||
L = DefaultLocator,
|
|||
B = DefaultBuilder,
|
|||
Rs = DefaultResources<'static, &'static str>,
|
|||
As = DefaultArtifacts<'static, &'static str>,
|
|||
>(LOG, Setup<SR, L, B, Rs, As>);
|
|||
|
|||
impl<SR, LOG> SetupFacade<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> SetupFacade<SR, LOG, L, B, Rs, As> {
|
|||
pub fn new_with(symbol_runner: SR, logger: LOG) -> Self {
|
|||
Self(
|
|||
logger,
|
|||
Setup(Rc::new(SetupInner {
|
|||
core: RegularSetupCore::new(symbol_runner),
|
|||
resources: RefCell::default(),
|
|||
})),
|
|||
)
|
|||
}
|
|||
}
|
|||
|
|||
impl<
|
|||
SR: 'static,
|
|||
LOG: 'static + Logger,
|
|||
L: 'static,
|
|||
B: 'static,
|
|||
Rs: 'static + Eq + Hash,
|
|||
As: 'static,
|
|||
> SetupFacade<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,
|
|||
R::Artifact: Clone,
|
|||
RegularSetupCore<SR, L, B>: SetupCore<R, Setup<SR, L, B, Rs, As>>,
|
|||
{
|
|||
let result = self.1.add(resource, force_run).await;
|
|||
match result {
|
|||
Ok((logger, t, did_run)) => {
|
|||
if self
|
|||
.0
|
|||
.put(logger.release().into_iter().filter(|e| e.0 <= 3))
|
|||
== 0
|
|||
{
|
|||
self.0.write(3, ".");
|
|||
}
|
|||
Ok((t, did_run))
|
|||
}
|
|||
Err((logger, e)) => {
|
|||
self.0.put(logger.release());
|
|||
Err(e)
|
|||
}
|
|||
}
|
|||
}
|
|||
pub async fn add<R: AddableResource>(&self, resource: R) -> AddResult<R>
|
|||
where
|
|||
RegularSetupCore<SR, L, B>: SetupCore<R, Setup<SR, L, B, Rs, As>>,
|
|||
Rs: FromResource<R>,
|
|||
As: FromArtifact<R> + Clone,
|
|||
R::Artifact: Clone,
|
|||
SR: SymbolRunner,
|
|||
{
|
|||
self.add_force(resource, false).await
|
|||
}
|
|||
|
|||
pub async fn run_symbol<S: Runnable>(
|
|||
&self,
|
|||
symbol: S,
|
|||
force: bool,
|
|||
) -> Result<bool, Box<dyn Error>>
|
|||
where
|
|||
RegularSetupCore<SR, L, B>: SymbolRunner,
|
|||
{
|
|||
symbol.run(&(self.1).0.core, &self.0, force).await
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
use super::SymbolRunner;
|
|||
use crate::async_utils::run;
|
|||
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
|
|||
}
|
|||
#[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, ()) {
|
|||
((), ())
|
|||
}
|
|||
}
|
|||
|
|||
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() {
|
|||
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);
|
|||
});
|
|||
}
|
|||
}
|
@ -1,12 +1,131 @@ |
|||
use crate::loggers::StoringLogger;
|
|||
use crate::async_utils::join;
|
|||
use crate::resources::Resource;
|
|||
use crate::to_artifact::ToArtifact;
|
|||
use async_trait::async_trait;
|
|||
use slog::{Drain, Filter, Logger, OwnedKVList, Record};
|
|||
use slog_async::AsyncRecord;
|
|||
use std::cell::RefCell;
|
|||
use std::error::Error;
|
|||
use std::fmt::Debug;
|
|||
use std::io::{self, Write};
|
|||
use std::rc::Rc;
|
|||
use std::sync::{Arc, Mutex};
|
|||
|
|||
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>>;
|
|||
pub type InternalAddResult<R> =
|
|||
Result<(StoringLogger, <R as ToArtifact>::Artifact, bool), (StoringLogger, Box<dyn Error>)>;
|
|||
|
|||
#[async_trait(?Send)]
|
|||
pub trait Add<R: AddableResource> {
|
|||
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R>;
|
|||
}
|
|||
|
|||
#[async_trait(?Send)]
|
|||
pub trait AddGeneric<X: ToArtifact> {
|
|||
async fn add_generic(&self, logger: &Rc<Logger>, x: X, force_run: bool) -> AddResult<X>;
|
|||
}
|
|||
|
|||
macro_rules! add_generic {
|
|||
( $($name:ident)* ) => (
|
|||
#[async_trait(?Send)]
|
|||
#[allow(non_snake_case)]
|
|||
impl<_S, $($name: AddableResource,)*>
|
|||
AddGeneric<($($name,)*)> for _S
|
|||
where
|
|||
$(
|
|||
_S: AddGeneric<$name>
|
|||
),*
|
|||
{
|
|||
#[allow(unused, clippy::shadow_unrelated)]
|
|||
async fn add_generic(&self, logger: &Rc<Logger>, ($($name,)*): ($($name,)*), force_run: bool) -> AddResult<($($name,)*)>
|
|||
{
|
|||
let ($($name,)*) = join!($(self.add_generic(logger, $name, force_run),)*);
|
|||
let mut did_run_any = false;
|
|||
$(
|
|||
let (artifact, did_run) = $name?;
|
|||
did_run_any = did_run_any || did_run;
|
|||
let $name = artifact;
|
|||
)*
|
|||
Ok((($($name,)*), did_run_any))
|
|||
}
|
|||
}
|
|||
);
|
|||
}
|
|||
|
|||
for_each_tuple!(add_generic);
|
|||
|
|||
#[async_trait(?Send)]
|
|||
impl<R: AddableResource + Debug, S: Add<R>> AddGeneric<R> for S {
|
|||
async fn add_generic(&self, logger: &Rc<Logger>, r: R, force_run: bool) -> AddResult<R> {
|
|||
self.add(logger, Rc::new(r), force_run).await
|
|||
}
|
|||
}
|
|||
|
|||
// From https://users.rust-lang.org/t/how-to-send-a-writer-into-a-thread/4965/10
|
|||
#[derive(Clone)]
|
|||
struct Output<W>(Rc<RefCell<W>>);
|
|||
|
|||
impl<W: Write> Output<W> {
|
|||
pub fn new(w: W) -> Self {
|
|||
Self(Rc::new(RefCell::new(w)))
|
|||
}
|
|||
}
|
|||
|
|||
impl<W: Write> Write for Output<W> {
|
|||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|||
self.0.borrow_mut().write(buf)
|
|||
}
|
|||
|
|||
fn flush(&mut self) -> io::Result<()> {
|
|||
self.0.borrow_mut().flush()
|
|||
}
|
|||
}
|
|||
|
|||
#[derive(Clone, Default)]
|
|||
pub struct Recorder(Arc<Mutex<Vec<AsyncRecord>>>);
|
|||
|
|||
impl Drain for Recorder {
|
|||
type Ok = ();
|
|||
type Err = slog::Never;
|
|||
|
|||
fn log(&self, record: &Record<'_>, logger_values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
|
|||
self
|
|||
.0
|
|||
.lock()
|
|||
.unwrap()
|
|||
.push(AsyncRecord::from(record, logger_values));
|
|||
Ok(())
|
|||
}
|
|||
}
|
|||
|
|||
impl Recorder {
|
|||
pub fn into_string(self, filter_level: slog::Level) -> String {
|
|||
let output = Output::new(vec![]);
|
|||
{
|
|||
let decorator = slog_term::PlainDecorator::new(output.clone());
|
|||
let drain = Filter::new(
|
|||
slog_term::CompactFormat::new(decorator).build(),
|
|||
move |record| record.level().is_at_least(filter_level),
|
|||
);
|
|||
let Ok(mutex) = Arc::try_unwrap(self.0) else { panic!("cannot unwrap Arc") }; // AsyncRecord does not implement Debug, so we cannot unwrap
|
|||
for record in mutex.into_inner().unwrap() {
|
|||
record.log_to(&drain).unwrap();
|
|||
}
|
|||
}
|
|||
String::from_utf8(Rc::try_unwrap(output.0).unwrap().into_inner())
|
|||
.expect("Record output should be valid UTF-8")
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
mod test {
|
|||
use super::Recorder;
|
|||
use slog::Level;
|
|||
|
|||
#[test]
|
|||
fn records_no_output() {
|
|||
let recorder = Recorder::default();
|
|||
assert_eq!(recorder.into_string(Level::Trace), "");
|
|||
}
|
|||
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue