From 60606133c17b775b65e060de99db09943815ec22 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 19:18:36 +0100 Subject: [PATCH 01/17] test(bootstrap): increase coverage --- Cargo.lock | 1 + crates/bootstrap/Cargo.toml | 6 +++ crates/bootstrap/src/config.rs | 16 +++++++ crates/bootstrap/src/global.rs | 80 +++++++++++++++++++++++++++++---- crates/bootstrap/src/lib.rs | 2 + crates/bootstrap/src/service.rs | 59 +++++++++++++++++++++++- 6 files changed, 155 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66877f7c6..b904e0913 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2525,6 +2525,7 @@ dependencies = [ "pin-project-lite", "scuffle-bootstrap-derive", "scuffle-context", + "scuffle-future-ext", "scuffle-workspace-hack", "tokio", ] diff --git a/crates/bootstrap/Cargo.toml b/crates/bootstrap/Cargo.toml index a84100b66..19619fc7d 100644 --- a/crates/bootstrap/Cargo.toml +++ b/crates/bootstrap/Cargo.toml @@ -10,6 +10,9 @@ documentation = "https://docs.rs/scuffle-bootstrap" license = "MIT OR Apache-2.0" keywords = ["bootstrap", "binary", "cli", "config"] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } + [dependencies] anyhow = "1.0" tokio = { version = "1", features = ["full"] } @@ -19,3 +22,6 @@ pin-project-lite = "0.2" scuffle-context.workspace = true scuffle-bootstrap-derive.workspace = true scuffle-workspace-hack.workspace = true + +[dev-dependencies] +scuffle-future-ext.workspace = true diff --git a/crates/bootstrap/src/config.rs b/crates/bootstrap/src/config.rs index 720c788a1..b368dbfea 100644 --- a/crates/bootstrap/src/config.rs +++ b/crates/bootstrap/src/config.rs @@ -17,3 +17,19 @@ impl ConfigParser for EmptyConfig { std::future::ready(Ok(EmptyConfig)) } } + +#[cfg(test)] +#[cfg_attr(all(test, coverage_nightly), coverage(off))] +mod tests { + use super::{ConfigParser, EmptyConfig}; + + #[tokio::test] + async fn unit_config() { + assert!(matches!(<()>::parse().await, Ok(()))); + } + + #[tokio::test] + async fn empty_config() { + assert!(matches!(EmptyConfig::parse().await, Ok(EmptyConfig))); + } +} diff --git a/crates/bootstrap/src/global.rs b/crates/bootstrap/src/global.rs index 35cd09055..765e79da6 100644 --- a/crates/bootstrap/src/global.rs +++ b/crates/bootstrap/src/global.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::config::{ConfigParser, EmptyConfig}; -fn default_runtime() -> tokio::runtime::Runtime { +fn default_runtime_builder() -> tokio::runtime::Builder { let worker_threads = std::env::var("TOKIO_WORKER_THREADS") .unwrap_or_default() .parse::() @@ -66,7 +66,7 @@ fn default_runtime() -> tokio::runtime::Runtime { builder.max_io_events_per_tick(max_io_events_per_tick); } - builder.build().expect("runtime build") + builder } pub trait Global: Send + Sync + 'static { @@ -75,7 +75,7 @@ pub trait Global: Send + Sync + 'static { /// Builds the tokio runtime for the application. #[inline(always)] fn tokio_runtime() -> tokio::runtime::Runtime { - default_runtime() + default_runtime_builder().build().expect("runtime build") } /// Called before loading the config. @@ -118,7 +118,7 @@ pub trait Global: Send + Sync + 'static { pub trait GlobalWithoutConfig: Send + Sync + 'static { #[inline(always)] fn tokio_runtime() -> tokio::runtime::Runtime { - default_runtime() + default_runtime_builder().build().expect("runtime build") } /// Initialize the global. @@ -157,10 +157,7 @@ impl Global for T { #[inline(always)] fn tokio_runtime() -> tokio::runtime::Runtime { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .expect("runtime build") + ::tokio_runtime() } #[inline(always)] @@ -190,3 +187,70 @@ impl Global for T { ::on_exit(self, result) } } + +#[cfg(test)] +#[cfg_attr(all(test, coverage_nightly), coverage(off))] +mod tests { + use std::sync::Arc; + use std::thread; + + use super::{Global, GlobalWithoutConfig}; + use crate::EmptyConfig; + + struct TestGlobal; + + impl Global for TestGlobal { + type Config = (); + + async fn init(_config: Self::Config) -> anyhow::Result> { + Ok(Arc::new(Self)) + } + } + + #[tokio::test] + async fn default_global() { + thread::spawn(|| { + // To get the coverage + TestGlobal::tokio_runtime(); + }); + + assert!(matches!(TestGlobal::pre_init(), Ok(()))); + let global = TestGlobal::init(()).await.unwrap(); + assert!(matches!(global.on_services_start().await, Ok(()))); + + assert!(matches!(global.on_exit(Ok(())).await, Ok(()))); + assert!(global.on_exit(Err(anyhow::anyhow!("error"))).await.is_err()); + + assert!(matches!(global.on_service_exit("test", Ok(())).await, Ok(()))); + assert!(global.on_service_exit("test", Err(anyhow::anyhow!("error"))).await.is_err()); + } + + struct TestGlobalWithoutConfig; + + impl GlobalWithoutConfig for TestGlobalWithoutConfig { + async fn init() -> anyhow::Result> { + Ok(Arc::new(Self)) + } + } + + #[tokio::test] + async fn default_global_no_config() { + thread::spawn(|| { + // To get the coverage + ::tokio_runtime(); + }); + + assert!(matches!(TestGlobalWithoutConfig::pre_init(), Ok(()))); + ::init(EmptyConfig).await.unwrap(); + let global = ::init().await.unwrap(); + assert!(matches!(Global::on_services_start(&global).await, Ok(()))); + + assert!(matches!(Global::on_exit(&global, Ok(())).await, Ok(()))); + assert!(Global::on_exit(&global, Err(anyhow::anyhow!("error"))).await.is_err()); + + assert!(matches!(Global::on_service_exit(&global, "test", Ok(())).await, Ok(()))); + assert!(Global::on_service_exit(&global, "test", Err(anyhow::anyhow!("error"))) + .await + .is_err()); + } +} diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index fdf599262..76f7c3347 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] + pub mod config; pub mod global; pub mod service; diff --git a/crates/bootstrap/src/service.rs b/crates/bootstrap/src/service.rs index 8b79d613a..553df70c4 100644 --- a/crates/bootstrap/src/service.rs +++ b/crates/bootstrap/src/service.rs @@ -7,7 +7,8 @@ pub trait Service: Send + Sync + 'static + Sized { None } - /// Initialize the service + /// Initialize the service and return `Ok(true)` if the service should be + /// run. fn enabled(&self, global: &Arc) -> impl std::future::Future> + Send { let _ = global; std::future::ready(Ok(true)) @@ -67,3 +68,59 @@ where Poll::Ready((this.name, res)) } } + +#[cfg(test)] +#[cfg_attr(all(test, coverage_nightly), coverage(off))] +mod tests { + use std::sync::Arc; + + use scuffle_future_ext::FutureExt; + + use super::{NamedFuture, Service}; + + struct DefaultService; + + impl Service<()> for DefaultService {} + + #[tokio::test] + async fn defaukt_service() { + let svc = DefaultService; + let global = Arc::new(()); + let (ctx, handler) = scuffle_context::Context::new(); + + assert_eq!(svc.name(), None); + assert!(svc.enabled(&global).await.unwrap()); + + handler.cancel(); + + assert!(matches!(svc.run(global, ctx).await, Ok(()))); + + assert!(handler + .shutdown() + .with_timeout(tokio::time::Duration::from_millis(200)) + .await + .is_ok()); + } + + #[tokio::test] + async fn future_service() { + let (ctx, handler) = scuffle_context::Context::new(); + let global = Arc::new(()); + + let fut_fn = |_global: Arc<()>, _ctx: scuffle_context::Context| async { anyhow::Result::<()>::Ok(()) }; + assert!(fut_fn.run(global, ctx).await.is_ok()); + + handler.cancel(); + assert!(handler + .shutdown() + .with_timeout(tokio::time::Duration::from_millis(200)) + .await + .is_ok()); + } + + #[tokio::test] + async fn named_future() { + let named_fut = NamedFuture::new("test", async { 42 }); + assert_eq!(named_fut.await, ("test", 42)); + } +} From c4799718c3e76b8c0e69466ae7286a8a6014b2fb Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 21:19:16 +0100 Subject: [PATCH 02/17] test(bootstrap): macro test --- Cargo.lock | 47 +++++++ Cargo.toml | 2 +- crates/bootstrap/derive/Cargo.toml | 10 ++ crates/bootstrap/derive/src/lib.rs | 31 +++++ ...le_bootstrap_derive__tests__main_test.snap | 119 ++++++++++++++++++ crates/signal/Cargo.toml | 2 +- 6 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap diff --git a/Cargo.lock b/Cargo.lock index b904e0913..360f358b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -594,6 +594,18 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "const-random" version = "0.1.18" @@ -848,6 +860,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1392,6 +1410,18 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "insta" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513e4067e16e69ed1db5ab56048ed65db32d10ba5fc1217f5393f8f17d8b5a5" +dependencies = [ + "console", + "linked-hash-map", + "once_cell", + "similar", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -1511,6 +1541,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2534,9 +2570,14 @@ dependencies = [ name = "scuffle-bootstrap-derive" version = "0.0.2" dependencies = [ + "anyhow", "darling", + "insta", + "postcompile", "proc-macro2", "quote", + "scuffle-bootstrap", + "scuffle-signal", "scuffle-workspace-hack", "syn", ] @@ -2940,6 +2981,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 1f90a643b..4e0f3dbb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ scuffle-http = { path = "crates/http", version = "0.0.4" } scuffle-metrics = { path = "crates/metrics", version = "0.0.4" } scuffle-pprof = { path = "crates/pprof", version = "0.0.2" } scuffle-batching = { path = "crates/batching", version = "0.0.4" } -scuffle-postcompile = { path = "crates/postcompile", version = "0.0.5" } +postcompile = { path = "crates/postcompile", version = "0.0.5" } scuffle-ffmpeg = { path = "crates/ffmpeg", version = "0.0.2" } scuffle-h3-webtransport = { path = "crates/h3-webtransport", version = "0.0.2" } scuffle-metrics-derive = { path = "crates/metrics/derive", version = "0.0.2" } diff --git a/crates/bootstrap/derive/Cargo.toml b/crates/bootstrap/derive/Cargo.toml index 52d1ef5a9..bd7bea17b 100644 --- a/crates/bootstrap/derive/Cargo.toml +++ b/crates/bootstrap/derive/Cargo.toml @@ -13,9 +13,19 @@ keywords = ["bootstrap", "derive", "macros"] [lib] proc-macro = true +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } + [dependencies] syn = "2" proc-macro2 = "1" quote = "1" darling = "0.20" scuffle-workspace-hack.workspace = true + +[dev-dependencies] +insta = "1.42.0" +anyhow = "1.0" +scuffle-signal = { workspace = true, features = ["bootstrap"] } +postcompile.workspace = true +scuffle-bootstrap = { path = ".." } diff --git a/crates/bootstrap/derive/src/lib.rs b/crates/bootstrap/derive/src/lib.rs index 10db18af5..38cfaa642 100644 --- a/crates/bootstrap/derive/src/lib.rs +++ b/crates/bootstrap/derive/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] + use proc_macro::TokenStream; mod main_impl; @@ -19,3 +21,32 @@ fn handle_error(input: Result) -> TokenStr Err(err) => err.to_compile_error().into(), } } + +#[cfg(test)] +#[cfg_attr(all(test, coverage_nightly), coverage(off))] +mod tests { + #[test] + fn main_test() { + insta::assert_snapshot!(postcompile::compile! { + use std::sync::Arc; + + use scuffle_bootstrap::main; + + struct TestGlobal; + + impl scuffle_signal::SignalConfig for TestGlobal {} + + impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { + async fn init() -> anyhow::Result> { + Ok(Arc::new(Self)) + } + } + + main! { + TestGlobal { + scuffle_signal::SignalSvc, + } + } + }); + } +} diff --git a/crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap b/crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap new file mode 100644 index 000000000..b2f67f8d1 --- /dev/null +++ b/crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap @@ -0,0 +1,119 @@ +--- +source: crates/bootstrap/derive/src/lib.rs +expression: "postcompile::compile!\n{\n use std::sync::Arc; use scuffle_bootstrap::main; struct TestGlobal; impl\n scuffle_signal::SignalConfig for TestGlobal {} impl\n scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal\n { async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } }\n main! { TestGlobal { scuffle_signal::SignalSvc, } }\n}" +snapshot_kind: text +--- +exit status: 0 +--- stdout +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use std::sync::Arc; +use scuffle_bootstrap::main; +struct TestGlobal; +impl scuffle_signal::SignalConfig for TestGlobal {} +impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { + async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } +} +#[automatically_derived] +fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> { + #[doc(hidden)] + pub const fn impl_global() {} + const _: () = impl_global::(); + ::scuffle_bootstrap::prelude::anyhow::Context::context(::pre_init(), + "pre_init")?; + let runtime = + ::tokio_runtime(); + let config = + ::scuffle_bootstrap::prelude::anyhow::Context::context(runtime.block_on(<::Config as + ::scuffle_bootstrap::config::ConfigParser>::parse()), + "config parse")?; + let ctx_handle = + ::scuffle_bootstrap::prelude::scuffle_context::Handler::global(); + let mut shared_global = ::core::option::Option::None; + let mut services_vec = + ::std::vec::Vec::<::scuffle_bootstrap::service::NamedFuture<::scuffle_bootstrap::prelude::tokio::task::JoinHandle>>>::new(); + let result = + runtime.block_on(async { + let global = + ::init(config).await?; + shared_global = ::core::option::Option::Some(global.clone()); + { + #[doc(hidden)] + pub async fn spawn_service(svc: + impl ::scuffle_bootstrap::service::Service, + global: &::std::sync::Arc, + ctx_handle: + &::scuffle_bootstrap::prelude::scuffle_context::Handler, + name: &'static str) + -> + anyhow::Result>>>> { + let name = + ::scuffle_bootstrap::service::Service::::name(&svc).unwrap_or_else(|| + name); + if ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::service::Service::::enabled(&svc, + &global).await, name)? { + Ok(Some(::scuffle_bootstrap::service::NamedFuture::new(name, + ::scuffle_bootstrap::prelude::tokio::spawn(::scuffle_bootstrap::service::Service::::run(svc, + global.clone(), ctx_handle.context()))))) + } else { Ok(None) } + } + let res = + spawn_service(scuffle_signal::SignalSvc, &global, + &ctx_handle, "scuffle_signal :: SignalSvc").await; + if let Some(spawned) = res? { services_vec.push(spawned); } + } + macro_rules! handle_service_exit { + ($remaining : ident) => + { + { + let ((name, result), _, remaining) = :: scuffle_bootstrap :: + prelude :: futures :: future :: + select_all($remaining).await; let result = :: + scuffle_bootstrap :: prelude :: anyhow :: Context :: + context(:: scuffle_bootstrap :: prelude :: anyhow :: Context + :: context(result, name) ? , name); < TestGlobal as :: + scuffle_bootstrap :: global :: Global > :: + on_service_exit(& global, name, result).await ? ; remaining + } + }; + } + let mut remaining = + { + let ((name, result), _, remaining) = + ::scuffle_bootstrap::prelude::futures::future::select_all(services_vec).await; + let result = + ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, + name)?, name); + ::on_service_exit(&global, + name, result).await?; + remaining + }; + while !remaining.is_empty() { + remaining = + { + let ((name, result), _, remaining) = + ::scuffle_bootstrap::prelude::futures::future::select_all(remaining).await; + let result = + ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, + name)?, name); + ::on_service_exit(&global, + name, result).await?; + remaining + }; + } + ::scuffle_bootstrap::prelude::anyhow::Ok(()) + }); + let ::core::option::Option::Some(global) = + shared_global else { return result; }; + runtime.block_on(::on_exit(&global, + result)) +} diff --git a/crates/signal/Cargo.toml b/crates/signal/Cargo.toml index 03e3a1eef..786db400c 100644 --- a/crates/signal/Cargo.toml +++ b/crates/signal/Cargo.toml @@ -25,7 +25,7 @@ tokio = { version = "1.41.1", features = ["macros", "rt", "time"] } tokio-test = "0.4.4" libc = "0.2" futures = "0.3" -scuffle-future-ext = { path = "../future-ext" } +scuffle-future-ext.workspace = true [features] bootstrap = ["scuffle-bootstrap", "scuffle-context", "anyhow", "tokio/macros"] From a47050df81d1025545a1e91d2fdf8e65c87446cd Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 21:29:54 +0100 Subject: [PATCH 03/17] test(bootstrap): move derive test --- Cargo.lock | 8 +- crates/bootstrap/Cargo.toml | 3 + crates/bootstrap/derive/Cargo.toml | 10 -- crates/bootstrap/derive/src/lib.rs | 31 ---- ...le_bootstrap_derive__tests__main_test.snap | 119 ------------- crates/bootstrap/src/lib.rs | 29 +++ .../scuffle_bootstrap__tests__main_test.snap | 168 ++++++++++++++++++ 7 files changed, 203 insertions(+), 165 deletions(-) delete mode 100644 crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap create mode 100644 crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap diff --git a/Cargo.lock b/Cargo.lock index 360f358b4..dd19c737e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2558,10 +2558,13 @@ version = "0.0.2" dependencies = [ "anyhow", "futures", + "insta", "pin-project-lite", + "postcompile", "scuffle-bootstrap-derive", "scuffle-context", "scuffle-future-ext", + "scuffle-signal", "scuffle-workspace-hack", "tokio", ] @@ -2570,14 +2573,9 @@ dependencies = [ name = "scuffle-bootstrap-derive" version = "0.0.2" dependencies = [ - "anyhow", "darling", - "insta", - "postcompile", "proc-macro2", "quote", - "scuffle-bootstrap", - "scuffle-signal", "scuffle-workspace-hack", "syn", ] diff --git a/crates/bootstrap/Cargo.toml b/crates/bootstrap/Cargo.toml index 19619fc7d..017520165 100644 --- a/crates/bootstrap/Cargo.toml +++ b/crates/bootstrap/Cargo.toml @@ -24,4 +24,7 @@ scuffle-bootstrap-derive.workspace = true scuffle-workspace-hack.workspace = true [dev-dependencies] +insta = "1.42.0" +postcompile = { workspace = true, features = ["prettyplease"] } scuffle-future-ext.workspace = true +scuffle-signal = { workspace = true, features = ["bootstrap"] } diff --git a/crates/bootstrap/derive/Cargo.toml b/crates/bootstrap/derive/Cargo.toml index bd7bea17b..52d1ef5a9 100644 --- a/crates/bootstrap/derive/Cargo.toml +++ b/crates/bootstrap/derive/Cargo.toml @@ -13,19 +13,9 @@ keywords = ["bootstrap", "derive", "macros"] [lib] proc-macro = true -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } - [dependencies] syn = "2" proc-macro2 = "1" quote = "1" darling = "0.20" scuffle-workspace-hack.workspace = true - -[dev-dependencies] -insta = "1.42.0" -anyhow = "1.0" -scuffle-signal = { workspace = true, features = ["bootstrap"] } -postcompile.workspace = true -scuffle-bootstrap = { path = ".." } diff --git a/crates/bootstrap/derive/src/lib.rs b/crates/bootstrap/derive/src/lib.rs index 38cfaa642..10db18af5 100644 --- a/crates/bootstrap/derive/src/lib.rs +++ b/crates/bootstrap/derive/src/lib.rs @@ -1,5 +1,3 @@ -#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] - use proc_macro::TokenStream; mod main_impl; @@ -21,32 +19,3 @@ fn handle_error(input: Result) -> TokenStr Err(err) => err.to_compile_error().into(), } } - -#[cfg(test)] -#[cfg_attr(all(test, coverage_nightly), coverage(off))] -mod tests { - #[test] - fn main_test() { - insta::assert_snapshot!(postcompile::compile! { - use std::sync::Arc; - - use scuffle_bootstrap::main; - - struct TestGlobal; - - impl scuffle_signal::SignalConfig for TestGlobal {} - - impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { - async fn init() -> anyhow::Result> { - Ok(Arc::new(Self)) - } - } - - main! { - TestGlobal { - scuffle_signal::SignalSvc, - } - } - }); - } -} diff --git a/crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap b/crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap deleted file mode 100644 index b2f67f8d1..000000000 --- a/crates/bootstrap/derive/src/snapshots/scuffle_bootstrap_derive__tests__main_test.snap +++ /dev/null @@ -1,119 +0,0 @@ ---- -source: crates/bootstrap/derive/src/lib.rs -expression: "postcompile::compile!\n{\n use std::sync::Arc; use scuffle_bootstrap::main; struct TestGlobal; impl\n scuffle_signal::SignalConfig for TestGlobal {} impl\n scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal\n { async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } }\n main! { TestGlobal { scuffle_signal::SignalSvc, } }\n}" -snapshot_kind: text ---- -exit status: 0 ---- stdout -#![feature(prelude_import)] -#[prelude_import] -use std::prelude::rust_2021::*; -#[macro_use] -extern crate std; -use std::sync::Arc; -use scuffle_bootstrap::main; -struct TestGlobal; -impl scuffle_signal::SignalConfig for TestGlobal {} -impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { - async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } -} -#[automatically_derived] -fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> { - #[doc(hidden)] - pub const fn impl_global() {} - const _: () = impl_global::(); - ::scuffle_bootstrap::prelude::anyhow::Context::context(::pre_init(), - "pre_init")?; - let runtime = - ::tokio_runtime(); - let config = - ::scuffle_bootstrap::prelude::anyhow::Context::context(runtime.block_on(<::Config as - ::scuffle_bootstrap::config::ConfigParser>::parse()), - "config parse")?; - let ctx_handle = - ::scuffle_bootstrap::prelude::scuffle_context::Handler::global(); - let mut shared_global = ::core::option::Option::None; - let mut services_vec = - ::std::vec::Vec::<::scuffle_bootstrap::service::NamedFuture<::scuffle_bootstrap::prelude::tokio::task::JoinHandle>>>::new(); - let result = - runtime.block_on(async { - let global = - ::init(config).await?; - shared_global = ::core::option::Option::Some(global.clone()); - { - #[doc(hidden)] - pub async fn spawn_service(svc: - impl ::scuffle_bootstrap::service::Service, - global: &::std::sync::Arc, - ctx_handle: - &::scuffle_bootstrap::prelude::scuffle_context::Handler, - name: &'static str) - -> - anyhow::Result>>>> { - let name = - ::scuffle_bootstrap::service::Service::::name(&svc).unwrap_or_else(|| - name); - if ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::service::Service::::enabled(&svc, - &global).await, name)? { - Ok(Some(::scuffle_bootstrap::service::NamedFuture::new(name, - ::scuffle_bootstrap::prelude::tokio::spawn(::scuffle_bootstrap::service::Service::::run(svc, - global.clone(), ctx_handle.context()))))) - } else { Ok(None) } - } - let res = - spawn_service(scuffle_signal::SignalSvc, &global, - &ctx_handle, "scuffle_signal :: SignalSvc").await; - if let Some(spawned) = res? { services_vec.push(spawned); } - } - macro_rules! handle_service_exit { - ($remaining : ident) => - { - { - let ((name, result), _, remaining) = :: scuffle_bootstrap :: - prelude :: futures :: future :: - select_all($remaining).await; let result = :: - scuffle_bootstrap :: prelude :: anyhow :: Context :: - context(:: scuffle_bootstrap :: prelude :: anyhow :: Context - :: context(result, name) ? , name); < TestGlobal as :: - scuffle_bootstrap :: global :: Global > :: - on_service_exit(& global, name, result).await ? ; remaining - } - }; - } - let mut remaining = - { - let ((name, result), _, remaining) = - ::scuffle_bootstrap::prelude::futures::future::select_all(services_vec).await; - let result = - ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, - name)?, name); - ::on_service_exit(&global, - name, result).await?; - remaining - }; - while !remaining.is_empty() { - remaining = - { - let ((name, result), _, remaining) = - ::scuffle_bootstrap::prelude::futures::future::select_all(remaining).await; - let result = - ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, - name)?, name); - ::on_service_exit(&global, - name, result).await?; - remaining - }; - } - ::scuffle_bootstrap::prelude::anyhow::Ok(()) - }); - let ::core::option::Option::Some(global) = - shared_global else { return result; }; - runtime.block_on(::on_exit(&global, - result)) -} diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index 76f7c3347..441c3be94 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -13,3 +13,32 @@ pub use service::Service; pub mod prelude { pub use {anyhow, futures, scuffle_context, tokio}; } + +#[cfg(test)] +#[cfg_attr(all(test, coverage_nightly), coverage(off))] +mod tests { + #[test] + fn main_test() { + insta::assert_snapshot!(postcompile::compile! { + use std::sync::Arc; + + use scuffle_bootstrap::main; + + struct TestGlobal; + + impl scuffle_signal::SignalConfig for TestGlobal {} + + impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { + async fn init() -> anyhow::Result> { + Ok(Arc::new(Self)) + } + } + + main! { + TestGlobal { + scuffle_signal::SignalSvc, + } + } + }); + } +} diff --git a/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap b/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap new file mode 100644 index 000000000..71b060fce --- /dev/null +++ b/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap @@ -0,0 +1,168 @@ +--- +source: crates/bootstrap/src/lib.rs +expression: "postcompile::compile!\n{\n use std::sync::Arc; use scuffle_bootstrap::main; struct TestGlobal; impl\n scuffle_signal::SignalConfig for TestGlobal {} impl\n scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal\n { async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } }\n main! { TestGlobal { scuffle_signal::SignalSvc, } }\n}" +snapshot_kind: text +--- +exit status: 0 +--- stdout +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use std::sync::Arc; +use scuffle_bootstrap::main; +struct TestGlobal; +impl scuffle_signal::SignalConfig for TestGlobal {} +impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { + async fn init() -> anyhow::Result> { + Ok(Arc::new(Self)) + } +} +#[automatically_derived] +fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> { + #[doc(hidden)] + pub const fn impl_global() {} + const _: () = impl_global::(); + ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::pre_init(), + "pre_init", + )?; + let runtime = ::tokio_runtime(); + let config = ::scuffle_bootstrap::prelude::anyhow::Context::context( + runtime + .block_on( + <::Config as ::scuffle_bootstrap::config::ConfigParser>::parse(), + ), + "config parse", + )?; + let ctx_handle = ::scuffle_bootstrap::prelude::scuffle_context::Handler::global(); + let mut shared_global = ::core::option::Option::None; + let mut services_vec = ::std::vec::Vec::< + ::scuffle_bootstrap::service::NamedFuture< + ::scuffle_bootstrap::prelude::tokio::task::JoinHandle>, + >, + >::new(); + let result = runtime + .block_on(async { + let global = ::init( + config, + ) + .await?; + shared_global = ::core::option::Option::Some(global.clone()); + { + #[doc(hidden)] + pub async fn spawn_service( + svc: impl ::scuffle_bootstrap::service::Service, + global: &::std::sync::Arc, + ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler, + name: &'static str, + ) -> anyhow::Result< + Option< + ::scuffle_bootstrap::service::NamedFuture< + ::scuffle_bootstrap::prelude::tokio::task::JoinHandle< + anyhow::Result<()>, + >, + >, + >, + > { + let name = ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::name(&svc) + .unwrap_or_else(|| name); + if ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::enabled(&svc, &global) + .await, + name, + )? { + Ok( + Some( + ::scuffle_bootstrap::service::NamedFuture::new( + name, + ::scuffle_bootstrap::prelude::tokio::spawn( + ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::run(svc, global.clone(), ctx_handle.context()), + ), + ), + ), + ) + } else { + Ok(None) + } + } + let res = spawn_service( + scuffle_signal::SignalSvc, + &global, + &ctx_handle, + "scuffle_signal :: SignalSvc", + ) + .await; + if let Some(spawned) = res? { + services_vec.push(spawned); + } + } + macro_rules! handle_service_exit { + ($remaining:ident) => { + { let ((name, result), _, remaining) = + ::scuffle_bootstrap::prelude::futures::future::select_all($remaining) + . await; let result = + ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, + name) ?, name); < TestGlobal as ::scuffle_bootstrap::global::Global > + ::on_service_exit(& global, name, result). await ?; remaining } + }; + } + let mut remaining = { + let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( + services_vec, + ) + .await; + let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::prelude::anyhow::Context::context( + result, + name, + )?, + name, + ); + ::on_service_exit( + &global, + name, + result, + ) + .await?; + remaining + }; + while !remaining.is_empty() { + remaining = { + let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( + remaining, + ) + .await; + let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::prelude::anyhow::Context::context( + result, + name, + )?, + name, + ); + ::on_service_exit( + &global, + name, + result, + ) + .await?; + remaining + }; + } + ::scuffle_bootstrap::prelude::anyhow::Ok(()) + }); + let ::core::option::Option::Some(global) = shared_global else { + return result; + }; + runtime + .block_on( + ::on_exit(&global, result), + ) +} From 765ed39b971cf4dc399cb70dda16a1cdb906c6e8 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 23:31:31 +0100 Subject: [PATCH 04/17] test(bootstrap): add second test --- crates/bootstrap/src/lib.rs | 35 +++ ...trap__tests__main_test_custom_service.snap | 229 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index 441c3be94..4f7f142cd 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -41,4 +41,39 @@ mod tests { } }); } + + #[test] + fn main_test_custom_service() { + insta::assert_snapshot!(postcompile::compile! { + use std::sync::Arc; + + use scuffle_bootstrap::main; + + struct TestGlobal; + + impl scuffle_signal::SignalConfig for TestGlobal {} + + impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { + async fn init() -> anyhow::Result> { + Ok(Arc::new(Self)) + } + } + + struct MySvc; + + impl scuffle_bootstrap::service::Service for MySvc { + async fn run(self, _: Arc, _: scuffle_context::Context) -> anyhow::Result<()> { + println!("running"); + Ok(()) + } + } + + main! { + TestGlobal { + scuffle_signal::SignalSvc, + MySvc, + } + } + }); + } } diff --git a/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap b/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap new file mode 100644 index 000000000..005e0b8dc --- /dev/null +++ b/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap @@ -0,0 +1,229 @@ +--- +source: crates/bootstrap/src/lib.rs +expression: "postcompile::compile!\n{\n use std::sync::Arc; use scuffle_bootstrap::main; struct TestGlobal; impl\n scuffle_signal::SignalConfig for TestGlobal {} impl\n scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal\n { async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } }\n struct MySvc; impl scuffle_bootstrap::service::Service for\n MySvc\n {\n async fn run(self, _: Arc, _: scuffle_context::Context) ->\n anyhow::Result<()> { println!(\"running\"); Ok(()) }\n } main! { TestGlobal { scuffle_signal::SignalSvc, MySvc, } }\n}" +snapshot_kind: text +--- +exit status: 0 +--- stdout +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use std::sync::Arc; +use scuffle_bootstrap::main; +struct TestGlobal; +impl scuffle_signal::SignalConfig for TestGlobal {} +impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { + async fn init() -> anyhow::Result> { + Ok(Arc::new(Self)) + } +} +struct MySvc; +impl scuffle_bootstrap::service::Service for MySvc { + async fn run( + self, + _: Arc, + _: scuffle_context::Context, + ) -> anyhow::Result<()> { + { + ::std::io::_print(format_args!("running\n")); + }; + Ok(()) + } +} +#[automatically_derived] +fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> { + #[doc(hidden)] + pub const fn impl_global() {} + const _: () = impl_global::(); + ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::pre_init(), + "pre_init", + )?; + let runtime = ::tokio_runtime(); + let config = ::scuffle_bootstrap::prelude::anyhow::Context::context( + runtime + .block_on( + <::Config as ::scuffle_bootstrap::config::ConfigParser>::parse(), + ), + "config parse", + )?; + let ctx_handle = ::scuffle_bootstrap::prelude::scuffle_context::Handler::global(); + let mut shared_global = ::core::option::Option::None; + let mut services_vec = ::std::vec::Vec::< + ::scuffle_bootstrap::service::NamedFuture< + ::scuffle_bootstrap::prelude::tokio::task::JoinHandle>, + >, + >::new(); + let result = runtime + .block_on(async { + let global = ::init( + config, + ) + .await?; + shared_global = ::core::option::Option::Some(global.clone()); + { + #[doc(hidden)] + pub async fn spawn_service( + svc: impl ::scuffle_bootstrap::service::Service, + global: &::std::sync::Arc, + ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler, + name: &'static str, + ) -> anyhow::Result< + Option< + ::scuffle_bootstrap::service::NamedFuture< + ::scuffle_bootstrap::prelude::tokio::task::JoinHandle< + anyhow::Result<()>, + >, + >, + >, + > { + let name = ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::name(&svc) + .unwrap_or_else(|| name); + if ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::enabled(&svc, &global) + .await, + name, + )? { + Ok( + Some( + ::scuffle_bootstrap::service::NamedFuture::new( + name, + ::scuffle_bootstrap::prelude::tokio::spawn( + ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::run(svc, global.clone(), ctx_handle.context()), + ), + ), + ), + ) + } else { + Ok(None) + } + } + let res = spawn_service( + scuffle_signal::SignalSvc, + &global, + &ctx_handle, + "scuffle_signal :: SignalSvc", + ) + .await; + if let Some(spawned) = res? { + services_vec.push(spawned); + } + } + { + #[doc(hidden)] + pub async fn spawn_service( + svc: impl ::scuffle_bootstrap::service::Service, + global: &::std::sync::Arc, + ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler, + name: &'static str, + ) -> anyhow::Result< + Option< + ::scuffle_bootstrap::service::NamedFuture< + ::scuffle_bootstrap::prelude::tokio::task::JoinHandle< + anyhow::Result<()>, + >, + >, + >, + > { + let name = ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::name(&svc) + .unwrap_or_else(|| name); + if ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::enabled(&svc, &global) + .await, + name, + )? { + Ok( + Some( + ::scuffle_bootstrap::service::NamedFuture::new( + name, + ::scuffle_bootstrap::prelude::tokio::spawn( + ::scuffle_bootstrap::service::Service::< + TestGlobal, + >::run(svc, global.clone(), ctx_handle.context()), + ), + ), + ), + ) + } else { + Ok(None) + } + } + let res = spawn_service(MySvc, &global, &ctx_handle, "MySvc").await; + if let Some(spawned) = res? { + services_vec.push(spawned); + } + } + macro_rules! handle_service_exit { + ($remaining:ident) => { + { let ((name, result), _, remaining) = + ::scuffle_bootstrap::prelude::futures::future::select_all($remaining) + . await; let result = + ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, + name) ?, name); < TestGlobal as ::scuffle_bootstrap::global::Global > + ::on_service_exit(& global, name, result). await ?; remaining } + }; + } + let mut remaining = { + let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( + services_vec, + ) + .await; + let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::prelude::anyhow::Context::context( + result, + name, + )?, + name, + ); + ::on_service_exit( + &global, + name, + result, + ) + .await?; + remaining + }; + while !remaining.is_empty() { + remaining = { + let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( + remaining, + ) + .await; + let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::prelude::anyhow::Context::context( + result, + name, + )?, + name, + ); + ::on_service_exit( + &global, + name, + result, + ) + .await?; + remaining + }; + } + ::scuffle_bootstrap::prelude::anyhow::Ok(()) + }); + let ::core::option::Option::Some(global) = shared_global else { + return result; + }; + runtime + .block_on( + ::on_exit(&global, result), + ) +} From c32d305f557d3a99f85a6b5be0422dd5ded948bc Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 20:05:08 +0100 Subject: [PATCH 05/17] fix(bootstrap): remove broken tests --- crates/bootstrap/src/lib.rs | 128 +++++----- .../scuffle_bootstrap__tests__main_test.snap | 168 ------------- ...trap__tests__main_test_custom_service.snap | 229 ------------------ 3 files changed, 65 insertions(+), 460 deletions(-) delete mode 100644 crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap delete mode 100644 crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index 4f7f142cd..58a1db5b4 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -14,66 +14,68 @@ pub mod prelude { pub use {anyhow, futures, scuffle_context, tokio}; } -#[cfg(test)] -#[cfg_attr(all(test, coverage_nightly), coverage(off))] -mod tests { - #[test] - fn main_test() { - insta::assert_snapshot!(postcompile::compile! { - use std::sync::Arc; - - use scuffle_bootstrap::main; - - struct TestGlobal; - - impl scuffle_signal::SignalConfig for TestGlobal {} - - impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { - async fn init() -> anyhow::Result> { - Ok(Arc::new(Self)) - } - } - - main! { - TestGlobal { - scuffle_signal::SignalSvc, - } - } - }); - } - - #[test] - fn main_test_custom_service() { - insta::assert_snapshot!(postcompile::compile! { - use std::sync::Arc; - - use scuffle_bootstrap::main; - - struct TestGlobal; - - impl scuffle_signal::SignalConfig for TestGlobal {} - - impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { - async fn init() -> anyhow::Result> { - Ok(Arc::new(Self)) - } - } - - struct MySvc; - - impl scuffle_bootstrap::service::Service for MySvc { - async fn run(self, _: Arc, _: scuffle_context::Context) -> anyhow::Result<()> { - println!("running"); - Ok(()) - } - } - - main! { - TestGlobal { - scuffle_signal::SignalSvc, - MySvc, - } - } - }); - } -} +// Note: Tests are disabled due to a problem with cargo caching + +// #[cfg(test)] +// #[cfg_attr(all(test, coverage_nightly), coverage(off))] +// mod tests { +// #[test] +// fn main_test() { +// insta::assert_snapshot!(postcompile::compile! { +// use std::sync::Arc; + +// use scuffle_bootstrap::main; + +// struct TestGlobal; + +// impl scuffle_signal::SignalConfig for TestGlobal {} + +// impl scuffle_bootstrap::global::GlobalWithoutConfig for +// TestGlobal { async fn init() -> anyhow::Result> { +// Ok(Arc::new(Self)) +// } +// } + +// main! { +// TestGlobal { +// scuffle_signal::SignalSvc, +// } +// } +// }); +// } + +// #[test] +// fn main_test_custom_service() { +// insta::assert_snapshot!(postcompile::compile! { +// use std::sync::Arc; + +// use scuffle_bootstrap::main; + +// struct TestGlobal; + +// impl scuffle_signal::SignalConfig for TestGlobal {} + +// impl scuffle_bootstrap::global::GlobalWithoutConfig for +// TestGlobal { async fn init() -> anyhow::Result> { +// Ok(Arc::new(Self)) +// } +// } + +// struct MySvc; + +// impl scuffle_bootstrap::service::Service for MySvc { +// async fn run(self, _: Arc, _: +// scuffle_context::Context) -> anyhow::Result<()> { +// println!("running"); Ok(()) +// } +// } + +// main! { +// TestGlobal { +// scuffle_signal::SignalSvc, +// MySvc, +// } +// } +// }); +// } +// } diff --git a/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap b/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap deleted file mode 100644 index 71b060fce..000000000 --- a/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test.snap +++ /dev/null @@ -1,168 +0,0 @@ ---- -source: crates/bootstrap/src/lib.rs -expression: "postcompile::compile!\n{\n use std::sync::Arc; use scuffle_bootstrap::main; struct TestGlobal; impl\n scuffle_signal::SignalConfig for TestGlobal {} impl\n scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal\n { async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } }\n main! { TestGlobal { scuffle_signal::SignalSvc, } }\n}" -snapshot_kind: text ---- -exit status: 0 ---- stdout -#![feature(prelude_import)] -#[prelude_import] -use std::prelude::rust_2021::*; -#[macro_use] -extern crate std; -use std::sync::Arc; -use scuffle_bootstrap::main; -struct TestGlobal; -impl scuffle_signal::SignalConfig for TestGlobal {} -impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { - async fn init() -> anyhow::Result> { - Ok(Arc::new(Self)) - } -} -#[automatically_derived] -fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> { - #[doc(hidden)] - pub const fn impl_global() {} - const _: () = impl_global::(); - ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::pre_init(), - "pre_init", - )?; - let runtime = ::tokio_runtime(); - let config = ::scuffle_bootstrap::prelude::anyhow::Context::context( - runtime - .block_on( - <::Config as ::scuffle_bootstrap::config::ConfigParser>::parse(), - ), - "config parse", - )?; - let ctx_handle = ::scuffle_bootstrap::prelude::scuffle_context::Handler::global(); - let mut shared_global = ::core::option::Option::None; - let mut services_vec = ::std::vec::Vec::< - ::scuffle_bootstrap::service::NamedFuture< - ::scuffle_bootstrap::prelude::tokio::task::JoinHandle>, - >, - >::new(); - let result = runtime - .block_on(async { - let global = ::init( - config, - ) - .await?; - shared_global = ::core::option::Option::Some(global.clone()); - { - #[doc(hidden)] - pub async fn spawn_service( - svc: impl ::scuffle_bootstrap::service::Service, - global: &::std::sync::Arc, - ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler, - name: &'static str, - ) -> anyhow::Result< - Option< - ::scuffle_bootstrap::service::NamedFuture< - ::scuffle_bootstrap::prelude::tokio::task::JoinHandle< - anyhow::Result<()>, - >, - >, - >, - > { - let name = ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::name(&svc) - .unwrap_or_else(|| name); - if ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::enabled(&svc, &global) - .await, - name, - )? { - Ok( - Some( - ::scuffle_bootstrap::service::NamedFuture::new( - name, - ::scuffle_bootstrap::prelude::tokio::spawn( - ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::run(svc, global.clone(), ctx_handle.context()), - ), - ), - ), - ) - } else { - Ok(None) - } - } - let res = spawn_service( - scuffle_signal::SignalSvc, - &global, - &ctx_handle, - "scuffle_signal :: SignalSvc", - ) - .await; - if let Some(spawned) = res? { - services_vec.push(spawned); - } - } - macro_rules! handle_service_exit { - ($remaining:ident) => { - { let ((name, result), _, remaining) = - ::scuffle_bootstrap::prelude::futures::future::select_all($remaining) - . await; let result = - ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, - name) ?, name); < TestGlobal as ::scuffle_bootstrap::global::Global > - ::on_service_exit(& global, name, result). await ?; remaining } - }; - } - let mut remaining = { - let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( - services_vec, - ) - .await; - let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::scuffle_bootstrap::prelude::anyhow::Context::context( - result, - name, - )?, - name, - ); - ::on_service_exit( - &global, - name, - result, - ) - .await?; - remaining - }; - while !remaining.is_empty() { - remaining = { - let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( - remaining, - ) - .await; - let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::scuffle_bootstrap::prelude::anyhow::Context::context( - result, - name, - )?, - name, - ); - ::on_service_exit( - &global, - name, - result, - ) - .await?; - remaining - }; - } - ::scuffle_bootstrap::prelude::anyhow::Ok(()) - }); - let ::core::option::Option::Some(global) = shared_global else { - return result; - }; - runtime - .block_on( - ::on_exit(&global, result), - ) -} diff --git a/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap b/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap deleted file mode 100644 index 005e0b8dc..000000000 --- a/crates/bootstrap/src/snapshots/scuffle_bootstrap__tests__main_test_custom_service.snap +++ /dev/null @@ -1,229 +0,0 @@ ---- -source: crates/bootstrap/src/lib.rs -expression: "postcompile::compile!\n{\n use std::sync::Arc; use scuffle_bootstrap::main; struct TestGlobal; impl\n scuffle_signal::SignalConfig for TestGlobal {} impl\n scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal\n { async fn init() -> anyhow::Result> { Ok(Arc::new(Self)) } }\n struct MySvc; impl scuffle_bootstrap::service::Service for\n MySvc\n {\n async fn run(self, _: Arc, _: scuffle_context::Context) ->\n anyhow::Result<()> { println!(\"running\"); Ok(()) }\n } main! { TestGlobal { scuffle_signal::SignalSvc, MySvc, } }\n}" -snapshot_kind: text ---- -exit status: 0 ---- stdout -#![feature(prelude_import)] -#[prelude_import] -use std::prelude::rust_2021::*; -#[macro_use] -extern crate std; -use std::sync::Arc; -use scuffle_bootstrap::main; -struct TestGlobal; -impl scuffle_signal::SignalConfig for TestGlobal {} -impl scuffle_bootstrap::global::GlobalWithoutConfig for TestGlobal { - async fn init() -> anyhow::Result> { - Ok(Arc::new(Self)) - } -} -struct MySvc; -impl scuffle_bootstrap::service::Service for MySvc { - async fn run( - self, - _: Arc, - _: scuffle_context::Context, - ) -> anyhow::Result<()> { - { - ::std::io::_print(format_args!("running\n")); - }; - Ok(()) - } -} -#[automatically_derived] -fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> { - #[doc(hidden)] - pub const fn impl_global() {} - const _: () = impl_global::(); - ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::pre_init(), - "pre_init", - )?; - let runtime = ::tokio_runtime(); - let config = ::scuffle_bootstrap::prelude::anyhow::Context::context( - runtime - .block_on( - <::Config as ::scuffle_bootstrap::config::ConfigParser>::parse(), - ), - "config parse", - )?; - let ctx_handle = ::scuffle_bootstrap::prelude::scuffle_context::Handler::global(); - let mut shared_global = ::core::option::Option::None; - let mut services_vec = ::std::vec::Vec::< - ::scuffle_bootstrap::service::NamedFuture< - ::scuffle_bootstrap::prelude::tokio::task::JoinHandle>, - >, - >::new(); - let result = runtime - .block_on(async { - let global = ::init( - config, - ) - .await?; - shared_global = ::core::option::Option::Some(global.clone()); - { - #[doc(hidden)] - pub async fn spawn_service( - svc: impl ::scuffle_bootstrap::service::Service, - global: &::std::sync::Arc, - ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler, - name: &'static str, - ) -> anyhow::Result< - Option< - ::scuffle_bootstrap::service::NamedFuture< - ::scuffle_bootstrap::prelude::tokio::task::JoinHandle< - anyhow::Result<()>, - >, - >, - >, - > { - let name = ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::name(&svc) - .unwrap_or_else(|| name); - if ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::enabled(&svc, &global) - .await, - name, - )? { - Ok( - Some( - ::scuffle_bootstrap::service::NamedFuture::new( - name, - ::scuffle_bootstrap::prelude::tokio::spawn( - ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::run(svc, global.clone(), ctx_handle.context()), - ), - ), - ), - ) - } else { - Ok(None) - } - } - let res = spawn_service( - scuffle_signal::SignalSvc, - &global, - &ctx_handle, - "scuffle_signal :: SignalSvc", - ) - .await; - if let Some(spawned) = res? { - services_vec.push(spawned); - } - } - { - #[doc(hidden)] - pub async fn spawn_service( - svc: impl ::scuffle_bootstrap::service::Service, - global: &::std::sync::Arc, - ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler, - name: &'static str, - ) -> anyhow::Result< - Option< - ::scuffle_bootstrap::service::NamedFuture< - ::scuffle_bootstrap::prelude::tokio::task::JoinHandle< - anyhow::Result<()>, - >, - >, - >, - > { - let name = ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::name(&svc) - .unwrap_or_else(|| name); - if ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::enabled(&svc, &global) - .await, - name, - )? { - Ok( - Some( - ::scuffle_bootstrap::service::NamedFuture::new( - name, - ::scuffle_bootstrap::prelude::tokio::spawn( - ::scuffle_bootstrap::service::Service::< - TestGlobal, - >::run(svc, global.clone(), ctx_handle.context()), - ), - ), - ), - ) - } else { - Ok(None) - } - } - let res = spawn_service(MySvc, &global, &ctx_handle, "MySvc").await; - if let Some(spawned) = res? { - services_vec.push(spawned); - } - } - macro_rules! handle_service_exit { - ($remaining:ident) => { - { let ((name, result), _, remaining) = - ::scuffle_bootstrap::prelude::futures::future::select_all($remaining) - . await; let result = - ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, - name) ?, name); < TestGlobal as ::scuffle_bootstrap::global::Global > - ::on_service_exit(& global, name, result). await ?; remaining } - }; - } - let mut remaining = { - let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( - services_vec, - ) - .await; - let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::scuffle_bootstrap::prelude::anyhow::Context::context( - result, - name, - )?, - name, - ); - ::on_service_exit( - &global, - name, - result, - ) - .await?; - remaining - }; - while !remaining.is_empty() { - remaining = { - let ((name, result), _, remaining) = ::scuffle_bootstrap::prelude::futures::future::select_all( - remaining, - ) - .await; - let result = ::scuffle_bootstrap::prelude::anyhow::Context::context( - ::scuffle_bootstrap::prelude::anyhow::Context::context( - result, - name, - )?, - name, - ); - ::on_service_exit( - &global, - name, - result, - ) - .await?; - remaining - }; - } - ::scuffle_bootstrap::prelude::anyhow::Ok(()) - }); - let ::core::option::Option::Some(global) = shared_global else { - return result; - }; - runtime - .block_on( - ::on_exit(&global, result), - ) -} From e47f5a79f48de03ba1eaff0bc0d9073ebf03562a Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 20:05:38 +0100 Subject: [PATCH 06/17] fix(bootsrap): call `on_services_start` --- crates/bootstrap/derive/src/main_impl.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bootstrap/derive/src/main_impl.rs b/crates/bootstrap/derive/src/main_impl.rs index 36f7a1470..4dff323ce 100644 --- a/crates/bootstrap/derive/src/main_impl.rs +++ b/crates/bootstrap/derive/src/main_impl.rs @@ -215,6 +215,8 @@ pub fn impl_main(input: TokenStream) -> Result { #(#services)* + #entry_as_global::on_services_start().await?; + macro_rules! handle_service_exit { ($remaining:ident) => {{ let ((name, result), _, remaining) = #crate_path::prelude::futures::future::select_all($remaining).await; From 8cf911d2c80a64af475f348ab44f2969336dadd1 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 20:05:51 +0100 Subject: [PATCH 07/17] docs(bootstrap): add docs --- crates/bootstrap/derive/src/lib.rs | 15 +++++ crates/bootstrap/src/global.rs | 101 +++++++++++++++++++++-------- crates/bootstrap/src/service.rs | 24 +++++++ crates/context/src/ext.rs | 9 ++- 4 files changed, 117 insertions(+), 32 deletions(-) diff --git a/crates/bootstrap/derive/src/lib.rs b/crates/bootstrap/derive/src/lib.rs index 10db18af5..94ae898b1 100644 --- a/crates/bootstrap/derive/src/lib.rs +++ b/crates/bootstrap/derive/src/lib.rs @@ -8,6 +8,21 @@ mod main_impl; // handle_error(service_impl::impl_service(args.into(), input.into())) // } +/// This macro is used to generate the main function for the given global type +/// and service types. It will run all the services in parallel and wait for +/// them to finish before exiting. +/// +/// # Example +/// +/// ```rust,ignore +/// # // We cant test this example because it depends on the parent crate and we don't want to introduce a cyclic dependency +/// scuffle_bootstrap::main! { +/// MyGlobal { +/// scuffle_signal::SignalSvc, +/// MyService, +/// } +/// } +/// ``` #[proc_macro] pub fn main(input: TokenStream) -> TokenStream { handle_error(main_impl::impl_main(input.into())) diff --git a/crates/bootstrap/src/global.rs b/crates/bootstrap/src/global.rs index 765e79da6..d77b9db4b 100644 --- a/crates/bootstrap/src/global.rs +++ b/crates/bootstrap/src/global.rs @@ -69,85 +69,132 @@ fn default_runtime_builder() -> tokio::runtime::Builder { builder } +/// This trait is implemented for the global type of your application. +/// It is intended to be used to store any global state of your application. +/// When using the [`main!`](crate::main) macro, one instance of this type will +/// be made available to all services. +/// +/// # See Also +/// +/// - [`Service`](crate::Service) +/// - [`main`](crate::main) pub trait Global: Send + Sync + 'static { type Config: ConfigParser + Send + 'static; - /// Builds the tokio runtime for the application. + /// Pre-initialization. + /// Called before initializing the tokio runtime and loading the config. + /// Returning an error from this function will cause the process to + /// immediately exit without calling [`on_exit`](Global::on_exit) first. #[inline(always)] - fn tokio_runtime() -> tokio::runtime::Runtime { - default_runtime_builder().build().expect("runtime build") + fn pre_init() -> anyhow::Result<()> { + Ok(()) } - /// Called before loading the config. + /// Builds the tokio runtime for the process. #[inline(always)] - fn pre_init() -> anyhow::Result<()> { - Ok(()) + fn tokio_runtime() -> tokio::runtime::Runtime { + default_runtime_builder().build().expect("runtime build") } /// Initialize the global. + /// Called to initialize the global. + /// Returning an error from this function will cause the process to + /// immediately exit without calling [`on_exit`](Global::on_exit) first. fn init(config: Self::Config) -> impl std::future::Future>> + Send; - /// Called when all services have been started. + /// Called right before all services start. + /// Returning an error from this function will prevent any service from + /// starting and [`on_exit`](Global::on_exit) will be called with the result + /// of this function. #[inline(always)] fn on_services_start(self: &Arc) -> impl std::future::Future> + Send { std::future::ready(Ok(())) } - /// Called when the shutdown process is complete, right before exiting the - /// process. + /// Called after a service exits. + /// + /// `name` is the name of the service that exited and `result` is the result + /// the service exited with. Returning an error from this function will + /// stop all currently running services and [`on_exit`](Global::on_exit) + /// will be called with the result of this function. #[inline(always)] - fn on_exit( + fn on_service_exit( self: &Arc, + name: &'static str, result: anyhow::Result<()>, ) -> impl std::future::Future> + Send { + let _ = name; std::future::ready(result) } - /// Called when a service exits. + /// Called after the shutdown is complete, right before exiting the + /// process. + /// + /// `result` is [`Err`](anyhow::Result) when the process exits due to an + /// error in one of the services or handler functions, otherwise `Ok(())`. #[inline(always)] - fn on_service_exit( + fn on_exit( self: &Arc, - name: &'static str, result: anyhow::Result<()>, ) -> impl std::future::Future> + Send { - let _ = name; std::future::ready(result) } } +/// Simplified version of [`Global`]. +/// Implementing this trait will automatically implement [`Global`] for your +/// type. This trait is intended to be used when you don't need a config for +/// your global. +/// +/// Refer to [`Global`] for details. pub trait GlobalWithoutConfig: Send + Sync + 'static { + /// Builds the tokio runtime for the process. #[inline(always)] fn tokio_runtime() -> tokio::runtime::Runtime { default_runtime_builder().build().expect("runtime build") } /// Initialize the global. + /// Called to initialize the global. + /// Returning an error from this function will cause the process to + /// immediately exit without calling [`on_exit`](Global::on_exit) first. fn init() -> impl std::future::Future>> + Send; - /// Called when all services have been started. + /// Called right before all services start. + /// Returning an error from this function will prevent any service from + /// starting and [`on_exit`](Global::on_exit) will be called with the result + /// of this function. #[inline(always)] fn on_services_start(self: &Arc) -> impl std::future::Future> + Send { std::future::ready(Ok(())) } - /// Called when the shutdown process is complete, right before exiting the - /// process. + /// Called after a service exits. + /// + /// `name` is the name of the service that exited and `result` is the result + /// the service exited with. Returning an error from this function will + /// stop all currently running services and [`on_exit`](Global::on_exit) + /// will be called with the result of this function. #[inline(always)] - fn on_exit( + fn on_service_exit( self: &Arc, + name: &'static str, result: anyhow::Result<()>, ) -> impl std::future::Future> + Send { + let _ = name; std::future::ready(result) } - /// Called when a service exits. + /// Called after the shutdown is complete, right before exiting the + /// process. + /// + /// `result` is [`Err`](anyhow::Result) when the process exits due to an + /// error in one of the services or handler functions, otherwise `Ok(())`. #[inline(always)] - fn on_service_exit( + fn on_exit( self: &Arc, - name: &'static str, result: anyhow::Result<()>, ) -> impl std::future::Future> + Send { - let _ = name; std::future::ready(result) } } @@ -165,6 +212,11 @@ impl Global for T { ::init() } + #[inline(always)] + fn on_services_start(self: &Arc) -> impl std::future::Future> + Send { + ::on_services_start(self) + } + #[inline(always)] fn on_service_exit( self: &Arc, @@ -174,11 +226,6 @@ impl Global for T { ::on_service_exit(self, name, result) } - #[inline(always)] - fn on_services_start(self: &Arc) -> impl std::future::Future> + Send { - ::on_services_start(self) - } - #[inline(always)] fn on_exit( self: &Arc, diff --git a/crates/bootstrap/src/service.rs b/crates/bootstrap/src/service.rs index 553df70c4..bfb9b8d9a 100644 --- a/crates/bootstrap/src/service.rs +++ b/crates/bootstrap/src/service.rs @@ -2,7 +2,17 @@ use std::pin::Pin; use std::sync::Arc; use std::task::{ready, Context, Poll}; +/// A service that can be run. +/// +/// This trait is used to define a service that can be run in parallel to other +/// services. +/// +/// # See Also +/// +/// - [`Global`](crate::Global) +/// - [`main`](crate::main) pub trait Service: Send + Sync + 'static + Sized { + /// Returns the name of the service, if any. fn name(&self) -> Option<&'static str> { None } @@ -14,6 +24,20 @@ pub trait Service: Send + Sync + 'static + Sized { std::future::ready(Ok(true)) } + /// Run the service. + /// This function should return a future that is pending as long as the + /// service is running. When the service finishes without any errors, + /// the future should resolve to `Ok(())`. As a best practice, the + /// service should stop as soon as the provided context is done. + /// + /// Note: Adding the `scuffle_signal::SignalSvc` service to the list of + /// services when calling [`main`](crate::main) will cancel the context as + /// soon as a shutdown signal is received. + /// + /// # See Also + /// + /// - [`Context`](scuffle_context::Context) + /// - `scuffle_signal::SignalSvc` fn run( self, global: Arc, diff --git a/crates/context/src/ext.rs b/crates/context/src/ext.rs index 86cb36653..7c6a55f69 100644 --- a/crates/context/src/ext.rs +++ b/crates/context/src/ext.rs @@ -7,7 +7,10 @@ use tokio_util::sync::{WaitForCancellationFuture, WaitForCancellationFutureOwned use crate::{Context, ContextTracker}; -/// This type is used to make the inner enum [`ContextRefInner`] private. +/// A reference to a context which implements [`Future`] and can be polled. +/// Can either be owned or borrowed. +/// +/// Create by using the [`From`] implementations. pub struct ContextRef<'a> { inner: ContextRefInner<'a>, } @@ -34,10 +37,6 @@ impl<'a> From<&'a Context> for ContextRef<'a> { } pin_project_lite::pin_project! { - /// A reference to a context which implements [`Future`] and can be polled. - /// Can either be owned or borrowed. - /// - /// Create by using the [`From`] implementations. #[project = ContextRefInnerProj] enum ContextRefInner<'a> { Owned { From dfe8e3a6d0cdf7ff17dce82f35e36b6cd32b42d0 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 20:13:56 +0100 Subject: [PATCH 08/17] fix(bootsrap): call `on_services_start` with right parameters --- crates/bootstrap/derive/src/main_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bootstrap/derive/src/main_impl.rs b/crates/bootstrap/derive/src/main_impl.rs index 4dff323ce..a460d22c8 100644 --- a/crates/bootstrap/derive/src/main_impl.rs +++ b/crates/bootstrap/derive/src/main_impl.rs @@ -215,7 +215,7 @@ pub fn impl_main(input: TokenStream) -> Result { #(#services)* - #entry_as_global::on_services_start().await?; + #entry_as_global::on_services_start(&#global_ident).await?; macro_rules! handle_service_exit { ($remaining:ident) => {{ From 4cf056bba42ea0d4770d5ebee82d9c1ee2b4ea70 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 20:14:13 +0100 Subject: [PATCH 09/17] docs(bootstrap): more docs --- crates/bootstrap/derive/src/lib.rs | 7 ++++++- crates/bootstrap/src/global.rs | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/bootstrap/derive/src/lib.rs b/crates/bootstrap/derive/src/lib.rs index 94ae898b1..a3d06408b 100644 --- a/crates/bootstrap/derive/src/lib.rs +++ b/crates/bootstrap/derive/src/lib.rs @@ -8,7 +8,7 @@ mod main_impl; // handle_error(service_impl::impl_service(args.into(), input.into())) // } -/// This macro is used to generate the main function for the given global type +/// This macro is used to generate the main function for a given global type /// and service types. It will run all the services in parallel and wait for /// them to finish before exiting. /// @@ -23,6 +23,11 @@ mod main_impl; /// } /// } /// ``` +/// +/// # See Also +/// +/// - `scuffle_bootstrap::Service` +/// - `scuffle_bootstrap::Global` #[proc_macro] pub fn main(input: TokenStream) -> TokenStream { handle_error(main_impl::impl_main(input.into())) diff --git a/crates/bootstrap/src/global.rs b/crates/bootstrap/src/global.rs index d77b9db4b..3253f238f 100644 --- a/crates/bootstrap/src/global.rs +++ b/crates/bootstrap/src/global.rs @@ -82,6 +82,7 @@ pub trait Global: Send + Sync + 'static { type Config: ConfigParser + Send + 'static; /// Pre-initialization. + /// /// Called before initializing the tokio runtime and loading the config. /// Returning an error from this function will cause the process to /// immediately exit without calling [`on_exit`](Global::on_exit) first. @@ -97,12 +98,14 @@ pub trait Global: Send + Sync + 'static { } /// Initialize the global. + /// /// Called to initialize the global. /// Returning an error from this function will cause the process to /// immediately exit without calling [`on_exit`](Global::on_exit) first. fn init(config: Self::Config) -> impl std::future::Future>> + Send; /// Called right before all services start. + /// /// Returning an error from this function will prevent any service from /// starting and [`on_exit`](Global::on_exit) will be called with the result /// of this function. @@ -142,6 +145,7 @@ pub trait Global: Send + Sync + 'static { } /// Simplified version of [`Global`]. +/// /// Implementing this trait will automatically implement [`Global`] for your /// type. This trait is intended to be used when you don't need a config for /// your global. @@ -155,12 +159,14 @@ pub trait GlobalWithoutConfig: Send + Sync + 'static { } /// Initialize the global. + /// /// Called to initialize the global. /// Returning an error from this function will cause the process to /// immediately exit without calling [`on_exit`](Global::on_exit) first. fn init() -> impl std::future::Future>> + Send; /// Called right before all services start. + /// /// Returning an error from this function will prevent any service from /// starting and [`on_exit`](Global::on_exit) will be called with the result /// of this function. From 8392491e8020f2d2a7bd9300cb6826b264240396 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 20:57:39 +0100 Subject: [PATCH 10/17] docs(bootstrap): readme --- crates/bootstrap/README.md | 39 ++++++++++++++++++++++++-- crates/bootstrap/src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/crates/bootstrap/README.md b/crates/bootstrap/README.md index 46742764e..0c6845369 100644 --- a/crates/bootstrap/README.md +++ b/crates/bootstrap/README.md @@ -3,7 +3,7 @@ > [!WARNING] > This crate is under active development and may not be stable. - [![crates.io](https://img.shields.io/crates/v/scuffle-bootstrap.svg)](https://crates.io/crates/scuffle-bootstrap) [![docs.rs](https://img.shields.io/docsrs/scuffle-bootstrap)](https://docs.rs/scuffle-bootstrap) +[![crates.io](https://img.shields.io/crates/v/scuffle-bootstrap.svg)](https://crates.io/crates/scuffle-bootstrap) [![docs.rs](https://img.shields.io/docsrs/scuffle-bootstrap)](https://docs.rs/scuffle-bootstrap) --- @@ -11,7 +11,42 @@ A utility crate for creating binaries. ## Usage -TODO(troy): Add usage examples to readme. +```rust +/// Our global state +struct Global; + +// Required by the signal service +impl scuffle_signal::SignalConfig for Global {} + +impl scuffle_bootstrap::global::GlobalWithoutConfig for Global { + async fn init() -> anyhow::Result> { + Ok(Arc::new(Self)) + } +} + +/// Our own custom service +struct MySvc; + +impl scuffle_bootstrap::service::Service for MySvc { + async fn run(self, _: Arc, _: scuffle_context::Context) -> anyhow::Result<()> { + println!("running"); + + // Do some work here + + // Wait for the context to be cacelled by the signal service + ctx.done().await; + Ok(()) + } +} + +// This generates the main function which runs all the services +main! { + Global { + scuffle_signal::SignalSvc, + MySvc, + } +} +``` ## License diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index 58a1db5b4..ca6a563bd 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -1,3 +1,59 @@ +//! # scuffle-bootstrap +//! +//! > [!WARNING] +//! > This crate is under active development and may not be stable. +//! +//! [![crates.io](https://img.shields.io/crates/v/scuffle-bootstrap.svg)](https://crates.io/crates/scuffle-bootstrap) [![docs.rs](https://img.shields.io/docsrs/scuffle-bootstrap)](https://docs.rs/scuffle-bootstrap) +//! +//! --- +//! +//! A utility crate for creating binaries. +//! +//! ## Usage +//! +//! ```rust,ignore +//! /// Our global state +//! struct Global; +//! +//! // Required by the signal service +//! impl scuffle_signal::SignalConfig for Global {} +//! +//! impl scuffle_bootstrap::global::GlobalWithoutConfig for Global { +//! async fn init() -> anyhow::Result> { +//! Ok(Arc::new(Self)) +//! } +//! } +//! +//! /// Our own custom service +//! struct MySvc; +//! +//! impl scuffle_bootstrap::service::Service for MySvc { +//! async fn run(self, _: Arc, _: scuffle_context::Context) -> anyhow::Result<()> { +//! println!("running"); +//! +//! // Do some work here +//! +//! // Wait for the context to be cacelled by the signal service +//! ctx.done().await; +//! Ok(()) +//! } +//! } +//! +//! // This generates the main function which runs all the services +//! main! { +//! Global { +//! scuffle_signal::SignalSvc, +//! MySvc, +//! } +//! } +//! ``` +//! +//! ## License +//! +//! This project is licensed under the [MIT](./LICENSE.MIT) or [Apache-2.0](./LICENSE.Apache-2.0) license. +//! You can choose between one of them if you use this work. +//! +//! `SPDX-License-Identifier: MIT OR Apache-2.0` #![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] pub mod config; From 4ba4e14707ca3f92a85cfa02710dcb57d120115b Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 21:10:38 +0100 Subject: [PATCH 11/17] docs(bootstrap): fix warning --- crates/bootstrap/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index ca6a563bd..a322b31b5 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -1,6 +1,6 @@ //! # scuffle-bootstrap //! -//! > [!WARNING] +//! > WARNING //! > This crate is under active development and may not be stable. //! //! [![crates.io](https://img.shields.io/crates/v/scuffle-bootstrap.svg)](https://crates.io/crates/scuffle-bootstrap) [![docs.rs](https://img.shields.io/docsrs/scuffle-bootstrap)](https://docs.rs/scuffle-bootstrap) From d479df6f09a983c91daa4253e8ca1229c07260ed Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 21:43:01 +0100 Subject: [PATCH 12/17] docs: remove shields --- crates/batching/src/lib.rs | 7 ------- crates/bootstrap/README.md | 6 ++++-- crates/bootstrap/src/lib.rs | 16 ++++++---------- crates/context/src/lib.rs | 7 ------- crates/signal/src/lib.rs | 7 ------- 5 files changed, 10 insertions(+), 33 deletions(-) diff --git a/crates/batching/src/lib.rs b/crates/batching/src/lib.rs index 56cd59178..c7c7922f8 100644 --- a/crates/batching/src/lib.rs +++ b/crates/batching/src/lib.rs @@ -1,12 +1,5 @@ //! # scuffle-batching //! -//! > WARNING -//! > This crate is under active development and may not be stable. -//! -//! [![crates.io](https://img.shields.io/crates/v/scuffle-batching.svg)](https://crates.io/crates/scuffle-batching) [![docs.rs](https://img.shields.io/docsrs/scuffle-batching)](https://docs.rs/scuffle-batching) -//! -//! --- -//! //! A crate designed to batch multiple requests into a single request. //! //! ## Why do we need this? diff --git a/crates/bootstrap/README.md b/crates/bootstrap/README.md index 0c6845369..cb1c9dc49 100644 --- a/crates/bootstrap/README.md +++ b/crates/bootstrap/README.md @@ -12,6 +12,8 @@ A utility crate for creating binaries. ## Usage ```rust +use std::sync::Arc; + /// Our global state struct Global; @@ -28,7 +30,7 @@ impl scuffle_bootstrap::global::GlobalWithoutConfig for Global { struct MySvc; impl scuffle_bootstrap::service::Service for MySvc { - async fn run(self, _: Arc, _: scuffle_context::Context) -> anyhow::Result<()> { + async fn run(self, global: Arc, ctx: scuffle_context::Context) -> anyhow::Result<()> { println!("running"); // Do some work here @@ -40,7 +42,7 @@ impl scuffle_bootstrap::service::Service for MySvc { } // This generates the main function which runs all the services -main! { +scuffle_bootstrap::main! { Global { scuffle_signal::SignalSvc, MySvc, diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index a322b31b5..e072e16f9 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -1,17 +1,12 @@ //! # scuffle-bootstrap //! -//! > WARNING -//! > This crate is under active development and may not be stable. -//! -//! [![crates.io](https://img.shields.io/crates/v/scuffle-bootstrap.svg)](https://crates.io/crates/scuffle-bootstrap) [![docs.rs](https://img.shields.io/docsrs/scuffle-bootstrap)](https://docs.rs/scuffle-bootstrap) -//! -//! --- -//! //! A utility crate for creating binaries. //! //! ## Usage //! -//! ```rust,ignore +//! ```rust,no_run +//! use std::sync::Arc; +//! //! /// Our global state //! struct Global; //! @@ -28,7 +23,8 @@ //! struct MySvc; //! //! impl scuffle_bootstrap::service::Service for MySvc { -//! async fn run(self, _: Arc, _: scuffle_context::Context) -> anyhow::Result<()> { +//! async fn run(self, global: Arc, ctx: scuffle_context::Context) -> anyhow::Result<()> { +//! # let _ = global; //! println!("running"); //! //! // Do some work here @@ -40,7 +36,7 @@ //! } //! //! // This generates the main function which runs all the services -//! main! { +//! scuffle_bootstrap::main! { //! Global { //! scuffle_signal::SignalSvc, //! MySvc, diff --git a/crates/context/src/lib.rs b/crates/context/src/lib.rs index 5ced1ae4b..69034eb0c 100644 --- a/crates/context/src/lib.rs +++ b/crates/context/src/lib.rs @@ -1,12 +1,5 @@ //! # scuffle-context //! -//! > WARNING -//! > This crate is under active development and may not be stable. -//! -//! [![crates.io](https://img.shields.io/crates/v/scuffle-context.svg)](https://crates.io/crates/scuffle-context) [![docs.rs](https://img.shields.io/docsrs/scuffle-context)](https://docs.rs/scuffle-context) -//! -//! --- -//! //! A crate designed to provide the ability to cancel futures using a context //! go-like approach, allowing for graceful shutdowns and cancellations. //! diff --git a/crates/signal/src/lib.rs b/crates/signal/src/lib.rs index 164aecac3..0c1c17c0a 100644 --- a/crates/signal/src/lib.rs +++ b/crates/signal/src/lib.rs @@ -1,12 +1,5 @@ //! # scuffle-signal //! -//! > WARNING -//! > This crate is under active development and may not be stable. -//! -//! [![crates.io](https://img.shields.io/crates/v/scuffle-signal.svg)](https://crates.io/crates/scuffle-signal) [![docs.rs](https://img.shields.io/docsrs/scuffle-signal)](https://docs.rs/scuffle-signal) -//! -//! --- -//! //! A crate designed to provide a more user friendly interface to //! `tokio::signal`. //! From e003daa6f328447ea5861ab0a7e57fbaaebd8baf Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Thu, 9 Jan 2025 22:15:15 +0100 Subject: [PATCH 13/17] docs(bootstrap): link types in root --- crates/bootstrap/README.md | 2 ++ crates/bootstrap/src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/crates/bootstrap/README.md b/crates/bootstrap/README.md index cb1c9dc49..d607b1cfb 100644 --- a/crates/bootstrap/README.md +++ b/crates/bootstrap/README.md @@ -9,6 +9,8 @@ A utility crate for creating binaries. +Refer to `Global`, `Service`, and `main` for more information. + ## Usage ```rust diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index e072e16f9..9c2ac2b07 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -2,6 +2,8 @@ //! //! A utility crate for creating binaries. //! +//! Refer to [`Global`], [`Service`], and [`main`] for more information. +//! //! ## Usage //! //! ```rust,no_run From 013be86c0719edb2afcf6c6f69d45e746afd684f Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Fri, 10 Jan 2025 18:24:04 +0100 Subject: [PATCH 14/17] chore(bootstrap): fmt --- crates/bootstrap/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index 9c2ac2b07..bab65c83b 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -48,8 +48,9 @@ //! //! ## License //! -//! This project is licensed under the [MIT](./LICENSE.MIT) or [Apache-2.0](./LICENSE.Apache-2.0) license. -//! You can choose between one of them if you use this work. +//! This project is licensed under the [MIT](./LICENSE.MIT) or +//! [Apache-2.0](./LICENSE.Apache-2.0) license. You can choose between one of +//! them if you use this work. //! //! `SPDX-License-Identifier: MIT OR Apache-2.0` #![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] From 1a54cc4db538b1fd95128f77c132c60529f4015e Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 10 Jan 2025 21:28:45 +0000 Subject: [PATCH 15/17] fix: main macro docs use a wrapper macro to allow for docs on the main macro --- Cargo.lock | 2 + crates/bootstrap/derive/Cargo.toml | 7 + crates/bootstrap/derive/src/lib.rs | 159 ++++++++++++++++---- crates/bootstrap/derive/src/service_impl.rs | 7 - crates/bootstrap/src/lib.rs | 111 ++++++-------- crates/bootstrap/src/service.rs | 4 + 6 files changed, 189 insertions(+), 101 deletions(-) delete mode 100644 crates/bootstrap/derive/src/service_impl.rs diff --git a/Cargo.lock b/Cargo.lock index dd19c737e..bc3b8188f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2574,6 +2574,8 @@ name = "scuffle-bootstrap-derive" version = "0.0.2" dependencies = [ "darling", + "insta", + "prettyplease", "proc-macro2", "quote", "scuffle-workspace-hack", diff --git a/crates/bootstrap/derive/Cargo.toml b/crates/bootstrap/derive/Cargo.toml index 52d1ef5a9..01294745e 100644 --- a/crates/bootstrap/derive/Cargo.toml +++ b/crates/bootstrap/derive/Cargo.toml @@ -10,6 +10,9 @@ license = "MIT OR Apache-2.0" description = "Derive macros for scuffle-bootstrap." keywords = ["bootstrap", "derive", "macros"] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } + [lib] proc-macro = true @@ -19,3 +22,7 @@ proc-macro2 = "1" quote = "1" darling = "0.20" scuffle-workspace-hack.workspace = true + +[dev-dependencies] +insta = "1" +prettyplease = "0.2" diff --git a/crates/bootstrap/derive/src/lib.rs b/crates/bootstrap/derive/src/lib.rs index a3d06408b..2606c2dba 100644 --- a/crates/bootstrap/derive/src/lib.rs +++ b/crates/bootstrap/derive/src/lib.rs @@ -1,33 +1,9 @@ +#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] + use proc_macro::TokenStream; mod main_impl; -// mod service_impl; - -// #[proc_macro_attribute] -// pub fn service(args: TokenStream, input: TokenStream) -> TokenStream { -// handle_error(service_impl::impl_service(args.into(), input.into())) -// } - -/// This macro is used to generate the main function for a given global type -/// and service types. It will run all the services in parallel and wait for -/// them to finish before exiting. -/// -/// # Example -/// -/// ```rust,ignore -/// # // We cant test this example because it depends on the parent crate and we don't want to introduce a cyclic dependency -/// scuffle_bootstrap::main! { -/// MyGlobal { -/// scuffle_signal::SignalSvc, -/// MyService, -/// } -/// } -/// ``` -/// -/// # See Also -/// -/// - `scuffle_bootstrap::Service` -/// - `scuffle_bootstrap::Global` + #[proc_macro] pub fn main(input: TokenStream) -> TokenStream { handle_error(main_impl::impl_main(input.into())) @@ -39,3 +15,132 @@ fn handle_error(input: Result) -> TokenStr Err(err) => err.to_compile_error().into(), } } + +#[cfg(test)] +#[cfg_attr(all(test, coverage_nightly), coverage(off))] +mod tests { + use super::*; + + #[test] + fn test_main() { + let input = quote::quote! { + MyGlobal { + MyService, + } + }; + + let output = match main_impl::impl_main(input) { + Ok(value) => value, + Err(err) => err.to_compile_error(), + }; + + let syntax_tree = prettyplease::unparse(&syn::parse_file(&output.to_string()).unwrap()); + + insta::assert_snapshot!(syntax_tree, @r##" + #[automatically_derived] + fn main() -> ::scuffle_bootstrap::prelude::anyhow::Result<()> { + #[doc(hidden)] + pub const fn impl_global() {} + const _: () = impl_global::(); + ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::pre_init(), + "pre_init", + )?; + let runtime = ::tokio_runtime(); + let config = ::scuffle_bootstrap::prelude::anyhow::Context::context( + runtime + .block_on( + <::Config as ::scuffle_bootstrap::config::ConfigParser>::parse(), + ), + "config parse", + )?; + let ctx_handle = ::scuffle_bootstrap::prelude::scuffle_context::Handler::global(); + let mut shared_global = ::core::option::Option::None; + let mut services_vec = ::std::vec::Vec::< + ::scuffle_bootstrap::service::NamedFuture< + ::scuffle_bootstrap::prelude::tokio::task::JoinHandle>, + >, + >::new(); + let result = runtime + .block_on(async { + let global = ::init(config) + .await?; + shared_global = ::core::option::Option::Some(global.clone()); + { + #[doc(hidden)] + pub async fn spawn_service( + svc: impl ::scuffle_bootstrap::service::Service, + global: &::std::sync::Arc, + ctx_handle: &::scuffle_bootstrap::prelude::scuffle_context::Handler, + name: &'static str, + ) -> anyhow::Result< + Option< + ::scuffle_bootstrap::service::NamedFuture< + ::scuffle_bootstrap::prelude::tokio::task::JoinHandle< + anyhow::Result<()>, + >, + >, + >, + > { + let name = ::scuffle_bootstrap::service::Service::< + MyGlobal, + >::name(&svc) + .unwrap_or_else(|| name); + if ::scuffle_bootstrap::prelude::anyhow::Context::context( + ::scuffle_bootstrap::service::Service::< + MyGlobal, + >::enabled(&svc, &global) + .await, + name, + )? { + Ok( + Some( + ::scuffle_bootstrap::service::NamedFuture::new( + name, + ::scuffle_bootstrap::prelude::tokio::spawn( + ::scuffle_bootstrap::service::Service::< + MyGlobal, + >::run(svc, global.clone(), ctx_handle.context()), + ), + ), + ), + ) + } else { + Ok(None) + } + } + let res = spawn_service(MyService, &global, &ctx_handle, "MyService") + .await; + if let Some(spawned) = res? { + services_vec.push(spawned); + } + } + ::on_services_start(&global) + .await?; + macro_rules! handle_service_exit { + ($remaining:ident) => { + { let ((name, result), _, remaining) = + ::scuffle_bootstrap::prelude::futures::future::select_all($remaining) + . await; let result = + ::scuffle_bootstrap::prelude::anyhow::Context::context(::scuffle_bootstrap::prelude::anyhow::Context::context(result, + name) ?, name); < MyGlobal as ::scuffle_bootstrap::global::Global > + ::on_service_exit(& global, name, result). await ?; remaining } + }; + } + let mut remaining = handle_service_exit!(services_vec); + while !remaining.is_empty() { + remaining = handle_service_exit!(remaining); + } + ::scuffle_bootstrap::prelude::anyhow::Ok(()) + }); + let ::core::option::Option::Some(global) = shared_global else { + return result; + }; + runtime + .block_on( + ::on_exit(&global, result), + ) + } + "##); + } +} diff --git a/crates/bootstrap/derive/src/service_impl.rs b/crates/bootstrap/derive/src/service_impl.rs deleted file mode 100644 index c21a07aa1..000000000 --- a/crates/bootstrap/derive/src/service_impl.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![allow(unused)] - -use proc_macro2::TokenStream; - -pub fn impl_service(input: TokenStream, args: TokenStream) -> Result { - todo!() -} diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index bab65c83b..6d30fbb7f 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -61,76 +61,53 @@ pub mod service; pub use config::{ConfigParser, EmptyConfig}; pub use global::Global; -pub use scuffle_bootstrap_derive::main; pub use service::Service; #[doc(hidden)] pub mod prelude { - pub use {anyhow, futures, scuffle_context, tokio}; + pub use {anyhow, futures, scuffle_bootstrap_derive, scuffle_context, tokio}; } -// Note: Tests are disabled due to a problem with cargo caching - -// #[cfg(test)] -// #[cfg_attr(all(test, coverage_nightly), coverage(off))] -// mod tests { -// #[test] -// fn main_test() { -// insta::assert_snapshot!(postcompile::compile! { -// use std::sync::Arc; - -// use scuffle_bootstrap::main; - -// struct TestGlobal; - -// impl scuffle_signal::SignalConfig for TestGlobal {} - -// impl scuffle_bootstrap::global::GlobalWithoutConfig for -// TestGlobal { async fn init() -> anyhow::Result> { -// Ok(Arc::new(Self)) -// } -// } - -// main! { -// TestGlobal { -// scuffle_signal::SignalSvc, -// } -// } -// }); -// } - -// #[test] -// fn main_test_custom_service() { -// insta::assert_snapshot!(postcompile::compile! { -// use std::sync::Arc; - -// use scuffle_bootstrap::main; - -// struct TestGlobal; - -// impl scuffle_signal::SignalConfig for TestGlobal {} - -// impl scuffle_bootstrap::global::GlobalWithoutConfig for -// TestGlobal { async fn init() -> anyhow::Result> { -// Ok(Arc::new(Self)) -// } -// } - -// struct MySvc; - -// impl scuffle_bootstrap::service::Service for MySvc { -// async fn run(self, _: Arc, _: -// scuffle_context::Context) -> anyhow::Result<()> { -// println!("running"); Ok(()) -// } -// } - -// main! { -// TestGlobal { -// scuffle_signal::SignalSvc, -// MySvc, -// } -// } -// }); -// } -// } +/// This macro is used to generate the main function for a given global type +/// and service types. It will run all the services in parallel and wait for +/// them to finish before exiting. +/// +/// # Example +/// +/// ```rust +/// # use std::sync::Arc; +/// # struct MyGlobal; +/// # struct MyService; +/// # impl scuffle_bootstrap::global::GlobalWithoutConfig for MyGlobal { +/// # async fn init() -> anyhow::Result> { +/// # Ok(Arc::new(Self)) +/// # } +/// # } +/// # impl scuffle_bootstrap::service::Service for MyService { +/// # async fn run(self, global: Arc, ctx: scuffle_context::Context) -> anyhow::Result<()> { +/// # println!("running"); +/// # ctx.done().await; +/// # Ok(()) +/// # } +/// # } +/// # impl scuffle_signal::SignalConfig for MyGlobal { +/// # } +/// scuffle_bootstrap::main! { +/// MyGlobal { +/// scuffle_signal::SignalSvc, +/// MyService, +/// } +/// } +/// ``` +/// +/// # See Also +/// +/// - [`Service`](crate::Service) +/// - [`Global`](crate::Global) +// We wrap the macro here so that the doc tests can be run & that the docs resolve for `Service` & `Global` +#[macro_export] +macro_rules! main { + ($($body:tt)*) => { + $crate::prelude::scuffle_bootstrap_derive::main! { $($body)* } + }; +} diff --git a/crates/bootstrap/src/service.rs b/crates/bootstrap/src/service.rs index bfb9b8d9a..b038df119 100644 --- a/crates/bootstrap/src/service.rs +++ b/crates/bootstrap/src/service.rs @@ -2,6 +2,10 @@ use std::pin::Pin; use std::sync::Arc; use std::task::{ready, Context, Poll}; +#[cfg(any(test, doctest))] +#[doc(hidden)] +pub use scuffle_signal::SignalSvc; + /// A service that can be run. /// /// This trait is used to define a service that can be run in parallel to other From 11c8fbdf921784a4580e24d55ee21296a327b9db Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Fri, 10 Jan 2025 23:40:02 +0100 Subject: [PATCH 16/17] docs(bootstrap): more docs --- crates/bootstrap/src/config.rs | 5 +++++ crates/bootstrap/src/global.rs | 14 ++++++++++++++ crates/bootstrap/src/lib.rs | 2 +- crates/bootstrap/src/service.rs | 1 + 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/bootstrap/src/config.rs b/crates/bootstrap/src/config.rs index b368dbfea..38ed4d288 100644 --- a/crates/bootstrap/src/config.rs +++ b/crates/bootstrap/src/config.rs @@ -1,3 +1,8 @@ +/// This trait is used to parse a configuration for the application. +/// +/// # See Also +/// +/// - [`Global`](crate::Global) pub trait ConfigParser: Sized { fn parse() -> impl std::future::Future>; } diff --git a/crates/bootstrap/src/global.rs b/crates/bootstrap/src/global.rs index 3253f238f..37dc572e2 100644 --- a/crates/bootstrap/src/global.rs +++ b/crates/bootstrap/src/global.rs @@ -1,3 +1,13 @@ +//! Global state for the application. +//! +//! # [`Global`] vs. [`GlobalWithoutConfig`] +//! +//! [`Global`] has a set of functions that are called at different stages of the +//! application lifecycle. To use [`Global`], your application is expected to +//! have a config type implementing [`ConfigParser`]. If your application does +//! not have a config, consider using the [`GlobalWithoutConfig`] trait which is +//! a simplified version of [`Global`]. + use std::sync::Arc; use crate::config::{ConfigParser, EmptyConfig}; @@ -74,6 +84,10 @@ fn default_runtime_builder() -> tokio::runtime::Builder { /// When using the [`main!`](crate::main) macro, one instance of this type will /// be made available to all services. /// +/// Using this trait requires a config type implementing [`ConfigParser`]. +/// If your application does not have a config, consider using the +/// [`GlobalWithoutConfig`] trait. +/// /// # See Also /// /// - [`Service`](crate::Service) diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index 6d30fbb7f..ebc23099d 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -60,7 +60,7 @@ pub mod global; pub mod service; pub use config::{ConfigParser, EmptyConfig}; -pub use global::Global; +pub use global::{Global, GlobalWithoutConfig}; pub use service::Service; #[doc(hidden)] diff --git a/crates/bootstrap/src/service.rs b/crates/bootstrap/src/service.rs index b038df119..f4a89a420 100644 --- a/crates/bootstrap/src/service.rs +++ b/crates/bootstrap/src/service.rs @@ -14,6 +14,7 @@ pub use scuffle_signal::SignalSvc; /// # See Also /// /// - [`Global`](crate::Global) +/// - [`GlobalWithoutConfig`](crate::GlobalWithoutConfig) /// - [`main`](crate::main) pub trait Service: Send + Sync + 'static + Sized { /// Returns the name of the service, if any. From 44b6b4a247338aa692805796c4be903bd874400f Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Sun, 12 Jan 2025 17:43:50 +0100 Subject: [PATCH 17/17] docs(bootstrap): improve docs --- Justfile | 3 ++ crates/bootstrap/src/config.rs | 5 +++ crates/bootstrap/src/global.rs | 57 +++++++++++++++++++++++++++++++++ crates/bootstrap/src/lib.rs | 4 ++- crates/bootstrap/src/service.rs | 11 ++++--- 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/Justfile b/Justfile index e146ffadb..ccfab6508 100644 --- a/Justfile +++ b/Justfile @@ -29,6 +29,9 @@ test *args: cargo +{{RUST_TOOLCHAIN}} llvm-cov report --lcov --output-path ./lcov.info cargo +{{RUST_TOOLCHAIN}} llvm-cov report --html +doc *args: + cargo +{{RUST_TOOLCHAIN}} doc --no-deps --all-features {{args}} + deny *args: cargo +{{RUST_TOOLCHAIN}} deny {{args}} --all-features check diff --git a/crates/bootstrap/src/config.rs b/crates/bootstrap/src/config.rs index 38ed4d288..b8edd57ed 100644 --- a/crates/bootstrap/src/config.rs +++ b/crates/bootstrap/src/config.rs @@ -1,8 +1,13 @@ /// This trait is used to parse a configuration for the application. /// +/// The avoid having to manually implement this trait, the `bootstrap!` macro in +/// the [`scuffle_settings`](../../scuffle_settings) crate can be used to +/// generate an implementation. +/// /// # See Also /// /// - [`Global`](crate::Global) +/// - [`scuffle_settings`](../../scuffle_settings) pub trait ConfigParser: Sized { fn parse() -> impl std::future::Future>; } diff --git a/crates/bootstrap/src/global.rs b/crates/bootstrap/src/global.rs index 37dc572e2..5d4118511 100644 --- a/crates/bootstrap/src/global.rs +++ b/crates/bootstrap/src/global.rs @@ -90,6 +90,7 @@ fn default_runtime_builder() -> tokio::runtime::Builder { /// /// # See Also /// +/// - [`GlobalWithoutConfig`] /// - [`Service`](crate::Service) /// - [`main`](crate::main) pub trait Global: Send + Sync + 'static { @@ -106,6 +107,34 @@ pub trait Global: Send + Sync + 'static { } /// Builds the tokio runtime for the process. + /// + /// If not overridden, a default runtime builder is used to build the + /// runtime. It uses the following environment variables: + /// - `TOKIO_WORKER_THREADS`: Number of worker threads to use. If 1, a + /// current thread runtime is used. + /// + /// See [`tokio::runtime::Builder::worker_threads`] for details. + /// - `TOKIO_MAX_BLOCKING_THREADS`: Maximum number of blocking threads. + /// + /// See [`tokio::runtime::Builder::max_blocking_threads`] for details. + /// - `TOKIO_DISABLE_TIME`: If `true` disables time. + /// + /// See [`tokio::runtime::Builder::enable_time`] for details. + /// - `TOKIO_DISABLE_IO`: If `true` disables IO. + /// + /// See [`tokio::runtime::Builder::enable_io`] for details. + /// - `TOKIO_THREAD_STACK_SIZE`: Thread stack size. + /// + /// See [`tokio::runtime::Builder::thread_stack_size`] for details. + /// - `TOKIO_GLOBAL_QUEUE_INTERVAL`: Global queue interval. + /// + /// See [`tokio::runtime::Builder::global_queue_interval`] for details. + /// - `TOKIO_EVENT_INTERVAL`: Event interval. + /// + /// See [`tokio::runtime::Builder::event_interval`] for details. + /// - `TOKIO_MAX_IO_EVENTS_PER_TICK`: Maximum IO events per tick. + /// + /// See [`tokio::runtime::Builder::max_io_events_per_tick`] for details. #[inline(always)] fn tokio_runtime() -> tokio::runtime::Runtime { default_runtime_builder().build().expect("runtime build") @@ -167,6 +196,34 @@ pub trait Global: Send + Sync + 'static { /// Refer to [`Global`] for details. pub trait GlobalWithoutConfig: Send + Sync + 'static { /// Builds the tokio runtime for the process. + /// + /// If not overridden, a default runtime builder is used to build the + /// runtime. It uses the following environment variables: + /// - `TOKIO_WORKER_THREADS`: Number of worker threads to use. If 1, a + /// current thread runtime is used. + /// + /// See [`tokio::runtime::Builder::worker_threads`] for details. + /// - `TOKIO_MAX_BLOCKING_THREADS`: Maximum number of blocking threads. + /// + /// See [`tokio::runtime::Builder::max_blocking_threads`] for details. + /// - `TOKIO_DISABLE_TIME`: If `true` disables time. + /// + /// See [`tokio::runtime::Builder::enable_time`] for details. + /// - `TOKIO_DISABLE_IO`: If `true` disables IO. + /// + /// See [`tokio::runtime::Builder::enable_io`] for details. + /// - `TOKIO_THREAD_STACK_SIZE`: Thread stack size. + /// + /// See [`tokio::runtime::Builder::thread_stack_size`] for details. + /// - `TOKIO_GLOBAL_QUEUE_INTERVAL`: Global queue interval. + /// + /// See [`tokio::runtime::Builder::global_queue_interval`] for details. + /// - `TOKIO_EVENT_INTERVAL`: Event interval. + /// + /// See [`tokio::runtime::Builder::event_interval`] for details. + /// - `TOKIO_MAX_IO_EVENTS_PER_TICK`: Maximum IO events per tick. + /// + /// See [`tokio::runtime::Builder::max_io_events_per_tick`] for details. #[inline(always)] fn tokio_runtime() -> tokio::runtime::Runtime { default_runtime_builder().build().expect("runtime build") diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index ebc23099d..30f20a6cf 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -59,7 +59,9 @@ pub mod config; pub mod global; pub mod service; -pub use config::{ConfigParser, EmptyConfig}; +pub use config::ConfigParser; +#[doc(hidden)] +pub use config::EmptyConfig; pub use global::{Global, GlobalWithoutConfig}; pub use service::Service; diff --git a/crates/bootstrap/src/service.rs b/crates/bootstrap/src/service.rs index f4a89a420..30d18cf79 100644 --- a/crates/bootstrap/src/service.rs +++ b/crates/bootstrap/src/service.rs @@ -35,14 +35,17 @@ pub trait Service: Send + Sync + 'static + Sized { /// the future should resolve to `Ok(())`. As a best practice, the /// service should stop as soon as the provided context is done. /// - /// Note: Adding the `scuffle_signal::SignalSvc` service to the list of - /// services when calling [`main`](crate::main) will cancel the context as - /// soon as a shutdown signal is received. + /// Note: Adding the + /// [`scuffle_signal::SignalSvc`](../../scuffle_signal/struct.SignalSvc. + /// html) service to the list of services when calling + /// [`main`](crate::main) will cancel the context as soon as a shutdown + /// signal is received. /// /// # See Also /// /// - [`Context`](scuffle_context::Context) - /// - `scuffle_signal::SignalSvc` + /// - [`scuffle_signal::SignalSvc`](../../scuffle_signal/struct.SignalSvc. + /// html) fn run( self, global: Arc,