indicatif/
progress_bar.rs

1#[cfg(test)]
2use portable_atomic::{AtomicBool, Ordering};
3use std::borrow::Cow;
4use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
5use std::time::Duration;
6#[cfg(not(target_arch = "wasm32"))]
7use std::time::Instant;
8use std::{fmt, io, thread};
9
10#[cfg(target_arch = "wasm32")]
11use instant::Instant;
12#[cfg(test)]
13use once_cell::sync::Lazy;
14
15use crate::draw_target::ProgressDrawTarget;
16use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString};
17use crate::style::ProgressStyle;
18use crate::{ProgressBarIter, ProgressIterator, ProgressState};
19
20/// A progress bar or spinner
21///
22/// The progress bar is an [`Arc`] around its internal state. When the progress bar is cloned it
23/// just increments the refcount (so the original and its clone share the same state).
24#[derive(Clone)]
25pub struct ProgressBar {
26    state: Arc<Mutex<BarState>>,
27    pos: Arc<AtomicPosition>,
28    ticker: Arc<Mutex<Option<Ticker>>>,
29}
30
31impl fmt::Debug for ProgressBar {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.debug_struct("ProgressBar").finish()
34    }
35}
36
37impl ProgressBar {
38    /// Creates a new progress bar with a given length
39    ///
40    /// This progress bar by default draws directly to stderr, and refreshes a maximum of 15 times
41    /// a second. To change the refresh rate, [set] the [draw target] to one with a different refresh
42    /// rate.
43    ///
44    /// [set]: ProgressBar::set_draw_target
45    /// [draw target]: ProgressDrawTarget
46    pub fn new(len: u64) -> Self {
47        Self::with_draw_target(Some(len), ProgressDrawTarget::stderr())
48    }
49
50    /// Creates a completely hidden progress bar
51    ///
52    /// This progress bar still responds to API changes but it does not have a length or render in
53    /// any way.
54    pub fn hidden() -> Self {
55        Self::with_draw_target(None, ProgressDrawTarget::hidden())
56    }
57
58    /// Creates a new progress bar with a given length and draw target
59    pub fn with_draw_target(len: Option<u64>, draw_target: ProgressDrawTarget) -> Self {
60        let pos = Arc::new(AtomicPosition::new());
61        Self {
62            state: Arc::new(Mutex::new(BarState::new(len, draw_target, pos.clone()))),
63            pos,
64            ticker: Arc::new(Mutex::new(None)),
65        }
66    }
67
68    /// Get a clone of the current progress bar style.
69    pub fn style(&self) -> ProgressStyle {
70        self.state().style.clone()
71    }
72
73    /// A convenience builder-like function for a progress bar with a given style
74    pub fn with_style(self, style: ProgressStyle) -> Self {
75        self.set_style(style);
76        self
77    }
78
79    /// A convenience builder-like function for a progress bar with a given tab width
80    pub fn with_tab_width(self, tab_width: usize) -> Self {
81        self.state().set_tab_width(tab_width);
82        self
83    }
84
85    /// A convenience builder-like function for a progress bar with a given prefix
86    ///
87    /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
88    /// (see [`ProgressStyle`]).
89    pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> Self {
90        let mut state = self.state();
91        state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
92        drop(state);
93        self
94    }
95
96    /// A convenience builder-like function for a progress bar with a given message
97    ///
98    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
99    /// [`ProgressStyle`]).
100    pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self {
101        let mut state = self.state();
102        state.state.message = TabExpandedString::new(message.into(), state.tab_width);
103        drop(state);
104        self
105    }
106
107    /// A convenience builder-like function for a progress bar with a given position
108    pub fn with_position(self, pos: u64) -> Self {
109        self.state().state.set_pos(pos);
110        self
111    }
112
113    /// A convenience builder-like function for a progress bar with a given elapsed time
114    pub fn with_elapsed(self, elapsed: Duration) -> Self {
115        self.state().state.started = Instant::now().checked_sub(elapsed).unwrap();
116        self
117    }
118
119    /// Sets the finish behavior for the progress bar
120    ///
121    /// This behavior is invoked when [`ProgressBar`] or
122    /// [`ProgressBarIter`] completes and
123    /// [`ProgressBar::is_finished()`] is false.
124    /// If you don't want the progress bar to be automatically finished then
125    /// call `with_finish(Abandon)`.
126    ///
127    /// [`ProgressBar`]: crate::ProgressBar
128    /// [`ProgressBarIter`]: crate::ProgressBarIter
129    /// [`ProgressBar::is_finished()`]: crate::ProgressBar::is_finished
130    pub fn with_finish(self, finish: ProgressFinish) -> Self {
131        self.state().on_finish = finish;
132        self
133    }
134
135    /// Creates a new spinner
136    ///
137    /// This spinner by default draws directly to stderr. This adds the default spinner style to it.
138    pub fn new_spinner() -> Self {
139        let rv = Self::with_draw_target(None, ProgressDrawTarget::stderr());
140        rv.set_style(ProgressStyle::default_spinner());
141        rv
142    }
143
144    /// Overrides the stored style
145    ///
146    /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it.
147    pub fn set_style(&self, style: ProgressStyle) {
148        self.state().set_style(style);
149    }
150
151    /// Sets the tab width (default: 8). All tabs will be expanded to this many spaces.
152    pub fn set_tab_width(&mut self, tab_width: usize) {
153        let mut state = self.state();
154        state.set_tab_width(tab_width);
155        state.draw(true, Instant::now()).unwrap();
156    }
157
158    /// Spawns a background thread to tick the progress bar
159    ///
160    /// When this is enabled a background thread will regularly tick the progress bar in the given
161    /// interval. This is useful to advance progress bars that are very slow by themselves.
162    ///
163    /// When steady ticks are enabled, calling [`ProgressBar::tick()`] on a progress bar does not
164    /// have any effect.
165    pub fn enable_steady_tick(&self, interval: Duration) {
166        // The way we test for ticker termination is with a single static `AtomicBool`. Since cargo
167        // runs tests concurrently, we have a `TICKER_TEST` lock to make sure tests using ticker
168        // don't step on each other. This check catches attempts to use tickers in tests without
169        // acquiring the lock.
170        #[cfg(test)]
171        {
172            let guard = TICKER_TEST.try_lock();
173            let lock_acquired = guard.is_ok();
174            // Drop the guard before panicking to avoid poisoning the lock (which would cause other
175            // ticker tests to fail)
176            drop(guard);
177            if lock_acquired {
178                panic!("you must acquire the TICKER_TEST lock in your test to use this method");
179            }
180        }
181
182        if interval.is_zero() {
183            return;
184        }
185
186        self.stop_and_replace_ticker(Some(interval));
187    }
188
189    /// Undoes [`ProgressBar::enable_steady_tick()`]
190    pub fn disable_steady_tick(&self) {
191        self.stop_and_replace_ticker(None);
192    }
193
194    fn stop_and_replace_ticker(&self, interval: Option<Duration>) {
195        let mut ticker_state = self.ticker.lock().unwrap();
196        if let Some(ticker) = ticker_state.take() {
197            ticker.stop();
198        }
199
200        *ticker_state = interval.map(|interval| Ticker::new(interval, &self.state));
201    }
202
203    /// Manually ticks the spinner or progress bar
204    ///
205    /// This automatically happens on any other change to a progress bar.
206    pub fn tick(&self) {
207        self.tick_inner(Instant::now());
208    }
209
210    fn tick_inner(&self, now: Instant) {
211        // Only tick if a `Ticker` isn't installed
212        if self.ticker.lock().unwrap().is_none() {
213            self.state().tick(now);
214        }
215    }
216
217    /// Advances the position of the progress bar by `delta`
218    pub fn inc(&self, delta: u64) {
219        self.pos.inc(delta);
220        let now = Instant::now();
221        if self.pos.allow(now) {
222            self.tick_inner(now);
223        }
224    }
225
226    /// A quick convenience check if the progress bar is hidden
227    pub fn is_hidden(&self) -> bool {
228        self.state().draw_target.is_hidden()
229    }
230
231    /// Indicates that the progress bar finished
232    pub fn is_finished(&self) -> bool {
233        self.state().state.is_finished()
234    }
235
236    /// Print a log line above the progress bar
237    ///
238    /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()`
239    /// will not do anything. If you want to write to the standard output in such cases as well, use
240    /// [`ProgressBar::suspend()`] instead.
241    ///
242    /// If the progress bar was added to a [`MultiProgress`], the log line will be
243    /// printed above all other progress bars.
244    ///
245    /// [`ProgressBar::suspend()`]: ProgressBar::suspend
246    /// [`MultiProgress`]: crate::MultiProgress
247    pub fn println<I: AsRef<str>>(&self, msg: I) {
248        self.state().println(Instant::now(), msg.as_ref());
249    }
250
251    /// Update the `ProgressBar`'s inner [`ProgressState`]
252    pub fn update(&self, f: impl FnOnce(&mut ProgressState)) {
253        self.state()
254            .update(Instant::now(), f, self.ticker.lock().unwrap().is_none());
255    }
256
257    /// Sets the position of the progress bar
258    pub fn set_position(&self, pos: u64) {
259        self.pos.set(pos);
260        let now = Instant::now();
261        if self.pos.allow(now) {
262            self.tick_inner(now);
263        }
264    }
265
266    /// Sets the length of the progress bar
267    pub fn set_length(&self, len: u64) {
268        self.state().set_length(Instant::now(), len);
269    }
270
271    /// Increase the length of the progress bar
272    pub fn inc_length(&self, delta: u64) {
273        self.state().inc_length(Instant::now(), delta);
274    }
275
276    /// Sets the current prefix of the progress bar
277    ///
278    /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
279    /// (see [`ProgressStyle`]).
280    pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) {
281        let mut state = self.state();
282        state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
283        state.update_estimate_and_draw(Instant::now());
284    }
285
286    /// Sets the current message of the progress bar
287    ///
288    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
289    /// [`ProgressStyle`]).
290    pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) {
291        let mut state = self.state();
292        state.state.message = TabExpandedString::new(msg.into(), state.tab_width);
293        state.update_estimate_and_draw(Instant::now());
294    }
295
296    /// Creates a new weak reference to this [`ProgressBar`]
297    pub fn downgrade(&self) -> WeakProgressBar {
298        WeakProgressBar {
299            state: Arc::downgrade(&self.state),
300            pos: Arc::downgrade(&self.pos),
301            ticker: Arc::downgrade(&self.ticker),
302        }
303    }
304
305    /// Resets the ETA calculation
306    ///
307    /// This can be useful if the progress bars made a large jump or was paused for a prolonged
308    /// time.
309    pub fn reset_eta(&self) {
310        self.state().reset(Instant::now(), Reset::Eta);
311    }
312
313    /// Resets elapsed time and the ETA calculation
314    pub fn reset_elapsed(&self) {
315        self.state().reset(Instant::now(), Reset::Elapsed);
316    }
317
318    /// Resets all of the progress bar state
319    pub fn reset(&self) {
320        self.state().reset(Instant::now(), Reset::All);
321    }
322
323    /// Finishes the progress bar and leaves the current message
324    pub fn finish(&self) {
325        self.state()
326            .finish_using_style(Instant::now(), ProgressFinish::AndLeave);
327    }
328
329    /// Finishes the progress bar and sets a message
330    ///
331    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
332    /// [`ProgressStyle`]).
333    pub fn finish_with_message(&self, msg: impl Into<Cow<'static, str>>) {
334        self.state()
335            .finish_using_style(Instant::now(), ProgressFinish::WithMessage(msg.into()));
336    }
337
338    /// Finishes the progress bar and completely clears it
339    pub fn finish_and_clear(&self) {
340        self.state()
341            .finish_using_style(Instant::now(), ProgressFinish::AndClear);
342    }
343
344    /// Finishes the progress bar and leaves the current message and progress
345    pub fn abandon(&self) {
346        self.state()
347            .finish_using_style(Instant::now(), ProgressFinish::Abandon);
348    }
349
350    /// Finishes the progress bar and sets a message, and leaves the current progress
351    ///
352    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
353    /// [`ProgressStyle`]).
354    pub fn abandon_with_message(&self, msg: impl Into<Cow<'static, str>>) {
355        self.state().finish_using_style(
356            Instant::now(),
357            ProgressFinish::AbandonWithMessage(msg.into()),
358        );
359    }
360
361    /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`]
362    ///
363    /// See [`ProgressBar::with_finish()`].
364    pub fn finish_using_style(&self) {
365        let mut state = self.state();
366        let finish = state.on_finish.clone();
367        state.finish_using_style(Instant::now(), finish);
368    }
369
370    /// Sets a different draw target for the progress bar
371    ///
372    /// This can be used to draw the progress bar to stderr (this is the default):
373    ///
374    /// ```rust,no_run
375    /// # use indicatif::{ProgressBar, ProgressDrawTarget};
376    /// let pb = ProgressBar::new(100);
377    /// pb.set_draw_target(ProgressDrawTarget::stderr());
378    /// ```
379    ///
380    /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after
381    /// running [`MultiProgress::add()`]) will unlink this progress bar. If you don't want this
382    /// behavior, call [`MultiProgress::set_draw_target()`] instead.
383    ///
384    /// Use [`ProgressBar::with_draw_target()`] to set the draw target during creation.
385    ///
386    /// [`MultiProgress`]: crate::MultiProgress
387    /// [`MultiProgress::add()`]: crate::MultiProgress::add
388    /// [`MultiProgress::set_draw_target()`]: crate::MultiProgress::set_draw_target
389    pub fn set_draw_target(&self, target: ProgressDrawTarget) {
390        let mut state = self.state();
391        state.draw_target.disconnect(Instant::now());
392        state.draw_target = target;
393    }
394
395    /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar
396    ///
397    /// Useful for external code that writes to the standard output.
398    ///
399    /// If the progress bar was added to a [`MultiProgress`], it will suspend the entire [`MultiProgress`].
400    ///
401    /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
402    /// anything on the progress bar will be blocked until `f` finishes.
403    /// Therefore, it is recommended to avoid long-running operations in `f`.
404    ///
405    /// ```rust,no_run
406    /// # use indicatif::ProgressBar;
407    /// let mut pb = ProgressBar::new(3);
408    /// pb.suspend(|| {
409    ///     println!("Log message");
410    /// })
411    /// ```
412    ///
413    /// [`MultiProgress`]: crate::MultiProgress
414    pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
415        self.state().suspend(Instant::now(), f)
416    }
417
418    /// Wraps an [`Iterator`] with the progress bar
419    ///
420    /// ```rust,no_run
421    /// # use indicatif::ProgressBar;
422    /// let v = vec![1, 2, 3];
423    /// let pb = ProgressBar::new(3);
424    /// for item in pb.wrap_iter(v.iter()) {
425    ///     // ...
426    /// }
427    /// ```
428    pub fn wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It> {
429        it.progress_with(self.clone())
430    }
431
432    /// Wraps an [`io::Read`] with the progress bar
433    ///
434    /// ```rust,no_run
435    /// # use std::fs::File;
436    /// # use std::io;
437    /// # use indicatif::ProgressBar;
438    /// # fn test () -> io::Result<()> {
439    /// let source = File::open("work.txt")?;
440    /// let mut target = File::create("done.txt")?;
441    /// let pb = ProgressBar::new(source.metadata()?.len());
442    /// io::copy(&mut pb.wrap_read(source), &mut target);
443    /// # Ok(())
444    /// # }
445    /// ```
446    pub fn wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R> {
447        ProgressBarIter {
448            progress: self.clone(),
449            it: read,
450        }
451    }
452
453    /// Wraps an [`io::Write`] with the progress bar
454    ///
455    /// ```rust,no_run
456    /// # use std::fs::File;
457    /// # use std::io;
458    /// # use indicatif::ProgressBar;
459    /// # fn test () -> io::Result<()> {
460    /// let mut source = File::open("work.txt")?;
461    /// let target = File::create("done.txt")?;
462    /// let pb = ProgressBar::new(source.metadata()?.len());
463    /// io::copy(&mut source, &mut pb.wrap_write(target));
464    /// # Ok(())
465    /// # }
466    /// ```
467    pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> {
468        ProgressBarIter {
469            progress: self.clone(),
470            it: write,
471        }
472    }
473
474    #[cfg(feature = "tokio")]
475    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
476    /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar
477    ///
478    /// ```rust,no_run
479    /// # use tokio::fs::File;
480    /// # use tokio::io;
481    /// # use indicatif::ProgressBar;
482    /// # async fn test() -> io::Result<()> {
483    /// let mut source = File::open("work.txt").await?;
484    /// let mut target = File::open("done.txt").await?;
485    /// let pb = ProgressBar::new(source.metadata().await?.len());
486    /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?;
487    /// # Ok(())
488    /// # }
489    /// ```
490    pub fn wrap_async_write<W: tokio::io::AsyncWrite + Unpin>(
491        &self,
492        write: W,
493    ) -> ProgressBarIter<W> {
494        ProgressBarIter {
495            progress: self.clone(),
496            it: write,
497        }
498    }
499
500    #[cfg(feature = "tokio")]
501    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
502    /// Wraps an [`tokio::io::AsyncRead`] with the progress bar
503    ///
504    /// ```rust,no_run
505    /// # use tokio::fs::File;
506    /// # use tokio::io;
507    /// # use indicatif::ProgressBar;
508    /// # async fn test() -> io::Result<()> {
509    /// let mut source = File::open("work.txt").await?;
510    /// let mut target = File::open("done.txt").await?;
511    /// let pb = ProgressBar::new(source.metadata().await?.len());
512    /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?;
513    /// # Ok(())
514    /// # }
515    /// ```
516    pub fn wrap_async_read<R: tokio::io::AsyncRead + Unpin>(&self, read: R) -> ProgressBarIter<R> {
517        ProgressBarIter {
518            progress: self.clone(),
519            it: read,
520        }
521    }
522
523    /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar
524    ///
525    /// ```
526    /// # use indicatif::ProgressBar;
527    /// # futures::executor::block_on(async {
528    /// use futures::stream::{self, StreamExt};
529    /// let pb = ProgressBar::new(10);
530    /// let mut stream = pb.wrap_stream(stream::iter('a'..='z'));
531    ///
532    /// assert_eq!(stream.next().await, Some('a'));
533    /// assert_eq!(stream.count().await, 25);
534    /// # }); // block_on
535    /// ```
536    #[cfg(feature = "futures")]
537    #[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
538    pub fn wrap_stream<S: futures_core::Stream>(&self, stream: S) -> ProgressBarIter<S> {
539        ProgressBarIter {
540            progress: self.clone(),
541            it: stream,
542        }
543    }
544
545    /// Returns the current position
546    pub fn position(&self) -> u64 {
547        self.state().state.pos()
548    }
549
550    /// Returns the current length
551    pub fn length(&self) -> Option<u64> {
552        self.state().state.len()
553    }
554
555    /// Returns the current ETA
556    pub fn eta(&self) -> Duration {
557        self.state().state.eta()
558    }
559
560    /// Returns the current rate of progress
561    pub fn per_sec(&self) -> f64 {
562        self.state().state.per_sec()
563    }
564
565    /// Returns the current expected duration
566    pub fn duration(&self) -> Duration {
567        self.state().state.duration()
568    }
569
570    /// Returns the current elapsed time
571    pub fn elapsed(&self) -> Duration {
572        self.state().state.elapsed()
573    }
574
575    /// Index in the `MultiState`
576    pub(crate) fn index(&self) -> Option<usize> {
577        self.state().draw_target.remote().map(|(_, idx)| idx)
578    }
579
580    /// Current message
581    pub fn message(&self) -> String {
582        self.state().state.message.expanded().to_string()
583    }
584
585    /// Current prefix
586    pub fn prefix(&self) -> String {
587        self.state().state.prefix.expanded().to_string()
588    }
589
590    #[inline]
591    pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
592        self.state.lock().unwrap()
593    }
594}
595
596/// A weak reference to a [`ProgressBar`].
597///
598/// Useful for creating custom steady tick implementations
599#[derive(Clone, Default)]
600pub struct WeakProgressBar {
601    state: Weak<Mutex<BarState>>,
602    pos: Weak<AtomicPosition>,
603    ticker: Weak<Mutex<Option<Ticker>>>,
604}
605
606impl WeakProgressBar {
607    /// Create a new [`WeakProgressBar`] that returns `None` when [`upgrade()`] is called.
608    ///
609    /// [`upgrade()`]: WeakProgressBar::upgrade
610    pub fn new() -> Self {
611        Self::default()
612    }
613
614    /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner
615    /// value if successful. Returns [`None`] if the inner value has since been dropped.
616    ///
617    /// [`ProgressBar`]: struct.ProgressBar.html
618    pub fn upgrade(&self) -> Option<ProgressBar> {
619        let state = self.state.upgrade()?;
620        let pos = self.pos.upgrade()?;
621        let ticker = self.ticker.upgrade()?;
622        Some(ProgressBar { state, pos, ticker })
623    }
624}
625
626pub(crate) struct Ticker {
627    stopping: Arc<(Mutex<bool>, Condvar)>,
628    join_handle: Option<thread::JoinHandle<()>>,
629}
630
631impl Drop for Ticker {
632    fn drop(&mut self) {
633        self.stop();
634        self.join_handle.take().map(|handle| handle.join());
635    }
636}
637
638#[cfg(test)]
639static TICKER_RUNNING: AtomicBool = AtomicBool::new(false);
640
641impl Ticker {
642    pub(crate) fn new(interval: Duration, bar_state: &Arc<Mutex<BarState>>) -> Self {
643        debug_assert!(!interval.is_zero());
644
645        // A `Mutex<bool>` is used as a flag to indicate whether the ticker was requested to stop.
646        // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify
647        // the thread and interrupt the ticker wait.
648        #[allow(clippy::mutex_atomic)]
649        let stopping = Arc::new((Mutex::new(false), Condvar::new()));
650        let control = TickerControl {
651            stopping: stopping.clone(),
652            state: Arc::downgrade(bar_state),
653        };
654
655        let join_handle = thread::spawn(move || control.run(interval));
656        Self {
657            stopping,
658            join_handle: Some(join_handle),
659        }
660    }
661
662    pub(crate) fn stop(&self) {
663        *self.stopping.0.lock().unwrap() = true;
664        self.stopping.1.notify_one();
665    }
666}
667
668struct TickerControl {
669    stopping: Arc<(Mutex<bool>, Condvar)>,
670    state: Weak<Mutex<BarState>>,
671}
672
673impl TickerControl {
674    fn run(&self, interval: Duration) {
675        #[cfg(test)]
676        TICKER_RUNNING.store(true, Ordering::SeqCst);
677
678        while let Some(arc) = self.state.upgrade() {
679            let mut state = arc.lock().unwrap();
680            if state.state.is_finished() {
681                break;
682            }
683
684            state.tick(Instant::now());
685
686            drop(state); // Don't forget to drop the lock before sleeping
687            drop(arc); // Also need to drop Arc otherwise BarState won't be dropped
688
689            // Wait for `interval` but return early if we are notified to stop
690            let result = self
691                .stopping
692                .1
693                .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| {
694                    !*stopped
695                })
696                .unwrap();
697
698            // If the wait didn't time out, it means we were notified to stop
699            if !result.1.timed_out() {
700                break;
701            }
702        }
703
704        #[cfg(test)]
705        TICKER_RUNNING.store(false, Ordering::SeqCst);
706    }
707}
708
709// Tests using the global TICKER_RUNNING flag need to be serialized
710#[cfg(test)]
711pub(crate) static TICKER_TEST: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716
717    #[allow(clippy::float_cmp)]
718    #[test]
719    fn test_pbar_zero() {
720        let pb = ProgressBar::new(0);
721        assert_eq!(pb.state().state.fraction(), 1.0);
722    }
723
724    #[allow(clippy::float_cmp)]
725    #[test]
726    fn test_pbar_maxu64() {
727        let pb = ProgressBar::new(!0);
728        assert_eq!(pb.state().state.fraction(), 0.0);
729    }
730
731    #[test]
732    fn test_pbar_overflow() {
733        let pb = ProgressBar::new(1);
734        pb.set_draw_target(ProgressDrawTarget::hidden());
735        pb.inc(2);
736        pb.finish();
737    }
738
739    #[test]
740    fn test_get_position() {
741        let pb = ProgressBar::new(1);
742        pb.set_draw_target(ProgressDrawTarget::hidden());
743        pb.inc(2);
744        let pos = pb.position();
745        assert_eq!(pos, 2);
746    }
747
748    #[test]
749    fn test_weak_pb() {
750        let pb = ProgressBar::new(0);
751        let weak = pb.downgrade();
752        assert!(weak.upgrade().is_some());
753        ::std::mem::drop(pb);
754        assert!(weak.upgrade().is_none());
755    }
756
757    #[test]
758    fn it_can_wrap_a_reader() {
759        let bytes = &b"I am an implementation of io::Read"[..];
760        let pb = ProgressBar::new(bytes.len() as u64);
761        let mut reader = pb.wrap_read(bytes);
762        let mut writer = Vec::new();
763        io::copy(&mut reader, &mut writer).unwrap();
764        assert_eq!(writer, bytes);
765    }
766
767    #[test]
768    fn it_can_wrap_a_writer() {
769        let bytes = b"implementation of io::Read";
770        let mut reader = &bytes[..];
771        let pb = ProgressBar::new(bytes.len() as u64);
772        let writer = Vec::new();
773        let mut writer = pb.wrap_write(writer);
774        io::copy(&mut reader, &mut writer).unwrap();
775        assert_eq!(writer.it, bytes);
776    }
777
778    #[test]
779    fn ticker_thread_terminates_on_drop() {
780        let _guard = TICKER_TEST.lock().unwrap();
781        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
782
783        let pb = ProgressBar::new_spinner();
784        pb.enable_steady_tick(Duration::from_millis(50));
785
786        // Give the thread time to start up
787        thread::sleep(Duration::from_millis(250));
788
789        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
790
791        drop(pb);
792        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
793    }
794
795    #[test]
796    fn ticker_thread_terminates_on_drop_2() {
797        let _guard = TICKER_TEST.lock().unwrap();
798        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
799
800        let pb = ProgressBar::new_spinner();
801        pb.enable_steady_tick(Duration::from_millis(50));
802        let pb2 = pb.clone();
803
804        // Give the thread time to start up
805        thread::sleep(Duration::from_millis(250));
806
807        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
808
809        drop(pb);
810        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
811
812        drop(pb2);
813        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
814    }
815}