Update tests, examples, benches
This commit is contained in:
1 parent
bacac903b3
commit
85ccd7e507
89 files changed
+942
-1785
No files matched your search
+4
-2
@@ -31,6 +31,7 @@ impl IsUnitType for () {}
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use dagx::{task, DagRunner, Task};
|
||||
/// # use futures::FutureExt;
|
||||
/// // Using tuple struct for simple constants
|
||||
/// struct Constant(i32);
|
||||
///
|
||||
@@ -65,7 +66,7 @@ impl IsUnitType for () {}
|
||||
/// let b = b.depends_on(&a);
|
||||
/// // Now b is a TaskHandle<i32>
|
||||
///
|
||||
/// dag.run().await.unwrap();
|
||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
/// assert_eq!(dag.get(&b).unwrap(), 20);
|
||||
/// # };
|
||||
/// ```
|
||||
@@ -89,6 +90,7 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use dagx::{task, DagRunner, Task};
|
||||
/// # use futures::FutureExt;
|
||||
/// // Tuple struct
|
||||
/// struct Value(i32);
|
||||
///
|
||||
@@ -125,7 +127,7 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
|
||||
/// // Multiple dependencies: tuple form
|
||||
/// let sum = dag.add_task(Add).depends_on((&x, &y));
|
||||
///
|
||||
/// dag.run().await.unwrap();
|
||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn depends_on<D>(self, deps: D) -> TaskHandle<Tk::Output>
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ impl std::fmt::Display for DagError {
|
||||
f,
|
||||
"Result not found for task #{}.\n\
|
||||
\n\
|
||||
Call dag.run().",
|
||||
Call dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).",
|
||||
task_id
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ fn test_dag_error_display_result_not_found() {
|
||||
|
||||
assert!(display.contains("Result not found"));
|
||||
assert!(display.contains("task #7"));
|
||||
assert!(display.contains("Call dag.run()"));
|
||||
assert!(display.contains("Call dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+37
-119
@@ -5,7 +5,6 @@
|
||||
//! We directly call extract_from_deps with mismatched types.
|
||||
|
||||
use crate::extract::ExtractInput;
|
||||
use futures::channel::oneshot;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -13,17 +12,10 @@ use std::sync::Arc;
|
||||
#[tokio::test]
|
||||
async fn test_result_type_mismatch_coverage() {
|
||||
// Tests line 95 in extract.rs - Result<T,E> type mismatch error message
|
||||
// Create a channel that sends the WRONG type (String instead of Result<i32, String>)
|
||||
let (tx, rx) = oneshot::channel::<Arc<String>>();
|
||||
|
||||
// Send a String value
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new("hello".to_string()));
|
||||
});
|
||||
|
||||
// Create a dependency that provides the WRONG type (String instead of Result<i32, String>)
|
||||
// Try to extract as Result<i32, String> - this will fail with type mismatch
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Result::<i32, String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new("hello") as Arc<dyn Any + Send + Sync>];
|
||||
let result = Result::<i32, String>::extract_from_deps(deps).await;
|
||||
|
||||
// Verify the error message contains the expected type information
|
||||
assert!(result.is_err());
|
||||
@@ -45,17 +37,10 @@ async fn test_result_type_mismatch_coverage() {
|
||||
#[tokio::test]
|
||||
async fn test_option_type_mismatch_coverage() {
|
||||
// Tests line 124 in extract.rs - Option<T> type mismatch error message
|
||||
// Create a channel that sends the WRONG type (i32 instead of Option<i32>)
|
||||
let (tx, rx) = oneshot::channel::<Arc<i32>>();
|
||||
|
||||
// Send an i32 value
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(42));
|
||||
});
|
||||
|
||||
// Create a dependency that provides the WRONG type (i32 instead of Option<i32>)
|
||||
// Try to extract as Option<i32> - this will fail with type mismatch
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Option::<i32>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(42) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Option::<i32>::extract_from_deps(deps).await;
|
||||
|
||||
// Verify the error message contains the expected type information
|
||||
assert!(result.is_err());
|
||||
@@ -77,17 +62,10 @@ async fn test_option_type_mismatch_coverage() {
|
||||
#[tokio::test]
|
||||
async fn test_vec_type_mismatch_coverage() {
|
||||
// Tests line 152 in extract.rs - Vec<T> type mismatch error message
|
||||
// Create a channel that sends the WRONG type (String instead of Vec<u8>)
|
||||
let (tx, rx) = oneshot::channel::<Arc<String>>();
|
||||
|
||||
// Send a String value
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new("not a vec".to_string()));
|
||||
});
|
||||
|
||||
// Create a dependency that provides the WRONG type (String instead of Vec<u8>)
|
||||
// Try to extract as Vec<u8> - this will fail with type mismatch
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Vec::<u8>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new("not a vec".to_string()) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Vec::<u8>::extract_from_deps(deps).await;
|
||||
|
||||
// Verify the error message contains the expected type information
|
||||
assert!(result.is_err());
|
||||
@@ -109,17 +87,10 @@ async fn test_vec_type_mismatch_coverage() {
|
||||
#[tokio::test]
|
||||
async fn test_hashmap_type_mismatch_coverage() {
|
||||
// Tests line 181 in extract.rs - HashMap<K,V> type mismatch error message
|
||||
// Create a channel that sends the WRONG type (Vec<i32> instead of HashMap)
|
||||
let (tx, rx) = oneshot::channel::<Arc<Vec<i32>>>();
|
||||
|
||||
// Send a Vec value
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(vec![1, 2, 3]));
|
||||
});
|
||||
|
||||
// Create a dependency that provides the WRONG type (Vec<i32> instead of HashMap)
|
||||
// Try to extract as HashMap<String, i32> - this will fail with type mismatch
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = HashMap::<String, i32>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(vec![1, 2, 3]) as Arc<dyn Any + Send + Sync>];
|
||||
let result = HashMap::<String, i32>::extract_from_deps(deps).await;
|
||||
|
||||
// Verify the error message contains the expected type information
|
||||
assert!(result.is_err());
|
||||
@@ -141,12 +112,12 @@ async fn test_hashmap_type_mismatch_coverage() {
|
||||
#[tokio::test]
|
||||
async fn test_arc_type_mismatch_coverage() {
|
||||
// Tests line 211 in extract.rs - Arc<T> type mismatch error message
|
||||
// Arc is special: it expects Arc<Arc<T>> from dependency
|
||||
// Arc is special: it expects Arc<Arc<T>> from channel
|
||||
// Create a dependency that provides Arc<String> instead of Arc<Arc<String>>
|
||||
let deps = vec![Arc::new("not double arc".to_string()) as Arc<dyn Any + Send + Sync>];
|
||||
|
||||
// Try to extract as Arc<String> - this will fail with type mismatch
|
||||
// because it expects Arc<Arc<String>> in the dependency
|
||||
// because it expects Arc<Arc<String>> in the channel
|
||||
let result = Arc::<String>::extract_from_deps(deps).await;
|
||||
|
||||
// Verify the error message contains the expected type information
|
||||
@@ -171,15 +142,9 @@ async fn test_arc_type_mismatch_coverage() {
|
||||
#[tokio::test]
|
||||
async fn test_result_with_wrong_ok_type() {
|
||||
// Test Result with wrong Ok type
|
||||
let (tx, rx) = oneshot::channel::<Arc<Result<String, String>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(Ok("test".to_string())));
|
||||
});
|
||||
|
||||
// Try to extract as Result<i32, String> - wrong Ok type
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Result::<i32, String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(Ok::<_, String>("test".to_string())) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Result::<i32, String>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -193,15 +158,9 @@ async fn test_result_with_wrong_ok_type() {
|
||||
#[tokio::test]
|
||||
async fn test_result_with_wrong_err_type() {
|
||||
// Test Result with wrong Err type
|
||||
let (tx, rx) = oneshot::channel::<Arc<Result<i32, i32>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(Err(42)));
|
||||
});
|
||||
|
||||
// Try to extract as Result<i32, String> - wrong Err type
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Result::<i32, String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(Err::<i32, i32>(42)) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Result::<i32, String>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -215,15 +174,9 @@ async fn test_result_with_wrong_err_type() {
|
||||
#[tokio::test]
|
||||
async fn test_option_with_wrong_inner_type() {
|
||||
// Test Option with wrong inner type
|
||||
let (tx, rx) = oneshot::channel::<Arc<Option<String>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(Some("test".to_string())));
|
||||
});
|
||||
|
||||
// Try to extract as Option<i32> - wrong inner type
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Option::<i32>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(Some("test".to_string())) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Option::<i32>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -237,15 +190,9 @@ async fn test_option_with_wrong_inner_type() {
|
||||
#[tokio::test]
|
||||
async fn test_vec_with_wrong_element_type() {
|
||||
// Test Vec with wrong element type
|
||||
let (tx, rx) = oneshot::channel::<Arc<Vec<String>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(vec!["a".to_string(), "b".to_string()]));
|
||||
});
|
||||
|
||||
// Try to extract as Vec<i32> - wrong element type
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Vec::<i32>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(vec!["a".to_string(), "b".to_string()]) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Vec::<i32>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -259,17 +206,11 @@ async fn test_vec_with_wrong_element_type() {
|
||||
#[tokio::test]
|
||||
async fn test_hashmap_with_wrong_key_type() {
|
||||
// Test HashMap with wrong key type
|
||||
let (tx, rx) = oneshot::channel::<Arc<HashMap<i32, String>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(1, "one".to_string());
|
||||
let _ = tx.send(Arc::new(map));
|
||||
});
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(1, "one".to_string());
|
||||
// Try to extract as HashMap<String, String> - wrong key type
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = HashMap::<String, String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(map) as Arc<dyn Any + Send + Sync>];
|
||||
let result = HashMap::<String, String>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -283,17 +224,11 @@ async fn test_hashmap_with_wrong_key_type() {
|
||||
#[tokio::test]
|
||||
async fn test_hashmap_with_wrong_value_type() {
|
||||
// Test HashMap with wrong value type
|
||||
let (tx, rx) = oneshot::channel::<Arc<HashMap<String, i32>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("key".to_string(), 42);
|
||||
let _ = tx.send(Arc::new(map));
|
||||
});
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("key".to_string(), 42);
|
||||
// Try to extract as HashMap<String, String> - wrong value type
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = HashMap::<String, String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(map) as Arc<dyn Any + Send + Sync>];
|
||||
let result = HashMap::<String, String>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -308,15 +243,9 @@ async fn test_hashmap_with_wrong_value_type() {
|
||||
async fn test_arc_with_wrong_inner_type() {
|
||||
// Test Arc with completely wrong inner type
|
||||
// Send Arc<i32> but expect Arc<String>
|
||||
let (tx, rx) = oneshot::channel::<Arc<Arc<i32>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(Arc::new(42)));
|
||||
});
|
||||
|
||||
// Try to extract as Arc<String> - wrong inner type
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Arc::<String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(Arc::new(Arc::new(42))) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Arc::<String>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -330,15 +259,9 @@ async fn test_arc_with_wrong_inner_type() {
|
||||
#[tokio::test]
|
||||
async fn test_primitive_type_mismatch_for_completeness() {
|
||||
// Test primitive type mismatch (already covered in existing tests but good for completeness)
|
||||
let (tx, rx) = oneshot::channel::<Arc<String>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new("not an i32".to_string()));
|
||||
});
|
||||
|
||||
// Try to extract as i32 - type mismatch
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = i32::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new("not an i32".to_string()) as Arc<dyn Any + Send + Sync>];
|
||||
let result = i32::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
@@ -352,15 +275,10 @@ async fn test_primitive_type_mismatch_for_completeness() {
|
||||
#[tokio::test]
|
||||
async fn test_complex_nested_result_option() {
|
||||
// Test complex nested type: send Result<String, String>, expect Result<Option<i32>, String>
|
||||
let (tx, rx) = oneshot::channel::<Arc<Result<String, String>>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(Ok("test".to_string())));
|
||||
});
|
||||
|
||||
// Try to extract as Result<Option<i32>, String> - nested type mismatch
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Result::<Option<i32>, String>::extract_from_channels(deps).await;
|
||||
let deps =
|
||||
vec![Arc::new(Ok::<String, String>("test".to_string())) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Result::<Option<i32>, String>::extract_from_deps(deps).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err();
|
||||
|
||||
+50
-130
@@ -38,11 +38,11 @@ impl TestTaskWithInput {
|
||||
async fn test_extract_result_type_single_dependency_error() {
|
||||
// Test Result<T,E> with wrong dependency count
|
||||
let deps = vec![
|
||||
Box::new(oneshot::channel::<Result<i32, String>>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<Result<i32, String>>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(Ok::<i32, String>(5)) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(Err::<i32, String>("test".to_string())) as Arc<dyn Any + Send + Sync>,
|
||||
]; // Two dependencies when expecting 1
|
||||
|
||||
let result = Result::<i32, String>::extract_from_channels(deps).await;
|
||||
let result = Result::<i32, String>::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
|
||||
}
|
||||
@@ -50,15 +50,9 @@ async fn test_extract_result_type_single_dependency_error() {
|
||||
#[tokio::test]
|
||||
async fn test_extract_result_type_success() {
|
||||
// Test Result<T,E> successful extraction
|
||||
let (tx, rx) = oneshot::channel::<Arc<Result<i32, String>>>();
|
||||
|
||||
// Send value in background
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(Ok(42)));
|
||||
});
|
||||
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Result::<i32, String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(Ok::<i32, String>(42)) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Result::<i32, String>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), Ok(42));
|
||||
}
|
||||
@@ -67,12 +61,12 @@ async fn test_extract_result_type_success() {
|
||||
async fn test_extract_option_type_single_dependency_error() {
|
||||
// Test Option<T> with wrong dependency count
|
||||
let deps = vec![
|
||||
Box::new(oneshot::channel::<Option<String>>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<Option<String>>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<Option<String>>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(None::<String>) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(None::<String>) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(None::<String>) as Arc<dyn Any + Send + Sync>,
|
||||
]; // Three dependencies when expecting 1
|
||||
|
||||
let result = Option::<String>::extract_from_channels(deps).await;
|
||||
let result = Option::<String>::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Expected 1 dependency, got 3"));
|
||||
}
|
||||
@@ -80,15 +74,8 @@ async fn test_extract_option_type_single_dependency_error() {
|
||||
#[tokio::test]
|
||||
async fn test_extract_option_type_success() {
|
||||
// Test Option<T> successful extraction
|
||||
let (tx, rx) = oneshot::channel::<Arc<Option<i32>>>();
|
||||
|
||||
// Send value in background
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new(Some(99)));
|
||||
});
|
||||
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = Option::<i32>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(Some(99)) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Option::<i32>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), Some(99));
|
||||
}
|
||||
@@ -98,7 +85,7 @@ async fn test_extract_vec_type_single_dependency_error() {
|
||||
// Test Vec<T> with wrong dependency count
|
||||
let deps = vec![]; // Zero dependencies when expecting 1
|
||||
|
||||
let result = Vec::<u8>::extract_from_channels(deps).await;
|
||||
let result = Vec::<u8>::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Expected 1 dependency, got 0"));
|
||||
}
|
||||
@@ -106,15 +93,8 @@ async fn test_extract_vec_type_single_dependency_error() {
|
||||
#[tokio::test]
|
||||
async fn test_extract_vec_type_success() {
|
||||
// Test Vec<T> successful extraction
|
||||
let (tx, rx) = oneshot::channel::<Arc<Vec<i32>>>();
|
||||
|
||||
// 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<dyn Any + Send>];
|
||||
let result = Vec::<i32>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(vec![1, 2, 3]) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Vec::<i32>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), vec![1, 2, 3]);
|
||||
}
|
||||
@@ -123,29 +103,29 @@ async fn test_extract_vec_type_success() {
|
||||
async fn test_extract_primitive_single_dependency_error() {
|
||||
// Test primitive types with wrong dependency count
|
||||
let deps = vec![
|
||||
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
]; // Two dependencies when expecting 1
|
||||
|
||||
let result = i32::extract_from_channels(deps).await;
|
||||
let result = i32::extract_from_deps(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::<String>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<String>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(String::default()) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(String::default()) as Arc<dyn Any + Send + Sync>,
|
||||
];
|
||||
let result = String::extract_from_channels(deps).await;
|
||||
let result = String::extract_from_deps(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::<f64>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<f64>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(0.0) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(0.0) as Arc<dyn Any + Send + Sync>,
|
||||
];
|
||||
let result = f64::extract_from_channels(deps).await;
|
||||
let result = f64::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
|
||||
}
|
||||
@@ -154,58 +134,36 @@ async fn test_extract_primitive_single_dependency_error() {
|
||||
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;
|
||||
let result = <()>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Even with dependencies (ignored), unit type succeeds
|
||||
let deps = vec![
|
||||
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
];
|
||||
let result = <()>::extract_from_channels(deps).await;
|
||||
let result = <()>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_extract_tuple_basic() {
|
||||
// Test tuple extraction with channels
|
||||
let (tx1, rx1) = oneshot::channel::<Arc<i32>>();
|
||||
let (tx2, rx2) = oneshot::channel::<Arc<i32>>();
|
||||
|
||||
// Send values in background
|
||||
tokio::spawn(async move {
|
||||
let _ = tx1.send(Arc::new(42));
|
||||
let _ = tx2.send(Arc::new(100));
|
||||
});
|
||||
|
||||
// Test tuple extraction
|
||||
let deps = vec![
|
||||
Box::new(rx1) as Box<dyn Any + Send>,
|
||||
Box::new(rx2) as Box<dyn Any + Send>,
|
||||
Arc::new(42) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(100) as Arc<dyn Any + Send + Sync>,
|
||||
];
|
||||
|
||||
// Extract a tuple (i32, i32)
|
||||
let result = <(i32, i32)>::extract_from_channels(deps).await;
|
||||
let result = <(i32, i32)>::extract_from_deps(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::<Arc<i32>>();
|
||||
// Explicitly drop tx to close the channel
|
||||
drop(tx);
|
||||
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
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
|
||||
// Try to extract as wrong type - put wrong receiver type in deps
|
||||
let deps = vec![Arc::new("hello".to_string()) as Arc<dyn Any + Send + Sync>];
|
||||
let result = i32::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
@@ -217,14 +175,8 @@ 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::<Arc<$t>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = tx.send(Arc::new($val));
|
||||
});
|
||||
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = <$t>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new($val) as Arc<dyn Any + Send + Sync>];
|
||||
let result = <$t>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), $val);
|
||||
};
|
||||
@@ -240,31 +192,16 @@ async fn test_extract_all_primitive_types() {
|
||||
#[tokio::test]
|
||||
async fn test_extract_larger_tuples() {
|
||||
// Test extraction for larger tuple sizes
|
||||
let (tx1, rx1) = oneshot::channel::<Arc<i32>>();
|
||||
let (tx2, rx2) = oneshot::channel::<Arc<i32>>();
|
||||
let (tx3, rx3) = oneshot::channel::<Arc<i32>>();
|
||||
let (tx4, rx4) = oneshot::channel::<Arc<i32>>();
|
||||
let (tx5, rx5) = oneshot::channel::<Arc<i32>>();
|
||||
|
||||
// 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<dyn Any + Send>,
|
||||
Box::new(rx2) as Box<dyn Any + Send>,
|
||||
Box::new(rx3) as Box<dyn Any + Send>,
|
||||
Box::new(rx4) as Box<dyn Any + Send>,
|
||||
Box::new(rx5) as Box<dyn Any + Send>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(1) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(2) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(3) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(4) as Arc<dyn Any + Send + Sync>,
|
||||
];
|
||||
|
||||
// Extract a 5-element tuple
|
||||
let result = <(i32, i32, i32, i32, i32)>::extract_from_channels(deps).await;
|
||||
let result = <(i32, i32, i32, i32, i32)>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), (0, 1, 2, 3, 4));
|
||||
}
|
||||
@@ -272,17 +209,11 @@ async fn test_extract_larger_tuples() {
|
||||
#[tokio::test]
|
||||
async fn test_extract_arc_type() {
|
||||
// Test Arc<T> extraction
|
||||
let (tx, rx) = oneshot::channel::<Arc<Arc<String>>>();
|
||||
|
||||
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<dyn Any + Send>];
|
||||
let result = Arc::<String>::extract_from_channels(deps).await;
|
||||
let deps = vec![Arc::new(test_arc) as Arc<dyn Any + Send + Sync>];
|
||||
let result = Arc::<String>::extract_from_deps(deps).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(*result.unwrap(), *test_arc_clone);
|
||||
}
|
||||
@@ -293,11 +224,11 @@ use std::collections::HashMap;
|
||||
#[tokio::test]
|
||||
async fn test_hashmap_extract_wrong_dependency_count() {
|
||||
let deps = vec![
|
||||
Box::new(oneshot::channel::<Arc<HashMap<String, i32>>>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<Arc<HashMap<String, i32>>>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(HashMap::<String, i32>::default()) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(HashMap::<String, i32>::default()) as Arc<dyn Any + Send + Sync>,
|
||||
];
|
||||
|
||||
let result = HashMap::<String, i32>::extract_from_channels(deps).await;
|
||||
let result = HashMap::<String, i32>::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
|
||||
}
|
||||
@@ -310,17 +241,6 @@ async fn test_hashmap_extract_type_mismatch() {
|
||||
assert!(result.unwrap_err().contains("Type mismatch"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_hashmap_extract_channel_closed() {
|
||||
let (tx, rx) = oneshot::channel::<Arc<HashMap<String, i32>>>();
|
||||
drop(tx);
|
||||
|
||||
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
|
||||
let result = HashMap::<String, i32>::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<dyn Any + Send + Sync>];
|
||||
@@ -348,7 +268,7 @@ async fn test_vec_extract_downcast_failure() {
|
||||
#[tokio::test]
|
||||
async fn test_arc_extract_wrong_dependency_count() {
|
||||
let deps = vec![];
|
||||
let result = Arc::<i32>::extract_from_channels(deps).await;
|
||||
let result = Arc::<i32>::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Expected 1 dependency, got 0"));
|
||||
}
|
||||
@@ -364,12 +284,12 @@ async fn test_arc_extract_downcast_failure() {
|
||||
#[tokio::test]
|
||||
async fn test_tuple_wrong_dependency_count() {
|
||||
let deps = vec![
|
||||
Box::new(oneshot::channel::<Arc<i32>>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<Arc<i32>>().1) as Box<dyn Any + Send>,
|
||||
Box::new(oneshot::channel::<Arc<i32>>().1) as Box<dyn Any + Send>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
Arc::new(0) as Arc<dyn Any + Send + Sync>,
|
||||
];
|
||||
|
||||
let result = <(i32, i32)>::extract_from_channels(deps).await;
|
||||
let result = <(i32, i32)>::extract_from_deps(deps).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
|
||||
+17
-10
@@ -24,6 +24,7 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use dagx::{task, DagRunner, Task};
|
||||
//! use futures::FutureExt; // for FutureExt::map
|
||||
//!
|
||||
//! // Source task with read-only state (tuple struct)
|
||||
//! struct Value(i32);
|
||||
@@ -52,7 +53,7 @@
|
||||
//! let sum = dag.add_task(Add).depends_on((&x, &y));
|
||||
//!
|
||||
//! // Execute and retrieve results
|
||||
//! dag.run().await.unwrap();
|
||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
//! assert_eq!(dag.get(sum).unwrap(), 5);
|
||||
//! # };
|
||||
//! ```
|
||||
@@ -290,6 +291,7 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use dagx::{task, DagRunner, Task};
|
||||
//! use futures::FutureExt;
|
||||
//!
|
||||
//! // Source task (tuple struct)
|
||||
//! struct Value(i32);
|
||||
@@ -322,7 +324,7 @@
|
||||
//! let plus1 = dag.add_task(Add(1)).depends_on(&base);
|
||||
//! let times2 = dag.add_task(Scale(2)).depends_on(&base);
|
||||
//!
|
||||
//! dag.run().await.unwrap();
|
||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
//!
|
||||
//! assert_eq!(dag.get(plus1).unwrap(), 11);
|
||||
//! assert_eq!(dag.get(times2).unwrap(), 20);
|
||||
@@ -335,6 +337,7 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use dagx::{task, DagRunner, Task};
|
||||
//! use futures::FutureExt;
|
||||
//!
|
||||
//! // Source task for String (tuple struct)
|
||||
//! struct Name(String);
|
||||
@@ -378,7 +381,7 @@
|
||||
//! let active = dag.add_task(Active(true));
|
||||
//! let result = dag.add_task(FormatUser).depends_on((&name, &age, &active));
|
||||
//!
|
||||
//! dag.run().await.unwrap();
|
||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
//!
|
||||
//! assert_eq!(dag.get(result).unwrap(), "User: Alice, Age: 30, Active: true");
|
||||
//! # };
|
||||
@@ -390,6 +393,7 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use dagx::{task, DagRunner, Task};
|
||||
//! use futures::FutureExt;
|
||||
//!
|
||||
//! // Source task (tuple struct)
|
||||
//! struct Value(i32);
|
||||
@@ -430,7 +434,7 @@
|
||||
//! // Layer 3: Final result
|
||||
//! let total = dag.add_task(Add).depends_on((&sum_xy, &prod_yz)); // 5 + 15 = 20
|
||||
//!
|
||||
//! dag.run().await.unwrap();
|
||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
//!
|
||||
//! assert_eq!(dag.get(total).unwrap(), 20);
|
||||
//! # };
|
||||
@@ -442,7 +446,8 @@
|
||||
//! the `#[task]` macro generates the necessary extraction logic:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use dagx::{task, DagRunner, Task};
|
||||
//! # use dagx::{task, DagRunner, Task};
|
||||
//! # use futures::FutureExt;
|
||||
//!
|
||||
//! // Just derive Clone - that's all you need!
|
||||
//! #[derive(Clone)]
|
||||
@@ -473,7 +478,7 @@
|
||||
//! let user = dag.add_task(CreateUser);
|
||||
//! let formatted = dag.add_task(FormatUser).depends_on(&user);
|
||||
//!
|
||||
//! dag.run().await.unwrap();
|
||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
//!
|
||||
//! assert_eq!(dag.get(formatted).unwrap(), "Alice is 30 years old");
|
||||
//! # };
|
||||
@@ -530,6 +535,7 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use dagx::{task, DagRunner, Task};
|
||||
//! # use futures::FutureExt;
|
||||
//! # struct Value(i32);
|
||||
//! # #[task]
|
||||
//! # impl Value {
|
||||
@@ -539,11 +545,11 @@
|
||||
//! # let dag = DagRunner::new();
|
||||
//! # let node = dag.add_task(Value(42));
|
||||
//! // Simple approach with .unwrap()
|
||||
//! dag.run().await.unwrap();
|
||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
//! let result = dag.get(node).unwrap();
|
||||
//!
|
||||
//! // Or handle errors explicitly
|
||||
//! match dag.run().await {
|
||||
//! match dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await {
|
||||
//! Ok(_) => println!("DAG executed successfully"),
|
||||
//! Err(e) => eprintln!("DAG execution failed: {}", e),
|
||||
//! }
|
||||
@@ -626,7 +632,8 @@
|
||||
//! You output `T`, the framework handles the Arc wrapping:
|
||||
//!
|
||||
//! ```
|
||||
//! use dagx::{task, DagRunner, Task};
|
||||
//! # use dagx::{task, DagRunner, Task};
|
||||
//! # use futures::FutureExt;
|
||||
//!
|
||||
//! // ✅ CORRECT: Just output Vec<String>, framework wraps in Arc internally
|
||||
//! struct FetchData;
|
||||
@@ -656,7 +663,7 @@
|
||||
//! let task2 = dag.add_task(ProcessData).depends_on(&data);
|
||||
//! let task3 = dag.add_task(ProcessData).depends_on(&data);
|
||||
//!
|
||||
//! dag.run().await.unwrap();
|
||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
|
||||
+13
-63
@@ -1,5 +1,7 @@
|
||||
//! Unit tests for node module
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::node::{ExecutableNode, TypedNode};
|
||||
use crate::task::Task;
|
||||
use crate::types::NodeId;
|
||||
@@ -16,28 +18,6 @@ impl TestTask {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_output_channels() {
|
||||
let node = TypedNode::new(NodeId(0), TestTask { value: 42 });
|
||||
|
||||
// Create channels for 3 dependents
|
||||
let (senders, receivers) = node.create_output_channels(3);
|
||||
|
||||
assert_eq!(senders.len(), 3);
|
||||
assert_eq!(receivers.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_output_channels_zero_dependents() {
|
||||
let node = TypedNode::new(NodeId(0), TestTask { value: 42 });
|
||||
|
||||
// Create channels for 0 dependents (sink node)
|
||||
let (senders, receivers) = node.create_output_channels(0);
|
||||
|
||||
assert_eq!(senders.len(), 0);
|
||||
assert_eq!(receivers.len(), 0);
|
||||
}
|
||||
|
||||
struct TaskWithDependency;
|
||||
|
||||
#[crate::task]
|
||||
@@ -48,15 +28,14 @@ impl TaskWithDependency {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_with_channels_missing_input() {
|
||||
async fn test_execute_with_deps_missing_input() {
|
||||
// Test when task expects input but doesn't receive it
|
||||
let node = Box::new(TypedNode::new(NodeId(0), TaskWithDependency));
|
||||
|
||||
// TaskWithDependency expects an i32 input, but we provide no receivers
|
||||
let receivers = vec![];
|
||||
let senders = vec![];
|
||||
// TaskWithDependency expects an i32 input, but we provide no dependencies
|
||||
let dependencies = vec![];
|
||||
|
||||
let result = node.execute_with_channels(receivers, senders).await;
|
||||
let result = node.execute_with_deps(dependencies).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
if let Err(crate::error::DagError::InvalidDependency { task_id }) = result {
|
||||
@@ -76,11 +55,11 @@ async fn test_execute_with_deps_success() {
|
||||
let dep_dependencies = vec![];
|
||||
|
||||
// Execute in background
|
||||
let dep_handle = tokio::spawn(async move {
|
||||
dependency_node
|
||||
.execute_with_channels(dep_receivers, dep_senders)
|
||||
let dep_result =
|
||||
tokio::spawn(async move { dependency_node.execute_with_deps(dep_dependencies).await })
|
||||
.await
|
||||
});
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Execute dependent node with receiver
|
||||
let dependencies = vec![dep_result as Arc<dyn std::any::Any + Send + Sync>];
|
||||
@@ -91,14 +70,14 @@ async fn test_execute_with_deps_success() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_with_channels_sink_node() {
|
||||
async fn test_execute_with_deps_sink_node() {
|
||||
// Test that sink nodes (no dependents) return their output
|
||||
let node = Box::new(TypedNode::new(NodeId(0), TestTask { value: 42 }));
|
||||
|
||||
// No senders means this is a sink node
|
||||
let dependencies = vec![];
|
||||
|
||||
let result = node.execute_with_channels(receivers, senders).await;
|
||||
let result = node.execute_with_deps(dependencies).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Check output was returned (Arc-wrapped)
|
||||
@@ -114,41 +93,12 @@ async fn test_execute_with_deps_non_sink_node() {
|
||||
|
||||
// Execute with senders (non-sink node)
|
||||
let node = Box::new(node);
|
||||
let exec_receivers = vec![];
|
||||
let result = node.execute_with_channels(exec_receivers, senders).await;
|
||||
let result = node.execute_with_deps(vec![]).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify output was sent through channel (Arc-wrapped)
|
||||
let arc_value = result.unwrap().downcast::<i32>().unwrap();
|
||||
|
||||
// Also verify output was returned (Arc-wrapped)
|
||||
let output = result.unwrap();
|
||||
let arc_value = output.downcast::<i32>().unwrap();
|
||||
assert_eq!(*arc_value, 42);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_with_multiple_dependents() {
|
||||
// Test that output is sent to multiple dependents
|
||||
let node = TypedNode::new(NodeId(0), TestTask { value: 100 });
|
||||
|
||||
// Create output channels for three dependents
|
||||
let (senders, receivers) = node.create_output_channels(3);
|
||||
|
||||
// Execute node
|
||||
let node = Box::new(node);
|
||||
let exec_receivers = vec![];
|
||||
let result = node.execute_with_channels(exec_receivers, senders).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify all three dependents receive the output (Arc-wrapped)
|
||||
for receiver in receivers {
|
||||
let rx = receiver
|
||||
.downcast::<futures::channel::oneshot::Receiver<std::sync::Arc<i32>>>()
|
||||
.unwrap();
|
||||
|
||||
let received_arc = rx.await;
|
||||
assert!(received_arc.is_ok());
|
||||
assert_eq!(*received_arc.unwrap(), 100);
|
||||
}
|
||||
}
|
||||
+8
-4
@@ -56,6 +56,7 @@ impl<'a> Drop for RunGuard<'a> {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use dagx::{task, DagRunner, Task};
|
||||
/// # use futures::FutureExt;
|
||||
/// // Task with state constructed via ::new()
|
||||
/// struct LoadValue { value: i32 }
|
||||
///
|
||||
@@ -84,7 +85,7 @@ impl<'a> Drop for RunGuard<'a> {
|
||||
/// let y = dag.add_task(LoadValue::new(3));
|
||||
/// let sum = dag.add_task(Add).depends_on((&x, &y));
|
||||
///
|
||||
/// dag.run().await.unwrap();
|
||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
///
|
||||
/// assert_eq!(dag.get(sum).unwrap(), 5);
|
||||
/// # };
|
||||
@@ -149,6 +150,7 @@ impl DagRunner {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use dagx::{task, DagRunner, Task};
|
||||
/// # use futures::FutureExt;
|
||||
/// // Task with state - shows you construct with specific value
|
||||
/// struct LoadValue {
|
||||
/// initial: i32,
|
||||
@@ -190,7 +192,7 @@ impl DagRunner {
|
||||
/// // Construct task with offset of 1
|
||||
/// let inc = dag.add_task(AddOffset::new(1)).depends_on(&base);
|
||||
///
|
||||
/// dag.run().await.unwrap();
|
||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
/// assert_eq!(dag.get(&inc).unwrap(), 11);
|
||||
/// # };
|
||||
/// ```
|
||||
@@ -254,6 +256,7 @@ impl DagRunner {
|
||||
///
|
||||
/// ```
|
||||
/// # use dagx::{task, DagRunner, Task};
|
||||
/// # use futures::FutureExt;
|
||||
/// // Tuple struct
|
||||
/// struct Value(i32);
|
||||
///
|
||||
@@ -277,7 +280,7 @@ impl DagRunner {
|
||||
/// let b = dag.add_task(Value(2));
|
||||
/// let sum = dag.add_task(Add).depends_on((&a, &b));
|
||||
///
|
||||
/// dag.run().await.unwrap(); // Executes all tasks
|
||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap(); // Executes all tasks
|
||||
/// # };
|
||||
/// ```
|
||||
#[inline]
|
||||
@@ -527,6 +530,7 @@ impl DagRunner {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use dagx::{task, DagRunner, Task};
|
||||
/// # use futures::FutureExt;
|
||||
/// struct Configuration {
|
||||
/// setting: i32,
|
||||
/// }
|
||||
@@ -548,7 +552,7 @@ impl DagRunner {
|
||||
/// // Construct task with specific setting value
|
||||
/// let task = dag.add_task(Configuration::new(42));
|
||||
///
|
||||
/// dag.run().await.unwrap();
|
||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
///
|
||||
/// assert_eq!(dag.get(task).unwrap(), 42);
|
||||
/// # };
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::error::DagError;
|
||||
use crate::runner::DagRunner;
|
||||
use crate::task::Task;
|
||||
use crate::types::TaskHandle;
|
||||
use futures::FutureExt;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -20,11 +21,9 @@ async fn test_get_type_mismatch_error() {
|
||||
// Task outputs i32
|
||||
let source = dag.add_task(task_fn(|_: ()| async { 42i32 }));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to get as String - TYPE MISMATCH!
|
||||
// Create a handle with the same ID but wrong type
|
||||
@@ -56,11 +55,9 @@ async fn test_get_type_mismatch_i32_to_vec() {
|
||||
// Task outputs i32
|
||||
let int_task = dag.add_task(task_fn(|_: ()| async { 123i32 }));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to get as Vec<u8>
|
||||
let wrong_handle: TaskHandle<Vec<u8>> = TaskHandle {
|
||||
@@ -102,11 +99,9 @@ async fn test_get_type_mismatch_struct_to_hashmap() {
|
||||
let dag = DagRunner::new();
|
||||
let task_handle = dag.add_task(CustomTask);
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to get as HashMap
|
||||
let wrong_handle: TaskHandle<HashMap<String, i32>> = TaskHandle {
|
||||
@@ -136,11 +131,9 @@ async fn test_get_type_mismatch_vec_different_types() {
|
||||
// Task outputs Vec<i32>
|
||||
let vec_task = dag.add_task(task_fn(|_: ()| async { vec![1, 2, 3] }));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to get as Vec<String>
|
||||
let wrong_handle: TaskHandle<Vec<String>> = TaskHandle {
|
||||
@@ -170,11 +163,9 @@ async fn test_get_type_mismatch_option_types() {
|
||||
// Task outputs Option<i32>
|
||||
let opt_task = dag.add_task(task_fn(|_: ()| async { Some(42) }));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to get as Option<String>
|
||||
let wrong_handle: TaskHandle<Option<String>> = TaskHandle {
|
||||
@@ -206,11 +197,9 @@ async fn test_multiple_type_mismatches_same_dag() {
|
||||
let string_task = dag.add_task(task_fn(|_: ()| async { "hello".to_string() }));
|
||||
let bool_task = dag.add_task(task_fn(|_: ()| async { true }));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to get int_task as String
|
||||
let wrong_int_handle: TaskHandle<String> = TaskHandle {
|
||||
|
||||
+14
-28
@@ -5,6 +5,8 @@ use crate::runner::DagRunner;
|
||||
use crate::task::Task;
|
||||
use crate::types::TaskHandle;
|
||||
|
||||
use futures::FutureExt;
|
||||
|
||||
// Initialize tracing subscriber for tests (idempotent)
|
||||
#[cfg(feature = "tracing")]
|
||||
fn init_tracing() {
|
||||
@@ -68,11 +70,9 @@ async fn test_get_wrong_type() {
|
||||
let dag = DagRunner::new();
|
||||
let handle = dag.add_task(TestTask { value: 42 });
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to get with wrong type - downcast will fail
|
||||
let fake_handle: TaskHandle<String> = TaskHandle {
|
||||
@@ -145,18 +145,12 @@ async fn test_concurrent_run_protection() {
|
||||
// Start two runs concurrently
|
||||
let handle1 = tokio::spawn(async move {
|
||||
barrier1.wait().await;
|
||||
dag1.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
dag1.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await
|
||||
});
|
||||
|
||||
let handle2 = tokio::spawn(async move {
|
||||
barrier2.wait().await;
|
||||
dag2.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
dag2.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await
|
||||
});
|
||||
|
||||
// One should succeed, one should fail (concurrent execution not supported)
|
||||
@@ -205,11 +199,9 @@ async fn test_multiple_get_calls() {
|
||||
let dag = DagRunner::new();
|
||||
let handle = dag.add_task(TestTask { value: 100 });
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Multiple get calls should all return the same value
|
||||
let result1 = dag.get(&handle).unwrap();
|
||||
@@ -233,11 +225,9 @@ async fn test_invalid_node_id_in_get() {
|
||||
_phantom: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = dag.get(invalid_handle);
|
||||
assert!(result.is_err());
|
||||
@@ -288,11 +278,7 @@ async fn test_task_panic_in_multi_task_layer() {
|
||||
let _good_task = dag.add_task(GoodTask).depends_on(&source);
|
||||
|
||||
// Run the DAG - the panic should be caught
|
||||
let result = dag
|
||||
.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await;
|
||||
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||
|
||||
// The run should fail due to the panic
|
||||
// Note: Tokio catches panics in spawned tasks, so the behavior depends on the runtime
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
use crate::task::{task_fn, Task};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Test the task_fn function and TaskFn struct
|
||||
#[tokio::test]
|
||||
@@ -87,7 +88,7 @@ impl Task for CustomTask {
|
||||
#[allow(refining_impl_trait)]
|
||||
fn extract_and_run(
|
||||
self,
|
||||
_receivers: Vec<Box<dyn std::any::Any + Send>>,
|
||||
_dependencies: Vec<Arc<dyn std::any::Any + Send + Sync>>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Self::Output, String>> + Send>> {
|
||||
Box::pin(async move {
|
||||
// This is a test helper - just return an error
|
||||
|
||||
+4
-3
@@ -6,9 +6,9 @@
|
||||
use crate::builder::{IsUnitType, TaskBuilder};
|
||||
use crate::task::Task;
|
||||
|
||||
/// Opaque node identifier (internal use)
|
||||
/// Opaque node identifier
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct NodeId(pub usize);
|
||||
pub struct NodeId(pub usize);
|
||||
|
||||
/// Opaque, typed token for a node's output.
|
||||
///
|
||||
@@ -23,6 +23,7 @@ pub(crate) struct NodeId(pub usize);
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use dagx::{task, DagRunner, Task};
|
||||
/// # use futures::FutureExt;
|
||||
/// # struct LoadValue { value: i32 }
|
||||
/// # impl LoadValue { pub fn new(v: i32) -> Self { Self { value: v } } }
|
||||
/// # #[task]
|
||||
@@ -33,7 +34,7 @@ pub(crate) struct NodeId(pub usize);
|
||||
/// let dag = DagRunner::new();
|
||||
/// let node = dag.add_task(LoadValue::new(42));
|
||||
///
|
||||
/// dag.run().await.unwrap();
|
||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||
///
|
||||
/// assert_eq!(dag.get(node).unwrap(), 42);
|
||||
/// # };
|
||||
|
||||
+16
-25
@@ -4,6 +4,7 @@
|
||||
|
||||
use crate::types::TaskHandle;
|
||||
use crate::{task, DagRunner, Task};
|
||||
use futures::FutureExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_unit_type_taskbuilder_to_handle_conversion() {
|
||||
@@ -31,11 +32,9 @@ async fn test_unit_type_taskbuilder_to_handle_conversion() {
|
||||
// specifically covering line 71
|
||||
let handle: TaskHandle<i32> = builder.into();
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Verify the task ran and we can get its output
|
||||
assert_eq!(dag.get(handle).unwrap(), 42);
|
||||
@@ -81,11 +80,9 @@ async fn test_unit_type_conversion_with_multiple_tasks() {
|
||||
let second_handle: TaskHandle<i32> = second_builder.into();
|
||||
let third_handle: TaskHandle<bool> = third_builder.into();
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Verify all outputs
|
||||
assert_eq!(dag.get(first_handle).unwrap(), "first");
|
||||
@@ -122,11 +119,9 @@ async fn test_unit_struct_with_state() {
|
||||
// Explicit conversion - triggers line 71
|
||||
let handle: TaskHandle<i32> = builder.into();
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(dag.get(handle).unwrap(), 42);
|
||||
}
|
||||
@@ -151,11 +146,9 @@ async fn test_taskbuilder_conversion_assignment() {
|
||||
let handle: TaskHandle<Vec<i32>> = builder.into();
|
||||
let id = handle.id;
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = dag.get(handle).unwrap();
|
||||
assert_eq!(result, vec![1, 2, 3]);
|
||||
@@ -197,11 +190,9 @@ async fn test_mixed_unit_and_dependent_tasks() {
|
||||
// Doubler needs dependencies, so we use it differently
|
||||
let doubler = dag.add_task(Doubler).depends_on(source_handle);
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(dag.get(source_handle).unwrap(), 10);
|
||||
assert_eq!(dag.get(doubler).unwrap(), 20);
|
||||
|
||||
Reference in new issue
Block a user