-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathtesting_utilities.rs
More file actions
373 lines (316 loc) · 12.4 KB
/
testing_utilities.rs
File metadata and controls
373 lines (316 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//! # Testing Utilities Example
//!
//! This example demonstrates how to use docker-wrapper's testing utilities
//! for integration testing with containers. These utilities provide RAII-style
//! lifecycle management, ensuring containers are automatically cleaned up.
//!
//! ## Features Demonstrated
//!
//! - `ContainerGuard`: Single container lifecycle management
//! - `ContainerGuardSet`: Multi-container test environments
//! - Automatic cleanup on drop (even on panic)
//! - Ready checks and connection strings
//! - Debug mode for failed tests
//!
//! ## Running the Examples
//!
//! Run specific tests:
//! ```bash
//! cargo test --example testing_utilities --features testing
//! ```
//!
//! Run with output:
//! ```bash
//! cargo test --example testing_utilities --features testing -- --nocapture
//! ```
#[cfg(test)]
use docker_wrapper::testing::{ContainerGuard, ContainerGuardSet};
#[cfg(test)]
use docker_wrapper::{RedisTemplate, Template};
#[cfg(test)]
use std::sync::atomic::{AtomicU16, Ordering};
#[cfg(test)]
use std::time::Duration;
/// Generate unique container names for parallel test safety.
#[cfg(test)]
fn unique_name(prefix: &str) -> String {
format!("{}-{}", prefix, uuid::Uuid::new_v4().simple())
}
/// Generate unique ports for parallel test safety.
/// Starts at 40000 and increments for each call.
#[cfg(test)]
fn unique_port() -> u16 {
static PORT: AtomicU16 = AtomicU16::new(40000);
PORT.fetch_add(1, Ordering::SeqCst)
}
// ============================================================================
// Basic Usage
// ============================================================================
/// Basic container guard usage with automatic cleanup.
///
/// The container is automatically stopped and removed when the guard
/// goes out of scope, even if the test panics.
#[tokio::test]
async fn test_basic_container_guard() {
let name = unique_name("basic-redis");
let port = unique_port();
// Create and start a Redis container with automatic lifecycle management
let guard = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.wait_for_ready(true) // Wait for Redis to accept connections
.start()
.await
.expect("Failed to start Redis container");
// Container is ready - verify it's running
assert!(
guard.is_running().await.expect("Failed to check status"),
"Container should be running"
);
// Access the underlying template if needed
let template = guard.template();
assert_eq!(template.config().name, name);
// Container is automatically stopped and removed here
}
/// Using connection strings for easy service access.
#[tokio::test]
async fn test_connection_string() {
let name = unique_name("conn-redis");
let port = unique_port();
let guard = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.wait_for_ready(true)
.start()
.await
.expect("Failed to start Redis");
// Get the connection string directly from the guard
let conn = guard.connection_string();
assert!(
conn.starts_with("redis://"),
"Should be a Redis connection URL"
);
assert!(
conn.contains(&port.to_string()),
"Should include the configured port"
);
println!("Redis available at: {}", conn);
}
// ============================================================================
// Configuration Options
// ============================================================================
/// Fast cleanup with custom stop timeout.
///
/// By default Docker waits 10 seconds for graceful shutdown. Use a shorter
/// timeout for faster test cleanup when you don't need graceful shutdown.
#[tokio::test]
async fn test_fast_cleanup() {
let name = unique_name("fast-redis");
let port = unique_port();
let guard = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.wait_for_ready(true)
.stop_timeout(Duration::from_secs(1)) // Fast 1-second timeout
.start()
.await
.expect("Failed to start Redis");
assert!(guard.is_running().await.unwrap());
// Cleanup will be fast due to short timeout
}
/// Keep containers running for debugging failed tests.
///
/// When `keep_on_panic(true)` is set, the container won't be removed if
/// the test panics, allowing you to inspect its state for debugging.
#[tokio::test]
async fn test_debug_mode() {
let name = unique_name("debug-redis");
let port = unique_port();
let guard = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.keep_on_panic(true) // Keep container if test panics
.capture_logs(true) // Print logs on panic
.wait_for_ready(true)
.start()
.await
.expect("Failed to start Redis");
// If this test panicked, the container would stay running
// and logs would be printed to stderr for debugging
assert!(guard.is_running().await.unwrap());
// Normal exit - container is cleaned up
}
/// Container reuse for faster local development.
///
/// With `reuse_if_running(true)`, if a container with the same name is
/// already running, it will be reused instead of starting a new one.
#[tokio::test]
async fn test_container_reuse() {
// Use a fixed name and port for reuse demonstration
let name = unique_name("reuse-redis");
let port = unique_port();
// First run - starts a new container
let guard1 = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.reuse_if_running(true)
.stop_on_drop(false) // Don't stop so next test can reuse
.remove_on_drop(false) // Don't remove so next test can reuse
.wait_for_ready(true)
.start()
.await
.expect("Failed to start Redis");
let was_reused_first = guard1.was_reused();
println!("First guard - was_reused: {}", was_reused_first);
// Second guard - should reuse the running container
let guard2 = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.reuse_if_running(true)
.wait_for_ready(true)
.start()
.await
.expect("Failed to start/reuse Redis");
// This time it should be reused (if container was still running)
println!("Second guard - was_reused: {}", guard2.was_reused());
assert!(guard2.was_reused(), "Second guard should reuse container");
// guard2 will clean up since we didn't disable cleanup
}
/// Manual cleanup when you need explicit control.
#[tokio::test]
async fn test_manual_cleanup() {
let name = unique_name("manual-redis");
let port = unique_port();
let guard = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.wait_for_ready(true)
.start()
.await
.expect("Failed to start Redis");
// Do some work...
assert!(guard.is_running().await.unwrap());
// Explicitly clean up before the guard goes out of scope
guard.cleanup().await.expect("Failed to cleanup");
// Guard will not try to clean up again on drop (idempotent)
}
// ============================================================================
// Multi-Container Tests
// ============================================================================
/// Using ContainerGuardSet for multi-container test environments.
///
/// This is useful when your test needs multiple services that may need
/// to communicate with each other.
#[tokio::test]
async fn test_multi_container_set() {
let network = unique_name("test-net");
let redis1_name = unique_name("redis-primary");
let redis2_name = unique_name("redis-replica");
let port1 = unique_port();
let port2 = unique_port();
// Create a set with multiple containers on a shared network
let guards = ContainerGuardSet::new()
.with_network(&network) // Containers can communicate via this network
.add(RedisTemplate::new(&redis1_name).port(port1))
.add(RedisTemplate::new(&redis2_name).port(port2))
.keep_on_panic(true) // Keep all containers for debugging
.start_all()
.await
.expect("Failed to start container set");
// Verify both containers are in the set
assert!(guards.contains(&redis1_name));
assert!(guards.contains(&redis2_name));
assert_eq!(guards.len(), 2);
// Get the shared network
assert_eq!(guards.network(), Some(network.as_str()));
// List all container names
println!("Running containers:");
for name in guards.names() {
println!(" - {}", name);
}
// All containers and the network are cleaned up when guards is dropped
}
/// ContainerGuardSet with custom options.
#[tokio::test]
async fn test_guard_set_options() {
let network = unique_name("options-net");
let port = unique_port();
let guards = ContainerGuardSet::new()
.with_network(&network)
.create_network(true) // Create network if it doesn't exist (default)
.remove_network_on_drop(true) // Clean up network after test (default)
.wait_for_ready(true) // Wait for all containers to be ready (default)
.keep_on_panic(false) // Clean up even on panic
.add(RedisTemplate::new(&unique_name("opt-redis")).port(port))
.start_all()
.await
.expect("Failed to start");
assert_eq!(guards.len(), 1);
assert!(!guards.is_empty());
}
// ============================================================================
// Accessing Container Information
// ============================================================================
/// Accessing container details and logs.
#[tokio::test]
async fn test_container_info() {
let name = unique_name("info-redis");
let port = unique_port();
let guard = ContainerGuard::new(RedisTemplate::new(&name).port(port))
.wait_for_ready(true)
.start()
.await
.expect("Failed to start Redis");
// Get container ID (if available)
if let Some(id) = guard.container_id() {
println!("Container ID: {}", id);
assert!(!id.is_empty());
}
// Get connection string (includes the port)
let conn = guard.connection_string();
println!("Connection string: {}", conn);
assert!(conn.contains(&port.to_string()));
// Get container logs
let logs = guard.logs().await.expect("Failed to get logs");
println!("Container logs:\n{}", logs);
assert!(logs.contains("Ready to accept connections"));
// Check running status
assert!(guard.is_running().await.expect("Failed to check status"));
}
// ============================================================================
// Test Fixtures Pattern
// ============================================================================
/// Create reusable test fixtures for common setups.
#[cfg(test)]
async fn redis_fixture(name: &str, port: u16) -> ContainerGuard<RedisTemplate> {
ContainerGuard::new(RedisTemplate::new(name).port(port))
.wait_for_ready(true)
.keep_on_panic(true)
.capture_logs(true)
.stop_timeout(Duration::from_secs(2))
.start()
.await
.expect("Failed to create Redis fixture")
}
/// Using the test fixture pattern.
#[tokio::test]
async fn test_with_fixture() {
let name = unique_name("fixture-redis");
let port = unique_port();
let redis = redis_fixture(&name, port).await;
// Fixture is ready to use
let conn = redis.connection_string();
println!("Fixture ready at: {}", conn);
// Clean up is automatic
}
// ============================================================================
// Main
// ============================================================================
fn main() {
println!("Testing Utilities Example");
println!("=========================");
println!();
println!("This example demonstrates docker-wrapper's testing utilities");
println!("for integration testing with automatic container lifecycle management.");
println!();
println!("Run the tests with:");
println!(" cargo test --example testing_utilities --features testing");
println!();
println!("Run with output:");
println!(" cargo test --example testing_utilities --features testing -- --nocapture");
println!();
println!("Features demonstrated:");
println!(" - ContainerGuard: Single container RAII management");
println!(" - ContainerGuardSet: Multi-container test environments");
println!(" - Automatic cleanup on drop and panic");
println!(" - Ready checks and connection strings");
println!(" - Debug mode for failed tests");
println!(" - Container reuse for faster development");
println!(" - Test fixture patterns");
}