1use parking_lot::RwLock;
6use std::{
7 collections::HashMap,
8 fmt,
9 future::Future,
10 sync::Arc,
11 time::{Duration, Instant},
12};
13use wae_types::{WaeError, WaeErrorKind, WaeResult as TestingResult};
14
15#[derive(Debug, Clone)]
17pub struct TestEnvConfig {
18 pub name: String,
20 pub enable_logging: bool,
22 pub enable_tracing: bool,
24 pub default_timeout: Duration,
26 pub custom: HashMap<String, String>,
28}
29
30impl Default for TestEnvConfig {
31 fn default() -> Self {
32 Self {
33 name: "test".to_string(),
34 enable_logging: true,
35 enable_tracing: false,
36 default_timeout: Duration::from_secs(30),
37 custom: HashMap::new(),
38 }
39 }
40}
41
42impl TestEnvConfig {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn name(mut self, name: impl Into<String>) -> Self {
50 self.name = name.into();
51 self
52 }
53
54 pub fn with_logging(mut self, enable: bool) -> Self {
56 self.enable_logging = enable;
57 self
58 }
59
60 pub fn with_tracing(mut self, enable: bool) -> Self {
62 self.enable_tracing = enable;
63 self
64 }
65
66 pub fn timeout(mut self, timeout: Duration) -> Self {
68 self.default_timeout = timeout;
69 self
70 }
71
72 pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
74 self.custom.insert(key.into(), value.into());
75 self
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum TestEnvState {
82 Uninitialized,
84 Initializing,
86 Initialized,
88 Destroying,
90 Destroyed,
92}
93
94pub trait TestLifecycleHook: Send + Sync {
98 fn before_setup(&self, _env: &TestEnv) -> TestingResult<()> {
104 Ok(())
105 }
106
107 fn after_setup(&self, _env: &TestEnv) -> TestingResult<()> {
113 Ok(())
114 }
115
116 fn before_teardown(&self, _env: &TestEnv) -> TestingResult<()> {
122 Ok(())
123 }
124
125 fn after_teardown(&self, _env: &TestEnv) -> TestingResult<()> {
131 Ok(())
132 }
133}
134
135impl<F> TestLifecycleHook for F
136where
137 F: Fn(&TestEnv) -> TestingResult<()> + Send + Sync,
138{
139 fn after_setup(&self, env: &TestEnv) -> TestingResult<()> {
140 self(env)
141 }
142}
143
144#[async_trait::async_trait]
148pub trait AsyncTestLifecycleHook: Send + Sync {
149 async fn before_setup_async(&self, _env: &TestEnv) -> TestingResult<()> {
155 Ok(())
156 }
157
158 async fn after_setup_async(&self, _env: &TestEnv) -> TestingResult<()> {
164 Ok(())
165 }
166
167 async fn before_teardown_async(&self, _env: &TestEnv) -> TestingResult<()> {
173 Ok(())
174 }
175
176 async fn after_teardown_async(&self, _env: &TestEnv) -> TestingResult<()> {
182 Ok(())
183 }
184}
185
186#[derive(Debug, Clone)]
190pub struct TestServiceConfig {
191 pub name: String,
193 pub enabled: bool,
195 pub startup_timeout: Duration,
197 pub config: HashMap<String, String>,
199}
200
201impl TestServiceConfig {
202 pub fn new(name: impl Into<String>) -> Self {
204 Self { name: name.into(), enabled: true, startup_timeout: Duration::from_secs(30), config: HashMap::new() }
205 }
206
207 pub fn enabled(mut self, enabled: bool) -> Self {
209 self.enabled = enabled;
210 self
211 }
212
213 pub fn startup_timeout(mut self, timeout: Duration) -> Self {
215 self.startup_timeout = timeout;
216 self
217 }
218
219 pub fn config(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
221 self.config.insert(key.into(), value.into());
222 self
223 }
224}
225
226pub struct TestEnv {
230 config: TestEnvConfig,
232 state: Arc<RwLock<TestEnvState>>,
234 created_at: Instant,
236 initialized_at: Arc<RwLock<Option<Instant>>>,
238 lifecycle_hooks: Arc<RwLock<Vec<Box<dyn TestLifecycleHook>>>>,
240 async_lifecycle_hooks: Arc<RwLock<Vec<Box<dyn AsyncTestLifecycleHook>>>>,
242 #[allow(clippy::type_complexity)]
244 cleanup_handlers: Arc<RwLock<Vec<Box<dyn Fn() + Send + Sync>>>>,
245 #[allow(clippy::type_complexity)]
247 async_cleanup_handlers: Arc<RwLock<Vec<Box<dyn Fn() -> std::pin::Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>>>>,
248 storage: Arc<RwLock<HashMap<String, Box<dyn std::any::Any + Send + Sync>>>>,
250 services: Arc<RwLock<HashMap<String, TestServiceConfig>>>,
252}
253
254impl fmt::Debug for TestEnv {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 f.debug_struct("TestEnv")
257 .field("config", &self.config)
258 .field("state", &self.state)
259 .field("created_at", &self.created_at)
260 .field("initialized_at", &self.initialized_at)
261 .field("services", &self.services)
262 .finish()
263 }
264}
265
266impl TestEnv {
267 pub fn new(config: TestEnvConfig) -> Self {
278 Self {
279 config,
280 state: Arc::new(RwLock::new(TestEnvState::Uninitialized)),
281 created_at: Instant::now(),
282 initialized_at: Arc::new(RwLock::new(None)),
283 lifecycle_hooks: Arc::new(RwLock::new(Vec::new())),
284 async_lifecycle_hooks: Arc::new(RwLock::new(Vec::new())),
285 cleanup_handlers: Arc::new(RwLock::new(Vec::new())),
286 async_cleanup_handlers: Arc::new(RwLock::new(Vec::new())),
287 storage: Arc::new(RwLock::new(HashMap::new())),
288 services: Arc::new(RwLock::new(HashMap::new())),
289 }
290 }
291
292 pub fn default_env() -> Self {
302 Self::new(TestEnvConfig::default())
303 }
304
305 pub fn setup(&self) -> TestingResult<()> {
322 {
323 let mut state = self.state.write();
324 if *state != TestEnvState::Uninitialized {
325 return Err(WaeError::new(WaeErrorKind::EnvironmentError {
326 reason: "Environment already initialized".to_string(),
327 }));
328 }
329 *state = TestEnvState::Initializing;
330 }
331
332 let result = (|| {
333 for hook in self.lifecycle_hooks.read().iter() {
334 hook.before_setup(self)?;
335 }
336
337 for hook in self.lifecycle_hooks.read().iter() {
338 hook.after_setup(self)?;
339 }
340
341 Ok(())
342 })();
343
344 let mut state = self.state.write();
345 match result {
346 Ok(_) => {
347 *state = TestEnvState::Initialized;
348 *self.initialized_at.write() = Some(Instant::now());
349 Ok(())
350 }
351 Err(e) => {
352 *state = TestEnvState::Uninitialized;
353 Err(e)
354 }
355 }
356 }
357
358 pub async fn setup_async(&self) -> TestingResult<()> {
366 {
367 let mut state = self.state.write();
368 if *state != TestEnvState::Uninitialized {
369 return Err(WaeError::new(WaeErrorKind::EnvironmentError {
370 reason: "Environment already initialized".to_string(),
371 }));
372 }
373 *state = TestEnvState::Initializing;
374 }
375
376 let result = (async {
377 for hook in self.lifecycle_hooks.read().iter() {
378 hook.before_setup(self)?;
379 }
380
381 for hook in self.async_lifecycle_hooks.read().iter() {
382 hook.before_setup_async(self).await?;
383 }
384
385 for hook in self.async_lifecycle_hooks.read().iter() {
386 hook.after_setup_async(self).await?;
387 }
388
389 for hook in self.lifecycle_hooks.read().iter() {
390 hook.after_setup(self)?;
391 }
392
393 Ok(())
394 })
395 .await;
396
397 let mut state = self.state.write();
398 match result {
399 Ok(_) => {
400 *state = TestEnvState::Initialized;
401 *self.initialized_at.write() = Some(Instant::now());
402 Ok(())
403 }
404 Err(e) => {
405 *state = TestEnvState::Uninitialized;
406 Err(e)
407 }
408 }
409 }
410
411 pub fn teardown(&self) -> TestingResult<()> {
429 {
430 let mut state = self.state.write();
431 if *state != TestEnvState::Initialized {
432 return Err(WaeError::new(WaeErrorKind::EnvironmentError {
433 reason: "Environment not initialized".to_string(),
434 }));
435 }
436 *state = TestEnvState::Destroying;
437 }
438
439 let result = (|| {
440 for hook in self.lifecycle_hooks.read().iter() {
441 hook.before_teardown(self)?;
442 }
443
444 let handlers = self.cleanup_handlers.write();
445 for handler in handlers.iter().rev() {
446 handler();
447 }
448
449 self.storage.write().clear();
450
451 for hook in self.lifecycle_hooks.read().iter() {
452 hook.after_teardown(self)?;
453 }
454
455 Ok(())
456 })();
457
458 let mut state = self.state.write();
459 *state = TestEnvState::Destroyed;
460 result
461 }
462
463 pub async fn teardown_async(&self) -> TestingResult<()> {
471 {
472 let mut state = self.state.write();
473 if *state != TestEnvState::Initialized {
474 return Err(WaeError::new(WaeErrorKind::EnvironmentError {
475 reason: "Environment not initialized".to_string(),
476 }));
477 }
478 *state = TestEnvState::Destroying;
479 }
480
481 let result = (async {
482 for hook in self.lifecycle_hooks.read().iter() {
483 hook.before_teardown(self)?;
484 }
485
486 for hook in self.async_lifecycle_hooks.read().iter() {
487 hook.before_teardown_async(self).await?;
488 }
489
490 {
491 let handlers = self.async_cleanup_handlers.write();
492 for handler in handlers.iter().rev() {
493 handler().await;
494 }
495 }
496
497 {
498 let handlers = self.cleanup_handlers.write();
499 for handler in handlers.iter().rev() {
500 handler();
501 }
502 }
503
504 self.storage.write().clear();
505
506 for hook in self.async_lifecycle_hooks.read().iter() {
507 hook.after_teardown_async(self).await?;
508 }
509
510 for hook in self.lifecycle_hooks.read().iter() {
511 hook.after_teardown(self)?;
512 }
513
514 Ok(())
515 })
516 .await;
517
518 let mut state = self.state.write();
519 *state = TestEnvState::Destroyed;
520 result
521 }
522
523 pub fn state(&self) -> TestEnvState {
534 self.state.read().clone()
535 }
536
537 pub fn elapsed(&self) -> Duration {
541 self.created_at.elapsed()
542 }
543
544 pub fn initialized_elapsed(&self) -> Option<Duration> {
548 self.initialized_at.read().map(|t| t.elapsed())
549 }
550
551 pub fn add_lifecycle_hook<H>(&self, hook: H)
573 where
574 H: TestLifecycleHook + 'static,
575 {
576 self.lifecycle_hooks.write().push(Box::new(hook));
577 }
578
579 pub fn add_async_lifecycle_hook<H>(&self, hook: H)
583 where
584 H: AsyncTestLifecycleHook + 'static,
585 {
586 self.async_lifecycle_hooks.write().push(Box::new(hook));
587 }
588
589 pub fn on_cleanup<F>(&self, handler: F)
602 where
603 F: Fn() + Send + Sync + 'static,
604 {
605 self.cleanup_handlers.write().push(Box::new(handler));
606 }
607
608 pub fn on_cleanup_async<F, Fut>(&self, handler: F)
612 where
613 F: Fn() -> Fut + Send + Sync + 'static,
614 Fut: Future<Output = ()> + Send + 'static,
615 {
616 self.async_cleanup_handlers.write().push(Box::new(move || Box::pin(handler())));
617 }
618
619 pub fn set<T: 'static + Send + Sync>(&self, key: &str, value: T) {
632 self.storage.write().insert(key.to_string(), Box::new(value));
633 }
634
635 pub fn get<T: 'static + Clone>(&self, key: &str) -> Option<T> {
650 let storage = self.storage.read();
651 storage.get(key).and_then(|v| v.downcast_ref::<T>().cloned())
652 }
653
654 pub fn remove<T: 'static>(&self, key: &str) -> Option<T> {
658 let mut storage = self.storage.write();
659 storage.remove(key).and_then(|v| v.downcast::<T>().ok()).map(|v| *v)
660 }
661
662 pub fn has(&self, key: &str) -> bool {
664 self.storage.read().contains_key(key)
665 }
666
667 pub fn config(&self) -> &TestEnvConfig {
679 &self.config
680 }
681
682 pub fn add_service(&self, service_config: TestServiceConfig) {
686 self.services.write().insert(service_config.name.clone(), service_config);
687 }
688
689 pub fn get_service(&self, name: &str) -> Option<TestServiceConfig> {
693 self.services.read().get(name).cloned()
694 }
695
696 pub fn enabled_services(&self) -> Vec<TestServiceConfig> {
700 self.services.read().values().filter(|s| s.enabled).cloned().collect()
701 }
702
703 pub async fn with_fixture<F, R>(&self, fixture: F) -> TestingResult<R>
711 where
712 F: FnOnce() -> TestingResult<R>,
713 {
714 self.setup()?;
715
716 let result = fixture();
717
718 self.teardown()?;
719
720 result
721 }
722
723 pub async fn run_test<F, Fut>(&self, test: F) -> TestingResult<()>
740 where
741 F: FnOnce() -> Fut,
742 Fut: Future<Output = TestingResult<()>>,
743 {
744 self.setup()?;
745
746 let result = test().await;
747
748 self.teardown()?;
749
750 result
751 }
752
753 pub async fn run_test_async<F, Fut>(&self, test: F) -> TestingResult<()>
761 where
762 F: FnOnce() -> Fut,
763 Fut: Future<Output = TestingResult<()>>,
764 {
765 self.setup_async().await?;
766
767 let result = test().await;
768
769 self.teardown_async().await?;
770
771 result
772 }
773}
774
775impl Drop for TestEnv {
776 fn drop(&mut self) {
777 let state = self.state.read().clone();
778 if state == TestEnvState::Initialized {
779 let _ = self.teardown();
780 }
781 }
782}
783
784pub struct TestEnvBuilder {
788 config: TestEnvConfig,
789 lifecycle_hooks: Vec<Box<dyn TestLifecycleHook>>,
790 async_lifecycle_hooks: Vec<Box<dyn AsyncTestLifecycleHook>>,
791 services: Vec<TestServiceConfig>,
792}
793
794impl TestEnvBuilder {
795 pub fn new() -> Self {
805 Self {
806 config: TestEnvConfig::default(),
807 lifecycle_hooks: Vec::new(),
808 async_lifecycle_hooks: Vec::new(),
809 services: Vec::new(),
810 }
811 }
812
813 pub fn name(mut self, name: impl Into<String>) -> Self {
823 self.config.name = name.into();
824 self
825 }
826
827 pub fn with_logging(mut self, enable: bool) -> Self {
837 self.config.enable_logging = enable;
838 self
839 }
840
841 pub fn with_tracing(mut self, enable: bool) -> Self {
851 self.config.enable_tracing = enable;
852 self
853 }
854
855 pub fn timeout(mut self, timeout: Duration) -> Self {
866 self.config.default_timeout = timeout;
867 self
868 }
869
870 pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
880 self.config.custom.insert(key.into(), value.into());
881 self
882 }
883
884 pub fn with_lifecycle_hook<H>(mut self, hook: H) -> Self
898 where
899 H: TestLifecycleHook + 'static,
900 {
901 self.lifecycle_hooks.push(Box::new(hook));
902 self
903 }
904
905 pub fn with_async_lifecycle_hook<H>(mut self, hook: H) -> Self
907 where
908 H: AsyncTestLifecycleHook + 'static,
909 {
910 self.async_lifecycle_hooks.push(Box::new(hook));
911 self
912 }
913
914 pub fn with_service(mut self, service: TestServiceConfig) -> Self {
926 self.services.push(service);
927 self
928 }
929
930 pub fn with_services<I>(mut self, services: I) -> Self
934 where
935 I: IntoIterator<Item = TestServiceConfig>,
936 {
937 self.services.extend(services);
938 self
939 }
940
941 pub fn build(self) -> TestEnv {
953 let env = TestEnv::new(self.config);
954
955 for hook in self.lifecycle_hooks {
956 env.lifecycle_hooks.write().push(hook);
957 }
958
959 for hook in self.async_lifecycle_hooks {
960 env.async_lifecycle_hooks.write().push(hook);
961 }
962
963 for service in self.services {
964 env.add_service(service);
965 }
966
967 env
968 }
969}
970
971impl Default for TestEnvBuilder {
972 fn default() -> Self {
973 Self::new()
974 }
975}
976
977pub fn create_test_env() -> TestEnv {
989 TestEnv::default_env()
990}
991
992pub fn create_test_env_with_config(config: TestEnvConfig) -> TestEnv {
1005 TestEnv::new(config)
1006}