diff options
Diffstat (limited to 'vendor/indicatif/src/progress_bar.rs')
-rw-r--r-- | vendor/indicatif/src/progress_bar.rs | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/vendor/indicatif/src/progress_bar.rs b/vendor/indicatif/src/progress_bar.rs new file mode 100644 index 000000000..938668e58 --- /dev/null +++ b/vendor/indicatif/src/progress_bar.rs @@ -0,0 +1,808 @@ +#[cfg(test)] +use portable_atomic::{AtomicBool, Ordering}; +use std::borrow::Cow; +use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak}; +use std::time::Duration; +#[cfg(not(target_arch = "wasm32"))] +use std::time::Instant; +use std::{fmt, io, thread}; + +#[cfg(target_arch = "wasm32")] +use instant::Instant; +#[cfg(test)] +use once_cell::sync::Lazy; + +use crate::draw_target::ProgressDrawTarget; +use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString}; +use crate::style::ProgressStyle; +use crate::{ProgressBarIter, ProgressIterator, ProgressState}; + +/// A progress bar or spinner +/// +/// The progress bar is an [`Arc`] around its internal state. When the progress bar is cloned it +/// just increments the refcount (so the original and its clone share the same state). +#[derive(Clone)] +pub struct ProgressBar { + state: Arc<Mutex<BarState>>, + pos: Arc<AtomicPosition>, + ticker: Arc<Mutex<Option<Ticker>>>, +} + +impl fmt::Debug for ProgressBar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ProgressBar").finish() + } +} + +impl ProgressBar { + /// Creates a new progress bar with a given length + /// + /// This progress bar by default draws directly to stderr, and refreshes a maximum of 15 times + /// a second. To change the refresh rate, set the draw target to one with a different refresh + /// rate. + pub fn new(len: u64) -> Self { + Self::with_draw_target(Some(len), ProgressDrawTarget::stderr()) + } + + /// Creates a completely hidden progress bar + /// + /// This progress bar still responds to API changes but it does not have a length or render in + /// any way. + pub fn hidden() -> Self { + Self::with_draw_target(None, ProgressDrawTarget::hidden()) + } + + /// Creates a new progress bar with a given length and draw target + pub fn with_draw_target(len: Option<u64>, draw_target: ProgressDrawTarget) -> Self { + let pos = Arc::new(AtomicPosition::new()); + Self { + state: Arc::new(Mutex::new(BarState::new(len, draw_target, pos.clone()))), + pos, + ticker: Arc::new(Mutex::new(None)), + } + } + + /// Get a clone of the current progress bar style. + pub fn style(&self) -> ProgressStyle { + self.state().style.clone() + } + + /// A convenience builder-like function for a progress bar with a given style + pub fn with_style(self, style: ProgressStyle) -> Self { + self.set_style(style); + self + } + + /// A convenience builder-like function for a progress bar with a given tab width + pub fn with_tab_width(self, tab_width: usize) -> Self { + self.state().set_tab_width(tab_width); + self + } + + /// A convenience builder-like function for a progress bar with a given prefix + /// + /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template + /// (see [`ProgressStyle`]). + pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> Self { + let mut state = self.state(); + state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width); + drop(state); + self + } + + /// A convenience builder-like function for a progress bar with a given message + /// + /// For the message to be visible, the `{msg}` placeholder must be present in the template (see + /// [`ProgressStyle`]). + pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self { + let mut state = self.state(); + state.state.message = TabExpandedString::new(message.into(), state.tab_width); + drop(state); + self + } + + /// A convenience builder-like function for a progress bar with a given position + pub fn with_position(self, pos: u64) -> Self { + self.state().state.set_pos(pos); + self + } + + /// A convenience builder-like function for a progress bar with a given elapsed time + pub fn with_elapsed(self, elapsed: Duration) -> Self { + self.state().state.started = Instant::now().checked_sub(elapsed).unwrap(); + self + } + + /// Sets the finish behavior for the progress bar + /// + /// This behavior is invoked when [`ProgressBar`] or + /// [`ProgressBarIter`] completes and + /// [`ProgressBar::is_finished()`] is false. + /// If you don't want the progress bar to be automatically finished then + /// call `on_finish(None)`. + /// + /// [`ProgressBar`]: crate::ProgressBar + /// [`ProgressBarIter`]: crate::ProgressBarIter + /// [`ProgressBar::is_finished()`]: crate::ProgressBar::is_finished + pub fn with_finish(self, finish: ProgressFinish) -> Self { + self.state().on_finish = finish; + self + } + + /// Creates a new spinner + /// + /// This spinner by default draws directly to stderr. This adds the default spinner style to it. + pub fn new_spinner() -> Self { + let rv = Self::with_draw_target(None, ProgressDrawTarget::stderr()); + rv.set_style(ProgressStyle::default_spinner()); + rv + } + + /// Overrides the stored style + /// + /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it. + pub fn set_style(&self, style: ProgressStyle) { + self.state().set_style(style); + } + + /// Sets the tab width (default: 8). All tabs will be expanded to this many spaces. + pub fn set_tab_width(&mut self, tab_width: usize) { + let mut state = self.state(); + state.set_tab_width(tab_width); + state.draw(true, Instant::now()).unwrap(); + } + + /// Spawns a background thread to tick the progress bar + /// + /// When this is enabled a background thread will regularly tick the progress bar in the given + /// interval. This is useful to advance progress bars that are very slow by themselves. + /// + /// When steady ticks are enabled, calling [`ProgressBar::tick()`] on a progress bar does not + /// have any effect. + pub fn enable_steady_tick(&self, interval: Duration) { + // The way we test for ticker termination is with a single static `AtomicBool`. Since cargo + // runs tests concurrently, we have a `TICKER_TEST` lock to make sure tests using ticker + // don't step on each other. This check catches attempts to use tickers in tests without + // acquiring the lock. + #[cfg(test)] + { + let guard = TICKER_TEST.try_lock(); + let lock_acquired = guard.is_ok(); + // Drop the guard before panicking to avoid poisoning the lock (which would cause other + // ticker tests to fail) + drop(guard); + if lock_acquired { + panic!("you must acquire the TICKER_TEST lock in your test to use this method"); + } + } + + if interval.is_zero() { + return; + } + + self.stop_and_replace_ticker(Some(interval)); + } + + /// Undoes [`ProgressBar::enable_steady_tick()`] + pub fn disable_steady_tick(&self) { + self.stop_and_replace_ticker(None); + } + + fn stop_and_replace_ticker(&self, interval: Option<Duration>) { + let mut ticker_state = self.ticker.lock().unwrap(); + if let Some(ticker) = ticker_state.take() { + ticker.stop(); + } + + *ticker_state = interval.map(|interval| Ticker::new(interval, &self.state)); + } + + /// Manually ticks the spinner or progress bar + /// + /// This automatically happens on any other change to a progress bar. + pub fn tick(&self) { + self.tick_inner(Instant::now()); + } + + fn tick_inner(&self, now: Instant) { + // Only tick if a `Ticker` isn't installed + if self.ticker.lock().unwrap().is_none() { + self.state().tick(now); + } + } + + /// Advances the position of the progress bar by `delta` + pub fn inc(&self, delta: u64) { + self.pos.inc(delta); + let now = Instant::now(); + if self.pos.allow(now) { + self.tick_inner(now); + } + } + + /// A quick convenience check if the progress bar is hidden + pub fn is_hidden(&self) -> bool { + self.state().draw_target.is_hidden() + } + + /// Indicates that the progress bar finished + pub fn is_finished(&self) -> bool { + self.state().state.is_finished() + } + + /// Print a log line above the progress bar + /// + /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()` + /// will not do anything. If you want to write to the standard output in such cases as well, use + /// [`suspend`] instead. + /// + /// If the progress bar was added to a [`MultiProgress`], the log line will be + /// printed above all other progress bars. + /// + /// [`suspend`]: ProgressBar::suspend + /// [`MultiProgress`]: crate::MultiProgress + pub fn println<I: AsRef<str>>(&self, msg: I) { + self.state().println(Instant::now(), msg.as_ref()); + } + + /// Update the `ProgressBar`'s inner [`ProgressState`] + pub fn update(&self, f: impl FnOnce(&mut ProgressState)) { + self.state() + .update(Instant::now(), f, self.ticker.lock().unwrap().is_none()); + } + + /// Sets the position of the progress bar + pub fn set_position(&self, pos: u64) { + self.pos.set(pos); + let now = Instant::now(); + if self.pos.allow(now) { + self.tick_inner(now); + } + } + + /// Sets the length of the progress bar + pub fn set_length(&self, len: u64) { + self.state().set_length(Instant::now(), len); + } + + /// Increase the length of the progress bar + pub fn inc_length(&self, delta: u64) { + self.state().inc_length(Instant::now(), delta); + } + + /// Sets the current prefix of the progress bar + /// + /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template + /// (see [`ProgressStyle`]). + pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) { + let mut state = self.state(); + state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width); + state.update_estimate_and_draw(Instant::now()); + } + + /// Sets the current message of the progress bar + /// + /// For the message to be visible, the `{msg}` placeholder must be present in the template (see + /// [`ProgressStyle`]). + pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) { + let mut state = self.state(); + state.state.message = TabExpandedString::new(msg.into(), state.tab_width); + state.update_estimate_and_draw(Instant::now()); + } + + /// Creates a new weak reference to this `ProgressBar` + pub fn downgrade(&self) -> WeakProgressBar { + WeakProgressBar { + state: Arc::downgrade(&self.state), + pos: Arc::downgrade(&self.pos), + ticker: Arc::downgrade(&self.ticker), + } + } + + /// Resets the ETA calculation + /// + /// This can be useful if the progress bars made a large jump or was paused for a prolonged + /// time. + pub fn reset_eta(&self) { + self.state().reset(Instant::now(), Reset::Eta); + } + + /// Resets elapsed time and the ETA calculation + pub fn reset_elapsed(&self) { + self.state().reset(Instant::now(), Reset::Elapsed); + } + + /// Resets all of the progress bar state + pub fn reset(&self) { + self.state().reset(Instant::now(), Reset::All); + } + + /// Finishes the progress bar and leaves the current message + pub fn finish(&self) { + self.state() + .finish_using_style(Instant::now(), ProgressFinish::AndLeave); + } + + /// Finishes the progress bar and sets a message + /// + /// For the message to be visible, the `{msg}` placeholder must be present in the template (see + /// [`ProgressStyle`]). + pub fn finish_with_message(&self, msg: impl Into<Cow<'static, str>>) { + self.state() + .finish_using_style(Instant::now(), ProgressFinish::WithMessage(msg.into())); + } + + /// Finishes the progress bar and completely clears it + pub fn finish_and_clear(&self) { + self.state() + .finish_using_style(Instant::now(), ProgressFinish::AndClear); + } + + /// Finishes the progress bar and leaves the current message and progress + pub fn abandon(&self) { + self.state() + .finish_using_style(Instant::now(), ProgressFinish::Abandon); + } + + /// Finishes the progress bar and sets a message, and leaves the current progress + /// + /// For the message to be visible, the `{msg}` placeholder must be present in the template (see + /// [`ProgressStyle`]). + pub fn abandon_with_message(&self, msg: impl Into<Cow<'static, str>>) { + self.state().finish_using_style( + Instant::now(), + ProgressFinish::AbandonWithMessage(msg.into()), + ); + } + + /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`] + /// + /// See [`ProgressBar::with_finish()`]. + pub fn finish_using_style(&self) { + let mut state = self.state(); + let finish = state.on_finish.clone(); + state.finish_using_style(Instant::now(), finish); + } + + /// Sets a different draw target for the progress bar + /// + /// This can be used to draw the progress bar to stderr (this is the default): + /// + /// ```rust,no_run + /// # use indicatif::{ProgressBar, ProgressDrawTarget}; + /// let pb = ProgressBar::new(100); + /// pb.set_draw_target(ProgressDrawTarget::stderr()); + /// ``` + /// + /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after + /// running [`MultiProgress::add`]) will unlink this progress bar. If you don't want this + /// behavior, call [`MultiProgress::set_draw_target`] instead. + /// + /// [`MultiProgress`]: crate::MultiProgress + /// [`MultiProgress::add`]: crate::MultiProgress::add + /// [`MultiProgress::set_draw_target`]: crate::MultiProgress::set_draw_target + pub fn set_draw_target(&self, target: ProgressDrawTarget) { + let mut state = self.state(); + state.draw_target.disconnect(Instant::now()); + state.draw_target = target; + } + + /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar + /// + /// Useful for external code that writes to the standard output. + /// + /// If the progress bar was added to a MultiProgress, it will suspend the entire MultiProgress + /// + /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print + /// anything on the progress bar will be blocked until `f` finishes. + /// Therefore, it is recommended to avoid long-running operations in `f`. + /// + /// ```rust,no_run + /// # use indicatif::ProgressBar; + /// let mut pb = ProgressBar::new(3); + /// pb.suspend(|| { + /// println!("Log message"); + /// }) + /// ``` + pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R { + self.state().suspend(Instant::now(), f) + } + + /// Wraps an [`Iterator`] with the progress bar + /// + /// ```rust,no_run + /// # use indicatif::ProgressBar; + /// let v = vec![1, 2, 3]; + /// let pb = ProgressBar::new(3); + /// for item in pb.wrap_iter(v.iter()) { + /// // ... + /// } + /// ``` + pub fn wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It> { + it.progress_with(self.clone()) + } + + /// Wraps an [`io::Read`] with the progress bar + /// + /// ```rust,no_run + /// # use std::fs::File; + /// # use std::io; + /// # use indicatif::ProgressBar; + /// # fn test () -> io::Result<()> { + /// let source = File::open("work.txt")?; + /// let mut target = File::create("done.txt")?; + /// let pb = ProgressBar::new(source.metadata()?.len()); + /// io::copy(&mut pb.wrap_read(source), &mut target); + /// # Ok(()) + /// # } + /// ``` + pub fn wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R> { + ProgressBarIter { + progress: self.clone(), + it: read, + } + } + + /// Wraps an [`io::Write`] with the progress bar + /// + /// ```rust,no_run + /// # use std::fs::File; + /// # use std::io; + /// # use indicatif::ProgressBar; + /// # fn test () -> io::Result<()> { + /// let mut source = File::open("work.txt")?; + /// let target = File::create("done.txt")?; + /// let pb = ProgressBar::new(source.metadata()?.len()); + /// io::copy(&mut source, &mut pb.wrap_write(target)); + /// # Ok(()) + /// # } + /// ``` + pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> { + ProgressBarIter { + progress: self.clone(), + it: write, + } + } + + #[cfg(feature = "tokio")] + #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] + /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar + /// + /// ```rust,no_run + /// # use tokio::fs::File; + /// # use tokio::io; + /// # use indicatif::ProgressBar; + /// # async fn test() -> io::Result<()> { + /// let mut source = File::open("work.txt").await?; + /// let mut target = File::open("done.txt").await?; + /// let pb = ProgressBar::new(source.metadata().await?.len()); + /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?; + /// # Ok(()) + /// # } + /// ``` + pub fn wrap_async_write<W: tokio::io::AsyncWrite + Unpin>( + &self, + write: W, + ) -> ProgressBarIter<W> { + ProgressBarIter { + progress: self.clone(), + it: write, + } + } + + #[cfg(feature = "tokio")] + #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] + /// Wraps an [`tokio::io::AsyncRead`] with the progress bar + /// + /// ```rust,no_run + /// # use tokio::fs::File; + /// # use tokio::io; + /// # use indicatif::ProgressBar; + /// # async fn test() -> io::Result<()> { + /// let mut source = File::open("work.txt").await?; + /// let mut target = File::open("done.txt").await?; + /// let pb = ProgressBar::new(source.metadata().await?.len()); + /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?; + /// # Ok(()) + /// # } + /// ``` + pub fn wrap_async_read<R: tokio::io::AsyncRead + Unpin>(&self, read: R) -> ProgressBarIter<R> { + ProgressBarIter { + progress: self.clone(), + it: read, + } + } + + /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar + /// + /// ``` + /// # use indicatif::ProgressBar; + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// let pb = ProgressBar::new(10); + /// let mut stream = pb.wrap_stream(stream::iter('a'..='z')); + /// + /// assert_eq!(stream.next().await, Some('a')); + /// assert_eq!(stream.count().await, 25); + /// # }); // block_on + /// ``` + #[cfg(feature = "futures")] + #[cfg_attr(docsrs, doc(cfg(feature = "futures")))] + pub fn wrap_stream<S: futures_core::Stream>(&self, stream: S) -> ProgressBarIter<S> { + ProgressBarIter { + progress: self.clone(), + it: stream, + } + } + + /// Returns the current position + pub fn position(&self) -> u64 { + self.state().state.pos() + } + + /// Returns the current length + pub fn length(&self) -> Option<u64> { + self.state().state.len() + } + + /// Returns the current ETA + pub fn eta(&self) -> Duration { + self.state().state.eta() + } + + /// Returns the current rate of progress + pub fn per_sec(&self) -> f64 { + self.state().state.per_sec() + } + + /// Returns the current expected duration + pub fn duration(&self) -> Duration { + self.state().state.duration() + } + + /// Returns the current elapsed time + pub fn elapsed(&self) -> Duration { + self.state().state.elapsed() + } + + /// Index in the `MultiState` + pub(crate) fn index(&self) -> Option<usize> { + self.state().draw_target.remote().map(|(_, idx)| idx) + } + + /// Current message + pub fn message(&self) -> String { + self.state().state.message.expanded().to_string() + } + + /// Current prefix + pub fn prefix(&self) -> String { + self.state().state.prefix.expanded().to_string() + } + + #[inline] + pub(crate) fn state(&self) -> MutexGuard<'_, BarState> { + self.state.lock().unwrap() + } +} + +/// A weak reference to a `ProgressBar`. +/// +/// Useful for creating custom steady tick implementations +#[derive(Clone, Default)] +pub struct WeakProgressBar { + state: Weak<Mutex<BarState>>, + pos: Weak<AtomicPosition>, + ticker: Weak<Mutex<Option<Ticker>>>, +} + +impl WeakProgressBar { + /// Create a new `WeakProgressBar` that returns `None` when [`upgrade`] is called. + /// + /// [`upgrade`]: WeakProgressBar::upgrade + pub fn new() -> Self { + Self::default() + } + + /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner + /// value if successful. Returns `None` if the inner value has since been dropped. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn upgrade(&self) -> Option<ProgressBar> { + let state = self.state.upgrade()?; + let pos = self.pos.upgrade()?; + let ticker = self.ticker.upgrade()?; + Some(ProgressBar { state, pos, ticker }) + } +} + +pub(crate) struct Ticker { + stopping: Arc<(Mutex<bool>, Condvar)>, + join_handle: Option<thread::JoinHandle<()>>, +} + +impl Drop for Ticker { + fn drop(&mut self) { + self.stop(); + self.join_handle.take().map(|handle| handle.join()); + } +} + +#[cfg(test)] +static TICKER_RUNNING: AtomicBool = AtomicBool::new(false); + +impl Ticker { + pub(crate) fn new(interval: Duration, bar_state: &Arc<Mutex<BarState>>) -> Self { + debug_assert!(!interval.is_zero()); + + // A `Mutex<bool>` is used as a flag to indicate whether the ticker was requested to stop. + // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify + // the thread and interrupt the ticker wait. + #[allow(clippy::mutex_atomic)] + let stopping = Arc::new((Mutex::new(false), Condvar::new())); + let control = TickerControl { + stopping: stopping.clone(), + state: Arc::downgrade(bar_state), + }; + + let join_handle = thread::spawn(move || control.run(interval)); + Self { + stopping, + join_handle: Some(join_handle), + } + } + + pub(crate) fn stop(&self) { + *self.stopping.0.lock().unwrap() = true; + self.stopping.1.notify_one(); + } +} + +struct TickerControl { + stopping: Arc<(Mutex<bool>, Condvar)>, + state: Weak<Mutex<BarState>>, +} + +impl TickerControl { + fn run(&self, interval: Duration) { + #[cfg(test)] + TICKER_RUNNING.store(true, Ordering::SeqCst); + + while let Some(arc) = self.state.upgrade() { + let mut state = arc.lock().unwrap(); + if state.state.is_finished() { + break; + } + + state.tick(Instant::now()); + + drop(state); // Don't forget to drop the lock before sleeping + drop(arc); // Also need to drop Arc otherwise BarState won't be dropped + + // Wait for `interval` but return early if we are notified to stop + let (_, result) = self + .stopping + .1 + .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| { + !*stopped + }) + .unwrap(); + + // If the wait didn't time out, it means we were notified to stop + if !result.timed_out() { + break; + } + } + + #[cfg(test)] + TICKER_RUNNING.store(false, Ordering::SeqCst); + } +} + +// Tests using the global TICKER_RUNNING flag need to be serialized +#[cfg(test)] +pub(crate) static TICKER_TEST: Lazy<Mutex<()>> = Lazy::new(Mutex::default); + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::float_cmp)] + #[test] + fn test_pbar_zero() { + let pb = ProgressBar::new(0); + assert_eq!(pb.state().state.fraction(), 1.0); + } + + #[allow(clippy::float_cmp)] + #[test] + fn test_pbar_maxu64() { + let pb = ProgressBar::new(!0); + assert_eq!(pb.state().state.fraction(), 0.0); + } + + #[test] + fn test_pbar_overflow() { + let pb = ProgressBar::new(1); + pb.set_draw_target(ProgressDrawTarget::hidden()); + pb.inc(2); + pb.finish(); + } + + #[test] + fn test_get_position() { + let pb = ProgressBar::new(1); + pb.set_draw_target(ProgressDrawTarget::hidden()); + pb.inc(2); + let pos = pb.position(); + assert_eq!(pos, 2); + } + + #[test] + fn test_weak_pb() { + let pb = ProgressBar::new(0); + let weak = pb.downgrade(); + assert!(weak.upgrade().is_some()); + ::std::mem::drop(pb); + assert!(weak.upgrade().is_none()); + } + + #[test] + fn it_can_wrap_a_reader() { + let bytes = &b"I am an implementation of io::Read"[..]; + let pb = ProgressBar::new(bytes.len() as u64); + let mut reader = pb.wrap_read(bytes); + let mut writer = Vec::new(); + io::copy(&mut reader, &mut writer).unwrap(); + assert_eq!(writer, bytes); + } + + #[test] + fn it_can_wrap_a_writer() { + let bytes = b"implementation of io::Read"; + let mut reader = &bytes[..]; + let pb = ProgressBar::new(bytes.len() as u64); + let writer = Vec::new(); + let mut writer = pb.wrap_write(writer); + io::copy(&mut reader, &mut writer).unwrap(); + assert_eq!(writer.it, bytes); + } + + #[test] + fn ticker_thread_terminates_on_drop() { + let _guard = TICKER_TEST.lock().unwrap(); + assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); + + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(50)); + + // Give the thread time to start up + thread::sleep(Duration::from_millis(250)); + + assert!(TICKER_RUNNING.load(Ordering::SeqCst)); + + drop(pb); + assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); + } + + #[test] + fn ticker_thread_terminates_on_drop_2() { + let _guard = TICKER_TEST.lock().unwrap(); + assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); + + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(50)); + let pb2 = pb.clone(); + + // Give the thread time to start up + thread::sleep(Duration::from_millis(250)); + + assert!(TICKER_RUNNING.load(Ordering::SeqCst)); + + drop(pb); + assert!(TICKER_RUNNING.load(Ordering::SeqCst)); + + drop(pb2); + assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); + } +} |