Skip to content

Commit b40b45b

Browse files
committed
[tests] make test code generic over type of weighted stream
We're going to add a variant of this in the next commit.
1 parent 038a5af commit b40b45b

File tree

3 files changed

+138
-40
lines changed

3 files changed

+138
-40
lines changed

src/future_queue.rs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use futures_util::{
66
Future, Stream, StreamExt as _,
77
};
88
use pin_project_lite::pin_project;
9-
use private::WeightedFuture;
109
use std::{
1110
fmt,
1211
pin::Pin,
@@ -167,25 +166,31 @@ where
167166
}
168167
}
169168

170-
mod private {
171-
use futures_util::Future;
169+
/// A trait for types which can be converted into a `Future` and a weight.
170+
///
171+
/// Provided in case it's necessary. This trait is only implemented for `(usize, impl Future)`.
172+
pub trait WeightedFuture: private::Sealed {
173+
type Future: Future;
172174

173-
pub trait WeightedFuture {
174-
type Future: Future;
175+
/// Turns this trait into its components.
176+
fn into_components(self) -> (usize, Self::Future);
177+
}
175178

176-
fn into_components(self) -> (usize, Self::Future);
177-
}
179+
mod private {
180+
pub trait Sealed {}
181+
}
178182

179-
impl<Fut> WeightedFuture for (usize, Fut)
180-
where
181-
Fut: Future,
182-
{
183-
type Future = Fut;
183+
impl<Fut> private::Sealed for (usize, Fut) where Fut: Future {}
184184

185-
#[inline]
186-
fn into_components(self) -> (usize, Self::Future) {
187-
self
188-
}
185+
impl<Fut> WeightedFuture for (usize, Fut)
186+
where
187+
Fut: Future,
188+
{
189+
type Future = Fut;
190+
191+
#[inline]
192+
fn into_components(self) -> (usize, Self::Future) {
193+
self
189194
}
190195
}
191196

src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,15 @@
9898
9999
mod future_queue;
100100

101-
pub use future_queue::FutureQueue;
101+
pub use crate::future_queue::FutureQueue;
102+
103+
/// Traits to aid in type definitions.
104+
///
105+
/// These traits are normally not required by end-user code, but may be necessary for some generic
106+
/// code.
107+
pub mod traits {
108+
pub use crate::future_queue::WeightedFuture;
109+
}
102110

103111
use futures_util::{Future, Stream};
104112

tests/test_properties.rs

Lines changed: 108 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,127 @@
11
// Copyright (c) The future-queue Contributors
22
// SPDX-License-Identifier: MIT OR Apache-2.0
33

4-
use future_queue::StreamExt as _;
5-
use futures::{stream, StreamExt as _};
4+
use future_queue::{traits::WeightedFuture, FutureQueue, StreamExt as _};
5+
use futures::{future::BoxFuture, stream, Future, FutureExt, Stream, StreamExt as _};
66
use proptest::prelude::*;
77
use proptest_derive::Arbitrary;
8-
use std::time::Duration;
8+
use std::{pin::Pin, time::Duration};
99
use tokio_stream::wrappers::UnboundedReceiverStream;
1010

1111
#[derive(Clone, Debug, Arbitrary)]
12-
struct TestState {
12+
struct TestState<G: GroupSpec> {
1313
#[proptest(strategy = "1usize..64")]
1414
max_weight: usize,
1515
#[proptest(strategy = "prop::collection::vec(TestFutureDesc::arbitrary(), 0..512usize)")]
16-
future_descriptions: Vec<TestFutureDesc>,
16+
future_descriptions: Vec<TestFutureDesc<G>>,
1717
}
1818

1919
#[derive(Copy, Clone, Debug, Arbitrary)]
20-
struct TestFutureDesc {
20+
struct TestFutureDesc<G: GroupSpec> {
2121
#[proptest(strategy = "duration_strategy()")]
2222
start_delay: Duration,
2323
#[proptest(strategy = "duration_strategy()")]
2424
delay: Duration,
2525
#[proptest(strategy = "0usize..8")]
2626
weight: usize,
27+
#[allow(dead_code)]
28+
group: G,
2729
}
2830

2931
fn duration_strategy() -> BoxedStrategy<Duration> {
3032
// Allow for a delay between 0ms and 1000ms uniformly at random.
3133
(0u64..1000).prop_map(Duration::from_millis).boxed()
3234
}
3335

36+
trait GroupSpec: Arbitrary + Send + Copy + 'static {
37+
type Item: Send;
38+
type CheckState: Default;
39+
40+
fn create_stream<St>(stream: St, state: &TestState<Self>) -> BoxedWeightedStream<()>
41+
where
42+
St: Stream<Item = Self::Item> + Send + 'static;
43+
44+
fn create_stream_item(
45+
desc: &TestFutureDesc<Self>,
46+
future: impl Future<Output = ()> + Send + 'static,
47+
) -> Self::Item;
48+
49+
fn check_started(
50+
check_state: &mut Self::CheckState,
51+
desc: &TestFutureDesc<Self>,
52+
state: &TestState<Self>,
53+
);
54+
55+
fn check_finished(
56+
check_state: &mut Self::CheckState,
57+
desc: &TestFutureDesc<Self>,
58+
state: &TestState<Self>,
59+
);
60+
}
61+
62+
trait WeightedStream: Stream {
63+
fn current_weight(&self) -> usize;
64+
}
65+
66+
impl<St, Fut> WeightedStream for FutureQueue<St>
67+
where
68+
St: Stream<Item = Fut>,
69+
Fut: WeightedFuture,
70+
{
71+
fn current_weight(&self) -> usize {
72+
self.current_weight()
73+
}
74+
}
75+
76+
type BoxedWeightedStream<Item> = Pin<Box<dyn WeightedStream<Item = Item> + Send>>;
77+
78+
impl GroupSpec for () {
79+
type Item = (usize, BoxFuture<'static, ()>);
80+
type CheckState = NonGroupedCheckState;
81+
82+
fn create_stream<St>(stream: St, state: &TestState<Self>) -> BoxedWeightedStream<()>
83+
where
84+
St: Stream<Item = Self::Item> + Send + 'static,
85+
{
86+
Box::pin(stream.future_queue(state.max_weight))
87+
}
88+
89+
fn create_stream_item(
90+
desc: &TestFutureDesc<Self>,
91+
future: impl Future<Output = ()> + Send + 'static,
92+
) -> Self::Item {
93+
(desc.weight, future.boxed())
94+
}
95+
96+
fn check_started(
97+
check_state: &mut Self::CheckState,
98+
desc: &TestFutureDesc<Self>,
99+
state: &TestState<Self>,
100+
) {
101+
// Check that current_weight doesn't go over the limit.
102+
assert!(
103+
check_state.current_weight < state.max_weight,
104+
"current weight {} exceeds max weight {}",
105+
check_state.current_weight,
106+
state.max_weight,
107+
);
108+
check_state.current_weight += desc.weight;
109+
}
110+
111+
fn check_finished(
112+
check_state: &mut Self::CheckState,
113+
desc: &TestFutureDesc<Self>,
114+
_state: &TestState<Self>,
115+
) {
116+
check_state.current_weight -= desc.weight;
117+
}
118+
}
119+
120+
#[derive(Debug, Default)]
121+
struct NonGroupedCheckState {
122+
current_weight: usize,
123+
}
124+
34125
#[test]
35126
fn test_examples() {
36127
let state = TestState {
@@ -39,25 +130,26 @@ fn test_examples() {
39130
start_delay: Duration::ZERO,
40131
delay: Duration::ZERO,
41132
weight: 0,
133+
group: (),
42134
}],
43135
};
44136
test_future_queue_impl(state);
45137
}
46138

47139
proptest! {
48140
#[test]
49-
fn proptest_future_queue(state: TestState) {
141+
fn proptest_future_queue(state: TestState<()>) {
50142
test_future_queue_impl(state)
51143
}
52144
}
53145

54146
#[derive(Clone, Copy, Debug)]
55-
enum FutureEvent {
56-
Started(usize, TestFutureDesc),
57-
Finished(usize, TestFutureDesc),
147+
enum FutureEvent<G: GroupSpec> {
148+
Started(usize, TestFutureDesc<G>),
149+
Finished(usize, TestFutureDesc<G>),
58150
}
59151

60-
fn test_future_queue_impl(state: TestState) {
152+
fn test_future_queue_impl<G: GroupSpec>(state: TestState<G>) {
61153
let runtime = tokio::runtime::Builder::new_current_thread()
62154
.enable_time()
63155
.start_paused(true)
@@ -88,7 +180,7 @@ fn test_future_queue_impl(state: TestState) {
88180
.expect("receiver held open by loop");
89181
};
90182
// Errors should never occur here.
91-
if let Err(err) = future_sender.send((desc.weight, delay_fut)) {
183+
if let Err(err) = future_sender.send(G::create_stream_item(&desc, delay_fut)) {
92184
panic!("future_receiver held open by loop: {}", err);
93185
}
94186
}
@@ -102,11 +194,11 @@ fn test_future_queue_impl(state: TestState) {
102194

103195
let mut completed_map = vec![false; state.future_descriptions.len()];
104196
let mut last_started_id: Option<usize> = None;
105-
let mut current_weight = 0;
197+
let mut check_state = G::CheckState::default();
106198

107199
runtime.block_on(async move {
108200
// Record values that have been completed in this map.
109-
let mut stream = stream.future_queue(state.max_weight);
201+
let mut stream = G::create_stream(stream, &state);
110202
let mut receiver_done = false;
111203
loop {
112204
tokio::select! {
@@ -122,19 +214,12 @@ fn test_future_queue_impl(state: TestState) {
122214
assert_eq!(expected_id, id, "expected future id to start != actual id that started");
123215
last_started_id = Some(id);
124216

125-
// Check that the current weight doesn't go over the limit.
126-
assert!(
127-
current_weight < state.max_weight,
128-
"current weight {} exceeds max weight {}",
129-
current_weight,
130-
state.max_weight,
131-
);
132-
current_weight += desc.weight;
217+
G::check_started(&mut check_state, &desc, &state);
133218
}
134219
Some(FutureEvent::Finished(id, desc)) => {
135220
// Record that this value was completed.
136221
completed_map[id] = true;
137-
current_weight -= desc.weight;
222+
G::check_finished(&mut check_state, &desc, &state);
138223
}
139224
None => {
140225
// All futures finished -- going to check for completion in stream.next() below.

0 commit comments

Comments
 (0)