#![cfg(feature = "in_memory")] use std::time::Duration; use indicatif::{ InMemoryTerm, MultiProgress, MultiProgressAlignment, ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle, TermLike, }; use pretty_assertions::assert_eq; #[test] fn basic_progress_bar() { let in_mem = InMemoryTerm::new(10, 80); let pb = ProgressBar::with_draw_target( Some(10), ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ); assert_eq!(in_mem.contents(), String::new()); pb.tick(); assert_eq!( in_mem.contents(), "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10" ); pb.inc(1); assert_eq!( in_mem.contents(), "███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10" ); pb.finish(); assert_eq!( in_mem.contents(), "██████████████████████████████████████████████████████████████████████████ 10/10" ); } #[test] fn progress_bar_builder_method_order() { let in_mem = InMemoryTerm::new(10, 80); // Test that `with_style` doesn't overwrite the message or prefix let pb = ProgressBar::with_draw_target( Some(10), ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ) .with_message("crate") .with_prefix("Downloading") .with_style( ProgressStyle::with_template("{prefix:>12.cyan.bold} {msg}: {wide_bar} {pos}/{len}") .unwrap(), ); assert_eq!(in_mem.contents(), String::new()); pb.tick(); assert_eq!( in_mem.contents(), " Downloading crate: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10" ); } #[test] fn progress_bar_percent_with_no_length() { let in_mem = InMemoryTerm::new(10, 80); let pb = ProgressBar::with_draw_target( None, ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ) .with_style(ProgressStyle::with_template("{wide_bar} {percent}%").unwrap()); assert_eq!(in_mem.contents(), String::new()); pb.tick(); assert_eq!( in_mem.contents(), "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0%" ); pb.set_length(10); pb.inc(1); assert_eq!( in_mem.contents(), "███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 10%" ); pb.finish(); assert_eq!( in_mem.contents(), "███████████████████████████████████████████████████████████████████████████ 100%" ); } #[test] fn multi_progress_single_bar_and_leave() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave)); assert_eq!(in_mem.contents(), String::new()); pb1.tick(); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); drop(pb1); assert_eq!( in_mem.contents(), r#"██████████████████████████████████████████████████████████████████████████ 10/10"# ); } #[test] fn multi_progress_single_bar_and_clear() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10)); assert_eq!(in_mem.contents(), String::new()); pb1.tick(); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); drop(pb1); assert_eq!(in_mem.contents(), ""); } #[test] fn multi_progress_two_bars() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave)); let pb2 = mp.add(ProgressBar::new(5)); assert_eq!(in_mem.contents(), String::new()); pb1.tick(); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); pb2.tick(); assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"# .trim_start() ); drop(pb1); assert_eq!( in_mem.contents(), r#" ██████████████████████████████████████████████████████████████████████████ 10/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"# .trim_start() ); drop(pb2); assert_eq!( in_mem.contents(), r#"██████████████████████████████████████████████████████████████████████████ 10/10"# ); } #[test] fn multi_progress() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave)); let pb2 = mp.add(ProgressBar::new(5)); let pb3 = mp.add(ProgressBar::new(100)); assert_eq!(in_mem.contents(), String::new()); pb1.tick(); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); pb2.tick(); assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"# .trim_start() ); pb3.tick(); assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"# .trim_start() ); drop(pb1); assert_eq!( in_mem.contents(), r#" ██████████████████████████████████████████████████████████████████████████ 10/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"# .trim_start() ); drop(pb2); assert_eq!( in_mem.contents(), r#" ██████████████████████████████████████████████████████████████████████████ 10/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"# .trim_start() ); drop(pb3); assert_eq!( in_mem.contents(), r#"██████████████████████████████████████████████████████████████████████████ 10/10"# ); } #[test] fn multi_progress_println() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10)); let pb2 = mp.add(ProgressBar::new(5)); let pb3 = mp.add(ProgressBar::new(100)); assert_eq!(in_mem.contents(), ""); pb1.inc(2); mp.println("message printed :)").unwrap(); assert_eq!( in_mem.contents(), r#" message printed :) ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10 "# .trim() ); mp.println("another great message!").unwrap(); assert_eq!( in_mem.contents(), r#" message printed :) another great message! ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10 "# .trim() ); pb2.inc(1); pb3.tick(); mp.println("one last message").unwrap(); assert_eq!( in_mem.contents(), r#" message printed :) another great message! one last message ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10 ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/5 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100 "# .trim() ); drop(pb1); drop(pb2); drop(pb3); assert_eq!( in_mem.contents(), r#" message printed :) another great message! one last message"# .trim() ); } #[test] fn multi_progress_suspend() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10)); let pb2 = mp.add(ProgressBar::new(10)); assert_eq!(in_mem.contents(), ""); pb1.inc(2); mp.println("message printed :)").unwrap(); assert_eq!( in_mem.contents(), r#" message printed :) ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10 "# .trim() ); mp.suspend(|| { in_mem.write_line("This is write_line output!").unwrap(); in_mem.write_line("And so is this").unwrap(); in_mem.move_cursor_down(1).unwrap(); }); assert_eq!( in_mem.contents(), r#" message printed :) This is write_line output! And so is this ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10 "# .trim() ); pb2.inc(1); mp.println("Another line printed").unwrap(); assert_eq!( in_mem.contents(), r#" message printed :) This is write_line output! And so is this Another line printed ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10 ███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10 "# .trim() ); drop(pb1); drop(pb2); assert_eq!( in_mem.contents(), r#" message printed :) This is write_line output! And so is this Another line printed"# .trim() ); } #[test] fn ticker_drop() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let mut spinner: Option = None; for i in 0..5 { let new_spinner = mp.add( ProgressBar::new_spinner() .with_finish(ProgressFinish::AndLeave) .with_message(format!("doing stuff {i}")), ); new_spinner.enable_steady_tick(Duration::from_millis(100)); spinner.replace(new_spinner); } drop(spinner); assert_eq!( in_mem.contents(), " doing stuff 0\n doing stuff 1\n doing stuff 2\n doing stuff 3\n doing stuff 4" ); } #[test] fn manually_inc_ticker() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let spinner = mp.add(ProgressBar::new_spinner().with_message("msg")); assert_eq!(in_mem.contents(), ""); spinner.inc(1); assert_eq!(in_mem.contents(), "⠁ msg"); spinner.inc(1); assert_eq!(in_mem.contents(), "⠉ msg"); // set_message / set_prefix shouldn't increase tick spinner.set_message("new message"); spinner.set_prefix("prefix"); assert_eq!(in_mem.contents(), "⠉ new message"); } #[test] fn multi_progress_prune_zombies() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb0 = mp .add(ProgressBar::new(10)) .with_finish(ProgressFinish::AndLeave); let pb1 = mp.add(ProgressBar::new(15)); pb0.tick(); assert_eq!( in_mem.contents(), "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10" ); pb0.inc(1); assert_eq!( in_mem.contents(), "███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10" ); drop(pb0); // Clear the screen mp.clear().unwrap(); // Write a line that we expect to remain. This helps ensure the adjustment to last_line_count is // working as expected, and `MultiState` isn't erasing lines when it shouldn't. in_mem.write_line("don't erase me plz").unwrap(); // pb0 is dead, so only pb1 should be drawn from now on pb1.tick(); assert_eq!( in_mem.contents(), "don't erase me plz\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/15" ); } #[test] fn multi_progress_prune_zombies_2() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave)); let pb2 = mp.add(ProgressBar::new(5)); let pb3 = mp .add(ProgressBar::new(100)) .with_finish(ProgressFinish::Abandon); let pb4 = mp .add(ProgressBar::new(500)) .with_finish(ProgressFinish::AndLeave); let pb5 = mp.add(ProgressBar::new(7)); assert_eq!(in_mem.contents(), String::new()); pb1.tick(); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); pb2.tick(); assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"# .trim_start() ); pb3.tick(); assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"# .trim_start() ); drop(pb1); drop(pb2); drop(pb3); assert_eq!( in_mem.contents(), r#" ██████████████████████████████████████████████████████████████████████████ 10/10 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"# .trim_start() ); mp.clear().unwrap(); assert_eq!(in_mem.contents(), ""); // A sacrificial line we expect shouldn't be touched in_mem.write_line("don't erase plz").unwrap(); mp.println("Test friend :)").unwrap(); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :)"# .trim_start() ); pb4.tick(); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/500"# .trim_start() ); drop(pb4); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :) ████████████████████████████████████████████████████████████████████████ 500/500"# .trim_start() ); mp.clear().unwrap(); assert_eq!(in_mem.contents(), "don't erase plz\nTest friend :)"); pb5.tick(); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/7"# .trim_start() ); mp.println("not your friend, buddy").unwrap(); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :) not your friend, buddy ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/7"# .trim_start() ); pb5.inc(1); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :) not your friend, buddy ██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/7"# .trim_start() ); mp.clear().unwrap(); in_mem.write_line("don't erase me either").unwrap(); pb5.inc(1); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :) not your friend, buddy don't erase me either █████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/7"# .trim_start() ); drop(pb5); assert_eq!( in_mem.contents(), r#" don't erase plz Test friend :) not your friend, buddy don't erase me either"# .trim_start() ); } #[test] fn basic_tab_expansion() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let mut spinner = mp.add(ProgressBar::new_spinner().with_message("Test\t:)")); spinner.tick(); // 8 is the default number of spaces assert_eq!(in_mem.contents(), "⠁ Test :)"); spinner.set_tab_width(4); assert_eq!(in_mem.contents(), "⠁ Test :)"); } #[test] fn tab_expansion_in_template() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let mut spinner = mp.add( ProgressBar::new_spinner() .with_message("Test\t:)") .with_prefix("Pre\tfix!") .with_style(ProgressStyle::with_template("{spinner}{prefix}\t{msg}").unwrap()), ); spinner.tick(); assert_eq!(in_mem.contents(), "⠁Pre fix! Test :)"); spinner.set_tab_width(4); assert_eq!(in_mem.contents(), "⠁Pre fix! Test :)"); spinner.set_tab_width(2); assert_eq!(in_mem.contents(), "⠁Pre fix! Test :)"); } #[test] fn progress_style_tab_width_unification() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); // Style will have default of 8 spaces for tabs let style = ProgressStyle::with_template("{msg}\t{msg}").unwrap(); let spinner = mp.add( ProgressBar::new_spinner() .with_message("OK") .with_tab_width(4), ); // Setting the spinner's style to |style| should override the style's tab width with that of bar spinner.set_style(style); spinner.tick(); assert_eq!(in_mem.contents(), "OK OK"); } #[test] fn multi_progress_clear_println() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); mp.println("Test of println").unwrap(); // Should have no effect mp.clear().unwrap(); assert_eq!(in_mem.contents(), "Test of println"); } #[test] fn multi_progress_clear_zombies_no_ticks() { _multi_progress_clear_zombies(0); } #[test] fn multi_progress_clear_zombies_one_tick() { _multi_progress_clear_zombies(1); } #[test] fn multi_progress_clear_zombies_two_ticks() { _multi_progress_clear_zombies(2); } // In the old (broken) implementation, zombie handling sometimes worked differently depending on // how many draws were between certain operations. Let's make sure that doesn't happen again. fn _multi_progress_clear_zombies(ticks: usize) { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let style = ProgressStyle::with_template("{msg}").unwrap(); let pb1 = mp.add( ProgressBar::new_spinner() .with_style(style.clone()) .with_message("pb1"), ); pb1.tick(); let pb2 = mp.add( ProgressBar::new_spinner() .with_style(style) .with_message("pb2"), ); pb2.tick(); assert_eq!(in_mem.contents(), "pb1\npb2"); pb1.finish_with_message("pb1 done"); drop(pb1); assert_eq!(in_mem.contents(), "pb1 done\npb2"); for _ in 0..ticks { pb2.tick(); } mp.clear().unwrap(); assert_eq!(in_mem.contents(), ""); } // This test reproduces examples/multi.rs in a simpler form #[test] fn multi_zombie_handling() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let style = ProgressStyle::with_template("{msg}").unwrap(); let pb1 = mp.add( ProgressBar::new_spinner() .with_style(style.clone()) .with_message("pb1"), ); pb1.tick(); let pb2 = mp.add( ProgressBar::new_spinner() .with_style(style.clone()) .with_message("pb2"), ); pb2.tick(); let pb3 = mp.add( ProgressBar::new_spinner() .with_style(style) .with_message("pb3"), ); pb3.tick(); mp.println("pb1 done!").unwrap(); pb1.finish_with_message("done"); assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3"); drop(pb1); assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3"); pb2.tick(); assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3"); pb3.tick(); assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3"); mp.println("pb3 done!").unwrap(); assert_eq!(in_mem.contents(), "pb1 done!\npb3 done!\npb2\npb3"); pb3.finish_with_message("done"); drop(pb3); pb2.tick(); mp.println("pb2 done!").unwrap(); pb2.finish_with_message("done"); drop(pb2); assert_eq!( in_mem.contents(), "pb1 done!\npb3 done!\npb2 done!\ndone\ndone" ); mp.clear().unwrap(); assert_eq!(in_mem.contents(), "pb1 done!\npb3 done!\npb2 done!"); } #[test] fn multi_progress_multiline_msg() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new_spinner().with_message("test1")); let pb2 = mp.add(ProgressBar::new_spinner().with_message("test2")); assert_eq!(in_mem.contents(), ""); pb1.inc(1); pb2.inc(1); assert_eq!( in_mem.contents(), r#" ⠁ test1 ⠁ test2 "# .trim() ); pb1.set_message("test1\n test1 line2\n test1 line3"); assert_eq!( in_mem.contents(), r#" ⠁ test1 test1 line2 test1 line3 ⠁ test2 "# .trim() ); pb1.inc(1); pb2.inc(1); assert_eq!( in_mem.contents(), r#" ⠉ test1 test1 line2 test1 line3 ⠉ test2 "# .trim() ); pb2.set_message("test2\n test2 line2"); assert_eq!( in_mem.contents(), r#" ⠉ test1 test1 line2 test1 line3 ⠉ test2 test2 line2 "# .trim() ); pb1.set_message("single line again"); assert_eq!( in_mem.contents(), r#" ⠉ single line again ⠉ test2 test2 line2 "# .trim() ); pb1.finish_with_message("test1 done!"); pb2.finish_with_message("test2 done!"); assert_eq!( in_mem.contents(), r#" test1 done! test2 done!"# ); } #[test] fn multi_progress_bottom_alignment() { let in_mem = InMemoryTerm::new(10, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); mp.set_alignment(MultiProgressAlignment::Bottom); let pb1 = mp.add(ProgressBar::new_spinner().with_message("test1")); let pb2 = mp.add(ProgressBar::new_spinner().with_message("test2")); pb1.tick(); pb2.tick(); pb1.finish_and_clear(); assert_eq!(in_mem.contents(), "\n⠁ test2"); pb2.finish_and_clear(); // `InMemoryTerm::contents` normally gets rid of trailing newlines, so write some text to ensure // the newlines are seen. in_mem.write_line("anchor").unwrap(); assert_eq!(in_mem.contents(), "\n\nanchor"); } #[test] fn progress_bar_terminal_wrap() { use std::cmp::min; let in_mem = InMemoryTerm::new(10, 20); let mut downloaded = 0; let total_size = 231231231; let pb = ProgressBar::with_draw_target( None, ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ); pb.set_style(ProgressStyle::default_bar() .template("{msg:>12.cyan.bold} {spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes}").unwrap() .progress_chars("#>-")); pb.set_message("Downloading"); assert_eq!( in_mem.contents(), r#" Downloading ⠁ [00:0 0:00] [------------- -------------------- -------] 0 B/0 B"# ); let new = min(downloaded + 223211, total_size); downloaded = new; pb.set_position(new); assert_eq!( in_mem.contents(), r#" Downloading ⠁ [00:0 0:00] [------------- -------------------- -------] 217.98 KiB/ 217.98 KiB"# ); let new = min(downloaded + 223211, total_size); pb.set_position(new); assert_eq!( in_mem.contents(), r#" Downloading ⠉ [00:0 0:00] [------------- -------------------- -------] 435.96 KiB/ 435.96 KiB"# ); pb.set_style( ProgressStyle::default_bar() .template("{msg:>12.green.bold} downloading {total_bytes:.green} in {elapsed:.green}") .unwrap(), ); pb.finish_with_message("Finished"); assert_eq!( in_mem.contents(), r#" Finished downloa ding 435.96 KiB in 0 s"# ); println!("{:?}", in_mem.contents()) } #[test] fn spinner_terminal_cleared_log_line_with_ansi_codes() { let in_mem = InMemoryTerm::new(10, 100); let pb = ProgressBar::with_draw_target( Some(10), ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ); pb.set_style(ProgressStyle::default_spinner()); assert_eq!(in_mem.contents(), String::new()); pb.finish_and_clear(); // Visually empty, but consists of an ANSII code pb.println("\u{1b}[1m"); pb.println("text\u{1b}[0m"); assert_eq!(in_mem.contents(), "\ntext"); } #[test] fn multi_progress_println_terminal_wrap() { let in_mem = InMemoryTerm::new(10, 48); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10)); let pb2 = mp.add(ProgressBar::new(5)); let pb3 = mp.add(ProgressBar::new(100)); assert_eq!(in_mem.contents(), ""); pb1.inc(2); mp.println("message printed that is longer than terminal width :)") .unwrap(); assert_eq!( in_mem.contents(), r#"message printed that is longer than terminal wid th :) ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10"# ); mp.println("another great message!").unwrap(); assert_eq!( in_mem.contents(), r#"message printed that is longer than terminal wid th :) another great message! ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10"# ); pb2.inc(1); pb3.tick(); mp.println("one last message but this one is also longer than terminal width") .unwrap(); assert_eq!( in_mem.contents(), r#"message printed that is longer than terminal wid th :) another great message! one last message but this one is also longer tha n terminal width ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10 ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/5 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"# .trim() ); drop(pb1); drop(pb2); drop(pb3); assert_eq!( in_mem.contents(), r#"message printed that is longer than terminal wid th :) another great message! one last message but this one is also longer tha n terminal width"# .trim() ); } #[test] fn basic_progress_bar_newline() { let in_mem = InMemoryTerm::new(10, 80); let pb = ProgressBar::with_draw_target( Some(10), ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ); assert_eq!(in_mem.contents(), String::new()); pb.println("\nhello"); pb.tick(); assert_eq!( in_mem.contents(), r#" hello ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); pb.inc(1); pb.println(""); assert_eq!( in_mem.contents(), r#" hello ███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10"# ); pb.finish(); assert_eq!( in_mem.contents(), " hello ██████████████████████████████████████████████████████████████████████████ 10/10" ); } #[test] fn multi_progress_many_bars() { let in_mem = InMemoryTerm::new(4, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave)); let mut spinners = vec![]; for i in 0..7 { let spinner = ProgressBar::new_spinner().with_message(i.to_string()); mp.add(spinner.clone()); spinners.push(spinner); } assert_eq!(in_mem.contents(), String::new()); pb1.tick(); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); assert_eq!( in_mem.moves_since_last_check(), r#"Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") Flush "# ); for spinner in &spinners { spinner.tick() } assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ⠁ 0 ⠁ 1 ⠁ 2"# .trim_start() ); assert_eq!( in_mem.moves_since_last_check(), r#"Clear Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str(" ") Flush Up(1) Clear Down(1) Clear Up(1) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str(" ") Flush Up(2) Clear Down(1) Clear Down(1) Clear Up(2) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str(" ") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush "# ); drop(pb1); assert_eq!( in_mem.contents(), r#" ██████████████████████████████████████████████████████████████████████████ 10/10 ⠁ 0 ⠁ 1 ⠁ 2"# .trim_start() ); assert_eq!( in_mem.moves_since_last_check(), r#"Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("██████████████████████████████████████████████████████████████████████████ 10/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush "# ); drop(spinners); assert_eq!(in_mem.contents(), r#""#); assert_eq!( in_mem.moves_since_last_check(), r#"Up(2) Clear Down(1) Clear Down(1) Clear Up(2) Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") NewLine Str("⠁ 3") Str("") NewLine Str("⠁ 4") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("⠁ 2") Str("") NewLine Str("⠁ 3") Str("") NewLine Str("⠁ 4") Str("") NewLine Str("⠁ 5") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("⠁ 3") Str("") NewLine Str("⠁ 4") Str("") NewLine Str("⠁ 5") Str("") NewLine Str("⠁ 6") Str(" ") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("⠁ 4") Str("") NewLine Str("⠁ 5") Str("") NewLine Str("⠁ 6") Str(" ") Flush Up(2) Clear Down(1) Clear Down(1) Clear Up(2) Str("⠁ 5") Str("") NewLine Str("⠁ 6") Str(" ") Flush Up(1) Clear Down(1) Clear Up(1) Str("⠁ 6") Str(" ") Flush Clear Str("") Flush "# ); } #[test] fn multi_progress_many_spinners() { let in_mem = InMemoryTerm::new(4, 80); let mp = MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone()))); let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave)); let mut spinners = vec![]; for i in 0..7 { let spinner = ProgressBar::new_spinner().with_message(i.to_string()); mp.add(spinner.clone()); spinners.push(spinner); } assert_eq!(in_mem.contents(), String::new()); pb1.tick(); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); assert_eq!( in_mem.moves_since_last_check(), r#"Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") Flush "# ); for spinner in &spinners { spinner.tick() } assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ⠁ 0 ⠁ 1 ⠁ 2"# .trim_start() ); assert_eq!( in_mem.moves_since_last_check(), r#"Clear Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str(" ") Flush Up(1) Clear Down(1) Clear Up(1) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str(" ") Flush Up(2) Clear Down(1) Clear Down(1) Clear Up(2) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str(" ") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush "# ); spinners.remove(3); assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ⠁ 0 ⠁ 1 ⠁ 2"# .trim_start() ); assert_eq!( in_mem.moves_since_last_check(), r#"Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush "# ); spinners.remove(4); assert_eq!( in_mem.contents(), r#" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10 ⠁ 0 ⠁ 1 ⠁ 2"# .trim_start() ); assert_eq!( in_mem.moves_since_last_check(), r#"Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 0") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") Flush "# ); drop(spinners); assert_eq!( in_mem.contents(), r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"# ); assert_eq!( in_mem.moves_since_last_check(), r#"Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 1") Str("") NewLine Str("⠁ 2") Str("") NewLine Str("⠁ 4") Str("") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 2") Str("") NewLine Str("⠁ 4") Str("") NewLine Str("⠁ 6") Str(" ") Flush Up(3) Clear Down(1) Clear Down(1) Clear Down(1) Clear Up(3) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 4") Str("") NewLine Str("⠁ 6") Str(" ") Flush Up(2) Clear Down(1) Clear Down(1) Clear Up(2) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") NewLine Str("⠁ 6") Str(" ") Flush Up(1) Clear Down(1) Clear Up(1) Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10") Str("") Flush "# ); } #[test] fn orphan_lines() { let in_mem = InMemoryTerm::new(10, 80); let pb = ProgressBar::with_draw_target( Some(10), ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ); assert_eq!(in_mem.contents(), String::new()); for i in 0..=10 { if i != 0 { pb.inc(1); } let n = 5 + i; pb.println("\n".repeat(n)); } pb.finish(); } #[test] fn orphan_lines_message_above_progress_bar() { let in_mem = InMemoryTerm::new(10, 80); let pb = ProgressBar::with_draw_target( Some(10), ProgressDrawTarget::term_like(Box::new(in_mem.clone())), ); assert_eq!(in_mem.contents(), String::new()); for i in 0..=10 { if i != 0 { pb.inc(1); } let n = 5 + i; // Test with messages of differing numbers of lines. The messages have the form: // n - 1 newlines followed by n * 11 dashes (`-`). The value of n ranges from 5 // (less than the terminal height) to 15 (greater than the terminal height). The // number 11 is intentionally not a factor of the terminal width (80), but large // enough that the strings of dashes eventually wrap. pb.println(format!("{}{}", "\n".repeat(n - 1), "-".repeat(n * 11))); // Check that the line above the progress bar is a string of dashes of length // n * 11 mod the terminal width. assert_eq!( format!("{}", "-".repeat(n * 11 % 80)), in_mem.contents().lines().rev().nth(1).unwrap(), ); } pb.finish(); }