//! Unit tests for types module // Allow tests to reference dagx:: paths (for proc macro generated code) extern crate self as dagx; use crate::types::{NodeId, TaskHandle}; use std::marker::PhantomData; #[test] fn test_node_id_operations() { let id1 = NodeId(1); let id2 = NodeId(2); let id3 = NodeId(1); // Test equality assert_eq!(id1, id3); assert_ne!(id1, id2); // Test ordering assert!(id1 < id2); assert!(id2 > id1); // Test Debug let debug_str = format!("{:?}", id1); assert!(debug_str.contains("NodeId")); assert!(debug_str.contains("1")); // Test Clone let cloned = id1; assert_eq!(id1, cloned); // Test Copy let copied = id1; assert_eq!(id1, copied); // Test Hash (compile-time check that it implements Hash) use std::collections::HashSet; let mut set = HashSet::new(); set.insert(id1); set.insert(id2); assert_eq!(set.len(), 2); } #[test] fn test_task_handle_clone() { let handle: TaskHandle = TaskHandle { id: NodeId(42), _phantom: PhantomData, }; // Test Clone implementation (lines 44-46 in types.rs) let cloned = handle; assert_eq!(handle.id.0, cloned.id.0); // Test that clone is actually a copy (same memory semantics) let handle2 = handle; let handle3 = handle2; assert_eq!(handle3.id.0, 42); } #[test] fn test_task_handle_copy() { let handle: TaskHandle = TaskHandle { id: NodeId(99), _phantom: PhantomData, }; // Test that TaskHandle implements Copy let copied = handle; let copied2 = handle; // This wouldn't compile if not Copy assert_eq!(copied.id.0, 99); assert_eq!(copied2.id.0, 99); } #[test] fn test_task_handle_with_different_types() { // Test that TaskHandle works with various types let int_handle: TaskHandle = TaskHandle { id: NodeId(1), _phantom: PhantomData, }; let string_handle: TaskHandle = TaskHandle { id: NodeId(2), _phantom: PhantomData, }; let vec_handle: TaskHandle> = TaskHandle { id: NodeId(3), _phantom: PhantomData, }; // Clone and copy should work for all let _ = int_handle; let _ = string_handle; let _ = vec_handle; let _ = int_handle; let _ = string_handle; let _ = vec_handle; } #[test] fn test_task_handle_explicit_clone() { // Test explicit .clone() call to cover line 47-48 let handle: TaskHandle = TaskHandle { id: NodeId(42), _phantom: PhantomData, }; #[allow(clippy::clone_on_copy)] let cloned = handle.clone(); assert_eq!(handle.id.0, cloned.id.0); assert_eq!(cloned.id.0, 42); } #[test] fn test_task_builder_from_conversion() { use crate::runner::DagRunner; // Test From for TaskHandle (line 69-74) struct SimpleTask; #[crate::task] impl SimpleTask { async fn run(&self) -> i32 { 42 } } let dag = DagRunner::new(); let builder = dag.add_task(SimpleTask); let builder_id = builder.id; // This conversion uses the From implementation let handle: TaskHandle = builder.into(); assert_eq!(handle.id, builder_id); } #[test] fn test_task_handle_ref_from_conversion() { // Test From<&TaskHandle> for TaskHandle (line 90-93) let handle: TaskHandle = TaskHandle { id: NodeId(123), _phantom: PhantomData, }; let handle_ref = &handle; let converted: TaskHandle = handle_ref.into(); assert_eq!(converted.id.0, 123); assert_eq!(handle.id, converted.id); } #[test] fn test_task_builder_ref_from_conversion() { use crate::runner::DagRunner; // Test From<&TaskBuilder> for TaskHandle (line 77-87, specifically line 83) struct SimpleTask2; #[crate::task] impl SimpleTask2 { async fn run(&self) -> String { "test".to_string() } } let dag = DagRunner::new(); let builder = dag.add_task(SimpleTask2); let builder_id = builder.id; // This conversion uses the From<&TaskBuilder> implementation let handle: TaskHandle = (&builder).into(); assert_eq!(handle.id, builder_id); } // ============================================================================ // Additional TaskBuilder conversion coverage tests // Port from commit sq (sqpzloqrlykp) - src/types/coverage_tests.rs // ============================================================================ #[tokio::test] async fn test_taskbuilder_conversion_with_multiple_tasks() { // Test conversion with multiple unit-type tasks to ensure IDs are preserved use crate::runner::DagRunner; struct FirstTask; #[crate::task] impl FirstTask { async fn run(&self) -> String { "first".to_string() } } struct SecondTask; #[crate::task] impl SecondTask { async fn run(&self) -> i32 { 100 } } struct ThirdTask; #[crate::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); // Save IDs before conversion let first_id = first_builder.id; let second_id = second_builder.id; let third_id = third_builder.id; // 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(); // Verify IDs are preserved through conversion assert_eq!(first_handle.id, first_id); assert_eq!(second_handle.id, second_id); assert_eq!(third_handle.id, third_id); dag.run(|fut| { tokio::spawn(fut); }) .await .unwrap(); // Verify all outputs are correct 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_taskbuilder_conversion_with_stateful_task() { // Test that stateful tasks (with fields) can have unit input and convert correctly use crate::runner::DagRunner; struct StatefulTask { value: i32, } impl StatefulTask { fn new(value: i32) -> Self { Self { value } } } #[crate::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)); let builder_id = builder.id; // Explicit conversion - ensures From works for stateful tasks let handle: TaskHandle = builder.into(); assert_eq!(handle.id, builder_id); dag.run(|fut| { tokio::spawn(fut); }) .await .unwrap(); assert_eq!(dag.get(handle).unwrap(), 42); } #[tokio::test] async fn test_taskbuilder_conversion_with_complex_output_types() { // Test conversion works with complex output types (Vec, Option, etc.) use crate::runner::DagRunner; struct VecTask; #[crate::task] impl VecTask { async fn run(&self) -> Vec { vec![1, 2, 3] } } struct OptionTask; #[crate::task] impl OptionTask { async fn run(&self) -> Option { Some("value".to_string()) } } let dag = DagRunner::new(); let vec_builder = dag.add_task(VecTask); let option_builder = dag.add_task(OptionTask); let vec_id = vec_builder.id; let option_id = option_builder.id; // Convert to handles with complex types let vec_handle: TaskHandle> = vec_builder.into(); let option_handle: TaskHandle> = option_builder.into(); assert_eq!(vec_handle.id, vec_id); assert_eq!(option_handle.id, option_id); dag.run(|fut| { tokio::spawn(fut); }) .await .unwrap(); assert_eq!(dag.get(vec_handle).unwrap(), vec![1, 2, 3]); assert_eq!(dag.get(option_handle).unwrap(), Some("value".to_string())); }