use super::async_ctx::AsyncContext;
use super::base::ExecutionContext;
use super::hybrid::owned_chunks;
use super::parallel::ParallelContext;
use std::sync::Arc;
#[derive(Debug, PartialEq)]
struct NonClone(u64);
#[test]
fn owned_chunks_move_values_without_clone_bound() {
let chunks = owned_chunks((0..5).map(NonClone).collect(), 2);
let values = chunks
.into_iter()
.flatten()
.map(|item| item.0)
.collect::<Vec<_>>();
assert_eq!(values, vec![0, 1, 2, 3, 4]);
}
#[test]
fn non_clone_parallel_context_execute_iter_consumes_items() {
let data = (0..6).map(NonClone).collect::<Vec<_>>();
let mapped = ParallelContext::with_chunk_size(2)
.execute_iter(data, |item| item.0.wrapping_mul(3))
.expect("parallel context map should consume non-clone items");
assert_eq!(mapped, vec![0, 3, 6, 9, 12, 15]);
}
#[test]
fn non_clone_async_context_execute_iter_consumes_items() {
let data = (0..4).map(NonClone).collect::<Vec<_>>();
let mapped = AsyncContext::with_batch_size(2)
.execute_iter(data, |item| item.0.wrapping_add(1))
.expect("async context map should consume non-clone items");
assert_eq!(mapped, vec![1, 2, 3, 4]);
}
#[tokio::test]
async fn async_context_map_runs_bounded_concurrently_and_preserves_order() {
use std::sync::atomic::{AtomicUsize, Ordering};
let context = ExecutionContext::Async(AsyncContext::new().with_max_concurrent(4));
let active_count = Arc::new(AtomicUsize::new(0));
let max_observed = Arc::new(AtomicUsize::new(0));
let active_count_clone = active_count.clone();
let max_observed_clone = max_observed.clone();
let mapped = context
.execute_async_iter((0..8).collect::<Vec<_>>(), move |value| {
let active_count = active_count_clone.clone();
let max_observed = max_observed_clone.clone();
async move {
let active = active_count.fetch_add(1, Ordering::SeqCst) + 1;
let mut current = max_observed.load(Ordering::SeqCst);
while active > current {
match max_observed.compare_exchange_weak(
current,
active,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
tokio::task::yield_now().await;
active_count.fetch_sub(1, Ordering::SeqCst);
value * 2
}
})
.await
.expect("async map should complete");
assert_eq!(mapped, vec![0, 2, 4, 6, 8, 10, 12, 14]);
assert!(
max_observed.load(Ordering::SeqCst) > 1,
"bounded concurrent map should execute tasks concurrently"
);
}
#[tokio::test]
async fn async_context_filter_runs_bounded_concurrently_and_preserves_order() {
let context = ExecutionContext::Async(AsyncContext::new().with_max_concurrent(4));
let filtered = context
.execute_async_filter((0..8).collect::<Vec<_>>(), |value| {
let value = *value;
async move {
tokio::task::yield_now().await;
value % 2 == 0
}
})
.await
.expect("async filter should complete");
assert_eq!(filtered, vec![0, 2, 4, 6]);
}
#[tokio::test]
async fn parallel_context_async_for_each_visits_every_item_once() {
use std::sync::atomic::{AtomicUsize, Ordering};
let visits = Arc::new((0..64).map(|_| AtomicUsize::new(0)).collect::<Vec<_>>());
let observed = Arc::clone(&visits);
ExecutionContext::Parallel(ParallelContext::new())
.execute_async_for_each((0..64).collect::<Vec<_>>(), move |index| {
let observed = Arc::clone(&observed);
async move {
observed[index].fetch_add(1, Ordering::Relaxed);
}
})
.await
.expect("parallel-context async for-each should complete");
assert!(
visits
.iter()
.all(|count| count.load(Ordering::Relaxed) == 1),
"parallel-context async for-each must visit every item exactly once"
);
}