Skip to content

Commit 277967a

Browse files
committed
docs: add comprehensive rustdoc for testing module
Adds detailed module-level documentation for the testing utilities with examples covering all features and common patterns. Documentation includes: - Why use this module (benefits overview) - Quick start example - Configuration options: - Lifecycle control (stop/remove on drop) - Debugging failed tests (keep_on_panic, capture_logs) - Ready checks (wait_for_ready) - Container reuse for faster local dev - Network support - Fast cleanup with stop timeout - Multi-container tests with ContainerGuardSet - Accessing container information - Common patterns: - Reusable test fixtures - Unique container names for parallel tests - Manual cleanup - Feature flag requirements Closes #224
1 parent d6b1ad4 commit 277967a

1 file changed

Lines changed: 281 additions & 8 deletions

File tree

src/testing.rs

Lines changed: 281 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,301 @@
1-
//! Testing utilities for container lifecycle management.
1+
//! # Testing Utilities
22
//!
3-
//! This module provides [`ContainerGuard`], an RAII wrapper that automatically
4-
//! manages container lifecycle. When the guard goes out of scope, containers
5-
//! are stopped and removed automatically.
3+
//! RAII-style container lifecycle management for integration tests.
64
//!
7-
//! # Example
5+
//! This module provides [`ContainerGuard`] and [`ContainerGuardSet`] for automatic
6+
//! container lifecycle management. Containers are automatically stopped and removed
7+
//! when guards go out of scope, ensuring clean test environments.
8+
//!
9+
//! ## Why Use This?
10+
//!
11+
//! - **Automatic cleanup**: No more forgotten containers cluttering your Docker
12+
//! - **Panic-safe**: Containers are cleaned up even if your test panics
13+
//! - **Debug-friendly**: Keep containers alive on failure for inspection
14+
//! - **Network support**: Automatic network creation for multi-container tests
15+
//! - **Ready checks**: Wait for services to be ready before running tests
16+
//!
17+
//! ## Quick Start
818
//!
919
//! ```rust,no_run
1020
//! use docker_wrapper::testing::ContainerGuard;
1121
//! use docker_wrapper::RedisTemplate;
1222
//!
1323
//! #[tokio::test]
14-
//! async fn test_redis() -> Result<(), Box<dyn std::error::Error>> {
24+
//! async fn test_with_redis() -> Result<(), Box<dyn std::error::Error>> {
25+
//! // Container starts and waits for Redis to be ready
1526
//! let guard = ContainerGuard::new(RedisTemplate::new("test-redis"))
27+
//! .wait_for_ready(true)
1628
//! .start()
1729
//! .await?;
1830
//!
19-
//! // Use the container...
20-
//! let url = guard.template().connection_url();
31+
//! // Get connection string directly from guard
32+
//! let url = guard.connection_string();
33+
//! // Use Redis at: redis://localhost:6379
2134
//!
2235
//! Ok(())
2336
//! // Container automatically stopped and removed here
2437
//! }
2538
//! ```
39+
//!
40+
//! ## Configuration Options
41+
//!
42+
//! ### Lifecycle Control
43+
//!
44+
//! ```rust,no_run
45+
//! # use docker_wrapper::testing::ContainerGuard;
46+
//! # use docker_wrapper::RedisTemplate;
47+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
48+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis"))
49+
//! .stop_on_drop(true) // Stop container on drop (default: true)
50+
//! .remove_on_drop(true) // Remove container on drop (default: true)
51+
//! .start()
52+
//! .await?;
53+
//! # Ok(())
54+
//! # }
55+
//! ```
56+
//!
57+
//! ### Debugging Failed Tests
58+
//!
59+
//! Keep containers running when tests fail for debugging:
60+
//!
61+
//! ```rust,no_run
62+
//! # use docker_wrapper::testing::ContainerGuard;
63+
//! # use docker_wrapper::RedisTemplate;
64+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
65+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis"))
66+
//! .keep_on_panic(true) // Keep container if test panics
67+
//! .capture_logs(true) // Print container logs on panic
68+
//! .start()
69+
//! .await?;
70+
//! # Ok(())
71+
//! # }
72+
//! ```
73+
//!
74+
//! ### Ready Checks
75+
//!
76+
//! Wait for the service to be ready before proceeding:
77+
//!
78+
//! ```rust,no_run
79+
//! # use docker_wrapper::testing::ContainerGuard;
80+
//! # use docker_wrapper::RedisTemplate;
81+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
82+
//! // Automatic wait during start
83+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis"))
84+
//! .wait_for_ready(true)
85+
//! .start()
86+
//! .await?;
87+
//! // Redis is guaranteed ready here
88+
//!
89+
//! // Or wait manually later
90+
//! let guard2 = ContainerGuard::new(RedisTemplate::new("redis2"))
91+
//! .start()
92+
//! .await?;
93+
//! guard2.wait_for_ready().await?;
94+
//! # Ok(())
95+
//! # }
96+
//! ```
97+
//!
98+
//! ### Container Reuse
99+
//!
100+
//! Speed up local development by reusing running containers:
101+
//!
102+
//! ```rust,no_run
103+
//! # use docker_wrapper::testing::ContainerGuard;
104+
//! # use docker_wrapper::RedisTemplate;
105+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
106+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis"))
107+
//! .reuse_if_running(true) // Reuse existing container if found
108+
//! .remove_on_drop(false) // Keep it for next test run
109+
//! .stop_on_drop(false)
110+
//! .start()
111+
//! .await?;
112+
//!
113+
//! if guard.was_reused() {
114+
//! println!("Reused existing container");
115+
//! }
116+
//! # Ok(())
117+
//! # }
118+
//! ```
119+
//!
120+
//! ### Network Support
121+
//!
122+
//! Attach containers to custom networks:
123+
//!
124+
//! ```rust,no_run
125+
//! # use docker_wrapper::testing::ContainerGuard;
126+
//! # use docker_wrapper::RedisTemplate;
127+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
128+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis"))
129+
//! .with_network("my-test-network") // Create and attach to network
130+
//! .remove_network_on_drop(true) // Clean up network after test
131+
//! .start()
132+
//! .await?;
133+
//! # Ok(())
134+
//! # }
135+
//! ```
136+
//!
137+
//! ### Fast Cleanup
138+
//!
139+
//! Use a short stop timeout for faster test cleanup:
140+
//!
141+
//! ```rust,no_run
142+
//! # use docker_wrapper::testing::ContainerGuard;
143+
//! # use docker_wrapper::RedisTemplate;
144+
//! # use std::time::Duration;
145+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
146+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis"))
147+
//! .stop_timeout(Duration::from_secs(1)) // 1 second graceful shutdown
148+
//! .start()
149+
//! .await?;
150+
//!
151+
//! // Or immediate SIGKILL
152+
//! let guard2 = ContainerGuard::new(RedisTemplate::new("redis2"))
153+
//! .stop_timeout(Duration::ZERO)
154+
//! .start()
155+
//! .await?;
156+
//! # Ok(())
157+
//! # }
158+
//! ```
159+
//!
160+
//! ## Multi-Container Tests
161+
//!
162+
//! Use [`ContainerGuardSet`] for tests requiring multiple services:
163+
//!
164+
//! ```rust,no_run
165+
//! use docker_wrapper::testing::ContainerGuardSet;
166+
//! use docker_wrapper::RedisTemplate;
167+
//!
168+
//! #[tokio::test]
169+
//! async fn test_multi_container() -> Result<(), Box<dyn std::error::Error>> {
170+
//! let guards = ContainerGuardSet::new()
171+
//! .with_network("test-network") // Shared network for all containers
172+
//! .add(RedisTemplate::new("redis-primary").port(6379))
173+
//! .add(RedisTemplate::new("redis-replica").port(6380))
174+
//! .keep_on_panic(true)
175+
//! .start_all()
176+
//! .await?;
177+
//!
178+
//! assert!(guards.contains("redis-primary"));
179+
//! assert!(guards.contains("redis-replica"));
180+
//! assert_eq!(guards.len(), 2);
181+
//!
182+
//! // All containers cleaned up together
183+
//! Ok(())
184+
//! }
185+
//! ```
186+
//!
187+
//! ## Accessing Container Information
188+
//!
189+
//! ```rust,no_run
190+
//! # use docker_wrapper::testing::ContainerGuard;
191+
//! # use docker_wrapper::RedisTemplate;
192+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
193+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis").port(6379))
194+
//! .start()
195+
//! .await?;
196+
//!
197+
//! // Connection string (for templates that support it)
198+
//! let conn = guard.connection_string();
199+
//!
200+
//! // Access underlying template
201+
//! let template = guard.template();
202+
//!
203+
//! // Get container ID
204+
//! if let Some(id) = guard.container_id() {
205+
//! println!("Container ID: {}", id);
206+
//! }
207+
//!
208+
//! // Query host port for a container port
209+
//! let host_port = guard.host_port(6379).await?;
210+
//!
211+
//! // Get container logs
212+
//! let logs = guard.logs().await?;
213+
//!
214+
//! // Check if running
215+
//! let running = guard.is_running().await?;
216+
//! # Ok(())
217+
//! # }
218+
//! ```
219+
//!
220+
//! ## Common Patterns
221+
//!
222+
//! ### Test Fixtures
223+
//!
224+
//! Create reusable test fixtures:
225+
//!
226+
//! ```rust,no_run
227+
//! use docker_wrapper::testing::ContainerGuard;
228+
//! use docker_wrapper::RedisTemplate;
229+
//! use docker_wrapper::template::TemplateError;
230+
//!
231+
//! async fn redis_fixture(name: &str) -> Result<ContainerGuard<RedisTemplate>, TemplateError> {
232+
//! ContainerGuard::new(RedisTemplate::new(name))
233+
//! .wait_for_ready(true)
234+
//! .keep_on_panic(true)
235+
//! .capture_logs(true)
236+
//! .start()
237+
//! .await
238+
//! }
239+
//!
240+
//! #[tokio::test]
241+
//! async fn test_using_fixture() -> Result<(), Box<dyn std::error::Error>> {
242+
//! let redis = redis_fixture("test-redis").await?;
243+
//! // Use redis...
244+
//! Ok(())
245+
//! }
246+
//! ```
247+
//!
248+
//! ### Unique Container Names
249+
//!
250+
//! Use UUIDs to avoid name conflicts in parallel tests:
251+
//!
252+
//! ```rust,no_run
253+
//! # use docker_wrapper::testing::ContainerGuard;
254+
//! # use docker_wrapper::RedisTemplate;
255+
//! fn unique_name(prefix: &str) -> String {
256+
//! format!("{}-{}", prefix, uuid::Uuid::new_v4())
257+
//! }
258+
//!
259+
//! #[tokio::test]
260+
//! async fn test_parallel_safe() -> Result<(), Box<dyn std::error::Error>> {
261+
//! let name = unique_name("redis");
262+
//! let guard = ContainerGuard::new(RedisTemplate::new(&name))
263+
//! .start()
264+
//! .await?;
265+
//! Ok(())
266+
//! }
267+
//! ```
268+
//!
269+
//! ### Manual Cleanup
270+
//!
271+
//! Trigger cleanup explicitly when needed:
272+
//!
273+
//! ```rust,no_run
274+
//! # use docker_wrapper::testing::ContainerGuard;
275+
//! # use docker_wrapper::RedisTemplate;
276+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
277+
//! let guard = ContainerGuard::new(RedisTemplate::new("redis"))
278+
//! .start()
279+
//! .await?;
280+
//!
281+
//! // Do some work...
282+
//!
283+
//! // Explicitly cleanup (idempotent - safe to call multiple times)
284+
//! guard.cleanup().await?;
285+
//!
286+
//! // Drop will not try to clean up again
287+
//! # Ok(())
288+
//! # }
289+
//! ```
290+
//!
291+
//! ## Feature Flag
292+
//!
293+
//! This module requires the `testing` feature:
294+
//!
295+
//! ```toml
296+
//! [dev-dependencies]
297+
//! docker-wrapper = { version = "0.10", features = ["testing", "template-redis"] }
298+
//! ```
26299
27300
use crate::command::DockerCommand;
28301
use crate::template::{HasConnectionString, Template, TemplateError};

0 commit comments

Comments
 (0)