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()