diff --git a/src/extract.rs b/src/extract.rs index a926b45..dc0e6ed 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -294,3 +294,6 @@ impl_extract_tuple!(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7); #[cfg(test)] mod tests; + +#[cfg(test)] +mod coverage_tests; diff --git a/src/extract/coverage_tests.rs b/src/extract/coverage_tests.rs new file mode 100644 index 0000000..15ec72e --- /dev/null +++ b/src/extract/coverage_tests.rs @@ -0,0 +1,378 @@ +//! Coverage tests for uncovered error paths in extract.rs +//! +//! These tests specifically target type mismatch error messages that aren't hit +//! in normal usage because the #[task] macro generates type-safe code. +//! We directly call extract_from_channels with mismatched types. + +use crate::extract::ExtractInput; +use futures::channel::oneshot; +use std::any::Any; +use std::collections::HashMap; +use std::sync::Arc; + +#[tokio::test] +async fn test_result_type_mismatch_coverage() { + // Tests line 95 in extract.rs - Result type mismatch error message + // Create a channel that sends the WRONG type (String instead of Result) + let (tx, rx) = oneshot::channel::>(); + + // Send a String value + tokio::spawn(async move { + let _ = tx.send(Arc::new("hello".to_string())); + }); + + // Try to extract as Result - this will fail with type mismatch + let deps = vec![Box::new(rx) as Box]; + let result = Result::::extract_from_channels(deps).await; + + // Verify the error message contains the expected type information + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + + // The error should mention Result type mismatch (line 95) + assert!( + err_msg.contains("Result"), + "Expected type mismatch error mentioning Result, got: {}", + err_msg + ); + assert!( + err_msg.contains("Type mismatch"), + "Expected 'Type mismatch' in error, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_option_type_mismatch_coverage() { + // Tests line 124 in extract.rs - Option type mismatch error message + // Create a channel that sends the WRONG type (i32 instead of Option) + let (tx, rx) = oneshot::channel::>(); + + // Send an i32 value + tokio::spawn(async move { + let _ = tx.send(Arc::new(42)); + }); + + // Try to extract as Option - this will fail with type mismatch + let deps = vec![Box::new(rx) as Box]; + let result = Option::::extract_from_channels(deps).await; + + // Verify the error message contains the expected type information + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + + // The error should mention Option type mismatch (line 124) + assert!( + err_msg.contains("Option"), + "Expected type mismatch error mentioning Option, got: {}", + err_msg + ); + assert!( + err_msg.contains("Type mismatch"), + "Expected 'Type mismatch' in error, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_vec_type_mismatch_coverage() { + // Tests line 152 in extract.rs - Vec type mismatch error message + // Create a channel that sends the WRONG type (String instead of Vec) + let (tx, rx) = oneshot::channel::>(); + + // Send a String value + tokio::spawn(async move { + let _ = tx.send(Arc::new("not a vec".to_string())); + }); + + // Try to extract as Vec - this will fail with type mismatch + let deps = vec![Box::new(rx) as Box]; + let result = Vec::::extract_from_channels(deps).await; + + // Verify the error message contains the expected type information + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + + // The error should mention Vec type mismatch (line 152) + assert!( + err_msg.contains("Vec"), + "Expected type mismatch error mentioning Vec, got: {}", + err_msg + ); + assert!( + err_msg.contains("Type mismatch"), + "Expected 'Type mismatch' in error, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_hashmap_type_mismatch_coverage() { + // Tests line 181 in extract.rs - HashMap type mismatch error message + // Create a channel that sends the WRONG type (Vec instead of HashMap) + let (tx, rx) = oneshot::channel::>>(); + + // Send a Vec value + tokio::spawn(async move { + let _ = tx.send(Arc::new(vec![1, 2, 3])); + }); + + // Try to extract as HashMap - this will fail with type mismatch + let deps = vec![Box::new(rx) as Box]; + let result = HashMap::::extract_from_channels(deps).await; + + // Verify the error message contains the expected type information + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + + // The error should mention HashMap type mismatch (line 181) + assert!( + err_msg.contains("HashMap"), + "Expected type mismatch error mentioning HashMap, got: {}", + err_msg + ); + assert!( + err_msg.contains("Type mismatch"), + "Expected 'Type mismatch' in error, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_arc_type_mismatch_coverage() { + // Tests line 211 in extract.rs - Arc type mismatch error message + // Arc is special: it expects Arc> from channel + // Create a channel that sends Arc instead of Arc> + let (tx, rx) = oneshot::channel::>(); + + // Send an Arc value (missing one level of Arc wrapping) + tokio::spawn(async move { + let _ = tx.send(Arc::new("not double arc".to_string())); + }); + + // Try to extract as Arc - this will fail with type mismatch + // because it expects Arc> in the channel + let deps = vec![Box::new(rx) as Box]; + let result = Arc::::extract_from_channels(deps).await; + + // Verify the error message contains the expected type information + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + + // The error should mention Arc type mismatch (line 211) + assert!( + err_msg.contains("Arc"), + "Expected type mismatch error mentioning Arc, got: {}", + err_msg + ); + assert!( + err_msg.contains("Type mismatch"), + "Expected 'Type mismatch' in error, got: {}", + err_msg + ); +} + +// Additional tests for better coverage of complex scenarios + +#[tokio::test] +async fn test_result_with_wrong_ok_type() { + // Test Result with wrong Ok type + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let _ = tx.send(Arc::new(Ok("test".to_string()))); + }); + + // Try to extract as Result - wrong Ok type + let deps = vec![Box::new(rx) as Box]; + let result = Result::::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Result") && err_msg.contains("Type mismatch"), + "Expected Result type mismatch, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_result_with_wrong_err_type() { + // Test Result with wrong Err type + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let _ = tx.send(Arc::new(Err(42))); + }); + + // Try to extract as Result - wrong Err type + let deps = vec![Box::new(rx) as Box]; + let result = Result::::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Result") && err_msg.contains("Type mismatch"), + "Expected Result type mismatch, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_option_with_wrong_inner_type() { + // Test Option with wrong inner type + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let _ = tx.send(Arc::new(Some("test".to_string()))); + }); + + // Try to extract as Option - wrong inner type + let deps = vec![Box::new(rx) as Box]; + let result = Option::::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Option") && err_msg.contains("Type mismatch"), + "Expected Option type mismatch, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_vec_with_wrong_element_type() { + // Test Vec with wrong element type + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let _ = tx.send(Arc::new(vec!["a".to_string(), "b".to_string()])); + }); + + // Try to extract as Vec - wrong element type + let deps = vec![Box::new(rx) as Box]; + let result = Vec::::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Vec") && err_msg.contains("Type mismatch"), + "Expected Vec type mismatch, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_hashmap_with_wrong_key_type() { + // Test HashMap with wrong key type + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let mut map = HashMap::new(); + map.insert(1, "one".to_string()); + let _ = tx.send(Arc::new(map)); + }); + + // Try to extract as HashMap - wrong key type + let deps = vec![Box::new(rx) as Box]; + let result = HashMap::::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("HashMap") && err_msg.contains("Type mismatch"), + "Expected HashMap type mismatch, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_hashmap_with_wrong_value_type() { + // Test HashMap with wrong value type + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let mut map = HashMap::new(); + map.insert("key".to_string(), 42); + let _ = tx.send(Arc::new(map)); + }); + + // Try to extract as HashMap - wrong value type + let deps = vec![Box::new(rx) as Box]; + let result = HashMap::::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("HashMap") && err_msg.contains("Type mismatch"), + "Expected HashMap type mismatch, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_arc_with_wrong_inner_type() { + // Test Arc with completely wrong inner type + // Send Arc but expect Arc + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let _ = tx.send(Arc::new(Arc::new(42))); + }); + + // Try to extract as Arc - wrong inner type + let deps = vec![Box::new(rx) as Box]; + let result = Arc::::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Arc") && err_msg.contains("Type mismatch"), + "Expected Arc type mismatch, got: {}", + err_msg + ); +} + +#[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::>(); + + 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]; + let result = i32::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Type mismatch"), + "Expected type mismatch error, got: {}", + err_msg + ); +} + +#[tokio::test] +async fn test_complex_nested_result_option() { + // Test complex nested type: send Result, expect Result, String> + let (tx, rx) = oneshot::channel::>>(); + + tokio::spawn(async move { + let _ = tx.send(Arc::new(Ok("test".to_string()))); + }); + + // Try to extract as Result, String> - nested type mismatch + let deps = vec![Box::new(rx) as Box]; + let result = Result::, String>::extract_from_channels(deps).await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err(); + assert!( + err_msg.contains("Result") && err_msg.contains("Type mismatch"), + "Expected Result type mismatch, got: {}", + err_msg + ); +} diff --git a/src/runner.rs b/src/runner.rs index f75aa35..65258bb 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -696,3 +696,6 @@ impl DagRunner { #[cfg(test)] mod tests; + +#[cfg(test)] +mod coverage_tests; diff --git a/src/runner/coverage_tests.rs b/src/runner/coverage_tests.rs new file mode 100644 index 0000000..7ad7bb8 --- /dev/null +++ b/src/runner/coverage_tests.rs @@ -0,0 +1,249 @@ +//! Coverage tests for runner module - specifically targeting uncovered error paths +//! +//! These tests ensure we hit the type mismatch error path in get() (line 601) + +use crate::error::DagError; +use crate::runner::DagRunner; +use crate::task::Task; +use crate::types::TaskHandle; +use std::collections::HashMap; + +#[tokio::test] +async fn test_get_type_mismatch_error() { + // Test the type mismatch error path in get() - line 601 in runner.rs + // This occurs when Arc downcast fails due to type mismatch + + use crate::task_fn; + + let dag = DagRunner::new(); + + // Task outputs i32 + let source = dag.add_task(task_fn(|_: ()| async { 42i32 })); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + // Try to get as String - TYPE MISMATCH! + // Create a handle with the same ID but wrong type + let wrong_handle: TaskHandle = TaskHandle { + id: source.id, + _phantom: std::marker::PhantomData, + }; + + let result = dag.get(wrong_handle); + + assert!(result.is_err()); + let err = result.unwrap_err(); + match err { + DagError::TypeMismatch { expected, found } => { + assert_eq!(expected, std::any::type_name::()); + assert_eq!(found, "unknown"); // This covers line 601! + } + _ => panic!("Expected TypeMismatch error, got: {:?}", err), + } +} + +#[tokio::test] +async fn test_get_type_mismatch_i32_to_vec() { + // Different type mismatch: i32 to Vec + use crate::task_fn; + + let dag = DagRunner::new(); + + // Task outputs i32 + let int_task = dag.add_task(task_fn(|_: ()| async { 123i32 })); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + // Try to get as Vec + let wrong_handle: TaskHandle> = TaskHandle { + id: int_task.id, + _phantom: std::marker::PhantomData, + }; + + let result = dag.get(wrong_handle); + + assert!(result.is_err()); + match result.unwrap_err() { + DagError::TypeMismatch { expected, found } => { + assert_eq!(expected, std::any::type_name::>()); + assert_eq!(found, "unknown"); + } + _ => panic!("Expected TypeMismatch error"), + } +} + +#[tokio::test] +async fn test_get_type_mismatch_struct_to_hashmap() { + // Complex type mismatch: custom struct to HashMap + + #[derive(Clone)] + #[allow(dead_code)] + struct CustomOutput { + value: i32, + } + + struct CustomTask; + + #[crate::task] + impl CustomTask { + async fn run(&self) -> CustomOutput { + CustomOutput { value: 42 } + } + } + + let dag = DagRunner::new(); + let task_handle = dag.add_task(CustomTask); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + // Try to get as HashMap + let wrong_handle: TaskHandle> = TaskHandle { + id: task_handle.id, + _phantom: std::marker::PhantomData, + }; + + let result = dag.get(wrong_handle); + + assert!(result.is_err()); + match result.unwrap_err() { + DagError::TypeMismatch { expected, found } => { + assert!(expected.contains("HashMap")); + assert_eq!(found, "unknown"); + } + _ => panic!("Expected TypeMismatch error"), + } +} + +#[tokio::test] +async fn test_get_type_mismatch_vec_different_types() { + // Type mismatch between Vec and Vec + use crate::task_fn; + + let dag = DagRunner::new(); + + // Task outputs Vec + let vec_task = dag.add_task(task_fn(|_: ()| async { vec![1, 2, 3] })); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + // Try to get as Vec + let wrong_handle: TaskHandle> = TaskHandle { + id: vec_task.id, + _phantom: std::marker::PhantomData, + }; + + let result = dag.get(wrong_handle); + + assert!(result.is_err()); + match result.unwrap_err() { + DagError::TypeMismatch { expected, found } => { + assert_eq!(expected, std::any::type_name::>()); + assert_eq!(found, "unknown"); + } + _ => panic!("Expected TypeMismatch error"), + } +} + +#[tokio::test] +async fn test_get_type_mismatch_option_types() { + // Type mismatch with Option types + use crate::task_fn; + + let dag = DagRunner::new(); + + // Task outputs Option + let opt_task = dag.add_task(task_fn(|_: ()| async { Some(42) })); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + // Try to get as Option + let wrong_handle: TaskHandle> = TaskHandle { + id: opt_task.id, + _phantom: std::marker::PhantomData, + }; + + let result = dag.get(wrong_handle); + + assert!(result.is_err()); + match result.unwrap_err() { + DagError::TypeMismatch { expected, found } => { + assert_eq!(expected, std::any::type_name::>()); + assert_eq!(found, "unknown"); + } + _ => panic!("Expected TypeMismatch error"), + } +} + +#[tokio::test] +async fn test_multiple_type_mismatches_same_dag() { + // Test multiple type mismatches in the same DAG + use crate::task_fn; + + let dag = DagRunner::new(); + + // Create tasks with different output types + let int_task = dag.add_task(task_fn(|_: ()| async { 100i32 })); + 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(); + + // Try to get int_task as String + let wrong_int_handle: TaskHandle = TaskHandle { + id: int_task.id, + _phantom: std::marker::PhantomData, + }; + assert!(matches!( + dag.get(wrong_int_handle).unwrap_err(), + DagError::TypeMismatch { found, .. } if found == "unknown" + )); + + // Try to get string_task as bool + let wrong_string_handle: TaskHandle = TaskHandle { + id: string_task.id, + _phantom: std::marker::PhantomData, + }; + assert!(matches!( + dag.get(wrong_string_handle).unwrap_err(), + DagError::TypeMismatch { found, .. } if found == "unknown" + )); + + // Try to get bool_task as i32 + let wrong_bool_handle: TaskHandle = TaskHandle { + id: bool_task.id, + _phantom: std::marker::PhantomData, + }; + assert!(matches!( + dag.get(wrong_bool_handle).unwrap_err(), + DagError::TypeMismatch { found, .. } if found == "unknown" + )); + + // Verify correct types still work + assert_eq!(dag.get(int_task).unwrap(), 100); + assert_eq!(dag.get(string_task).unwrap(), "hello"); + assert!(dag.get(bool_task).unwrap()); +} diff --git a/src/types.rs b/src/types.rs index e0f4e81..cca4e5d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -95,3 +95,6 @@ impl From<&TaskHandle> for TaskHandle { #[cfg(test)] mod tests; + +#[cfg(test)] +mod coverage_tests; diff --git a/src/types/coverage_tests.rs b/src/types/coverage_tests.rs new file mode 100644 index 0000000..e5b5dd2 --- /dev/null +++ b/src/types/coverage_tests.rs @@ -0,0 +1,283 @@ +//! Coverage tests for types module - specifically targeting the TaskBuilder to TaskHandle conversion +//! +//! These tests ensure we hit line 71 in the From implementation + +use crate::types::TaskHandle; +use crate::{task, DagRunner, Task}; + +#[tokio::test] +async fn test_unit_type_taskbuilder_to_handle_conversion() { + // Test that explicitly uses the From conversion for unit-type tasks + // This covers line 71 in types.rs + + // Unit struct (no fields) - its Input type is () + struct UnitTask; + + #[task] + impl UnitTask { + async fn run(&self) -> i32 { + 42 + } + } + + let dag = DagRunner::new(); + + // This creates a TaskBuilder + // Since UnitTask has Input = (), it can be converted to TaskHandle + let builder = dag.add_task(UnitTask); + + // Explicitly convert to TaskHandle using From implementation + // This triggers the From implementation at line 69-74, + // specifically covering line 71 + let handle: TaskHandle = builder.into(); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + // Verify the task ran and we can get its output + assert_eq!(dag.get(handle).unwrap(), 42); +} + +#[tokio::test] +async fn test_unit_type_conversion_with_multiple_tasks() { + // Test conversion with multiple unit-type tasks + + struct FirstTask; + #[task] + impl FirstTask { + async fn run(&self) -> String { + "first".to_string() + } + } + + struct SecondTask; + #[task] + impl SecondTask { + async fn run(&self) -> i32 { + 100 + } + } + + struct ThirdTask; + #[task] + impl ThirdTask { + async fn run(&self) -> bool { + true + } + } + + let dag = DagRunner::new(); + + // Create builders for all tasks + let first_builder = dag.add_task(FirstTask); + let second_builder = dag.add_task(SecondTask); + let third_builder = dag.add_task(ThirdTask); + + // Convert each builder to handle explicitly + let first_handle: TaskHandle = first_builder.into(); + let second_handle: TaskHandle = second_builder.into(); + let third_handle: TaskHandle = third_builder.into(); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + // Verify all outputs + assert_eq!(dag.get(first_handle).unwrap(), "first"); + assert_eq!(dag.get(second_handle).unwrap(), 100); + assert!(dag.get(third_handle).unwrap()); +} + +#[tokio::test] +async fn test_unit_struct_with_state() { + // Even structs with fields can have unit input type + + struct StatefulTask { + value: i32, + } + + impl StatefulTask { + fn new(value: i32) -> Self { + Self { value } + } + } + + #[task] + impl StatefulTask { + async fn run(&self) -> i32 { + self.value * 2 + } + } + + let dag = DagRunner::new(); + + // Create task with initial state + let builder = dag.add_task(StatefulTask::new(21)); + + // Explicit conversion - triggers line 71 + let handle: TaskHandle = builder.into(); + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + assert_eq!(dag.get(handle).unwrap(), 42); +} + +#[tokio::test] +async fn test_taskbuilder_conversion_assignment() { + // Test the conversion through assignment (different from .into()) + + struct SimpleTask; + #[task] + impl SimpleTask { + async fn run(&self) -> Vec { + vec![1, 2, 3] + } + } + + let dag = DagRunner::new(); + let builder = dag.add_task(SimpleTask); + + // Conversion through type annotation and assignment + // This also uses From implementation + let handle: TaskHandle> = builder.into(); + let id = handle.id; + + dag.run(|fut| { + tokio::spawn(fut); + }) + .await + .unwrap(); + + let result = dag.get(handle).unwrap(); + assert_eq!(result, vec![1, 2, 3]); + + // Verify the ID was preserved correctly through conversion + let another_handle = TaskHandle::> { + id, + _phantom: std::marker::PhantomData, + }; + assert_eq!(dag.get(another_handle).unwrap(), vec![1, 2, 3]); +} + +#[tokio::test] +async fn test_mixed_unit_and_dependent_tasks() { + // Test that unit-type tasks work alongside tasks with dependencies + + struct Source; + #[task] + impl Source { + async fn run(&self) -> i32 { + 10 + } + } + + struct Doubler; + #[task] + impl Doubler { + async fn run(&self, input: &i32) -> i32 { + input * 2 + } + } + + let dag = DagRunner::new(); + + // Source is unit-type, can be converted + let source_builder = dag.add_task(Source); + let source_handle: TaskHandle = source_builder.into(); // Covers line 71 + + // 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(); + + assert_eq!(dag.get(source_handle).unwrap(), 10); + assert_eq!(dag.get(doubler).unwrap(), 20); +} + +#[test] +fn test_taskbuilder_conversion_without_running() { + // Test that conversion works even without running the DAG + // This ensures the conversion is purely a type-level operation + + struct TestTask; + #[task] + impl TestTask { + async fn run(&self) -> String { + "test".to_string() + } + } + + let dag = DagRunner::new(); + let builder = dag.add_task(TestTask); + let builder_id = builder.id; + + // Convert without running the DAG + let handle: TaskHandle = builder.into(); + + // Verify the ID was transferred correctly + assert_eq!(handle.id, builder_id); +} + +#[test] +fn test_multiple_conversions_preserve_ids() { + // Ensure that multiple conversions maintain correct IDs + + struct Task1; + #[task] + impl Task1 { + async fn run(&self) -> i32 { + 1 + } + } + + struct Task2; + #[task] + impl Task2 { + async fn run(&self) -> i32 { + 2 + } + } + + struct Task3; + #[task] + impl Task3 { + async fn run(&self) -> i32 { + 3 + } + } + + let dag = DagRunner::new(); + + let builder1 = dag.add_task(Task1); + let builder2 = dag.add_task(Task2); + let builder3 = dag.add_task(Task3); + + let id1 = builder1.id; + let id2 = builder2.id; + let id3 = builder3.id; + + // Convert all builders + let handle1: TaskHandle = builder1.into(); + let handle2: TaskHandle = builder2.into(); + let handle3: TaskHandle = builder3.into(); + + // Verify IDs are preserved and sequential + assert_eq!(handle1.id, id1); + assert_eq!(handle2.id, id2); + assert_eq!(handle3.id, id3); + assert_eq!(handle1.id.0 + 1, handle2.id.0); + assert_eq!(handle2.id.0 + 1, handle3.id.0); +} diff --git a/tests/parallelism/threads.rs b/tests/parallelism/threads.rs index b5c8f12..b81172a 100644 --- a/tests/parallelism/threads.rs +++ b/tests/parallelism/threads.rs @@ -309,8 +309,10 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> { b2_1_start - b1_1_start }; + // Use a more generous threshold to account for coverage instrumentation overhead + // 50ms is enough to prove they're parallel (not sequential) while being robust to overhead assert!( - branch_start_diff < Duration::from_millis(10), + branch_start_diff < Duration::from_millis(50), "Branches should start in parallel, diff was {:?}", branch_start_diff );