Merge remote-tracking branch 'origin/master' into pr-4320
# Conflicts: # include/modules/hyprland/workspaces.hpp # man/waybar-hyprland-workspaces.5.scd # src/modules/hyprland/workspace.cpp # src/modules/hyprland/workspaces.cpp
This commit is contained in:
@@ -84,6 +84,33 @@ TEST_CASE("Load simple config with include", "[config]") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Load simple config with wildcard include", "[config]") {
|
||||
waybar::Config conf;
|
||||
conf.load("test/config/include-wildcard.json");
|
||||
|
||||
auto& data = conf.getConfig();
|
||||
SECTION("validate cpu include file") { REQUIRE(data["cpu"]["format"].asString() == "goo"); }
|
||||
SECTION("validate memory include file") { REQUIRE(data["memory"]["format"].asString() == "foo"); }
|
||||
}
|
||||
|
||||
TEST_CASE("Load config using relative paths and wildcards", "[config]") {
|
||||
waybar::Config conf;
|
||||
|
||||
const char* old_config_path = std::getenv(waybar::Config::CONFIG_PATH_ENV);
|
||||
setenv(waybar::Config::CONFIG_PATH_ENV, "test/config", 1);
|
||||
|
||||
conf.load("test/config/include-relative-path.json");
|
||||
|
||||
auto& data = conf.getConfig();
|
||||
SECTION("validate cpu include file") { REQUIRE(data["cpu"]["format"].asString() == "goo"); }
|
||||
SECTION("validate memory include file") { REQUIRE(data["memory"]["format"].asString() == "foo"); }
|
||||
|
||||
if (old_config_path)
|
||||
setenv(waybar::Config::CONFIG_PATH_ENV, old_config_path, 1);
|
||||
else
|
||||
unsetenv(waybar::Config::CONFIG_PATH_ENV);
|
||||
}
|
||||
|
||||
TEST_CASE("Load multiple bar config with include", "[config]") {
|
||||
waybar::Config conf;
|
||||
conf.load("test/config/include-multi.json");
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"include": ["modules/*.jsonc"],
|
||||
"position": "top",
|
||||
"nullOption": null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"include": ["test/config/modules/*.jsonc"],
|
||||
"position": "top",
|
||||
"nullOption": null
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"interval": 2,
|
||||
"format": "goo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"memory": {
|
||||
"interval": 2,
|
||||
"format": "foo",
|
||||
}
|
||||
}
|
||||
+166
-11
@@ -4,56 +4,211 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#endif
|
||||
|
||||
#include "fixtures/IPCTestFixture.hpp"
|
||||
#include <system_error>
|
||||
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace hyprland = waybar::modules::hyprland;
|
||||
|
||||
TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExists", "[getSocketFolder]") {
|
||||
namespace {
|
||||
class IPCTestHelper : public hyprland::IPC {
|
||||
public:
|
||||
static void resetSocketFolder() { socketFolder_.clear(); }
|
||||
static void resetLuaProtocolDetection() { s_luaProtocolDetected_.reset(); }
|
||||
static void setLuaProtocolDetected(bool value) { s_luaProtocolDetected_ = value; }
|
||||
using hyprland::IPC::buildLuaDispatch;
|
||||
using hyprland::IPC::isLuaProtocol;
|
||||
};
|
||||
|
||||
std::size_t countOpenFds() {
|
||||
#if defined(__linux__)
|
||||
std::size_t count = 0;
|
||||
for (const auto& _ : fs::directory_iterator("/proc/self/fd")) {
|
||||
(void)_;
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("XDGRuntimeDirExists", "[getSocketFolder]") {
|
||||
// Test case: XDG_RUNTIME_DIR exists and contains "hypr" directory
|
||||
// Arrange
|
||||
tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
|
||||
constexpr auto instanceSig = "instance_sig";
|
||||
const fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
|
||||
std::error_code ec;
|
||||
fs::remove_all(tempDir, ec);
|
||||
fs::path expectedPath = tempDir / "hypr" / instanceSig;
|
||||
fs::create_directories(tempDir / "hypr" / instanceSig);
|
||||
fs::create_directories(expectedPath);
|
||||
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
// Act
|
||||
fs::path actualPath = getSocketFolder(instanceSig);
|
||||
fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
|
||||
|
||||
// Assert expected result
|
||||
REQUIRE(actualPath == expectedPath);
|
||||
fs::remove_all(tempDir, ec);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirDoesNotExist", "[getSocketFolder]") {
|
||||
TEST_CASE("XDGRuntimeDirDoesNotExist", "[getSocketFolder]") {
|
||||
// Test case: XDG_RUNTIME_DIR does not exist
|
||||
// Arrange
|
||||
constexpr auto instanceSig = "instance_sig";
|
||||
unsetenv("XDG_RUNTIME_DIR");
|
||||
fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig;
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
// Act
|
||||
fs::path actualPath = getSocketFolder(instanceSig);
|
||||
fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
|
||||
|
||||
// Assert expected result
|
||||
REQUIRE(actualPath == expectedPath);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") {
|
||||
TEST_CASE("XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") {
|
||||
// Test case: XDG_RUNTIME_DIR exists but does not contain "hypr" directory
|
||||
// Arrange
|
||||
constexpr auto instanceSig = "instance_sig";
|
||||
fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
|
||||
std::error_code ec;
|
||||
fs::remove_all(tempDir, ec);
|
||||
fs::create_directories(tempDir);
|
||||
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
|
||||
fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig;
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
// Act
|
||||
fs::path actualPath = getSocketFolder(instanceSig);
|
||||
fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);
|
||||
|
||||
// Assert expected result
|
||||
REQUIRE(actualPath == expectedPath);
|
||||
fs::remove_all(tempDir, ec);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(IPCTestFixture, "getSocket1Reply throws on no socket", "[getSocket1Reply]") {
|
||||
TEST_CASE("Socket folder is resolved per instance signature", "[getSocketFolder]") {
|
||||
const fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
|
||||
std::error_code ec;
|
||||
fs::remove_all(tempDir, ec);
|
||||
fs::create_directories(tempDir / "hypr");
|
||||
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
const auto firstPath = hyprland::IPC::getSocketFolder("instance_a");
|
||||
const auto secondPath = hyprland::IPC::getSocketFolder("instance_b");
|
||||
|
||||
REQUIRE(firstPath == tempDir / "hypr" / "instance_a");
|
||||
REQUIRE(secondPath == tempDir / "hypr" / "instance_b");
|
||||
REQUIRE(firstPath != secondPath);
|
||||
|
||||
fs::remove_all(tempDir, ec);
|
||||
}
|
||||
|
||||
TEST_CASE("getSocket1Reply throws on no socket", "[getSocket1Reply]") {
|
||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
std::string request = "test_request";
|
||||
|
||||
CHECK_THROWS(getSocket1Reply(request));
|
||||
CHECK_THROWS(hyprland::IPC::getSocket1Reply(request));
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd-leak]") {
|
||||
const auto baseline = countOpenFds();
|
||||
|
||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
CHECK_THROWS(hyprland::IPC::getSocket1Reply("test_request"));
|
||||
}
|
||||
const auto after_missing_signature = countOpenFds();
|
||||
REQUIRE(after_missing_signature == baseline);
|
||||
|
||||
setenv("HYPRLAND_INSTANCE_SIGNATURE", "definitely-not-running", 1);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
CHECK_THROWS(hyprland::IPC::getSocket1Reply("test_request"));
|
||||
}
|
||||
const auto after_connect_failures = countOpenFds();
|
||||
REQUIRE(after_connect_failures == baseline);
|
||||
}
|
||||
#endif
|
||||
|
||||
// --- Tests for new Lua IPC dispatch functions ---
|
||||
|
||||
TEST_CASE("buildLuaDispatch workspace", "[buildLuaDispatch]") {
|
||||
SECTION("numeric workspace") {
|
||||
auto result = IPCTestHelper::buildLuaDispatch("workspace", "1");
|
||||
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"1\" })");
|
||||
}
|
||||
SECTION("named workspace") {
|
||||
auto result = IPCTestHelper::buildLuaDispatch("workspace", "name:term");
|
||||
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"name:term\" })");
|
||||
}
|
||||
SECTION("relative workspace") {
|
||||
auto result = IPCTestHelper::buildLuaDispatch("workspace", "e+1");
|
||||
REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"e+1\" })");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("buildLuaDispatch focusworkspaceoncurrentmonitor", "[buildLuaDispatch]") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("focusworkspaceoncurrentmonitor", "3");
|
||||
REQUIRE(
|
||||
result ==
|
||||
"/dispatch hl.dsp.focus({ workspace = \"3\", on_current_monitor = true })");
|
||||
}
|
||||
|
||||
TEST_CASE("buildLuaDispatch togglespecialworkspace", "[buildLuaDispatch]") {
|
||||
SECTION("with name") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "scratchpad");
|
||||
REQUIRE(result ==
|
||||
"/dispatch hl.dsp.workspace.toggle_special(\"scratchpad\")");
|
||||
}
|
||||
SECTION("empty arg") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "");
|
||||
REQUIRE(result == "/dispatch hl.dsp.workspace.toggle_special()");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("buildLuaDispatch unknown dispatcher fallback", "[buildLuaDispatch]") {
|
||||
auto result =
|
||||
IPCTestHelper::buildLuaDispatch("unknown_dispatcher", "some_arg");
|
||||
REQUIRE(result ==
|
||||
"/dispatch hl.dsp.unknown_dispatcher(\"some_arg\")");
|
||||
}
|
||||
|
||||
TEST_CASE("dispatch throws when Hyprland is not running", "[dispatch]") {
|
||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
IPCTestHelper::resetLuaProtocolDetection();
|
||||
|
||||
CHECK_THROWS(hyprland::IPC::dispatch("workspace", "1"));
|
||||
}
|
||||
|
||||
TEST_CASE("isLuaProtocol uses cached value and avoids socket call",
|
||||
"[isLuaProtocol]") {
|
||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
SECTION("cached false") {
|
||||
IPCTestHelper::setLuaProtocolDetected(false);
|
||||
// Should return false without throwing (no socket call needed)
|
||||
REQUIRE(IPCTestHelper::isLuaProtocol() == false);
|
||||
}
|
||||
|
||||
SECTION("cached true") {
|
||||
IPCTestHelper::setLuaProtocolDetected(true);
|
||||
// Should return true without throwing (no socket call needed)
|
||||
REQUIRE(IPCTestHelper::isLuaProtocol() == true);
|
||||
}
|
||||
|
||||
// Cleanup: reset detection so other tests aren't affected
|
||||
IPCTestHelper::resetLuaProtocolDetection();
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace hyprland = waybar::modules::hyprland;
|
||||
|
||||
class IPCTestFixture : public hyprland::IPC {
|
||||
public:
|
||||
IPCTestFixture() : IPC() { IPC::socketFolder_ = ""; }
|
||||
~IPCTestFixture() { fs::remove_all(tempDir); }
|
||||
|
||||
protected:
|
||||
const char* instanceSig = "instance_sig";
|
||||
fs::path tempDir = fs::temp_directory_path() / "hypr_test";
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
class IPCMock : public IPCTestFixture {
|
||||
public:
|
||||
// Mock getSocket1Reply to return an empty string
|
||||
static std::string getSocket1Reply(const std::string& rq) { return ""; }
|
||||
|
||||
protected:
|
||||
const char* instanceSig = "instance_sig";
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
#endif
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "fixtures/GlibTestsFixture.hpp"
|
||||
|
||||
@@ -141,3 +142,33 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thr
|
||||
producer.join();
|
||||
REQUIRE(count == NUM_EVENTS);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal queue stays bounded under burst load",
|
||||
"[signal][thread][util][perf]") {
|
||||
constexpr int NUM_EVENTS = 200;
|
||||
constexpr std::size_t MAX_QUEUED_EVENTS = 8;
|
||||
std::vector<int> received;
|
||||
|
||||
SafeSignal<int> test_signal;
|
||||
test_signal.set_max_queued_events(MAX_QUEUED_EVENTS);
|
||||
|
||||
setTimeout(500);
|
||||
|
||||
test_signal.connect([&](auto value) { received.push_back(value); });
|
||||
|
||||
run([&]() {
|
||||
std::thread producer([&]() {
|
||||
for (int i = 1; i <= NUM_EVENTS; ++i) {
|
||||
test_signal.emit(i);
|
||||
}
|
||||
});
|
||||
producer.join();
|
||||
|
||||
Glib::signal_timeout().connect_once([this]() { this->quit(); }, 50);
|
||||
});
|
||||
|
||||
REQUIRE(received.size() <= MAX_QUEUED_EVENTS);
|
||||
REQUIRE_FALSE(received.empty());
|
||||
REQUIRE(received.back() == NUM_EVENTS);
|
||||
REQUIRE(received.front() == NUM_EVENTS - static_cast<int>(received.size()) + 1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#if __has_include(<catch2/catch_test_macros.hpp>)
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#else
|
||||
#include <catch2/catch.hpp>
|
||||
#endif
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
|
||||
std::mutex reap_mtx;
|
||||
std::list<pid_t> reap;
|
||||
|
||||
extern "C" int waybar_test_execl(const char* path, const char* arg, ...);
|
||||
extern "C" int waybar_test_execlp(const char* file, const char* arg, ...);
|
||||
|
||||
#define execl waybar_test_execl
|
||||
#define execlp waybar_test_execlp
|
||||
#include "util/command.hpp"
|
||||
#undef execl
|
||||
#undef execlp
|
||||
|
||||
extern "C" int waybar_test_execl(const char* path, const char* arg, ...) {
|
||||
(void)path;
|
||||
(void)arg;
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern "C" int waybar_test_execlp(const char* file, const char* arg, ...) {
|
||||
(void)file;
|
||||
(void)arg;
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
TEST_CASE("command::execNoRead returns 127 when shell exec fails", "[util][command]") {
|
||||
const auto result = waybar::util::command::execNoRead("echo should-not-run");
|
||||
REQUIRE(result.exit_code == waybar::util::command::kExecFailureExitCode);
|
||||
REQUIRE(result.out.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("command::forkExec child exits 127 when shell exec fails", "[util][command]") {
|
||||
const auto pid = waybar::util::command::forkExec("echo should-not-run", "test-output");
|
||||
REQUIRE(pid > 0);
|
||||
|
||||
int status = -1;
|
||||
REQUIRE(waitpid(pid, &status, 0) == pid);
|
||||
REQUIRE(WIFEXITED(status));
|
||||
REQUIRE(WEXITSTATUS(status) == waybar::util::command::kExecFailureExitCode);
|
||||
|
||||
std::scoped_lock<std::mutex> lock(reap_mtx);
|
||||
reap.remove(pid);
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
class CssReloadHelperTest : public waybar::CssReloadHelper {
|
||||
public:
|
||||
CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this]() { callback(); }) {}
|
||||
CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this](const std::string&) { callback(); }) {}
|
||||
|
||||
void callback() { m_callbackCounter++; }
|
||||
|
||||
|
||||
+7
-7
@@ -45,7 +45,7 @@ static const bool LC_TIME_is_sane = []() {
|
||||
|
||||
ss << std::put_time(&tm, "%x %X");
|
||||
return ss.str() == "01/03/2022 12:00:00 PM";
|
||||
} catch (std::exception &) {
|
||||
} catch (std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
@@ -77,7 +77,7 @@ TEST_CASE("Format UTC time", "[clock][util]") {
|
||||
CHECK(fmt_lib::format(loc, "{:%F %r}", tm) == "2022-01-03 01:04:05 PM");
|
||||
#endif
|
||||
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103130405");
|
||||
} catch (const std::runtime_error &) {
|
||||
} catch (const std::runtime_error&) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ TEST_CASE("Format UTC time", "[clock][util]") {
|
||||
CHECK(fmt_lib::format(loc, "{:%F %T}", tm) == "2022-01-03 13:04:05");
|
||||
#endif
|
||||
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103130405");
|
||||
} catch (const std::runtime_error &) {
|
||||
} catch (const std::runtime_error&) {
|
||||
WARN("Locale en_GB not found, skip tests");
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ TEST_CASE("Format UTC time", "[clock][util]") {
|
||||
CHECK(fmt_lib::format("{:%Y%m%d%H%M%S}", tm) == "20220103130405");
|
||||
|
||||
std::locale::global(loc);
|
||||
} catch (const std::runtime_error &) {
|
||||
} catch (const std::runtime_error&) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ TEST_CASE("Format zoned time", "[clock][util]") {
|
||||
CHECK(fmt_lib::format(loc, "{:%F %r}", tm) == "2022-01-03 08:04:05 AM");
|
||||
#endif
|
||||
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103080405");
|
||||
} catch (const std::runtime_error &) {
|
||||
} catch (const std::runtime_error&) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ TEST_CASE("Format zoned time", "[clock][util]") {
|
||||
CHECK(fmt_lib::format(loc, "{:%F %T}", tm) == "2022-01-03 08:04:05");
|
||||
#endif
|
||||
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103080405");
|
||||
} catch (const std::runtime_error &) {
|
||||
} catch (const std::runtime_error&) {
|
||||
WARN("Locale en_GB not found, skip tests");
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ TEST_CASE("Format zoned time", "[clock][util]") {
|
||||
CHECK(fmt_lib::format("{:%Y%m%d%H%M%S}", tm) == "20220103080405");
|
||||
|
||||
std::locale::global(loc);
|
||||
} catch (const std::runtime_error &) {
|
||||
} catch (const std::runtime_error&) {
|
||||
WARN("Locale en_US not found, skip tests");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ test_src = files(
|
||||
'../../src/config.cpp',
|
||||
'JsonParser.cpp',
|
||||
'SafeSignal.cpp',
|
||||
'sleeper_thread.cpp',
|
||||
'command.cpp',
|
||||
'css_reload_helper.cpp',
|
||||
'../../src/util/css_reload_helper.cpp',
|
||||
)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#if __has_include(<catch2/catch_test_macros.hpp>)
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#else
|
||||
#include <catch2/catch.hpp>
|
||||
#endif
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "util/sleeper_thread.hpp"
|
||||
|
||||
namespace waybar::util {
|
||||
SafeSignal<bool>& prepare_for_sleep() {
|
||||
static SafeSignal<bool> signal;
|
||||
return signal;
|
||||
}
|
||||
} // namespace waybar::util
|
||||
|
||||
namespace {
|
||||
int run_in_subprocess(int (*task)()) {
|
||||
const auto pid = fork();
|
||||
if (pid < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (pid == 0) {
|
||||
alarm(5);
|
||||
_exit(task());
|
||||
}
|
||||
|
||||
int status = -1;
|
||||
if (waitpid(pid, &status, 0) != pid) {
|
||||
return -1;
|
||||
}
|
||||
if (!WIFEXITED(status)) {
|
||||
return -1;
|
||||
}
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int run_reassignment_regression() {
|
||||
waybar::util::SleeperThread thread;
|
||||
thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); };
|
||||
thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(1)); };
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_control_flag_stress() {
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
waybar::util::SleeperThread thread;
|
||||
thread = [&thread] { thread.sleep_for(std::chrono::milliseconds(1)); };
|
||||
|
||||
std::thread waker([&thread] {
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
thread.wake_up();
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
thread.stop();
|
||||
waker.join();
|
||||
if (thread.isRunning()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("SleeperThread reassignment does not terminate process", "[util][sleeper_thread]") {
|
||||
REQUIRE(run_in_subprocess(run_reassignment_regression) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("SleeperThread control flags are stable under concurrent wake and stop",
|
||||
"[util][sleeper_thread]") {
|
||||
REQUIRE(run_in_subprocess(run_control_flag_stress) == 0);
|
||||
}
|
||||
Reference in New Issue
Block a user