Skip to main content

vector/test_util/
mod.rs

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; // The default time to wait in `wait_for`
53const WAIT_FOR_MIN_MILLIS: u64 = 5; // The minimum time to pause before retrying
54const WAIT_FOR_MAX_MILLIS: u64 = 500; // The maximum time to pause before retrying
55
56pub 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    // TOML cannot represent JSON null; strip None-valued fields before serializing.
103    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    // Windows: ANSI colors are not supported by cmd.exe
136    // Color is false for everything except unix.
137    #[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    // Initialize metrics as well
145    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
247// TODO refactor to have a single implementation for `Event`, `LogEvent` and `Metric`.
248fn 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
304/// Generates event metrics with the provided tags and timestamp.
305///
306/// # Parameters
307/// - `count`: the number of metrics to generate
308/// - `batch`: the batch notifier to use with the stream
309/// - `tags`: the tags to apply to each metric event
310/// - `timestamp`: the timestamp to use for each metric event
311/// - `timestamp_offset`: the offset from the `timestamp` to use for each additional metric
312///
313/// # Returns
314/// A tuple of the generated metric events and the stream of the generated events
315pub 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            // this ensures we get Origin Metadata, with an undefined service but that's ok.
337            .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
512// Wait for a Future to resolve, or the duration to elapse (will panic)
513pub 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        // quadratic backoff up to a maximum delay
526        delay = (delay * 2).min(WAIT_FOR_MAX_MILLIS);
527    }
528}
529
530// Wait for 5 seconds
531pub 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
539// Wait (for 5 secs) for a TCP socket to be reachable
540pub 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
551// Allows specifying a custom duration to wait for a TCP socket to be reachable
552pub 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
582// Retries a func every `retry` duration until given an Ok(T); panics after `until` elapses
583pub 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    /// Succeeds once first connection has been made.
611    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
752/// Collect the first `n` events from a stream while a future is spawned
753/// in the background. This is used for tests where the collect has to
754/// happen concurrent with the sending process (ie the stream is
755/// handling finalization, which is required for the future to receive
756/// an acknowledgement).
757pub 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    // TODO: Switch to using `select!` so that we can drive `future` to completion while also driving `collect_n`,
763    // such that if `future` panics, we break out and don't continue driving `collect_n`. In most cases, `future`
764    // completing successfully is what actually drives events into `stream`, so continuing to wait for all N events when
765    // the catalyst has failed is.... almost never the desired behavior.
766    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
772/// Collect all the ready events from a stream after spawning a future
773/// in the background and letting it run for a given interval. This is
774/// used for tests where the collect has to happen concurrent with the
775/// sending process (ie the stream is handling finalization, which is
776/// required for the future to receive an acknowledgement).
777pub 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    // helper which errors the first 3x, and succeeds on the 4th
799    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}