//! Unit tests for extract module #![allow(dead_code)] // Test helpers generate unused code via macro use crate::extract::ExtractInput; use crate::task::Task; use std::any::Any; use std::sync::Arc; // Test task for error scenarios #[allow(dead_code)] struct TestTask { value: i32, } #[crate::task] #[allow(dead_code)] impl TestTask { #[cfg(not(tarpaulin_include))] async fn run(&self) -> i32 { self.value } } #[allow(dead_code)] struct TestTaskWithInput; #[crate::task] #[allow(dead_code)] impl TestTaskWithInput { #[cfg(not(tarpaulin_include))] async fn run(input: &i32) -> i32 { input * 2 } } #[tokio::test] async fn test_extract_result_type_single_dependency_error() { // Test Result with wrong dependency count let deps = vec![ Box::new(oneshot::channel::>().1) as Box, Box::new(oneshot::channel::>().1) as Box, ]; // Two dependencies when expecting 1 let result = Result::::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 2")); } #[tokio::test] async fn test_extract_result_type_success() { // Test Result successful extraction let (tx, rx) = oneshot::channel::>>(); // Send value in background tokio::spawn(async move { let _ = tx.send(Arc::new(Ok(42))); }); let deps = vec![Box::new(rx) as Box]; let result = Result::::extract_from_channels(deps).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), Ok(42)); } #[tokio::test] async fn test_extract_option_type_single_dependency_error() { // Test Option with wrong dependency count let deps = vec![ Box::new(oneshot::channel::>().1) as Box, Box::new(oneshot::channel::>().1) as Box, Box::new(oneshot::channel::>().1) as Box, ]; // Three dependencies when expecting 1 let result = Option::::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 3")); } #[tokio::test] async fn test_extract_option_type_success() { // Test Option successful extraction let (tx, rx) = oneshot::channel::>>(); // Send value in background tokio::spawn(async move { let _ = tx.send(Arc::new(Some(99))); }); let deps = vec![Box::new(rx) as Box]; let result = Option::::extract_from_channels(deps).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), Some(99)); } #[tokio::test] async fn test_extract_vec_type_single_dependency_error() { // Test Vec with wrong dependency count let deps = vec![]; // Zero dependencies when expecting 1 let result = Vec::::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 0")); } #[tokio::test] async fn test_extract_vec_type_success() { // Test Vec successful extraction let (tx, rx) = oneshot::channel::>>(); // Send value in background tokio::spawn(async move { let _ = tx.send(Arc::new(vec![1, 2, 3])); }); let deps = vec![Box::new(rx) as Box]; let result = Vec::::extract_from_channels(deps).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), vec![1, 2, 3]); } #[tokio::test] async fn test_extract_primitive_single_dependency_error() { // Test primitive types with wrong dependency count let deps = vec![ Box::new(oneshot::channel::().1) as Box, Box::new(oneshot::channel::().1) as Box, ]; // Two dependencies when expecting 1 let result = i32::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 2")); // Test with String let deps = vec![ Box::new(oneshot::channel::().1) as Box, Box::new(oneshot::channel::().1) as Box, ]; let result = String::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 2")); // Test with f64 let deps = vec![ Box::new(oneshot::channel::().1) as Box, Box::new(oneshot::channel::().1) as Box, ]; let result = f64::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 2")); } #[tokio::test] async fn test_extract_unit_type() { // Test that unit type always succeeds regardless of dependencies let deps = vec![]; let result = <()>::extract_from_channels(deps).await; assert!(result.is_ok()); // Even with dependencies (ignored), unit type succeeds let deps = vec![ Box::new(oneshot::channel::().1) as Box, Box::new(oneshot::channel::().1) as Box, ]; let result = <()>::extract_from_channels(deps).await; assert!(result.is_ok()); } #[tokio::test] async fn test_extract_tuple_basic() { // Test tuple extraction with channels let (tx1, rx1) = oneshot::channel::>(); let (tx2, rx2) = oneshot::channel::>(); // Send values in background tokio::spawn(async move { let _ = tx1.send(Arc::new(42)); let _ = tx2.send(Arc::new(100)); }); let deps = vec![ Box::new(rx1) as Box, Box::new(rx2) as Box, ]; // Extract a tuple (i32, i32) let result = <(i32, i32)>::extract_from_channels(deps).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), (42, 100)); } #[tokio::test] async fn test_extract_channel_closed() { // Test error when channel is closed let (tx, rx) = oneshot::channel::>(); // Explicitly drop tx to close the channel drop(tx); let deps = vec![Box::new(rx) as Box]; let result = i32::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Channel closed")); } #[tokio::test] async fn test_extract_type_mismatch() { // Test type mismatch during downcast // Try to extract as wrong type - put wrong parameter type in deps let deps = vec![Arc::new("hello".to_string()) as Arc]; let result = i32::extract_from_deps(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Type mismatch")); } #[tokio::test] async fn test_extract_all_primitive_types() { // Test that all primitive types in impl_extract_single! work correctly macro_rules! test_type { ($t:ty, $val:expr) => { let (tx, rx) = oneshot::channel::>(); tokio::spawn(async move { let _ = tx.send(Arc::new($val)); }); let deps = vec![Box::new(rx) as Box]; let result = <$t>::extract_from_channels(deps).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), $val); }; } test_type!(i8, 42i8); test_type!(i16, 42i16); test_type!(i32, 42i32); test_type!(i64, 42i64); test_type!(String, "test".to_string()); } #[tokio::test] async fn test_extract_larger_tuples() { // Test extraction for larger tuple sizes let (tx1, rx1) = oneshot::channel::>(); let (tx2, rx2) = oneshot::channel::>(); let (tx3, rx3) = oneshot::channel::>(); let (tx4, rx4) = oneshot::channel::>(); let (tx5, rx5) = oneshot::channel::>(); // Send values in background tokio::spawn(async move { let _ = tx1.send(Arc::new(0)); let _ = tx2.send(Arc::new(1)); let _ = tx3.send(Arc::new(2)); let _ = tx4.send(Arc::new(3)); let _ = tx5.send(Arc::new(4)); }); let deps = vec![ Box::new(rx1) as Box, Box::new(rx2) as Box, Box::new(rx3) as Box, Box::new(rx4) as Box, Box::new(rx5) as Box, ]; // Extract a 5-element tuple let result = <(i32, i32, i32, i32, i32)>::extract_from_channels(deps).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), (0, 1, 2, 3, 4)); } #[tokio::test] async fn test_extract_arc_type() { // Test Arc extraction let (tx, rx) = oneshot::channel::>>(); let test_arc = Arc::new("test".to_string()); let test_arc_clone = test_arc.clone(); tokio::spawn(async move { let _ = tx.send(Arc::new(test_arc)); }); let deps = vec![Box::new(rx) as Box]; let result = Arc::::extract_from_channels(deps).await; assert!(result.is_ok()); assert_eq!(*result.unwrap(), *test_arc_clone); } // Error path tests for ExtractInput implementations use std::collections::HashMap; #[tokio::test] async fn test_hashmap_extract_wrong_dependency_count() { let deps = vec![ Box::new(oneshot::channel::>>().1) as Box, Box::new(oneshot::channel::>>().1) as Box, ]; let result = HashMap::::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 2")); } #[tokio::test] async fn test_hashmap_extract_type_mismatch() { let deps = vec![Arc::new("wrong type".to_string()) as Arc]; let result = HashMap::::extract_from_deps(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Type mismatch")); } #[tokio::test] async fn test_hashmap_extract_channel_closed() { let (tx, rx) = oneshot::channel::>>(); drop(tx); let deps = vec![Box::new(rx) as Box]; let result = HashMap::::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Channel closed")); } #[tokio::test] async fn test_result_extract_downcast_failure() { let deps = vec![Arc::new("wrong".to_string()) as Arc]; let result = Result::::extract_from_deps(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Type mismatch")); } #[tokio::test] async fn test_option_extract_downcast_failure() { let deps = vec![Arc::new("wrong".to_string()) as Arc]; let result = Option::::extract_from_deps(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Type mismatch")); } #[tokio::test] async fn test_vec_extract_downcast_failure() { let deps = vec![Arc::new("wrong".to_string()) as Arc]; let result = Vec::::extract_from_deps(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Type mismatch")); } #[tokio::test] async fn test_arc_extract_wrong_dependency_count() { let deps = vec![]; let result = Arc::::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Expected 1 dependency, got 0")); } #[tokio::test] async fn test_arc_extract_downcast_failure() { let deps = vec![Arc::new("wrong".to_string()) as Arc]; let result = Arc::::extract_from_deps(deps).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Type mismatch")); } #[tokio::test] async fn test_tuple_wrong_dependency_count() { let deps = vec![ Box::new(oneshot::channel::>().1) as Box, Box::new(oneshot::channel::>().1) as Box, Box::new(oneshot::channel::>().1) as Box, ]; let result = <(i32, i32)>::extract_from_channels(deps).await; assert!(result.is_err()); assert!(result .unwrap_err() .contains("Expected 2 dependencies, got 3")); }