This commit includes multiple test-related improvements from porting
parallel branch work and fixing test failures:
- Add justfile for development workflows (dagx-10)
- Quick dev loop (fmt + clippy + test)
- Pre-commit checks, pre-push validation, pre-release checks
- Coverage, mutation testing, benchmarks, examples
- Security audits, Miri, and more
- Port type mismatch tests for DagRunner::get() (dagx-13)
- Test i32 to Vec<u8> mismatch
- Test custom struct to HashMap mismatch
- Test Vec<i32> to Vec<String> mismatch
- Test Option<i32> to Option<String> mismatch
- Port TaskBuilder conversion tests (dagx-14)
- Test conversion with multiple tasks preserves IDs
- Test conversion with stateful tasks
- Test conversion with complex output types (Vec, Option)
- Add custom_types.rs example (dagx-15)
- Demonstrates custom User type with fan-out pattern
- Shows Arc wrapping is transparent to users
- Comprehensive documentation
- Fix all failing tests (dagx-16)
- Remove incompatible task_fn tests from task/tests.rs
(task_fn requires Arc-wrapped types, tests used raw types)
- Fix listener type downcasts in node/tests.rs
(Listener<Arc<i32>> not Listener<i32>)
- Add #[allow(dead_code)] to unused test structs
- Cleanup unnecessary #[allow(dead_code)] in deps/tests.rs (dagx-9)
All 60 tests now pass.
330 lines
8.2 KiB
Rust
330 lines
8.2 KiB
Rust
//! 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<i32> = 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<String> = 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<i32> = TaskHandle {
|
|
id: NodeId(1),
|
|
_phantom: PhantomData,
|
|
};
|
|
|
|
let string_handle: TaskHandle<String> = TaskHandle {
|
|
id: NodeId(2),
|
|
_phantom: PhantomData,
|
|
};
|
|
|
|
let vec_handle: TaskHandle<Vec<u8>> = 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<i32> = 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<TaskBuilder> 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<i32> = builder.into();
|
|
assert_eq!(handle.id, builder_id);
|
|
}
|
|
|
|
#[test]
|
|
fn test_task_handle_ref_from_conversion() {
|
|
// Test From<&TaskHandle<T>> for TaskHandle<T> (line 90-93)
|
|
let handle: TaskHandle<String> = TaskHandle {
|
|
id: NodeId(123),
|
|
_phantom: PhantomData,
|
|
};
|
|
|
|
let handle_ref = &handle;
|
|
let converted: TaskHandle<String> = 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<String> = (&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<String> = first_builder.into();
|
|
let second_handle: TaskHandle<i32> = second_builder.into();
|
|
let third_handle: TaskHandle<bool> = 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<TaskBuilder> works for stateful tasks
|
|
let handle: TaskHandle<i32> = 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<i32> {
|
|
vec![1, 2, 3]
|
|
}
|
|
}
|
|
|
|
struct OptionTask;
|
|
#[crate::task]
|
|
impl OptionTask {
|
|
async fn run(&self) -> Option<String> {
|
|
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<i32>> = vec_builder.into();
|
|
let option_handle: TaskHandle<Option<String>> = 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()));
|
|
}
|