Files
dagx/src/types/tests.rs
T
swaits 10af81ade7 test: comprehensive test improvements and infrastructure
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.
2025-10-14 10:12:07 -06:00

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()));
}