From 560f02509b0b3bd09824d0aabe5ea29763ffbc7f Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 8 Mar 2026 22:30:14 -0500 Subject: [PATCH] test(util): cover command stream line delivery and EOF flushing Add focused coverage for the new GLib command stream helper. These tests verify that complete lines are emitted as they arrive and that EOF flushes a final unterminated line without duplicating a newline-terminated one. That behavior is the contract the custom module will rely on when its continuous command handling moves onto this helper. Signed-off-by: Austin Horstman --- test/utils/command_line_stream.cpp | 69 ++++++++++++++++++++++++ test/utils/fixtures/GlibTestsFixture.hpp | 11 +++- test/utils/meson.build | 2 + 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 test/utils/command_line_stream.cpp diff --git a/test/utils/command_line_stream.cpp b/test/utils/command_line_stream.cpp new file mode 100644 index 00000000..8c1639e3 --- /dev/null +++ b/test/utils/command_line_stream.cpp @@ -0,0 +1,69 @@ +#if __has_include() +#include +#else +#include +#endif + +#include + +#include +#include +#include + +#include "util/command_line_stream.hpp" + +namespace { + +struct StreamResult { + std::vector lines; + std::optional exit_code; + bool timed_out = false; +}; + +auto run_stream_command(const std::string& cmd) -> StreamResult { + StreamResult result; + auto loop = Glib::MainLoop::create(); + + auto timeout = Glib::signal_timeout().connect( + [&]() { + result.timed_out = true; + loop->quit(); + return false; + }, + 3000); + + waybar::util::command::LineStream stream( + "", + [&](const std::string& line) { result.lines.push_back(line); }, + [&](int exit_code) { + result.exit_code = exit_code; + loop->quit(); + }); + + stream.start(cmd); + loop->run(); + timeout.disconnect(); + return result; +} + +} // namespace + +TEST_CASE("command::LineStream emits complete lines and flushes trailing output", + "[util][command_line_stream]") { + const auto result = run_stream_command("printf 'first\\nsecond'"); + + REQUIRE_FALSE(result.timed_out); + REQUIRE(result.exit_code.has_value()); + REQUIRE(*result.exit_code == 0); + REQUIRE(result.lines == std::vector{"first", "second"}); +} + +TEST_CASE("command::LineStream does not emit an extra line after newline-terminated output", + "[util][command_line_stream]") { + const auto result = run_stream_command("printf 'first\\nsecond\\n'"); + + REQUIRE_FALSE(result.timed_out); + REQUIRE(result.exit_code.has_value()); + REQUIRE(*result.exit_code == 0); + REQUIRE(result.lines == std::vector{"first", "second"}); +} diff --git a/test/utils/fixtures/GlibTestsFixture.hpp b/test/utils/fixtures/GlibTestsFixture.hpp index a21c8e07..95ce7f95 100644 --- a/test/utils/fixtures/GlibTestsFixture.hpp +++ b/test/utils/fixtures/GlibTestsFixture.hpp @@ -6,10 +6,16 @@ class GlibTestsFixture : public sigc::trackable { public: GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} + ~GlibTestsFixture() { timeout_.disconnect(); } void setTimeout(int timeout) { - Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, - timeout); + timeout_.disconnect(); + timeout_ = Glib::signal_timeout().connect( + []() { + throw std::runtime_error("Test timed out"); + return false; + }, + timeout); } void run(std::function fn) { @@ -21,4 +27,5 @@ class GlibTestsFixture : public sigc::trackable { protected: Glib::RefPtr main_loop_; + sigc::connection timeout_; }; diff --git a/test/utils/meson.build b/test/utils/meson.build index e8dd37fa..0d9130de 100644 --- a/test/utils/meson.build +++ b/test/utils/meson.build @@ -15,8 +15,10 @@ test_src = files( 'SafeSignal.cpp', 'sleeper_thread.cpp', 'command.cpp', + 'command_line_stream.cpp', 'css_reload_helper.cpp', '../../src/util/css_reload_helper.cpp', + '../../src/util/command_line_stream.cpp', ) if tz_dep.found()