use super::ConcurrentStreamExt;
use futures::StreamExt;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
async fn yield_now_times(times: u64) {
let mut left = times;
core::future::poll_fn(move |cx| {
if left == 0 {
core::task::Poll::Ready(())
} else {
left -= 1;
cx.waker().wake_by_ref();
core::task::Poll::Pending
}
})
.await;
}
#[test]
fn concurrent_map_yields_every_item_with_correct_values() {
let mut results: Vec<u64> = futures::executor::block_on(
futures::stream::iter(0..200u64)
.concurrent_map(8, |x| async move { x * 2 })
.collect(),
);
results.sort_unstable();
let expected: Vec<u64> = (0..200u64).map(|x| x * 2).collect();
assert_eq!(results, expected);
}
#[test]
fn concurrent_map_bounds_in_flight_concurrency_to_limit() {
const LIMIT: usize = 4;
const ITEMS: u64 = 40;
let in_flight = Arc::new(AtomicUsize::new(0));
let peak = Arc::new(AtomicUsize::new(0));
let in_flight_for_items = Arc::clone(&in_flight);
let peak_for_items = Arc::clone(&peak);
let processed: Vec<u64> = futures::executor::block_on(
futures::stream::iter(0..ITEMS)
.concurrent_map(LIMIT, move |x| {
let in_flight = Arc::clone(&in_flight_for_items);
let peak = Arc::clone(&peak_for_items);
async move {
let now = in_flight.fetch_add(1, Ordering::SeqCst) + 1;
peak.fetch_max(now, Ordering::SeqCst);
while peak.load(Ordering::SeqCst) < LIMIT {
yield_now_times(1).await;
}
in_flight.fetch_sub(1, Ordering::SeqCst);
x
}
})
.collect(),
);
assert_eq!(processed.len(), ITEMS as usize);
let observed_peak = peak.load(Ordering::SeqCst);
assert!(
observed_peak <= LIMIT,
"in-flight peak {observed_peak} exceeded the bound {LIMIT}"
);
assert_eq!(
observed_peak, LIMIT,
"the buffer must fill to its bound before an item completes"
);
}
#[test]
fn concurrent_map_with_limit_one_runs_inline_and_sequentially() {
const ITEMS: u64 = 50;
let in_flight = Arc::new(AtomicUsize::new(0));
let peak = Arc::new(AtomicUsize::new(0));
let in_flight_for_items = Arc::clone(&in_flight);
let peak_for_items = Arc::clone(&peak);
let mut results: Vec<u64> = futures::executor::block_on(
futures::stream::iter(0..ITEMS)
.concurrent_map(1, move |x| {
let in_flight = Arc::clone(&in_flight_for_items);
let peak = Arc::clone(&peak_for_items);
async move {
let now = in_flight.fetch_add(1, Ordering::SeqCst) + 1;
peak.fetch_max(now, Ordering::SeqCst);
yield_now_times(1).await;
in_flight.fetch_sub(1, Ordering::SeqCst);
x * 2
}
})
.collect(),
);
results.sort_unstable();
assert_eq!(results, (0..ITEMS).map(|x| x * 2).collect::<Vec<_>>());
assert_eq!(
peak.load(Ordering::SeqCst),
1,
"limit == 1 must run sequentially with no concurrency"
);
}
#[test]
fn concurrent_map_ordered_preserves_input_order() {
const ITEMS: u64 = 64;
let results: Vec<u64> = futures::executor::block_on(
futures::stream::iter(0..ITEMS)
.concurrent_map_ordered(8, |x| async move {
yield_now_times(16u64.saturating_sub(x)).await;
x * 2
})
.collect(),
);
let expected: Vec<u64> = (0..ITEMS).map(|x| x * 2).collect();
assert_eq!(results, expected, "ordered map must preserve input order");
}
#[test]
fn concurrent_for_each_visits_every_item_exactly_once() {
let count = Arc::new(AtomicUsize::new(0));
let count_for_items = Arc::clone(&count);
futures::executor::block_on(futures::stream::iter(0..150u64).concurrent_for_each(
8,
move |_| {
let count = Arc::clone(&count_for_items);
async move {
count.fetch_add(1, Ordering::Relaxed);
}
},
));
assert_eq!(count.load(Ordering::Relaxed), 150);
}