indicatif/
state.rs

1use std::borrow::Cow;
2use std::io;
3use std::sync::{Arc, OnceLock};
4use std::time::Duration;
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::Instant;
7
8use portable_atomic::{AtomicU64, AtomicU8, Ordering};
9#[cfg(target_arch = "wasm32")]
10use web_time::Instant;
11
12use crate::draw_target::{LineType, ProgressDrawTarget};
13use crate::style::ProgressStyle;
14
15pub(crate) struct BarState {
16    pub(crate) draw_target: ProgressDrawTarget,
17    pub(crate) on_finish: ProgressFinish,
18    pub(crate) style: ProgressStyle,
19    pub(crate) state: ProgressState,
20    pub(crate) tab_width: usize,
21}
22
23impl BarState {
24    pub(crate) fn new(
25        len: Option<u64>,
26        draw_target: ProgressDrawTarget,
27        pos: Arc<AtomicPosition>,
28    ) -> Self {
29        Self {
30            draw_target,
31            on_finish: ProgressFinish::default(),
32            style: ProgressStyle::default_bar(),
33            state: ProgressState::new(len, pos),
34            tab_width: DEFAULT_TAB_WIDTH,
35        }
36    }
37
38    /// Finishes the progress bar using the [`ProgressFinish`] behavior stored
39    /// in the [`ProgressStyle`].
40    pub(crate) fn finish_using_style(&mut self, now: Instant, finish: ProgressFinish) {
41        self.state.status = Status::DoneVisible;
42        match finish {
43            ProgressFinish::AndLeave => {
44                if let Some(len) = self.state.len {
45                    self.state.pos.set(len);
46                }
47            }
48            ProgressFinish::WithMessage(msg) => {
49                if let Some(len) = self.state.len {
50                    self.state.pos.set(len);
51                }
52                self.state.message = TabExpandedString::new(msg, self.tab_width);
53            }
54            ProgressFinish::AndClear => {
55                if let Some(len) = self.state.len {
56                    self.state.pos.set(len);
57                }
58                self.state.status = Status::DoneHidden;
59            }
60            ProgressFinish::Abandon => {}
61            ProgressFinish::AbandonWithMessage(msg) => {
62                self.state.message = TabExpandedString::new(msg, self.tab_width);
63            }
64        }
65
66        // There's no need to update the estimate here; once the `status` is no longer
67        // `InProgress`, we will use the length and elapsed time to estimate.
68        let _ = self.draw(true, now);
69    }
70
71    pub(crate) fn reset(&mut self, now: Instant, mode: Reset) {
72        // Always reset the estimator; this is the only reset that will occur if mode is
73        // `Reset::Eta`.
74        self.state.est.reset(now);
75
76        if let Reset::Elapsed | Reset::All = mode {
77            self.state.started = now;
78        }
79
80        if let Reset::All = mode {
81            self.state.pos.reset(now);
82            self.state.status = Status::InProgress;
83
84            for tracker in self.style.format_map.values_mut() {
85                tracker.reset(&self.state, now);
86            }
87
88            let _ = self.draw(false, now);
89        }
90    }
91
92    pub(crate) fn update(&mut self, now: Instant, f: impl FnOnce(&mut ProgressState), tick: bool) {
93        f(&mut self.state);
94        if tick {
95            self.tick(now);
96        }
97    }
98
99    pub(crate) fn unset_length(&mut self, now: Instant) {
100        self.state.len = None;
101        self.update_estimate_and_draw(now);
102    }
103
104    pub(crate) fn set_length(&mut self, now: Instant, len: u64) {
105        self.state.len = Some(len);
106        self.update_estimate_and_draw(now);
107    }
108
109    pub(crate) fn inc_length(&mut self, now: Instant, delta: u64) {
110        if let Some(len) = self.state.len {
111            self.state.len = Some(len.saturating_add(delta));
112        }
113        self.update_estimate_and_draw(now);
114    }
115
116    pub(crate) fn dec_length(&mut self, now: Instant, delta: u64) {
117        if let Some(len) = self.state.len {
118            self.state.len = Some(len.saturating_sub(delta));
119        }
120        self.update_estimate_and_draw(now);
121    }
122
123    pub(crate) fn set_tab_width(&mut self, tab_width: usize) {
124        self.tab_width = tab_width;
125        self.state.message.set_tab_width(tab_width);
126        self.state.prefix.set_tab_width(tab_width);
127        self.style.set_tab_width(tab_width);
128    }
129
130    pub(crate) fn set_style(&mut self, style: ProgressStyle) {
131        self.style = style;
132        self.style.set_tab_width(self.tab_width);
133    }
134
135    pub(crate) fn tick(&mut self, now: Instant) {
136        self.state.tick = self.state.tick.saturating_add(1);
137        self.update_estimate_and_draw(now);
138    }
139
140    pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) {
141        let pos = self.state.pos.pos.load(Ordering::Relaxed);
142        self.state.est.record(pos, now);
143
144        for tracker in self.style.format_map.values_mut() {
145            tracker.tick(&self.state, now);
146        }
147
148        let _ = self.draw(false, now);
149    }
150
151    pub(crate) fn println(&mut self, now: Instant, msg: &str) {
152        let width = self.draw_target.width();
153        let mut drawable = match self.draw_target.drawable(true, now) {
154            Some(drawable) => drawable,
155            None => return,
156        };
157
158        let mut draw_state = drawable.state();
159        let lines: Vec<LineType> = msg.lines().map(|l| LineType::Text(Into::into(l))).collect();
160        // Empty msg should trigger newline as we are in println
161        if lines.is_empty() {
162            draw_state.lines.push(LineType::Empty);
163        } else {
164            draw_state.lines.extend(lines);
165        }
166
167        if let Some(width) = width {
168            if !matches!(self.state.status, Status::DoneHidden) {
169                self.style
170                    .format_state(&self.state, &mut draw_state.lines, width);
171            }
172        }
173
174        drop(draw_state);
175        let _ = drawable.draw();
176    }
177
178    pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, now: Instant, f: F) -> R {
179        if let Some((state, _)) = self.draw_target.remote() {
180            return state.write().unwrap().suspend(f, now);
181        }
182
183        if let Some(drawable) = self.draw_target.drawable(true, now) {
184            let _ = drawable.clear();
185        }
186
187        let ret = f();
188        let _ = self.draw(true, Instant::now());
189        ret
190    }
191
192    pub(crate) fn draw(&mut self, mut force_draw: bool, now: Instant) -> io::Result<()> {
193        // `|= self.is_finished()` should not be needed here, but we used to always draw for
194        // finished progress bars, so it's kept as to not cause compatibility issues in weird cases.
195        force_draw |= self.state.is_finished();
196        let mut drawable = match self.draw_target.drawable(force_draw, now) {
197            Some(drawable) => drawable,
198            None => return Ok(()),
199        };
200
201        // Getting the width can be expensive; thus this should happen after checking drawable.
202        let width = drawable.width();
203
204        let mut draw_state = drawable.state();
205
206        if let Some(width) = width {
207            if !matches!(self.state.status, Status::DoneHidden) {
208                self.style
209                    .format_state(&self.state, &mut draw_state.lines, width);
210            }
211        }
212
213        drop(draw_state);
214        drawable.draw()
215    }
216}
217
218impl Drop for BarState {
219    fn drop(&mut self) {
220        // Progress bar is already finished.  Do not need to do anything other than notify
221        // the `MultiProgress` that we're now a zombie.
222        if self.state.is_finished() {
223            self.draw_target.mark_zombie();
224            return;
225        }
226
227        self.finish_using_style(Instant::now(), self.on_finish.clone());
228
229        // Notify the `MultiProgress` that we're now a zombie.
230        self.draw_target.mark_zombie();
231    }
232}
233
234pub(crate) enum Reset {
235    Eta,
236    Elapsed,
237    All,
238}
239
240/// The state of a progress bar at a moment in time.
241#[non_exhaustive]
242pub struct ProgressState {
243    pos: Arc<AtomicPosition>,
244    len: Option<u64>,
245    pub(crate) tick: u64,
246    pub(crate) started: Instant,
247    status: Status,
248    est: Estimator,
249    pub(crate) message: TabExpandedString,
250    pub(crate) prefix: TabExpandedString,
251}
252
253impl ProgressState {
254    pub(crate) fn new(len: Option<u64>, pos: Arc<AtomicPosition>) -> Self {
255        let now = Instant::now();
256        Self {
257            pos,
258            len,
259            tick: 0,
260            status: Status::InProgress,
261            started: now,
262            est: Estimator::new(now),
263            message: TabExpandedString::NoTabs("".into()),
264            prefix: TabExpandedString::NoTabs("".into()),
265        }
266    }
267
268    /// Indicates that the progress bar finished.
269    pub fn is_finished(&self) -> bool {
270        match self.status {
271            Status::InProgress => false,
272            Status::DoneVisible => true,
273            Status::DoneHidden => true,
274        }
275    }
276
277    /// Returns the completion as a floating-point number between 0 and 1
278    pub fn fraction(&self) -> f32 {
279        let pos = self.pos.pos.load(Ordering::Relaxed);
280        let pct = match (pos, self.len) {
281            (_, None) => 0.0,
282            (_, Some(0)) => 1.0,
283            (0, _) => 0.0,
284            (pos, Some(len)) => pos as f32 / len as f32,
285        };
286        pct.clamp(0.0, 1.0)
287    }
288
289    /// The expected ETA
290    pub fn eta(&self) -> Duration {
291        if self.is_finished() {
292            return Duration::new(0, 0);
293        }
294
295        let len = match self.len {
296            Some(len) => len,
297            None => return Duration::new(0, 0),
298        };
299
300        let pos = self.pos.pos.load(Ordering::Relaxed);
301
302        let sps = self.est.steps_per_second(Instant::now());
303
304        // Infinite duration should only ever happen at the beginning, so in this case it's okay to
305        // just show an ETA of 0 until progress starts to occur.
306        if sps == 0.0 {
307            return Duration::new(0, 0);
308        }
309
310        secs_to_duration(len.saturating_sub(pos) as f64 / sps)
311    }
312
313    /// The expected total duration (that is, elapsed time + expected ETA)
314    pub fn duration(&self) -> Duration {
315        if self.len.is_none() || self.is_finished() {
316            return Duration::new(0, 0);
317        }
318        self.started.elapsed().saturating_add(self.eta())
319    }
320
321    /// The number of steps per second
322    pub fn per_sec(&self) -> f64 {
323        if let Status::InProgress = self.status {
324            self.est.steps_per_second(Instant::now())
325        } else {
326            self.pos() as f64 / self.started.elapsed().as_secs_f64()
327        }
328    }
329
330    pub fn elapsed(&self) -> Duration {
331        self.started.elapsed()
332    }
333
334    pub fn pos(&self) -> u64 {
335        self.pos.pos.load(Ordering::Relaxed)
336    }
337
338    pub fn set_pos(&mut self, pos: u64) {
339        self.pos.set(pos);
340    }
341
342    #[allow(clippy::len_without_is_empty)]
343    pub fn len(&self) -> Option<u64> {
344        self.len
345    }
346
347    pub fn set_len(&mut self, len: u64) {
348        self.len = Some(len);
349    }
350}
351
352#[derive(Debug, PartialEq, Eq, Clone)]
353pub(crate) enum TabExpandedString {
354    NoTabs(Cow<'static, str>),
355    WithTabs {
356        original: Cow<'static, str>,
357        expanded: OnceLock<String>,
358        tab_width: usize,
359    },
360}
361
362impl TabExpandedString {
363    pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self {
364        if !s.contains('\t') {
365            Self::NoTabs(s)
366        } else {
367            Self::WithTabs {
368                original: s,
369                tab_width,
370                expanded: OnceLock::new(),
371            }
372        }
373    }
374
375    pub(crate) fn expanded(&self) -> &str {
376        match &self {
377            Self::NoTabs(s) => {
378                debug_assert!(!s.contains('\t'));
379                s
380            }
381            Self::WithTabs {
382                original,
383                tab_width,
384                expanded,
385            } => expanded.get_or_init(|| original.replace('\t', &" ".repeat(*tab_width))),
386        }
387    }
388
389    pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
390        if let Self::WithTabs {
391            expanded,
392            tab_width,
393            ..
394        } = self
395        {
396            if *tab_width != new_tab_width {
397                *tab_width = new_tab_width;
398                expanded.take();
399            }
400        }
401    }
402}
403
404/// Double-smoothed exponentially weighted estimator
405///
406/// This uses an exponentially weighted *time-based* estimator, meaning that it exponentially
407/// downweights old data based on its age. The rate at which this occurs is currently a constant
408/// value of 15 seconds for 90% weighting. This means that all data older than 15 seconds has a
409/// collective weight of 0.1 in the estimate, and all data older than 30 seconds has a collective
410/// weight of 0.01, and so on.
411///
412/// The primary value exposed by `Estimator` is `steps_per_second`. This value is doubly-smoothed,
413/// meaning that is the result of using an exponentially weighted estimator (as described above) to
414/// estimate the value of another exponentially weighted estimator, which estimates the value of
415/// the raw data.
416///
417/// The purpose of this extra smoothing step is to reduce instantaneous fluctations in the estimate
418/// when large updates are received. Without this, estimates might have a large spike followed by a
419/// slow asymptotic approach to zero (until the next spike).
420#[derive(Debug)]
421pub(crate) struct Estimator {
422    smoothed_steps_per_sec: f64,
423    double_smoothed_steps_per_sec: f64,
424    prev_steps: u64,
425    prev_time: Instant,
426    start_time: Instant,
427}
428
429impl Estimator {
430    fn new(now: Instant) -> Self {
431        Self {
432            smoothed_steps_per_sec: 0.0,
433            double_smoothed_steps_per_sec: 0.0,
434            prev_steps: 0,
435            prev_time: now,
436            start_time: now,
437        }
438    }
439
440    fn record(&mut self, new_steps: u64, now: Instant) {
441        // sanity check: don't record data if time or steps have not advanced
442        if new_steps <= self.prev_steps || now <= self.prev_time {
443            // Reset on backwards seek to prevent breakage from seeking to the end for length determination
444            // See https://github.com/console-rs/indicatif/issues/480
445            if new_steps < self.prev_steps {
446                self.prev_steps = new_steps;
447                self.reset(now);
448            }
449            return;
450        }
451
452        let delta_steps = new_steps - self.prev_steps;
453        let delta_t = duration_to_secs(now - self.prev_time);
454
455        // the rate of steps we saw in this update
456        let new_steps_per_second = delta_steps as f64 / delta_t;
457
458        // update the estimate: a weighted average of the old estimate and new data
459        let weight = estimator_weight(delta_t);
460        self.smoothed_steps_per_sec =
461            self.smoothed_steps_per_sec * weight + new_steps_per_second * (1.0 - weight);
462
463        // An iterative estimate like `smoothed_steps_per_sec` is supposed to be an exponentially
464        // weighted average from t=0 back to t=-inf; Since we initialize it to 0, we neglect the
465        // (non-existent) samples in the weighted average prior to the first one, so the resulting
466        // average must be normalized. We normalize the single estimate here in order to use it as
467        // a source for the double smoothed estimate. See comment on normalization in
468        // `steps_per_second` for details.
469        let delta_t_start = duration_to_secs(now - self.start_time);
470        let total_weight = 1.0 - estimator_weight(delta_t_start);
471        let normalized_smoothed_steps_per_sec = self.smoothed_steps_per_sec / total_weight;
472
473        // determine the double smoothed value (EWA smoothing of the single EWA)
474        self.double_smoothed_steps_per_sec = self.double_smoothed_steps_per_sec * weight
475            + normalized_smoothed_steps_per_sec * (1.0 - weight);
476
477        self.prev_steps = new_steps;
478        self.prev_time = now;
479    }
480
481    /// Reset the state of the estimator. Once reset, estimates will not depend on any data prior
482    /// to `now`. This does not reset the stored position of the progress bar.
483    pub(crate) fn reset(&mut self, now: Instant) {
484        self.smoothed_steps_per_sec = 0.0;
485        self.double_smoothed_steps_per_sec = 0.0;
486
487        // only reset prev_time, not prev_steps
488        self.prev_time = now;
489        self.start_time = now;
490    }
491
492    /// Average time per step in seconds, using double exponential smoothing
493    fn steps_per_second(&self, now: Instant) -> f64 {
494        // Because the value stored in the Estimator is only updated when the Estimator receives an
495        // update, this value will become stuck if progress stalls. To return an accurate estimate,
496        // we determine how much time has passed since the last update, and treat this as a
497        // pseudo-update with 0 steps.
498        let delta_t = duration_to_secs(now - self.prev_time);
499        let reweight = estimator_weight(delta_t);
500
501        // Normalization of estimates:
502        //
503        // The raw estimate is a single value (smoothed_steps_per_second) that is iteratively
504        // updated. At each update, the previous value of the estimate is downweighted according to
505        // its age, receiving the iterative weight W(t) = 0.1 ^ (t/15).
506        //
507        // Since W(Sum(t_n)) = Prod(W(t_n)), the total weight of a sample after a series of
508        // iterative steps is simply W(t_e) - W(t_b), where t_e is the time since the end of the
509        // sample, and t_b is the time since the beginning. The resulting estimate is therefore a
510        // weighted average with sample weights W(t_e) - W(t_b).
511        //
512        // Notice that the weighting function generates sample weights that sum to 1 only when the
513        // sample times span from t=0 to t=inf; but this is not the case. We have a first sample
514        // with finite, positive t_b = t_f. In the raw estimate, we handle times prior to t_f by
515        // setting an initial value of 0, meaning that these (non-existent) samples have no weight.
516        //
517        // Therefore, the raw estimate must be normalized by dividing it by the sum of the weights
518        // in the weighted average. This sum is just W(0) - W(t_f), where t_f is the time since the
519        // first sample, and W(0) = 1.
520        let delta_t_start = duration_to_secs(now - self.start_time);
521        let total_weight = 1.0 - estimator_weight(delta_t_start);
522
523        // Generate updated values for `smoothed_steps_per_sec` and `double_smoothed_steps_per_sec`
524        // (sps and dsps) without storing them. Note that we normalize sps when using it as a
525        // source to update dsps, and then normalize dsps itself before returning it.
526        let sps = self.smoothed_steps_per_sec * reweight / total_weight;
527        let dsps = self.double_smoothed_steps_per_sec * reweight + sps * (1.0 - reweight);
528        dsps / total_weight
529    }
530}
531
532pub(crate) struct AtomicPosition {
533    pub(crate) pos: AtomicU64,
534    capacity: AtomicU8,
535    prev: AtomicU64,
536    start: Instant,
537}
538
539impl AtomicPosition {
540    pub(crate) fn new() -> Self {
541        Self {
542            pos: AtomicU64::new(0),
543            capacity: AtomicU8::new(MAX_BURST),
544            prev: AtomicU64::new(0),
545            start: Instant::now(),
546        }
547    }
548
549    pub(crate) fn allow(&self, now: Instant) -> bool {
550        if now < self.start {
551            return false;
552        }
553
554        let mut capacity = self.capacity.load(Ordering::Acquire);
555        // `prev` is the number of ns after `self.started` we last returned `true`
556        let prev = self.prev.load(Ordering::Acquire);
557        // `elapsed` is the number of ns since `self.started`
558        let elapsed = (now - self.start).as_nanos() as u64;
559        // `diff` is the number of ns since we last returned `true`
560        let diff = elapsed.saturating_sub(prev);
561
562        // If `capacity` is 0 and not enough time (1ms) has passed since `prev`
563        // to add new capacity, return `false`. The goal of this method is to
564        // make this decision as efficient as possible.
565        if capacity == 0 && diff < INTERVAL {
566            return false;
567        }
568
569        // We now calculate `new`, the number of INTERVALs since we last returned `true`,
570        // and `remainder`, which represents a number of ns less than INTERVAL which we cannot
571        // convert into capacity now, so we're saving it for later. We do this by
572        // subtracting this from `elapsed` before storing it into `self.prev`.
573        let (new, remainder) = ((diff / INTERVAL), (diff % INTERVAL));
574        // We add `new` to `capacity`, subtract one for returning `true` from here,
575        // then make sure it does not exceed a maximum of `MAX_BURST`.
576        capacity = Ord::min(MAX_BURST as u128, (capacity as u128) + (new as u128) - 1) as u8;
577
578        // Then, we just store `capacity` and `prev` atomically for the next iteration
579        self.capacity.store(capacity, Ordering::Release);
580        self.prev.store(elapsed - remainder, Ordering::Release);
581        true
582    }
583
584    fn reset(&self, now: Instant) {
585        self.set(0);
586        let elapsed = (now.saturating_duration_since(self.start)).as_nanos() as u64;
587        self.prev.store(elapsed, Ordering::Release);
588    }
589
590    pub(crate) fn inc(&self, delta: u64) {
591        self.pos.fetch_add(delta, Ordering::SeqCst);
592    }
593
594    pub(crate) fn dec(&self, delta: u64) {
595        self.pos.fetch_sub(delta, Ordering::SeqCst);
596    }
597
598    pub(crate) fn set(&self, pos: u64) {
599        self.pos.store(pos, Ordering::Release);
600    }
601}
602
603const INTERVAL: u64 = 1_000_000;
604const MAX_BURST: u8 = 10;
605
606/// Behavior of a progress bar when it is finished
607///
608/// This is invoked when a [`ProgressBar`] or [`ProgressBarIter`] completes and
609/// [`ProgressBar::is_finished`] is false.
610///
611/// [`ProgressBar`]: crate::ProgressBar
612/// [`ProgressBarIter`]: crate::ProgressBarIter
613/// [`ProgressBar::is_finished`]: crate::ProgressBar::is_finished
614#[derive(Clone, Debug)]
615pub enum ProgressFinish {
616    /// Finishes the progress bar and leaves the current message
617    ///
618    /// Same behavior as calling [`ProgressBar::finish()`](crate::ProgressBar::finish).
619    AndLeave,
620    /// Finishes the progress bar and sets a message
621    ///
622    /// Same behavior as calling [`ProgressBar::finish_with_message()`](crate::ProgressBar::finish_with_message).
623    WithMessage(Cow<'static, str>),
624    /// Finishes the progress bar and completely clears it (this is the default)
625    ///
626    /// Same behavior as calling [`ProgressBar::finish_and_clear()`](crate::ProgressBar::finish_and_clear).
627    AndClear,
628    /// Finishes the progress bar and leaves the current message and progress
629    ///
630    /// Same behavior as calling [`ProgressBar::abandon()`](crate::ProgressBar::abandon).
631    Abandon,
632    /// Finishes the progress bar and sets a message, and leaves the current progress
633    ///
634    /// Same behavior as calling [`ProgressBar::abandon_with_message()`](crate::ProgressBar::abandon_with_message).
635    AbandonWithMessage(Cow<'static, str>),
636}
637
638impl Default for ProgressFinish {
639    fn default() -> Self {
640        Self::AndClear
641    }
642}
643
644/// Get the appropriate dilution weight for Estimator data given the data's age (in seconds)
645///
646/// Whenever an update occurs, we will create a new estimate using a weight `w_i` like so:
647///
648/// ```math
649/// <new estimate> = <previous estimate> * w_i + <new data> * (1 - w_i)
650/// ```
651///
652/// In other words, the new estimate is a weighted average of the previous estimate and the new
653/// data. We want to choose weights such that for any set of samples where `t_0, t_1, ...` are
654/// the durations of the samples:
655///
656/// ```math
657/// Sum(t_i) = ews ==> Prod(w_i) = 0.1
658/// ```
659///
660/// With this constraint it is easy to show that
661///
662/// ```math
663/// w_i = 0.1 ^ (t_i / ews)
664/// ```
665///
666/// Notice that the constraint implies that estimates are independent of the durations of the
667/// samples, a very useful feature.
668fn estimator_weight(age: f64) -> f64 {
669    const EXPONENTIAL_WEIGHTING_SECONDS: f64 = 15.0;
670    0.1_f64.powf(age / EXPONENTIAL_WEIGHTING_SECONDS)
671}
672
673fn duration_to_secs(d: Duration) -> f64 {
674    d.as_secs() as f64 + f64::from(d.subsec_nanos()) / 1_000_000_000f64
675}
676
677fn secs_to_duration(s: f64) -> Duration {
678    let secs = s.trunc() as u64;
679    let nanos = (s.fract() * 1_000_000_000f64) as u32;
680    Duration::new(secs, nanos)
681}
682
683#[derive(Debug)]
684pub(crate) enum Status {
685    InProgress,
686    DoneVisible,
687    DoneHidden,
688}
689
690pub(crate) const DEFAULT_TAB_WIDTH: usize = 8;
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695    use crate::ProgressBar;
696
697    // https://github.com/rust-lang/rust-clippy/issues/10281
698    #[allow(clippy::uninlined_format_args)]
699    #[test]
700    fn test_steps_per_second() {
701        let test_rate = |items_per_second| {
702            let mut now = Instant::now();
703            let mut est = Estimator::new(now);
704            let mut pos = 0;
705
706            for _ in 0..20 {
707                pos += items_per_second;
708                now += Duration::from_secs(1);
709                est.record(pos, now);
710            }
711            let avg_steps_per_second = est.steps_per_second(now);
712
713            assert!(avg_steps_per_second > 0.0);
714            assert!(avg_steps_per_second.is_finite());
715
716            let absolute_error = (avg_steps_per_second - items_per_second as f64).abs();
717            let relative_error = absolute_error / items_per_second as f64;
718            assert!(
719                relative_error < 1.0 / 1e9,
720                "Expected rate: {}, actual: {}, relative error: {}",
721                items_per_second,
722                avg_steps_per_second,
723                relative_error
724            );
725        };
726
727        test_rate(1);
728        test_rate(1_000);
729        test_rate(1_000_000);
730        test_rate(1_000_000_000);
731        test_rate(1_000_000_001);
732        test_rate(100_000_000_000);
733        test_rate(1_000_000_000_000);
734        test_rate(100_000_000_000_000);
735        test_rate(1_000_000_000_000_000);
736    }
737
738    #[test]
739    fn test_double_exponential_ave() {
740        let mut now = Instant::now();
741        let mut est = Estimator::new(now);
742        let mut pos = 0;
743
744        // note: this is the default weight set in the Estimator
745        let weight = 15;
746
747        for _ in 0..weight {
748            pos += 1;
749            now += Duration::from_secs(1);
750            est.record(pos, now);
751        }
752        now += Duration::from_secs(weight);
753
754        // The first level EWA:
755        //   -> 90% weight @ 0 eps, 9% weight @ 1 eps, 1% weight @ 0 eps
756        //   -> then normalized by deweighting the 1% weight (before -30 seconds)
757        let single_target = 0.09 / 0.99;
758
759        // The second level EWA:
760        //   -> same logic as above, but using the first level EWA as the source
761        let double_target = (0.9 * single_target + 0.09) / 0.99;
762        assert_eq!(est.steps_per_second(now), double_target);
763    }
764
765    #[test]
766    fn test_estimator_rewind_position() {
767        let mut now = Instant::now();
768        let mut est = Estimator::new(now);
769
770        now += Duration::from_secs(1);
771        est.record(1, now);
772
773        // should not panic
774        now += Duration::from_secs(1);
775        est.record(0, now);
776
777        // check that reset occurred (estimator at 1 event per sec)
778        now += Duration::from_secs(1);
779        est.record(1, now);
780        assert_eq!(est.steps_per_second(now), 1.0);
781
782        // check that progress bar handles manual seeking
783        let pb = ProgressBar::hidden();
784        pb.set_length(10);
785        pb.set_position(1);
786        pb.tick();
787        // Should not panic.
788        pb.set_position(0);
789    }
790
791    #[test]
792    fn test_reset_eta() {
793        let mut now = Instant::now();
794        let mut est = Estimator::new(now);
795
796        // two per second, then reset
797        now += Duration::from_secs(1);
798        est.record(2, now);
799        est.reset(now);
800
801        // now one per second, and verify
802        now += Duration::from_secs(1);
803        est.record(3, now);
804        assert_eq!(est.steps_per_second(now), 1.0);
805    }
806
807    #[test]
808    fn test_duration_stuff() {
809        let duration = Duration::new(42, 100_000_000);
810        let secs = duration_to_secs(duration);
811        assert_eq!(secs_to_duration(secs), duration);
812    }
813
814    #[test]
815    fn test_atomic_position_large_time_difference() {
816        let atomic_position = AtomicPosition::new();
817        let later = atomic_position.start + Duration::from_nanos(INTERVAL * u64::from(u8::MAX));
818        // Should not panic.
819        atomic_position.allow(later);
820    }
821
822    #[test]
823    fn test_atomic_position_reset() {
824        const ELAPSE_TIME: Duration = Duration::from_millis(20);
825        let mut pos = AtomicPosition::new();
826        pos.reset(pos.start + ELAPSE_TIME);
827
828        // prev should be exactly ELAPSE_TIME after reset
829        assert_eq!(*pos.pos.get_mut(), 0);
830        assert_eq!(*pos.prev.get_mut(), ELAPSE_TIME.as_nanos() as u64);
831    }
832}