Add unit tests for long progress bar messages

Verify that messages exceeding the terminal width are fully printed
without any portion being truncated.
This commit is contained in:
Marek Blaha 2025-07-16 11:50:12 +02:00
parent da07e21631
commit 3afb91e609
2 changed files with 112 additions and 2 deletions

View File

@ -27,12 +27,14 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include <libdnf5-cli/progressbar/download_progress_bar.hpp>
#include <libdnf5-cli/progressbar/multi_progress_bar.hpp>
#include <regex>
CPPUNIT_TEST_SUITE_REGISTRATION(ProgressbarInteractiveTest);
namespace {
// We look for control sequnces (such as move cursor up N times and carriage return)
// We look for control sequences (such as move cursor up N times and carriage return)
// and perform them. It basically simulates a terminal emulator.
//
// It can look like: "\x1b[9A\r" = move cursor 9 times up followed by carriage return
@ -71,7 +73,7 @@ std::string perform_control_sequences(std::string target) {
state = CONTROL_SEQUENCE_AMOUNT;
} else if ((state == CONTROL_SEQUENCE_INTRO || state == CONTROL_SEQUENCE_AMOUNT) && current_value == 'A') {
if (amount > current_row) {
CPPUNIT_FAIL(fmt::format("Cursor up control sequnce outside of output"));
CPPUNIT_FAIL(fmt::format("Cursor up control sequence outside of output"));
}
current_row -= amount;
state = EMPTY;
@ -117,6 +119,31 @@ std::string perform_control_sequences(std::string target) {
return libdnf5::utils::string::join(output, "\n");
}
int count_cursor_up_lines(const std::string & text) {
int lines_up = 0;
// Regular expression to find "\x1b[A" or "\x1b[NA"
std::regex cursor_up_regex("\\x1b\\[(\\d*)A");
auto end_of_sequence = std::sregex_iterator();
for (std::sregex_iterator cursor_up = std::sregex_iterator(text.begin(), text.end(), cursor_up_regex);
cursor_up != end_of_sequence;
++cursor_up) {
std::smatch match = *cursor_up;
std::string number = match[1].str(); // The captured number string
if (number.empty()) {
// Case: "\x1b[A" (equivalent to N=1)
++lines_up;
} else {
// Case: "\x1b[NA"
lines_up += std::stoi(number);
}
}
return lines_up;
}
} //namespace
void ProgressbarInteractiveTest::setUp() {
@ -211,6 +238,87 @@ void ProgressbarInteractiveTest::test_download_progress_bar_with_messages() {
ASSERT_MATCHES(expected, oss.str());
}
void ProgressbarInteractiveTest::test_download_progress_bar_with_long_messages() {
// The messages that do not fit within the terminal line are not trimmed.
// After the message is removed, the correct number of "cursor up" control
// sequences is issued.
auto download_progress_bar = std::make_unique<libdnf5::cli::progressbar::DownloadProgressBar>(10, "test");
libdnf5::cli::progressbar::MultiProgressBar multi_progress_bar;
multi_progress_bar.set_total_bar_visible_limit(libdnf5::cli::progressbar::MultiProgressBar::NEVER_VISIBLE_LIMIT);
auto download_progress_bar_raw = download_progress_bar.get();
multi_progress_bar.add_bar(std::move(download_progress_bar));
download_progress_bar_raw->set_ticks(4);
download_progress_bar_raw->set_state(libdnf5::cli::progressbar::ProgressBarState::STARTED);
{
// A string exactly 70 characters long (there will be a ">>> " prefix
// prepended) fits on one terminal line
download_progress_bar_raw->add_message(
libdnf5::cli::progressbar::MessageType::INFO,
".....1.........2.........3.........4.........5.........6.........7");
std::ostringstream oss;
oss << multi_progress_bar;
Pattern expected =
"\\[1/1\\] test 40% | ????? ??B\\/s | 4.0 B | ???????\n"
">>> .....1.........2.........3.........4.........5.........6.........7";
ASSERT_MATCHES(expected, perform_control_sequences(oss.str()));
download_progress_bar_raw->pop_message();
oss << multi_progress_bar;
expected = "\\[1/1\\] test 40% | ????? ??B\\/s | 4.0 B | ???????\n";
CPPUNIT_ASSERT_EQUAL(1, count_cursor_up_lines(oss.str()));
}
{
// A message longer than 70 characters will be split to multiple lines
download_progress_bar_raw->add_message(
libdnf5::cli::progressbar::MessageType::INFO,
".....1.........2.........3.........4.........5.........6.........71");
std::ostringstream oss;
oss << multi_progress_bar;
// whole message is printed
Pattern expected =
"\\[1/1\\] test 40% | ????? ??B\\/s | 4.0 B | ???????\n"
">>> .....1.........2.........3.........4.........5.........6.........71";
ASSERT_MATCHES(expected, perform_control_sequences(oss.str()));
download_progress_bar_raw->pop_message();
oss << multi_progress_bar;
expected = "\\[1/1\\] test 40% | ????? ??B\\/s | 4.0 B | ???????\n";
// two "cursor up" lines expected
CPPUNIT_ASSERT_EQUAL(2, count_cursor_up_lines(oss.str()));
}
{
// two cols wide characters are correctly counted
download_progress_bar_raw->add_message(
libdnf5::cli::progressbar::MessageType::INFO,
".....1.........2..もで...3.........4.........5.........6.........71");
std::ostringstream oss;
oss << multi_progress_bar;
// whole message is printed
Pattern expected =
"\\[1/1\\] test 40% | ????? ??B\\/s | 4.0 B | ???????\n"
">>> .....1.........2..もで...3.........4.........5.........6.........71";
ASSERT_MATCHES(expected, perform_control_sequences(oss.str()));
download_progress_bar_raw->pop_message();
oss << multi_progress_bar;
expected = "\\[1/1\\] test 40% | ????? ??B\\/s | 4.0 B | ???????\n";
// two "cursor up" lines expected
CPPUNIT_ASSERT_EQUAL(2, count_cursor_up_lines(oss.str()));
}
}
void ProgressbarInteractiveTest::test_multi_progress_bar_with_total_finished() {
// In interactive mode finished multi progressbar ends with a new line.

View File

@ -30,6 +30,7 @@ class ProgressbarInteractiveTest : public CppUnit::TestCase {
CPPUNIT_TEST(test_perform_control_sequences);
CPPUNIT_TEST(test_download_progress_bar);
CPPUNIT_TEST(test_download_progress_bar_with_messages);
CPPUNIT_TEST(test_download_progress_bar_with_long_messages);
CPPUNIT_TEST(test_multi_progress_bar_with_total_finished);
CPPUNIT_TEST(test_multi_progress_bar_with_messages_with_total);
CPPUNIT_TEST(test_multi_progress_bars_with_messages_with_total);
@ -47,6 +48,7 @@ public:
void test_perform_control_sequences();
void test_download_progress_bar();
void test_download_progress_bar_with_messages();
void test_download_progress_bar_with_long_messages();
void test_multi_progress_bar_with_total_finished();
void test_multi_progress_bar_with_messages_with_total();
void test_multi_progress_bars_with_messages_with_total();