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 <khaneliman12@gmail.com>
This commit is contained in:
Austin Horstman
2026-07-04 08:40:31 -05:00
committed by Austin Horstman
parent 2355486cb3
commit 560f02509b
3 changed files with 80 additions and 2 deletions
+69
View File
@@ -0,0 +1,69 @@
#if __has_include(<catch2/catch_test_macros.hpp>)
#include <catch2/catch_test_macros.hpp>
#else
#include <catch2/catch.hpp>
#endif
#include <glibmm/main.h>
#include <optional>
#include <string>
#include <vector>
#include "util/command_line_stream.hpp"
namespace {
struct StreamResult {
std::vector<std::string> lines;
std::optional<int> 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<std::string>{"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<std::string>{"first", "second"});
}
+8 -1
View File
@@ -6,9 +6,15 @@
class GlibTestsFixture : public sigc::trackable { class GlibTestsFixture : public sigc::trackable {
public: public:
GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {}
~GlibTestsFixture() { timeout_.disconnect(); }
void setTimeout(int timeout) { void setTimeout(int timeout) {
Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, timeout_.disconnect();
timeout_ = Glib::signal_timeout().connect(
[]() {
throw std::runtime_error("Test timed out");
return false;
},
timeout); timeout);
} }
@@ -21,4 +27,5 @@ class GlibTestsFixture : public sigc::trackable {
protected: protected:
Glib::RefPtr<Glib::MainLoop> main_loop_; Glib::RefPtr<Glib::MainLoop> main_loop_;
sigc::connection timeout_;
}; };
+2
View File
@@ -15,8 +15,10 @@ test_src = files(
'SafeSignal.cpp', 'SafeSignal.cpp',
'sleeper_thread.cpp', 'sleeper_thread.cpp',
'command.cpp', 'command.cpp',
'command_line_stream.cpp',
'css_reload_helper.cpp', 'css_reload_helper.cpp',
'../../src/util/css_reload_helper.cpp', '../../src/util/css_reload_helper.cpp',
'../../src/util/command_line_stream.cpp',
) )
if tz_dep.found() if tz_dep.found()