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}