From f8fd5a87df9548773179d636ed600d5190f8b0e2 Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 1 Aug 2025 11:01:37 +0100 Subject: [PATCH 1/3] Add test helpers and apply across tests --- crates/comenqd/src/daemon.rs | 28 ++++++++++-------------- tests/cucumber.rs | 1 + tests/steps/listener_steps.rs | 8 ++----- tests/steps/worker_steps.rs | 15 +++---------- tests/util/mod.rs | 5 +++++ tests/util/test_helpers.rs | 41 +++++++++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 tests/util/mod.rs create mode 100644 tests/util/test_helpers.rs diff --git a/crates/comenqd/src/daemon.rs b/crates/comenqd/src/daemon.rs index 3c23254..8791dc9 100644 --- a/crates/comenqd/src/daemon.rs +++ b/crates/comenqd/src/daemon.rs @@ -246,6 +246,13 @@ mod tests { //! Tests for the daemon tasks. use super::*; use tempfile::tempdir; + mod helpers { + include!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tests/util/test_helpers.rs" + )); + } + use helpers::{octocrab_for, temp_config}; use test_support::wait_for_file; use tokio::io::AsyncWriteExt; use tokio::net::{UnixListener, UnixStream}; @@ -257,10 +264,8 @@ mod tests { async fn setup_run_worker(status: u16) -> (MockServer, Arc, Receiver, Arc) { let dir = tempdir().expect("tempdir"); let cfg = Arc::new(Config { - github_token: "t".into(), - socket_path: dir.path().join("sock"), - queue_path: dir.path().join("q"), cooldown_period_seconds: 0, + ..temp_config(&dir) }); let (sender, rx) = channel(&cfg.queue_path).expect("channel"); let req = CommentRequest { @@ -281,14 +286,7 @@ mod tests { .mount(&server) .await; - let octo = Arc::new( - Octocrab::builder() - .personal_token("t".to_string()) - .base_uri(server.uri()) - .expect("base_uri") - .build() - .expect("build octocrab"), - ); + let octo = octocrab_for(&server); (server, cfg, rx, octo) } @@ -307,10 +305,8 @@ mod tests { async fn run_creates_queue_directory() { let dir = tempdir().expect("Failed to create temporary directory"); let cfg = Config { - github_token: "t".into(), - socket_path: dir.path().join("sock"), - queue_path: dir.path().join("q"), cooldown_period_seconds: 1, + ..temp_config(&dir) }; assert!(!cfg.queue_path.exists()); @@ -368,10 +364,8 @@ mod tests { async fn run_listener_accepts_connections() { let dir = tempdir().expect("tempdir"); let cfg = Arc::new(Config { - github_token: "t".into(), - socket_path: dir.path().join("sock"), - queue_path: dir.path().join("q"), cooldown_period_seconds: 1, + ..temp_config(&dir) }); let (sender, mut receiver) = channel(&cfg.queue_path).expect("channel"); diff --git a/tests/cucumber.rs b/tests/cucumber.rs index cabfd77..fc22ddd 100644 --- a/tests/cucumber.rs +++ b/tests/cucumber.rs @@ -1,5 +1,6 @@ mod steps; mod support; +mod util; use cucumber::World as _; use steps::{ CliWorld, ClientWorld, CommentWorld, ConfigWorld, ListenerWorld, PackagingWorld, WorkerWorld, diff --git a/tests/steps/listener_steps.rs b/tests/steps/listener_steps.rs index d15309e..bb2d195 100644 --- a/tests/steps/listener_steps.rs +++ b/tests/steps/listener_steps.rs @@ -12,6 +12,7 @@ use tokio::io::AsyncWriteExt; use tokio::net::UnixStream; use tokio::sync::{mpsc, watch}; +use crate::util::temp_config; use comenq_lib::CommentRequest; use comenqd::config::Config; use comenqd::daemon::{queue_writer, run_listener}; @@ -36,12 +37,7 @@ impl std::fmt::Debug for ListenerWorld { #[given("a running listener task")] async fn running_listener(world: &mut ListenerWorld) { let dir = TempDir::new().expect("tempdir"); - let cfg = Arc::new(Config { - github_token: String::from("t"), - socket_path: dir.path().join("sock"), - queue_path: dir.path().join("q"), - cooldown_period_seconds: 1, - }); + let cfg = Arc::new(temp_config(&dir)); let (sender, receiver) = channel(&cfg.queue_path).expect("channel"); let (client_tx, writer_rx) = mpsc::unbounded_channel(); let (shutdown_tx, shutdown_rx) = watch::channel(()); diff --git a/tests/steps/worker_steps.rs b/tests/steps/worker_steps.rs index 297e466..8b40b50 100644 --- a/tests/steps/worker_steps.rs +++ b/tests/steps/worker_steps.rs @@ -7,11 +7,11 @@ use std::sync::Arc; use std::time::Duration; +use crate::util::{octocrab_for, temp_config}; use comenq_lib::CommentRequest; use comenqd::config::Config; use comenqd::daemon::run_worker; use cucumber::{World, given, then, when}; -use octocrab::Octocrab; use tempfile::TempDir; use tokio::time::sleep; use wiremock::matchers::{method, path}; @@ -37,10 +37,8 @@ impl std::fmt::Debug for WorkerWorld { async fn queued_request(world: &mut WorkerWorld) { let dir = TempDir::new().expect("tempdir"); let cfg = Arc::new(Config { - github_token: "t".into(), - socket_path: dir.path().join("sock"), - queue_path: dir.path().join("q"), cooldown_period_seconds: 0, + ..temp_config(&dir) }); let (mut sender, receiver) = channel(&cfg.queue_path).expect("channel"); let req = CommentRequest { @@ -83,14 +81,7 @@ async fn worker_runs(world: &mut WorkerWorld) { let cfg = world.cfg.as_ref().unwrap().clone(); let rx = world.receiver.take().unwrap(); let server = world.server.as_ref().unwrap(); - let octocrab = Arc::new( - Octocrab::builder() - .personal_token("t".to_string()) - .base_uri(server.uri()) - .expect("base_uri") - .build() - .expect("build octocrab"), - ); + let octocrab = octocrab_for(server); let handle = tokio::spawn(async move { let _ = run_worker(cfg, rx, octocrab).await; }); diff --git a/tests/util/mod.rs b/tests/util/mod.rs new file mode 100644 index 0000000..9f06273 --- /dev/null +++ b/tests/util/mod.rs @@ -0,0 +1,5 @@ +//! Test utility exports. + +pub mod test_helpers; + +pub use test_helpers::{octocrab_for, temp_config}; diff --git a/tests/util/test_helpers.rs b/tests/util/test_helpers.rs new file mode 100644 index 0000000..0855787 --- /dev/null +++ b/tests/util/test_helpers.rs @@ -0,0 +1,41 @@ +//! Helper functions for tests. +//! +//! Provides constructors for daemon [`Config`] with temporary paths and a +//! simplified way to create an [`Octocrab`] client for a [`MockServer`]. + +#![expect(clippy::expect_used, reason = "simplify test setup")] + +use std::sync::Arc; + +use comenqd::config::Config; +use octocrab::Octocrab; +use tempfile::TempDir; +use wiremock::MockServer; + +/// Build a daemon [`Config`] using paths inside the given temporary directory. +/// +/// The returned configuration uses a dummy GitHub token and sets the +/// cooldown to one second. +pub fn temp_config(tmp: &TempDir) -> Config { + Config { + github_token: "t".into(), + socket_path: tmp.path().join("sock"), + queue_path: tmp.path().join("q"), + cooldown_period_seconds: 1, + } +} + +/// Construct an [`Octocrab`] client targeting the provided [`MockServer`]. +/// +/// The client is built with a placeholder personal token and its base URL set +/// to the mock server URI. +pub fn octocrab_for(server: &MockServer) -> Arc { + Arc::new( + Octocrab::builder() + .personal_token("t".to_string()) + .base_uri(server.uri()) + .expect("base_uri") + .build() + .expect("build octocrab"), + ) +} From b7eeb2bcf3f87aa7900a643eb900f296025886a1 Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 1 Aug 2025 18:01:36 +0100 Subject: [PATCH 2/3] Move test helpers to support crate --- Cargo.lock | 4 ++++ Cargo.toml | 1 + crates/comenqd/src/daemon.rs | 16 ++++------------ test-support/Cargo.toml | 4 ++++ .../src/daemon.rs | 17 ++++++++--------- test-support/src/lib.rs | 2 ++ tests/steps/worker_steps.rs | 7 +++---- tests/util/mod.rs | 6 ++---- 8 files changed, 28 insertions(+), 29 deletions(-) rename tests/util/test_helpers.rs => test-support/src/daemon.rs (56%) diff --git a/Cargo.lock b/Cargo.lock index 0f5a37b..d6d0fb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2448,7 +2448,11 @@ dependencies = [ name = "test-support" version = "0.1.0" dependencies = [ + "comenqd", + "octocrab", + "tempfile", "tokio", + "wiremock", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 57bf4b2..dfe1eb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } anyhow = "1.0" thiserror = "1.0" ortho_config = { git = "https://github.com/leynos/ortho-config.git", tag = "v0.4.0" } +tempfile = "3.10" [lints.clippy] pedantic = { level = "warn", priority = -1 } diff --git a/crates/comenqd/src/daemon.rs b/crates/comenqd/src/daemon.rs index 8791dc9..502c942 100644 --- a/crates/comenqd/src/daemon.rs +++ b/crates/comenqd/src/daemon.rs @@ -246,14 +246,7 @@ mod tests { //! Tests for the daemon tasks. use super::*; use tempfile::tempdir; - mod helpers { - include!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../tests/util/test_helpers.rs" - )); - } - use helpers::{octocrab_for, temp_config}; - use test_support::wait_for_file; + use test_support::{octocrab_for, temp_config, wait_for_file}; use tokio::io::AsyncWriteExt; use tokio::net::{UnixListener, UnixStream}; use tokio::sync::{mpsc, watch}; @@ -263,10 +256,9 @@ mod tests { async fn setup_run_worker(status: u16) -> (MockServer, Arc, Receiver, Arc) { let dir = tempdir().expect("tempdir"); - let cfg = Arc::new(Config { - cooldown_period_seconds: 0, - ..temp_config(&dir) - }); + let mut base = temp_config(&dir); + base.cooldown_period_seconds = 0; + let cfg = Arc::new(base); let (sender, rx) = channel(&cfg.queue_path).expect("channel"); let req = CommentRequest { owner: "o".into(), diff --git a/test-support/Cargo.toml b/test-support/Cargo.toml index d8fdef4..b7d8a03 100644 --- a/test-support/Cargo.toml +++ b/test-support/Cargo.toml @@ -5,3 +5,7 @@ edition = "2024" [dependencies] tokio = { workspace = true } +comenqd = { path = "../crates/comenqd" } +octocrab = { workspace = true } +tempfile = { workspace = true } +wiremock = "0.6" diff --git a/tests/util/test_helpers.rs b/test-support/src/daemon.rs similarity index 56% rename from tests/util/test_helpers.rs rename to test-support/src/daemon.rs index 0855787..c6fa395 100644 --- a/tests/util/test_helpers.rs +++ b/test-support/src/daemon.rs @@ -1,7 +1,7 @@ -//! Helper functions for tests. +//! Helper utilities for daemon tests. //! -//! Provides constructors for daemon [`Config`] with temporary paths and a -//! simplified way to create an [`Octocrab`] client for a [`MockServer`]. +//! Provides constructors for temporary daemon [`Config`]s and simplified +//! creation of [`Octocrab`] clients targeting a [`MockServer`]. #![expect(clippy::expect_used, reason = "simplify test setup")] @@ -12,10 +12,9 @@ use octocrab::Octocrab; use tempfile::TempDir; use wiremock::MockServer; -/// Build a daemon [`Config`] using paths inside the given temporary directory. +/// Build a [`Config`] using paths inside `tmp`. /// -/// The returned configuration uses a dummy GitHub token and sets the -/// cooldown to one second. +/// The configuration uses a dummy GitHub token and a one second cooldown. pub fn temp_config(tmp: &TempDir) -> Config { Config { github_token: "t".into(), @@ -25,10 +24,10 @@ pub fn temp_config(tmp: &TempDir) -> Config { } } -/// Construct an [`Octocrab`] client targeting the provided [`MockServer`]. +/// Construct an [`Octocrab`] client for a [`MockServer`]. /// -/// The client is built with a placeholder personal token and its base URL set -/// to the mock server URI. +/// The client is initialised with a placeholder token and its base URL +/// configured to the mock server's URI. pub fn octocrab_for(server: &MockServer) -> Arc { Arc::new( Octocrab::builder() diff --git a/test-support/src/lib.rs b/test-support/src/lib.rs index 3775971..71ee044 100644 --- a/test-support/src/lib.rs +++ b/test-support/src/lib.rs @@ -1,5 +1,7 @@ //! Test support utilities. +pub mod daemon; pub mod util; +pub use daemon::{octocrab_for, temp_config}; pub use util::wait_for_file; diff --git a/tests/steps/worker_steps.rs b/tests/steps/worker_steps.rs index 8b40b50..250b216 100644 --- a/tests/steps/worker_steps.rs +++ b/tests/steps/worker_steps.rs @@ -36,10 +36,9 @@ impl std::fmt::Debug for WorkerWorld { #[given("a queued comment request")] async fn queued_request(world: &mut WorkerWorld) { let dir = TempDir::new().expect("tempdir"); - let cfg = Arc::new(Config { - cooldown_period_seconds: 0, - ..temp_config(&dir) - }); + let mut base = temp_config(&dir); + base.cooldown_period_seconds = 0; + let cfg = Arc::new(base); let (mut sender, receiver) = channel(&cfg.queue_path).expect("channel"); let req = CommentRequest { owner: "o".into(), diff --git a/tests/util/mod.rs b/tests/util/mod.rs index 9f06273..0de4bac 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -1,5 +1,3 @@ -//! Test utility exports. +//! Re-exports of common test helpers. -pub mod test_helpers; - -pub use test_helpers::{octocrab_for, temp_config}; +pub use test_support::{octocrab_for, temp_config}; From 9d13533dff53b0738239f6ee03898d19a21e48f3 Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 1 Aug 2025 20:12:13 +0100 Subject: [PATCH 3/3] Refine daemon tests and deps (#32) --- crates/comenqd/src/daemon.rs | 45 ++++++++++-------------------------- test-support/Cargo.toml | 2 +- tests/cucumber.rs | 1 - 3 files changed, 13 insertions(+), 35 deletions(-) diff --git a/crates/comenqd/src/daemon.rs b/crates/comenqd/src/daemon.rs index 2bfde52..9fdcd1f 100644 --- a/crates/comenqd/src/daemon.rs +++ b/crates/comenqd/src/daemon.rs @@ -2,7 +2,6 @@ //! //! This module provides the run function used by `main` which spawns the //! Unix socket listener and the queue worker. - use crate::config::Config; use anyhow::Result; use comenq_lib::CommentRequest; @@ -17,7 +16,6 @@ use tokio::io::AsyncReadExt; use tokio::net::{UnixListener, UnixStream}; use tokio::sync::{mpsc, watch}; use yaque::{Receiver, Sender, channel}; - fn build_octocrab(token: &str) -> Result { Ok(Octocrab::builder() .personal_token(token.to_string()) @@ -32,7 +30,6 @@ fn prepare_listener(path: &Path) -> Result { stdfs::set_permissions(path, stdfs::Permissions::from_mode(0o660))?; Ok(listener) } - async fn ensure_queue_dir(path: &Path) -> Result<()> { fs::create_dir_all(path).await?; Ok(()) @@ -89,7 +86,6 @@ pub async fn run(config: Config) -> Result<()> { let (client_tx, client_rx) = mpsc::unbounded_channel(); let cfg = Arc::new(config); let (shutdown_tx, shutdown_rx) = watch::channel(()); - let writer = tokio::spawn(queue_writer(queue_tx, client_rx)); let listener = tokio::spawn(run_listener(cfg.clone(), client_tx, shutdown_rx)); let worker = tokio::spawn(run_worker(cfg.clone(), rx, octocrab)); @@ -104,10 +100,8 @@ pub async fn run(config: Config) -> Result<()> { Err(e) => return Err(e.into()), }, } - let _ = shutdown_tx.send(()); writer.await??; - Ok(()) } @@ -259,12 +253,19 @@ mod tests { use tokio::time::{Duration, sleep}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; + fn cfg_with_cooldown(dir: &TempDir, secs: u64) -> Config { + Config { + cooldown_period_seconds: secs, + ..temp_config(dir) + } + } async fn setup_run_worker(status: u16) -> (MockServer, Arc, Receiver, Arc) { let dir = tempdir().expect("tempdir"); - let mut base = temp_config(&dir); - base.cooldown_period_seconds = 0; - let cfg = Arc::new(base); + let cfg = Arc::new(Config { + cooldown_period_seconds: 0, + ..temp_config(&dir) + }); let (sender, rx) = channel(&cfg.queue_path).expect("channel"); let req = CommentRequest { owner: "o".into(), @@ -285,7 +286,6 @@ mod tests { .await; let octo = octocrab_for(&server); - (server, cfg, rx, octo) } @@ -302,17 +302,10 @@ mod tests { #[tokio::test] async fn run_creates_queue_directory() { let dir = tempdir().expect("Failed to create temporary directory"); - let cfg = Config { - cooldown_period_seconds: 1, - ..temp_config(&dir) - }; - + let cfg = cfg_with_cooldown(&dir, 1); assert!(!cfg.queue_path.exists()); - let handle = tokio::spawn(run(cfg.clone())); - wait_for_file(&cfg.queue_path, 200, Duration::from_millis(10)).await; - handle.abort(); assert!(cfg.queue_path.is_dir(), "queue directory not created"); } @@ -325,7 +318,6 @@ mod tests { let listener = prepare_listener(&sock).expect("prepare listener"); drop(listener); - let meta = stdfs::metadata(&sock).expect("metadata"); assert_eq!(meta.permissions().mode() & 0o777, 0o660); } @@ -340,7 +332,6 @@ mod tests { let (mut client, server) = UnixStream::pair().expect("pair"); let handle = tokio::spawn(handle_client(server, client_tx)); - let req = CommentRequest { owner: "o".into(), repo: "r".into(), @@ -352,7 +343,6 @@ mod tests { client.shutdown().await.expect("shutdown"); handle.await.expect("join").expect("client"); drop(writer); // stop queue writer - let guard = receiver.recv().await.expect("recv"); let stored: CommentRequest = serde_json::from_slice(&guard).expect("parse"); assert_eq!(stored, req); @@ -361,20 +351,13 @@ mod tests { #[tokio::test] async fn run_listener_accepts_connections() { let dir = tempdir().expect("tempdir"); - let cfg = Arc::new(Config { - cooldown_period_seconds: 1, - ..temp_config(&dir) - }); - + let cfg = Arc::new(cfg_with_cooldown(&dir, 1)); let (sender, mut receiver) = channel(&cfg.queue_path).expect("channel"); let (client_tx, writer_rx) = mpsc::unbounded_channel(); let (shutdown_tx, shutdown_rx) = watch::channel(()); let writer = tokio::spawn(queue_writer(sender, writer_rx)); - let listener_task = tokio::spawn(run_listener(cfg.clone(), client_tx, shutdown_rx)); - wait_for_file(&cfg.socket_path, 10, Duration::from_millis(10)).await; - let mut stream = UnixStream::connect(&cfg.socket_path) .await .expect("connect"); @@ -387,11 +370,9 @@ mod tests { let payload = serde_json::to_vec(&req).expect("serialize"); stream.write_all(&payload).await.expect("write"); stream.shutdown().await.expect("shutdown"); - let guard = receiver.recv().await.expect("recv"); let stored: CommentRequest = serde_json::from_slice(&guard).expect("parse"); assert_eq!(stored, req); - listener_task.abort(); let _ = shutdown_tx.send(()); drop(writer); @@ -403,7 +384,6 @@ mod tests { let h = tokio::spawn(run_worker(cfg.clone(), rx, octo)); sleep(Duration::from_millis(50)).await; h.abort(); - assert_eq!(server.received_requests().await.unwrap().len(), 1); assert_eq!(std::fs::read_dir(&cfg.queue_path).unwrap().count(), 0); } @@ -414,7 +394,6 @@ mod tests { let h = tokio::spawn(run_worker(cfg.clone(), rx, octo)); sleep(Duration::from_millis(50)).await; h.abort(); - assert_eq!(server.received_requests().await.unwrap().len(), 1); assert!(std::fs::read_dir(&cfg.queue_path).unwrap().count() > 0); } diff --git a/test-support/Cargo.toml b/test-support/Cargo.toml index b7d8a03..ac68fa2 100644 --- a/test-support/Cargo.toml +++ b/test-support/Cargo.toml @@ -8,4 +8,4 @@ tokio = { workspace = true } comenqd = { path = "../crates/comenqd" } octocrab = { workspace = true } tempfile = { workspace = true } -wiremock = "0.6" +wiremock = "^0.6" diff --git a/tests/cucumber.rs b/tests/cucumber.rs index aea14ea..fc22ddd 100644 --- a/tests/cucumber.rs +++ b/tests/cucumber.rs @@ -1,5 +1,4 @@ mod steps; -mod util; mod support; mod util; use cucumber::World as _;