From 2d674f9a603d07b68b306bd4b0bc9402b513c858 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Mon, 6 Jan 2025 14:59:31 +0100 Subject: [PATCH 1/8] fix(signal): more tests and docs --- Cargo.lock | 1 + crates/signal/Cargo.toml | 4 ++ crates/signal/README.md | 28 ++++++++ crates/signal/src/lib.rs | 142 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 169 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d224b4d45..38deceb97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,6 +2761,7 @@ dependencies = [ "scuffle-future-ext", "scuffle-workspace-hack", "tokio", + "tokio-test", ] [[package]] diff --git a/crates/signal/Cargo.toml b/crates/signal/Cargo.toml index 77548e433..03e3a1eef 100644 --- a/crates/signal/Cargo.toml +++ b/crates/signal/Cargo.toml @@ -10,6 +10,9 @@ license = "MIT OR Apache-2.0" description = "Ergonomic async signal handling." keywords = ["signal", "async"] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } + [dependencies] tokio = { version = "1", default-features = false, features = ["signal"] } scuffle-bootstrap = { workspace = true, optional = true } @@ -19,6 +22,7 @@ scuffle-workspace-hack.workspace = true [dev-dependencies] 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" } diff --git a/crates/signal/README.md b/crates/signal/README.md index 1d7fc4f70..737017185 100644 --- a/crates/signal/README.md +++ b/crates/signal/README.md @@ -14,6 +14,34 @@ A crate designed to provide a more user friendly interface to `tokio::signal`. The `tokio::signal` module provides a way for us to wait for a signal to be received in a non-blocking way. This crate extends that with a more helpful interface allowing the ability to listen to multiple signals concurrently. +## Example + +```rust +use scuffle_signal::SignalHandler; +use tokio::signal::unix::SignalKind; + +let mut handler = SignalHandler::new() + .with_signal(SignalKind::user_defined1()) + .with_signal(SignalKind::user_defined2()); + +// Wait for a signal to be received +let signal = handler.await; + +// Handle the signal +let user_defined1 = SignalKind::user_defined1(); +let user_defined2 = SignalKind::user_defined2(); +match signal { + user_defined1 => { + // Handle SIGUSR1 + println!("received SIGUSR1"); + }, + user_defined2 => { + // Handle SIGUSR2 + println!("received SIGUSR1"); + }, +} +``` + ## Status This crate is currently under development and is not yet stable. diff --git a/crates/signal/src/lib.rs b/crates/signal/src/lib.rs index b521f873e..a6eba963a 100644 --- a/crates/signal/src/lib.rs +++ b/crates/signal/src/lib.rs @@ -1,4 +1,69 @@ -#![doc = include_str!("../README.md")] +//! # 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`. +//! +//! ## Why do we need this? +//! +//! The `tokio::signal` module provides a way for us to wait for a signal to be +//! received in a non-blocking way. This crate extends that with a more helpful +//! interface allowing the ability to listen to multiple signals concurrently. +//! +//! ## Example +//! +//! ```rust +//! use scuffle_signal::SignalHandler; +//! use tokio::signal::unix::SignalKind; +//! +//! # tokio_test::block_on(async { +//! let mut handler = SignalHandler::new() +//! .with_signal(SignalKind::user_defined1()) +//! .with_signal(SignalKind::user_defined2()); +//! +//! # // Safety: This is a test, and we control the process. +//! # unsafe { +//! # libc::raise(SignalKind::user_defined1().as_raw_value()); +//! # } +//! // Wait for a signal to be received +//! let signal = handler.await; +//! +//! // Handle the signal +//! let user_defined1 = SignalKind::user_defined1(); +//! let user_defined2 = SignalKind::user_defined2(); +//! match signal { +//! user_defined1 => { +//! // Handle SIGUSR1 +//! println!("received SIGUSR1"); +//! }, +//! user_defined2 => { +//! // Handle SIGUSR2 +//! println!("received SIGUSR2"); +//! }, +//! } +//! # }); +//! ``` +//! +//! ## Status +//! +//! This crate is currently under development and is not yet stable. +//! +//! Unit tests are not yet fully implemented. Use at your own risk. +//! +//! ## 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))] use std::pin::Pin; use std::task::{Context, Poll}; @@ -21,6 +86,40 @@ pub use bootstrap::{SignalConfig, SignalSvc}; /// /// After a signal is received you can poll the handler again to wait for /// another signal. Dropping the handle will cancel the signal subscription +/// +/// # Example +/// +/// ```rust +/// use scuffle_signal::SignalHandler; +/// use tokio::signal::unix::SignalKind; +/// +/// # tokio_test::block_on(async { +/// let mut handler = SignalHandler::new() +/// .with_signal(SignalKind::user_defined1()) +/// .with_signal(SignalKind::user_defined2()); +/// +/// # // Safety: This is a test, and we control the process. +/// # unsafe { +/// # libc::raise(SignalKind::user_defined1().as_raw_value()); +/// # } +/// // Wait for a signal to be received +/// let signal = handler.await; +/// +/// // Handle the signal +/// let user_defined1 = SignalKind::user_defined1(); +/// let user_defined2 = SignalKind::user_defined2(); +/// match signal { +/// user_defined1 => { +/// // Handle SIGUSR1 +/// println!("received SIGUSR1"); +/// }, +/// user_defined2 => { +/// // Handle SIGUSR2 +/// println!("received SIGUSR2"); +/// }, +/// } +/// # }); +/// ``` #[derive(Debug)] #[must_use = "signal handlers must be used to wait for signals"] pub struct SignalHandler { @@ -88,7 +187,7 @@ impl SignalHandler { } /// Poll for a signal to be received. - /// Does not require Pinning the handler. + /// Does not require pinning the handler. pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll { for (kind, signal) in self.signals.iter_mut() { if signal.poll_recv(cx).is_ready() { @@ -108,6 +207,7 @@ impl std::future::Future for SignalHandler { } } +#[cfg_attr(all(coverage_nightly, test), coverage(off))] #[cfg(test)] mod tests { use std::time::Duration; @@ -124,10 +224,10 @@ mod tests { } #[tokio::test] - async fn test_signal_handler() { - let mut handler = SignalHandler::new() - .with_signal(SignalKind::user_defined1()) - .with_signal(SignalKind::user_defined2()); + async fn signal_handler() { + let mut handler = SignalHandler::with_signals([SignalKind::user_defined1()]) + .with_signal(SignalKind::user_defined2()) + .with_signal(SignalKind::user_defined1()); raise_signal(SignalKind::user_defined1()); @@ -147,4 +247,34 @@ mod tests { assert_eq!(recv, SignalKind::user_defined2(), "expected SIGUSR2"); } + + #[tokio::test] + async fn add_signal() { + let mut handler = SignalHandler::new(); + + handler + .add_signal(SignalKind::user_defined1()) + .add_signal(SignalKind::user_defined2()) + .add_signal(SignalKind::user_defined2()); + + raise_signal(SignalKind::user_defined1()); + + let recv = handler.recv().with_timeout(Duration::from_millis(5)).await.unwrap(); + + assert_eq!(recv, SignalKind::user_defined1(), "expected SIGUSR1"); + + raise_signal(SignalKind::user_defined2()); + + let recv = handler.recv().with_timeout(Duration::from_millis(5)).await.unwrap(); + + assert_eq!(recv, SignalKind::user_defined2(), "expected SIGUSR2"); + } + + #[tokio::test] + async fn no_signals() { + let mut handler = SignalHandler::default(); + + // Expected to timeout + assert!(handler.recv().with_timeout(Duration::from_millis(50)).await.is_err()); + } } From 0c6ba225e75040f75a48b9f4544cc4e96e29eb91 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 01:26:34 +0100 Subject: [PATCH 2/8] test(signal): improve coverage --- crates/signal/src/bootstrap.rs | 173 +++++++++++++++++++++++++++++++++ crates/signal/src/lib.rs | 46 ++++----- 2 files changed, 196 insertions(+), 23 deletions(-) diff --git a/crates/signal/src/bootstrap.rs b/crates/signal/src/bootstrap.rs index cd8b79eef..946f19966 100644 --- a/crates/signal/src/bootstrap.rs +++ b/crates/signal/src/bootstrap.rs @@ -73,3 +73,176 @@ impl Service for SignalSvc { Ok(()) } } + +#[cfg_attr(all(coverage_nightly, test), coverage(off))] +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use scuffle_bootstrap::{global::GlobalWithoutConfig, Service}; + use scuffle_future_ext::FutureExt; + use tokio::signal::unix::SignalKind; + + use super::{SignalConfig, SignalSvc}; + use crate::{tests::raise_signal, SignalHandler}; + + struct TestGlobal; + + impl GlobalWithoutConfig for TestGlobal { + fn init() -> impl std::future::Future>> + Send { + std::future::ready(Ok(Arc::new(TestGlobal))) + } + } + + impl SignalConfig for TestGlobal {} + + #[tokio::test] + async fn bootstrap_service() { + let (ctx, handler) = scuffle_context::Context::new(); + let svc = SignalSvc; + let global = TestGlobal::init().await.unwrap(); + + assert!(svc.enabled(&global).await.unwrap()); + let result = tokio::spawn(svc.run(global, ctx)); + + // Wait for the service to start + tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; + + raise_signal(tokio::signal::unix::SignalKind::interrupt()); + assert!(result.await.is_ok()); + + assert!(handler + .shutdown() + .with_timeout(tokio::time::Duration::from_millis(100)) + .await + .is_ok()); + } + + #[tokio::test] + async fn bootstrap_service_force_shutdown() { + let (ctx, handler) = scuffle_context::Context::new(); + + // Block the global context + let _global_ctx = scuffle_context::Context::global(); + + let svc = SignalSvc; + let global = TestGlobal::init().await.unwrap(); + + assert!(svc.enabled(&global).await.unwrap()); + let result = tokio::spawn(svc.run(global, ctx)); + + // Wait for the service to start + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + + raise_signal(tokio::signal::unix::SignalKind::interrupt()); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + raise_signal(tokio::signal::unix::SignalKind::interrupt()); + + match result.with_timeout(tokio::time::Duration::from_millis(100)).await { + Ok(Ok(Err(e))) => { + assert_eq!(e.to_string(), "received signal, shutting down immediately: SignalKind(2)"); + } + _ => panic!("unexpected result"), + } + + assert!(handler + .shutdown() + .with_timeout(tokio::time::Duration::from_millis(100)) + .await + .is_ok()); + } + + struct NoSignalsTestGlobal; + + impl GlobalWithoutConfig for NoSignalsTestGlobal { + fn init() -> impl std::future::Future>> + Send { + std::future::ready(Ok(Arc::new(NoSignalsTestGlobal))) + } + } + + impl SignalConfig for NoSignalsTestGlobal { + fn signals(&self) -> Vec { + vec![] + } + + fn timeout(&self) -> Option { + None + } + } + + #[tokio::test] + async fn bootstrap_service_no_signals() { + let (ctx, handler) = scuffle_context::Context::new(); + let svc = SignalSvc; + let global = NoSignalsTestGlobal::init().await.unwrap(); + + assert!(!svc.enabled(&global).await.unwrap()); + let result = tokio::spawn(svc.run(global, ctx)); + + // Wait for the service to start + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + + // Make a new handler to catch the raised signal as it is expected to not be caught by the service + let mut signal_handler = SignalHandler::new().with_signal(SignalKind::terminate()); + + raise_signal(tokio::signal::unix::SignalKind::terminate()); + + // Wait for a signal to be received + assert_eq!(signal_handler.recv().await, SignalKind::terminate()); + + // Expected to timeout + assert!(result.with_timeout(tokio::time::Duration::from_millis(100)).await.is_err()); + + assert!(handler + .shutdown() + .with_timeout(tokio::time::Duration::from_millis(100)) + .await + .is_ok()); + } + + struct NoTimeoutTestGlobal; + + impl GlobalWithoutConfig for NoTimeoutTestGlobal { + fn init() -> impl std::future::Future>> + Send { + std::future::ready(Ok(Arc::new(NoTimeoutTestGlobal))) + } + } + + impl SignalConfig for NoTimeoutTestGlobal { + fn timeout(&self) -> Option { + Some(std::time::Duration::from_millis(5)) + } + } + + #[tokio::test] + async fn bootstrap_service_timeout_force_shutdown() { + let (ctx, handler) = scuffle_context::Context::new(); + + // Block the global context + let _global_ctx = scuffle_context::Context::global(); + + let svc = SignalSvc; + let global = NoTimeoutTestGlobal::init().await.unwrap(); + + assert!(svc.enabled(&global).await.unwrap()); + let result = tokio::spawn(svc.run(global, ctx)); + + // Wait for the service to start + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + + raise_signal(tokio::signal::unix::SignalKind::terminate()); + + match result.with_timeout(tokio::time::Duration::from_millis(100)).await { + Ok(Ok(Err(e))) => { + assert_eq!(e.to_string(), "timeout reached, shutting down immediately"); + } + _ => panic!("unexpected result"), + } + + assert!(handler + .shutdown() + .with_timeout(tokio::time::Duration::from_millis(100)) + .await + .is_ok()); + } +} diff --git a/crates/signal/src/lib.rs b/crates/signal/src/lib.rs index a6eba963a..164aecac3 100644 --- a/crates/signal/src/lib.rs +++ b/crates/signal/src/lib.rs @@ -24,27 +24,27 @@ //! //! # tokio_test::block_on(async { //! let mut handler = SignalHandler::new() -//! .with_signal(SignalKind::user_defined1()) -//! .with_signal(SignalKind::user_defined2()); +//! .with_signal(SignalKind::interrupt()) +//! .with_signal(SignalKind::terminate()); //! //! # // Safety: This is a test, and we control the process. //! # unsafe { -//! # libc::raise(SignalKind::user_defined1().as_raw_value()); +//! # libc::raise(SignalKind::interrupt().as_raw_value()); //! # } //! // Wait for a signal to be received //! let signal = handler.await; //! //! // Handle the signal -//! let user_defined1 = SignalKind::user_defined1(); -//! let user_defined2 = SignalKind::user_defined2(); +//! let interrupt = SignalKind::interrupt(); +//! let terminate = SignalKind::terminate(); //! match signal { -//! user_defined1 => { -//! // Handle SIGUSR1 -//! println!("received SIGUSR1"); +//! interrupt => { +//! // Handle SIGINT +//! println!("received SIGINT"); //! }, -//! user_defined2 => { -//! // Handle SIGUSR2 -//! println!("received SIGUSR2"); +//! terminate => { +//! // Handle SIGTERM +//! println!("received SIGTERM"); //! }, //! } //! # }); @@ -95,27 +95,27 @@ pub use bootstrap::{SignalConfig, SignalSvc}; /// /// # tokio_test::block_on(async { /// let mut handler = SignalHandler::new() -/// .with_signal(SignalKind::user_defined1()) -/// .with_signal(SignalKind::user_defined2()); +/// .with_signal(SignalKind::interrupt()) +/// .with_signal(SignalKind::terminate()); /// /// # // Safety: This is a test, and we control the process. /// # unsafe { -/// # libc::raise(SignalKind::user_defined1().as_raw_value()); +/// # libc::raise(SignalKind::interrupt().as_raw_value()); /// # } /// // Wait for a signal to be received /// let signal = handler.await; /// /// // Handle the signal -/// let user_defined1 = SignalKind::user_defined1(); -/// let user_defined2 = SignalKind::user_defined2(); +/// let interrupt = SignalKind::interrupt(); +/// let terminate = SignalKind::terminate(); /// match signal { -/// user_defined1 => { -/// // Handle SIGUSR1 -/// println!("received SIGUSR1"); +/// interrupt => { +/// // Handle SIGINT +/// println!("received SIGINT"); /// }, -/// user_defined2 => { -/// // Handle SIGUSR2 -/// println!("received SIGUSR2"); +/// terminate => { +/// // Handle SIGTERM +/// println!("received SIGTERM"); /// }, /// } /// # }); @@ -216,7 +216,7 @@ mod tests { use super::*; - fn raise_signal(kind: SignalKind) { + pub fn raise_signal(kind: SignalKind) { // Safety: This is a test, and we control the process. unsafe { libc::raise(kind.as_raw_value()); From cda3bf45a419ae8468de233bf5fb9ba791f52e95 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 01:27:44 +0100 Subject: [PATCH 3/8] chore(signal): fmt --- crates/signal/src/bootstrap.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/signal/src/bootstrap.rs b/crates/signal/src/bootstrap.rs index 946f19966..e564927f1 100644 --- a/crates/signal/src/bootstrap.rs +++ b/crates/signal/src/bootstrap.rs @@ -79,12 +79,14 @@ impl Service for SignalSvc { mod tests { use std::sync::Arc; - use scuffle_bootstrap::{global::GlobalWithoutConfig, Service}; + use scuffle_bootstrap::global::GlobalWithoutConfig; + use scuffle_bootstrap::Service; use scuffle_future_ext::FutureExt; use tokio::signal::unix::SignalKind; use super::{SignalConfig, SignalSvc}; - use crate::{tests::raise_signal, SignalHandler}; + use crate::tests::raise_signal; + use crate::SignalHandler; struct TestGlobal; @@ -182,7 +184,8 @@ mod tests { // Wait for the service to start tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - // Make a new handler to catch the raised signal as it is expected to not be caught by the service + // Make a new handler to catch the raised signal as it is expected to not be + // caught by the service let mut signal_handler = SignalHandler::new().with_signal(SignalKind::terminate()); raise_signal(tokio::signal::unix::SignalKind::terminate()); From 00b3b4131e3ea857e4f57a88f8381912b8bd70a0 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 01:44:36 +0100 Subject: [PATCH 4/8] fix(signal): readme --- crates/signal/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/signal/README.md b/crates/signal/README.md index 737017185..cf40d52ff 100644 --- a/crates/signal/README.md +++ b/crates/signal/README.md @@ -21,23 +21,23 @@ use scuffle_signal::SignalHandler; use tokio::signal::unix::SignalKind; let mut handler = SignalHandler::new() - .with_signal(SignalKind::user_defined1()) - .with_signal(SignalKind::user_defined2()); + .with_signal(SignalKind::interrupt()) + .with_signal(SignalKind::terminate()); // Wait for a signal to be received let signal = handler.await; // Handle the signal -let user_defined1 = SignalKind::user_defined1(); -let user_defined2 = SignalKind::user_defined2(); +let user_defined1 = SignalKind::interrupt(); +let terminate = SignalKind::terminate(); match signal { - user_defined1 => { - // Handle SIGUSR1 - println!("received SIGUSR1"); + interrupt => { + // Handle SIGINT + println!("received SIGINT"); }, - user_defined2 => { - // Handle SIGUSR2 - println!("received SIGUSR1"); + terminate => { + // Handle SIGTERM + println!("received SIGTERM"); }, } ``` From 3a9dab943135b2918b06cc1e34dc10b64bba9386 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 01:54:19 +0100 Subject: [PATCH 5/8] test(signal): last line of coverage --- crates/signal/src/bootstrap.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/signal/src/bootstrap.rs b/crates/signal/src/bootstrap.rs index e564927f1..bcc47e69d 100644 --- a/crates/signal/src/bootstrap.rs +++ b/crates/signal/src/bootstrap.rs @@ -88,21 +88,25 @@ mod tests { use crate::tests::raise_signal; use crate::SignalHandler; - struct TestGlobal; + struct NoTimeoutTestGlobal; - impl GlobalWithoutConfig for TestGlobal { + impl GlobalWithoutConfig for NoTimeoutTestGlobal { fn init() -> impl std::future::Future>> + Send { - std::future::ready(Ok(Arc::new(TestGlobal))) + std::future::ready(Ok(Arc::new(NoTimeoutTestGlobal))) } } - impl SignalConfig for TestGlobal {} + impl SignalConfig for NoTimeoutTestGlobal { + fn timeout(&self) -> Option { + None + } + } #[tokio::test] - async fn bootstrap_service() { + async fn bootstrap_service_no_timeout() { let (ctx, handler) = scuffle_context::Context::new(); let svc = SignalSvc; - let global = TestGlobal::init().await.unwrap(); + let global = NoTimeoutTestGlobal::init().await.unwrap(); assert!(svc.enabled(&global).await.unwrap()); let result = tokio::spawn(svc.run(global, ctx)); @@ -128,7 +132,7 @@ mod tests { let _global_ctx = scuffle_context::Context::global(); let svc = SignalSvc; - let global = TestGlobal::init().await.unwrap(); + let global = NoTimeoutTestGlobal::init().await.unwrap(); assert!(svc.enabled(&global).await.unwrap()); let result = tokio::spawn(svc.run(global, ctx)); @@ -203,15 +207,15 @@ mod tests { .is_ok()); } - struct NoTimeoutTestGlobal; + struct SmallTimeoutTestGlobal; - impl GlobalWithoutConfig for NoTimeoutTestGlobal { + impl GlobalWithoutConfig for SmallTimeoutTestGlobal { fn init() -> impl std::future::Future>> + Send { - std::future::ready(Ok(Arc::new(NoTimeoutTestGlobal))) + std::future::ready(Ok(Arc::new(SmallTimeoutTestGlobal))) } } - impl SignalConfig for NoTimeoutTestGlobal { + impl SignalConfig for SmallTimeoutTestGlobal { fn timeout(&self) -> Option { Some(std::time::Duration::from_millis(5)) } @@ -225,7 +229,7 @@ mod tests { let _global_ctx = scuffle_context::Context::global(); let svc = SignalSvc; - let global = NoTimeoutTestGlobal::init().await.unwrap(); + let global = SmallTimeoutTestGlobal::init().await.unwrap(); assert!(svc.enabled(&global).await.unwrap()); let result = tokio::spawn(svc.run(global, ctx)); From e5a88e072a04c4640e932ea28b95985f89623979 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 02:08:22 +0100 Subject: [PATCH 6/8] test(signal): last line of coverage but really now --- crates/signal/src/bootstrap.rs | 37 +++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/crates/signal/src/bootstrap.rs b/crates/signal/src/bootstrap.rs index bcc47e69d..c34bc67b7 100644 --- a/crates/signal/src/bootstrap.rs +++ b/crates/signal/src/bootstrap.rs @@ -88,11 +88,42 @@ mod tests { use crate::tests::raise_signal; use crate::SignalHandler; + struct TestGlobal; + + impl GlobalWithoutConfig for TestGlobal { + fn init() -> impl std::future::Future>> + Send { + std::future::ready(Ok(Arc::new(Self))) + } + } + + impl SignalConfig for TestGlobal {} + + #[tokio::test] + async fn default_bootstrap_service() { + let (ctx, handler) = scuffle_context::Context::new(); + let svc = SignalSvc; + let global = NoTimeoutTestGlobal::init().await.unwrap(); + + assert!(svc.enabled(&global).await.unwrap()); + let result = tokio::spawn(svc.run(global, ctx)); + + // Wait for the service to start + tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; + + raise_signal(tokio::signal::unix::SignalKind::interrupt()); + assert!(result.await.is_ok()); + + assert!(handler + .shutdown() + .with_timeout(tokio::time::Duration::from_millis(100)) + .await + .is_ok()); + } struct NoTimeoutTestGlobal; impl GlobalWithoutConfig for NoTimeoutTestGlobal { fn init() -> impl std::future::Future>> + Send { - std::future::ready(Ok(Arc::new(NoTimeoutTestGlobal))) + std::future::ready(Ok(Arc::new(Self))) } } @@ -162,7 +193,7 @@ mod tests { impl GlobalWithoutConfig for NoSignalsTestGlobal { fn init() -> impl std::future::Future>> + Send { - std::future::ready(Ok(Arc::new(NoSignalsTestGlobal))) + std::future::ready(Ok(Arc::new(Self))) } } @@ -211,7 +242,7 @@ mod tests { impl GlobalWithoutConfig for SmallTimeoutTestGlobal { fn init() -> impl std::future::Future>> + Send { - std::future::ready(Ok(Arc::new(SmallTimeoutTestGlobal))) + std::future::ready(Ok(Arc::new(Self))) } } From 13a373f867da50df9cf43051c5eade7e796aac11 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 02:08:54 +0100 Subject: [PATCH 7/8] chore(context): lint --- crates/context/src/lib.rs | 64 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/crates/context/src/lib.rs b/crates/context/src/lib.rs index 2188d0ea9..5ced1ae4b 100644 --- a/crates/context/src/lib.rs +++ b/crates/context/src/lib.rs @@ -309,11 +309,11 @@ mod tests { #[tokio::test] async fn new() { let (ctx, handler) = Context::new(); - assert_eq!(handler.is_done(), false); - assert_eq!(ctx.is_done(), false); + assert!(!handler.is_done()); + assert!(!ctx.is_done()); let handler = Handler::default(); - assert_eq!(handler.is_done(), false); + assert!(!handler.is_done()); } #[tokio::test] @@ -322,19 +322,19 @@ mod tests { let (child_ctx, child_handler) = ctx.new_child(); let child_ctx2 = ctx.clone(); - assert_eq!(handler.is_done(), false); - assert_eq!(ctx.is_done(), false); - assert_eq!(child_handler.is_done(), false); - assert_eq!(child_ctx.is_done(), false); - assert_eq!(child_ctx2.is_done(), false); + assert!(!handler.is_done()); + assert!(!ctx.is_done()); + assert!(!child_handler.is_done()); + assert!(!child_ctx.is_done()); + assert!(!child_ctx2.is_done()); handler.cancel(); - assert_eq!(handler.is_done(), true); - assert_eq!(ctx.is_done(), true); - assert_eq!(child_handler.is_done(), true); - assert_eq!(child_ctx.is_done(), true); - assert_eq!(child_ctx2.is_done(), true); + assert!(handler.is_done()); + assert!(ctx.is_done()); + assert!(child_handler.is_done()); + assert!(child_ctx.is_done()); + assert!(child_ctx2.is_done()); } #[tokio::test] @@ -342,25 +342,25 @@ mod tests { let (ctx, handler) = Context::new(); let (child_ctx, child_handler) = ctx.new_child(); - assert_eq!(handler.is_done(), false); - assert_eq!(ctx.is_done(), false); - assert_eq!(child_handler.is_done(), false); - assert_eq!(child_ctx.is_done(), false); + assert!(!handler.is_done()); + assert!(!ctx.is_done()); + assert!(!child_handler.is_done()); + assert!(!child_ctx.is_done()); child_handler.cancel(); - assert_eq!(handler.is_done(), false); - assert_eq!(ctx.is_done(), false); - assert_eq!(child_handler.is_done(), true); - assert_eq!(child_ctx.is_done(), true); + assert!(!handler.is_done()); + assert!(!ctx.is_done()); + assert!(child_handler.is_done()); + assert!(child_ctx.is_done()); } #[tokio::test] async fn shutdown() { let (ctx, handler) = Context::new(); - assert_eq!(handler.is_done(), false); - assert_eq!(ctx.is_done(), false); + assert!(!handler.is_done()); + assert!(!ctx.is_done()); // This is expected to timeout assert!(handler @@ -368,8 +368,8 @@ mod tests { .with_timeout(std::time::Duration::from_millis(200)) .await .is_err()); - assert_eq!(handler.is_done(), true); - assert_eq!(ctx.is_done(), true); + assert!(handler.is_done()); + assert!(ctx.is_done()); assert!(ctx .into_done() .with_timeout(std::time::Duration::from_millis(200)) @@ -391,23 +391,23 @@ mod tests { .with_timeout(std::time::Duration::from_millis(200)) .await .is_ok()); - assert_eq!(handler.is_done(), true); + assert!(handler.is_done()); } #[tokio::test] async fn global_handler() { let handler = Handler::global(); - assert_eq!(handler.is_done(), false); + assert!(!handler.is_done()); handler.cancel(); - assert_eq!(handler.is_done(), true); - assert_eq!(Handler::global().is_done(), true); - assert_eq!(Context::global().is_done(), true); + assert!(handler.is_done()); + assert!(Handler::global().is_done()); + assert!(Context::global().is_done()); let (child_ctx, child_handler) = Handler::global().new_child(); - assert_eq!(child_handler.is_done(), true); - assert_eq!(child_ctx.is_done(), true); + assert!(child_handler.is_done()); + assert!(child_ctx.is_done()); } } From c44f94109f7f5b590408c2c8be339a801e0a0ca1 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Tue, 7 Jan 2025 02:19:38 +0100 Subject: [PATCH 8/8] test(signal): last line of coverage but really really now I swear --- crates/signal/src/bootstrap.rs | 75 ++++++++++++++-------------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/crates/signal/src/bootstrap.rs b/crates/signal/src/bootstrap.rs index c34bc67b7..4095120bd 100644 --- a/crates/signal/src/bootstrap.rs +++ b/crates/signal/src/bootstrap.rs @@ -88,30 +88,31 @@ mod tests { use crate::tests::raise_signal; use crate::SignalHandler; - struct TestGlobal; - - impl GlobalWithoutConfig for TestGlobal { - fn init() -> impl std::future::Future>> + Send { - std::future::ready(Ok(Arc::new(Self))) - } - } + async fn force_shutdown_two_signals() { + let (ctx, handler) = scuffle_context::Context::new(); - impl SignalConfig for TestGlobal {} + // Block the global context + let _global_ctx = scuffle_context::Context::global(); - #[tokio::test] - async fn default_bootstrap_service() { - let (ctx, handler) = scuffle_context::Context::new(); let svc = SignalSvc; - let global = NoTimeoutTestGlobal::init().await.unwrap(); + let global = ::init().await.unwrap(); assert!(svc.enabled(&global).await.unwrap()); let result = tokio::spawn(svc.run(global, ctx)); // Wait for the service to start - tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; raise_signal(tokio::signal::unix::SignalKind::interrupt()); - assert!(result.await.is_ok()); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + raise_signal(tokio::signal::unix::SignalKind::interrupt()); + + match result.with_timeout(tokio::time::Duration::from_millis(100)).await { + Ok(Ok(Err(e))) => { + assert_eq!(e.to_string(), "received signal, shutting down immediately: SignalKind(2)"); + } + _ => panic!("unexpected result"), + } assert!(handler .shutdown() @@ -119,6 +120,21 @@ mod tests { .await .is_ok()); } + + struct TestGlobal; + + impl GlobalWithoutConfig for TestGlobal { + fn init() -> impl std::future::Future>> + Send { + std::future::ready(Ok(Arc::new(Self))) + } + } + + impl SignalConfig for TestGlobal {} + + #[tokio::test] + async fn default_bootstrap_service() { + force_shutdown_two_signals::().await; + } struct NoTimeoutTestGlobal; impl GlobalWithoutConfig for NoTimeoutTestGlobal { @@ -157,36 +173,7 @@ mod tests { #[tokio::test] async fn bootstrap_service_force_shutdown() { - let (ctx, handler) = scuffle_context::Context::new(); - - // Block the global context - let _global_ctx = scuffle_context::Context::global(); - - let svc = SignalSvc; - let global = NoTimeoutTestGlobal::init().await.unwrap(); - - assert!(svc.enabled(&global).await.unwrap()); - let result = tokio::spawn(svc.run(global, ctx)); - - // Wait for the service to start - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - - raise_signal(tokio::signal::unix::SignalKind::interrupt()); - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - raise_signal(tokio::signal::unix::SignalKind::interrupt()); - - match result.with_timeout(tokio::time::Duration::from_millis(100)).await { - Ok(Ok(Err(e))) => { - assert_eq!(e.to_string(), "received signal, shutting down immediately: SignalKind(2)"); - } - _ => panic!("unexpected result"), - } - - assert!(handler - .shutdown() - .with_timeout(tokio::time::Duration::from_millis(100)) - .await - .is_ok()); + force_shutdown_two_signals::().await; } struct NoSignalsTestGlobal;