1#![allow(missing_docs)]
2
3use std::{
4 collections::HashMap,
5 convert::Infallible,
6 fs::File,
7 future::{Future, ready},
8 io::Read,
9 iter,
10 net::SocketAddr,
11 path::{Path, PathBuf},
12 pin::Pin,
13 sync::{
14 Arc,
15 atomic::{AtomicUsize, Ordering},
16 },
17 task::{Context, Poll, ready},
18};
19
20use chrono::{DateTime, SubsecRound, Utc};
21use futures::{FutureExt, SinkExt, Stream, StreamExt, TryStreamExt, stream, task::noop_waker_ref};
22use openssl::ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode};
23use rand::{RngExt, rng};
24use rand_distr::Alphanumeric;
25use tokio::{
26 io::{AsyncRead, AsyncWrite, AsyncWriteExt, Result as IoResult},
27 net::{TcpListener, TcpStream, ToSocketAddrs},
28 runtime,
29 sync::oneshot,
30 task::JoinHandle,
31 time::{Duration, Instant, sleep},
32};
33use tokio_stream::wrappers::TcpListenerStream;
34#[cfg(unix)]
35use tokio_stream::wrappers::UnixListenerStream;
36use tokio_util::codec::{Encoder, FramedRead, FramedWrite, LinesCodec};
37use vector_common::decompression::CappedDecoder;
38use vector_lib::{
39 buffers::topology::channel::LimitedReceiver,
40 event::{
41 BatchNotifier, BatchStatusReceiver, Event, EventArray, LogEvent, Metric, MetricKind,
42 MetricTags, MetricValue,
43 },
44};
45
46use crate::{
47 config::{Config, GenerateConfig},
48 topology::{RunningTopology, ShutdownErrorReceiver},
49 trace,
50};
51
52const WAIT_FOR_SECS: u64 = 5; const WAIT_FOR_MIN_MILLIS: u64 = 5; const WAIT_FOR_MAX_MILLIS: u64 = 500; pub mod addr;
57pub mod compression;
58pub mod stats;
59
60#[cfg(any(test, feature = "test-utils"))]
61pub mod components;
62#[cfg(test)]
63pub mod http;
64#[cfg(test)]
65pub mod integration;
66#[cfg(test)]
67pub mod metrics;
68#[cfg(test)]
69pub mod mock;
70
71#[macro_export]
72macro_rules! assert_downcast_matches {
73 ($e:expr_2021, $t:ty, $v:pat) => {{
74 match $e.downcast_ref::<$t>() {
75 Some($v) => (),
76 got => panic!("Assertion failed: got wrong error variant {:?}", got),
77 }
78 }};
79}
80
81#[macro_export]
82macro_rules! log_event {
83 ($($key:expr_2021 => $value:expr_2021),* $(,)?) => {
84 #[allow(unused_variables)]
85 {
86 let mut event = $crate::event::Event::Log($crate::event::LogEvent::default());
87 let log = event.as_mut_log();
88 $(
89 log.insert(::vrl::event_path!($key), $value);
90 )*
91 event
92 }
93 };
94}
95
96pub fn test_generate_config<T>()
97where
98 for<'de> T: GenerateConfig + serde::Deserialize<'de>,
99{
100 let generated = T::generate_config();
101
102 let toml_cfg = toml::to_string(&crate::generate::strip_nulls(generated.clone())).unwrap();
104 toml::from_str::<T>(&toml_cfg).unwrap_or_else(|e| {
105 panic!("Invalid config generated from TOML string:\n\n{e}\n'{toml_cfg}'")
106 });
107
108 let yaml_cfg = serde_yaml::to_string(&generated).unwrap();
109 serde_yaml::from_str::<T>(&yaml_cfg).unwrap_or_else(|e| {
110 panic!("Invalid config generated from YAML string:\n\n{e}\n'{yaml_cfg}'")
111 });
112
113 let json_cfg = serde_json::to_string(&generated).unwrap();
114 serde_json::from_str::<T>(&json_cfg).unwrap_or_else(|e| {
115 panic!("Invalid config generated from JSON string:\n\n{e}\n'{json_cfg}'")
116 });
117}
118
119pub fn open_fixture(path: impl AsRef<Path>) -> crate::Result<serde_json::Value> {
120 let test_file = File::open(path)?;
121 let value: serde_json::Value = serde_json::from_reader(test_file)?;
122 Ok(value)
123}
124
125pub fn trace_init() {
126 #[cfg(unix)]
127 let color = {
128 use std::io::IsTerminal;
129 std::io::stdout().is_terminal()
130 || std::env::var("NEXTEST")
131 .ok()
132 .and(Some(true))
133 .unwrap_or(false)
134 };
135 #[cfg(not(unix))]
138 let color = false;
139
140 let levels = std::env::var("VECTOR_LOG").unwrap_or_else(|_| "error".to_string());
141
142 trace::init(color, false, &levels, 10, None);
143
144 vector_lib::metrics::init_test();
146}
147
148pub async fn send_lines(
149 addr: SocketAddr,
150 lines: impl IntoIterator<Item = String>,
151) -> Result<SocketAddr, Infallible> {
152 send_encodable(addr, LinesCodec::new(), lines).await
153}
154
155pub async fn send_encodable<I, E: From<std::io::Error> + std::fmt::Debug>(
156 addr: SocketAddr,
157 encoder: impl Encoder<I, Error = E>,
158 lines: impl IntoIterator<Item = I>,
159) -> Result<SocketAddr, Infallible> {
160 let stream = TcpStream::connect(&addr).await.unwrap();
161
162 let local_addr = stream.local_addr().unwrap();
163
164 let mut sink = FramedWrite::new(stream, encoder);
165
166 let mut lines = stream::iter(lines).map(Ok);
167 sink.send_all(&mut lines).await.unwrap();
168
169 let stream = sink.get_mut();
170 stream.shutdown().await.unwrap();
171
172 Ok(local_addr)
173}
174
175pub async fn send_lines_tls(
176 addr: SocketAddr,
177 host: String,
178 lines: impl Iterator<Item = String>,
179 ca: impl Into<Option<&Path>>,
180 client_cert: impl Into<Option<&Path>>,
181 client_key: impl Into<Option<&Path>>,
182) -> Result<SocketAddr, Infallible> {
183 let stream = TcpStream::connect(&addr).await.unwrap();
184
185 let local_addr = stream.local_addr().unwrap();
186
187 let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
188 if let Some(ca) = ca.into() {
189 connector.set_ca_file(ca).unwrap();
190 } else {
191 connector.set_verify(SslVerifyMode::NONE);
192 }
193
194 if let Some(cert_file) = client_cert.into() {
195 connector.set_certificate_chain_file(cert_file).unwrap();
196 }
197
198 if let Some(key_file) = client_key.into() {
199 connector
200 .set_private_key_file(key_file, SslFiletype::PEM)
201 .unwrap();
202 }
203
204 let ssl = connector
205 .build()
206 .configure()
207 .unwrap()
208 .into_ssl(&host)
209 .unwrap();
210
211 let mut stream = tokio_openssl::SslStream::new(ssl, stream).unwrap();
212 Pin::new(&mut stream).connect().await.unwrap();
213 let mut sink = FramedWrite::new(stream, LinesCodec::new());
214
215 let mut lines = stream::iter(lines).map(Ok);
216 sink.send_all(&mut lines).await.unwrap();
217
218 let stream = sink.get_mut().get_mut();
219 stream.shutdown().await.unwrap();
220
221 Ok(local_addr)
222}
223
224pub fn temp_file() -> PathBuf {
225 let path = std::env::temp_dir();
226 let file_name = random_string(16);
227 path.join(file_name + ".log")
228}
229
230pub fn temp_dir() -> PathBuf {
231 let path = std::env::temp_dir();
232 let dir_name = random_string(16);
233 path.join(dir_name)
234}
235
236pub fn random_table_name() -> String {
237 format!("test_{}", random_string(10).to_lowercase())
238}
239
240pub fn map_event_batch_stream(
241 stream: impl Stream<Item = Event>,
242 batch: Option<BatchNotifier>,
243) -> impl Stream<Item = EventArray> {
244 stream.map(move |event| event.with_batch_notifier_option(&batch).into())
245}
246
247fn map_batch_stream(
249 stream: impl Stream<Item = LogEvent>,
250 batch: Option<BatchNotifier>,
251) -> impl Stream<Item = EventArray> {
252 stream.map(move |log| vec![log.with_batch_notifier_option(&batch)].into())
253}
254
255pub fn generate_lines_with_stream<Gen: FnMut(usize) -> String>(
256 generator: Gen,
257 count: usize,
258 batch: Option<BatchNotifier>,
259) -> (Vec<String>, impl Stream<Item = EventArray>) {
260 let lines = (0..count).map(generator).collect::<Vec<_>>();
261 let stream = map_batch_stream(
262 stream::iter(lines.clone()).map(LogEvent::from_str_legacy),
263 batch,
264 );
265 (lines, stream)
266}
267
268pub fn random_lines_with_stream(
269 len: usize,
270 count: usize,
271 batch: Option<BatchNotifier>,
272) -> (Vec<String>, impl Stream<Item = EventArray>) {
273 let generator = move |_| random_string(len);
274 generate_lines_with_stream(generator, count, batch)
275}
276
277pub fn generate_events_with_stream<Gen: FnMut(usize) -> Event>(
278 generator: Gen,
279 count: usize,
280 batch: Option<BatchNotifier>,
281) -> (Vec<Event>, impl Stream<Item = EventArray>) {
282 let events = (0..count).map(generator).collect::<Vec<_>>();
283 let stream = map_batch_stream(
284 stream::iter(events.clone()).map(|event| event.into_log()),
285 batch,
286 );
287 (events, stream)
288}
289
290pub fn random_metrics_with_stream(
291 count: usize,
292 batch: Option<BatchNotifier>,
293 tags: Option<MetricTags>,
294) -> (Vec<Event>, impl Stream<Item = EventArray>) {
295 random_metrics_with_stream_timestamp(
296 count,
297 batch,
298 tags,
299 Utc::now().trunc_subsecs(3),
300 std::time::Duration::from_secs(2),
301 )
302}
303
304pub fn random_metrics_with_stream_timestamp(
316 count: usize,
317 batch: Option<BatchNotifier>,
318 tags: Option<MetricTags>,
319 timestamp: DateTime<Utc>,
320 timestamp_offset: std::time::Duration,
321) -> (Vec<Event>, impl Stream<Item = EventArray>) {
322 let events: Vec<_> = (0..count)
323 .map(|index| {
324 let ts = timestamp + (timestamp_offset * index as u32);
325 Event::Metric(
326 Metric::new(
327 format!("counter_{}", rng().random::<u32>()),
328 MetricKind::Incremental,
329 MetricValue::Counter {
330 value: index as f64,
331 },
332 )
333 .with_timestamp(Some(ts))
334 .with_tags(tags.clone()),
335 )
336 .with_source_type("a_source_like_none_other")
338 })
339 .collect();
340
341 let stream = map_event_batch_stream(stream::iter(events.clone()), batch);
342 (events, stream)
343}
344
345pub fn random_events_with_stream(
346 len: usize,
347 count: usize,
348 batch: Option<BatchNotifier>,
349) -> (Vec<Event>, impl Stream<Item = EventArray>) {
350 let events = (0..count)
351 .map(|_| Event::from(LogEvent::from_str_legacy(random_string(len))))
352 .collect::<Vec<_>>();
353 let stream = map_batch_stream(
354 stream::iter(events.clone()).map(|event| event.into_log()),
355 batch,
356 );
357 (events, stream)
358}
359
360pub fn random_updated_events_with_stream<F>(
361 len: usize,
362 count: usize,
363 batch: Option<BatchNotifier>,
364 update_fn: F,
365) -> (Vec<Event>, impl Stream<Item = EventArray>)
366where
367 F: Fn((usize, LogEvent)) -> LogEvent,
368{
369 let events = (0..count)
370 .map(|_| LogEvent::from_str_legacy(random_string(len)))
371 .enumerate()
372 .map(update_fn)
373 .map(Event::Log)
374 .collect::<Vec<_>>();
375 let stream = map_batch_stream(
376 stream::iter(events.clone()).map(|event| event.into_log()),
377 batch,
378 );
379 (events, stream)
380}
381
382pub fn create_events_batch_with_fn<F: Fn() -> Event>(
383 create_event_fn: F,
384 num_events: usize,
385) -> (Vec<Event>, BatchStatusReceiver) {
386 let mut events = (0..num_events)
387 .map(|_| create_event_fn())
388 .collect::<Vec<_>>();
389 let receiver = BatchNotifier::apply_to(&mut events);
390 (events, receiver)
391}
392
393pub fn random_string(len: usize) -> String {
394 rng()
395 .sample_iter(&Alphanumeric)
396 .take(len)
397 .map(char::from)
398 .collect::<String>()
399}
400
401pub fn random_lines(len: usize) -> impl Iterator<Item = String> {
402 iter::repeat_with(move || random_string(len))
403}
404
405pub fn random_map(max_size: usize, field_len: usize) -> HashMap<String, String> {
406 let size = rng().random_range(0..max_size);
407
408 (0..size)
409 .map(move |_| (random_string(field_len), random_string(field_len)))
410 .collect()
411}
412
413pub fn random_maps(
414 max_size: usize,
415 field_len: usize,
416) -> impl Iterator<Item = HashMap<String, String>> {
417 iter::repeat_with(move || random_map(max_size, field_len))
418}
419
420pub async fn collect_n<S>(rx: S, n: usize) -> Vec<S::Item>
421where
422 S: Stream,
423{
424 rx.take(n).collect().await
425}
426
427pub async fn collect_n_stream<T, S: Stream<Item = T> + Unpin>(stream: &mut S, n: usize) -> Vec<T> {
428 let mut events = Vec::with_capacity(n);
429
430 while events.len() < n {
431 let e = stream.next().await.unwrap();
432 events.push(e);
433 }
434 events
435}
436
437pub async fn collect_ready<S>(mut rx: S) -> Vec<S::Item>
438where
439 S: Stream + Unpin,
440{
441 let waker = noop_waker_ref();
442 let mut cx = Context::from_waker(waker);
443
444 let mut vec = Vec::new();
445 loop {
446 match rx.poll_next_unpin(&mut cx) {
447 Poll::Ready(Some(item)) => vec.push(item),
448 Poll::Ready(None) | Poll::Pending => return vec,
449 }
450 }
451}
452
453pub async fn collect_limited<T: Send + 'static>(mut rx: LimitedReceiver<T>) -> Vec<T> {
454 let mut items = Vec::new();
455 while let Some(item) = rx.next().await {
456 items.push(item);
457 }
458 items
459}
460
461pub async fn collect_n_limited<T: Send + 'static>(mut rx: LimitedReceiver<T>, n: usize) -> Vec<T> {
462 let mut items = Vec::new();
463 while items.len() < n {
464 match rx.next().await {
465 Some(item) => items.push(item),
466 None => break,
467 }
468 }
469 items
470}
471
472pub fn lines_from_file<P: AsRef<Path>>(path: P) -> Vec<String> {
473 trace!(message = "Reading file.", path = %path.as_ref().display());
474 let mut file = File::open(path).unwrap();
475 let mut output = String::new();
476 file.read_to_string(&mut output).unwrap();
477 output.lines().map(|s| s.to_owned()).collect()
478}
479
480pub fn lines_from_gzip_file<P: AsRef<Path>>(path: P) -> Vec<String> {
481 trace!(message = "Reading gzip file.", path = %path.as_ref().display());
482 let mut file = File::open(path).unwrap();
483 let mut gzip_bytes = Vec::new();
484 file.read_to_end(&mut gzip_bytes).unwrap();
485 let output = CappedDecoder::gzip(&gzip_bytes[..]).decompress().unwrap();
486 String::from_utf8(output)
487 .unwrap()
488 .lines()
489 .map(|s| s.to_owned())
490 .collect()
491}
492
493#[cfg(test)]
494pub fn lines_from_zstd_file<P: AsRef<Path>>(path: P) -> Vec<String> {
495 trace!(message = "Reading zstd file.", path = %path.as_ref().display());
496 let file = File::open(path).unwrap();
497 let output = CappedDecoder::zstd(file).unwrap().decompress().unwrap();
498 String::from_utf8(output)
499 .unwrap()
500 .lines()
501 .map(|s| s.to_owned())
502 .collect()
503}
504
505pub fn runtime() -> runtime::Runtime {
506 runtime::Builder::new_multi_thread()
507 .enable_all()
508 .build()
509 .unwrap()
510}
511
512pub async fn wait_for_duration<F, Fut>(mut f: F, duration: Duration)
514where
515 F: FnMut() -> Fut,
516 Fut: Future<Output = bool> + Send + 'static,
517{
518 let started = Instant::now();
519 let mut delay = WAIT_FOR_MIN_MILLIS;
520 while !f().await {
521 sleep(Duration::from_millis(delay)).await;
522 if started.elapsed() > duration {
523 panic!("Timed out while waiting");
524 }
525 delay = (delay * 2).min(WAIT_FOR_MAX_MILLIS);
527 }
528}
529
530pub async fn wait_for<F, Fut>(f: F)
532where
533 F: FnMut() -> Fut,
534 Fut: Future<Output = bool> + Send + 'static,
535{
536 wait_for_duration(f, Duration::from_secs(WAIT_FOR_SECS)).await
537}
538
539pub async fn wait_for_tcp<A>(addr: A)
541where
542 A: ToSocketAddrs + Clone + Send + 'static,
543{
544 wait_for(move || {
545 let addr = addr.clone();
546 async move { TcpStream::connect(addr).await.is_ok() }
547 })
548 .await
549}
550
551pub async fn wait_for_tcp_duration(addr: SocketAddr, duration: Duration) {
553 wait_for_duration(
554 || async move { TcpStream::connect(addr).await.is_ok() },
555 duration,
556 )
557 .await
558}
559
560pub async fn wait_for_atomic_usize<T, F>(value: T, unblock: F)
561where
562 T: AsRef<AtomicUsize>,
563 F: Fn(usize) -> bool,
564{
565 let value = value.as_ref();
566 wait_for(|| ready(unblock(value.load(Ordering::SeqCst)))).await
567}
568
569pub async fn wait_for_atomic_usize_timeout_ms<T, F>(value: T, unblock: F, timeout_ms: u64)
570where
571 T: AsRef<AtomicUsize>,
572 F: Fn(usize) -> bool,
573{
574 let value = value.as_ref();
575 wait_for_duration(
576 || ready(unblock(value.load(Ordering::SeqCst))),
577 Duration::from_millis(timeout_ms),
578 )
579 .await
580}
581
582pub async fn retry_until<'a, F, Fut, T, E>(mut f: F, retry: Duration, until: Duration) -> T
584where
585 F: FnMut() -> Fut,
586 Fut: Future<Output = Result<T, E>> + Send + 'a,
587{
588 let started = Instant::now();
589 while started.elapsed() < until {
590 match f().await {
591 Ok(res) => return res,
592 Err(_) => tokio::time::sleep(retry).await,
593 }
594 }
595 panic!("Timeout")
596}
597
598pub struct CountReceiver<T> {
599 count: Arc<AtomicUsize>,
600 trigger: Option<oneshot::Sender<()>>,
601 connected: Option<oneshot::Receiver<()>>,
602 handle: JoinHandle<Vec<T>>,
603}
604
605impl<T: Send + 'static> CountReceiver<T> {
606 pub fn count(&self) -> usize {
607 self.count.load(Ordering::Relaxed)
608 }
609
610 pub async fn connected(&mut self) {
612 if let Some(tripwire) = self.connected.take() {
613 tripwire.await.unwrap();
614 }
615 }
616
617 fn new<F, Fut>(make_fut: F) -> CountReceiver<T>
618 where
619 F: FnOnce(Arc<AtomicUsize>, oneshot::Receiver<()>, oneshot::Sender<()>) -> Fut,
620 Fut: Future<Output = Vec<T>> + Send + 'static,
621 {
622 let count = Arc::new(AtomicUsize::new(0));
623 let (trigger, tripwire) = oneshot::channel();
624 let (trigger_connected, connected) = oneshot::channel();
625
626 CountReceiver {
627 count: Arc::clone(&count),
628 trigger: Some(trigger),
629 connected: Some(connected),
630 handle: tokio::spawn(make_fut(count, tripwire, trigger_connected)),
631 }
632 }
633
634 pub fn receive_items_stream<S, F, Fut>(make_stream: F) -> CountReceiver<T>
635 where
636 S: Stream<Item = T> + Send + 'static,
637 F: FnOnce(oneshot::Receiver<()>, oneshot::Sender<()>) -> Fut + Send + 'static,
638 Fut: Future<Output = S> + Send + 'static,
639 {
640 CountReceiver::new(|count, tripwire, connected| async move {
641 let stream = make_stream(tripwire, connected).await;
642 stream
643 .inspect(move |_| {
644 count.fetch_add(1, Ordering::Relaxed);
645 })
646 .collect::<Vec<T>>()
647 .await
648 })
649 }
650}
651
652impl<T> Future for CountReceiver<T> {
653 type Output = Vec<T>;
654
655 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
656 let this = self.get_mut();
657 if let Some(trigger) = this.trigger.take() {
658 _ = trigger.send(());
659 }
660
661 let result = ready!(this.handle.poll_unpin(cx));
662 Poll::Ready(result.unwrap())
663 }
664}
665
666impl CountReceiver<String> {
667 pub fn receive_lines(addr: SocketAddr) -> CountReceiver<String> {
668 CountReceiver::new(|count, tripwire, connected| async move {
669 let listener = TcpListener::bind(addr).await.unwrap();
670 CountReceiver::receive_lines_stream(
671 TcpListenerStream::new(listener),
672 count,
673 tripwire,
674 Some(connected),
675 )
676 .await
677 })
678 }
679
680 #[cfg(unix)]
681 pub fn receive_lines_unix<P>(path: P) -> CountReceiver<String>
682 where
683 P: AsRef<Path> + Send + 'static,
684 {
685 CountReceiver::new(|count, tripwire, connected| async move {
686 let listener = tokio::net::UnixListener::bind(path).unwrap();
687 CountReceiver::receive_lines_stream(
688 UnixListenerStream::new(listener),
689 count,
690 tripwire,
691 Some(connected),
692 )
693 .await
694 })
695 }
696
697 async fn receive_lines_stream<S, T>(
698 stream: S,
699 count: Arc<AtomicUsize>,
700 tripwire: oneshot::Receiver<()>,
701 mut connected: Option<oneshot::Sender<()>>,
702 ) -> Vec<String>
703 where
704 S: Stream<Item = IoResult<T>>,
705 T: AsyncWrite + AsyncRead,
706 {
707 stream
708 .take_until(tripwire)
709 .map_ok(|socket| FramedRead::new(socket, LinesCodec::new()))
710 .map(|x| {
711 connected.take().map(|trigger| trigger.send(()));
712 x.unwrap()
713 })
714 .flatten()
715 .map(|x| x.unwrap())
716 .inspect(move |_| {
717 count.fetch_add(1, Ordering::Relaxed);
718 })
719 .collect::<Vec<String>>()
720 .await
721 }
722}
723
724impl CountReceiver<Event> {
725 pub fn receive_events<S>(stream: S) -> CountReceiver<Event>
726 where
727 S: Stream<Item = Event> + Send + 'static,
728 {
729 CountReceiver::new(|count, tripwire, connected| async move {
730 connected.send(()).unwrap();
731 stream
732 .take_until(tripwire)
733 .inspect(move |_| {
734 count.fetch_add(1, Ordering::Relaxed);
735 })
736 .collect::<Vec<Event>>()
737 .await
738 })
739 }
740}
741
742pub async fn start_topology(
743 mut config: Config,
744 require_healthy: impl Into<Option<bool>>,
745) -> (RunningTopology, ShutdownErrorReceiver) {
746 config.healthchecks.set_require_healthy(require_healthy);
747 RunningTopology::start_init_validated(config, Default::default())
748 .await
749 .unwrap()
750}
751
752pub async fn spawn_collect_n<F, S>(future: F, stream: S, n: usize) -> Vec<Event>
758where
759 F: Future<Output = ()> + Send + 'static,
760 S: Stream<Item = Event>,
761{
762 let sender = tokio::spawn(future);
767 let events = collect_n(stream, n).await;
768 sender.await.expect("Failed to send data");
769 events
770}
771
772pub async fn spawn_collect_ready<F, S>(future: F, stream: S, sleep: u64) -> Vec<Event>
778where
779 F: Future<Output = ()> + Send + 'static,
780 S: Stream<Item = Event> + Unpin,
781{
782 let sender = tokio::spawn(future);
783 tokio::time::sleep(Duration::from_secs(sleep)).await;
784 let events = collect_ready(stream).await;
785 sender.await.expect("Failed to send data");
786 events
787}
788
789#[cfg(test)]
790mod tests {
791 use std::{
792 sync::{Arc, RwLock},
793 time::Duration,
794 };
795
796 use super::retry_until;
797
798 async fn retry_until_helper(count: Arc<RwLock<i32>>) -> Result<(), ()> {
800 if *count.read().unwrap() < 3 {
801 let mut c = count.write().unwrap();
802 *c += 1;
803 return Err(());
804 }
805 Ok(())
806 }
807
808 #[tokio::test]
809 async fn retry_until_before_timeout() {
810 let count = Arc::new(RwLock::new(0));
811 let func = || {
812 let count = Arc::clone(&count);
813 retry_until_helper(count)
814 };
815
816 retry_until(func, Duration::from_millis(10), Duration::from_secs(1)).await;
817 }
818}