diff options
| author | Nikolai Nechaev <nikolay_nechaev@mail.ru> | 2026-04-28 19:17:12 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-28 11:17:12 +0100 |
| commit | d4c6ff434c5c7aa66bc7c28e5ce336d2a694bfcc (patch) | |
| tree | 3813063b81dc435177baba35ffdb655d4e3bd154 | |
| parent | e1bda229bdf070f1eed01858f862ca975783df71 (diff) | |
hyprtester: minor refactoring/restructure (#14154)
* hyprtester: Minor misc fixes
- Use `std::filesystem::path` instead of `std::string` in some places;
- Use `std::ranges::sort`, as suggested by clang-tidy;
- Reset colors at the end of every log line;
- Give more specific error messages in main.
* hyprtester: Fix a bug in `hyprlandAlive()`
Successful standard library calls are permitted to (and do) modify the
value of `errno` arbitrarily. Checking the value of `errno` only makes
sense when the library function returns an error. So, the correct
condition of a successful signal delivery is that the call to `kill`
returns 0.
Btw, given the possible other error values of `kill(2)`, should we even
consider `errno` at all?
* hyprtester: Restructure main
Split main's `main()` into multiple functions, separate responsibilities
more cleanly.
Add default values for cmd-line options to the help message.
* hyprtester: Remove redundant path canonicalization
* hyprtester: Run unified clean up code before every test
* tests: Use RAII for clients in client tests
Turn clients in client tests into classes. Constructors
initialize, destructors deinitialize.
Not only is it better design when entities (clients) are
modeled as classes, keeping all the methods connected,
this also ensures the tests perform their custom cleanup.
This will be especially important when we introduce early
termination on failure into the tests.
* hyprtester: Rewrite the testing framework!
- Implement a new test definition system used via the `TEST_CASE` and
`SUBTEST` macros.
- Adds `ASSERT*` and reimplements `EXPECT*` macros. The former now
terminate the test immediately. Also add some new macros, such as
`FAIL_TEST`.
- Move existing tests to the new system. For trivial cases, break
existing test functions down to multiple test cases.
- For non-trivial cases and a few other possible improvements, leave
TODOs.
* tests: Use `EXPECT*` instead of `ASSERT*` in some places
`ASSERT*` macros terminate the test as soon as they fail. They are
appropriate to use when, if something fails, the rest of the test
does not make sense and more error messages from failing checks will
just pollute the output.
`EXPECT*` macros mark the test as failed but its execution continues.
It makes sense to use them when error messages from the following checks
in the test may be useful for someone who will be reading the log.
I changed some uses of `ASSERT*` in main tests to `EXPECT*`. But I was
not reading tests very carefully, so maybe I was wrong in some places.
My rule of thumb was:
- Checks that ensure that a kitty opens / a certain set/number of
windows exists are fatal;
- A series of checks, especially on the same string returned by
`Hyprland`, are non-fatal;
- Checks in tests with tightly coupled steps (i.e., do this, check,
do this with the result of the previous step, check, etc) are fatal.
32 files changed, 1131 insertions, 1648 deletions
diff --git a/hyprtester/src/Log.hpp b/hyprtester/src/Log.hpp index 7eeb3ece..87f0890d 100644 --- a/hyprtester/src/Log.hpp +++ b/hyprtester/src/Log.hpp @@ -3,6 +3,8 @@ #include <format> #include <print> +#include "shared.hpp" + namespace NLog { template <typename... Args> //NOLINTNEXTLINE @@ -11,7 +13,7 @@ namespace NLog { logMsg += std::vformat(fmt.get(), std::make_format_args(args...)); - std::println("{}", logMsg); + std::println("{}{}", logMsg, Colors::RESET); std::fflush(stdout); } -}
\ No newline at end of file +} diff --git a/hyprtester/src/hyprctlCompat.cpp b/hyprtester/src/hyprctlCompat.cpp index 2c8e0644..dbeee93d 100644 --- a/hyprtester/src/hyprctlCompat.cpp +++ b/hyprtester/src/hyprctlCompat.cpp @@ -43,7 +43,7 @@ std::vector<SInstanceData> instances() { } catch (std::exception& e) { return {}; } for (const auto& el : std::filesystem::directory_iterator(getRuntimeDir())) { - if (!el.is_directory() || !std::filesystem::exists(el.path().string() + "/hyprland.lock")) + if (!std::filesystem::exists(el.path() / "hyprland.lock")) continue; // read lock @@ -74,7 +74,7 @@ std::vector<SInstanceData> instances() { std::erase_if(result, [&](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; }); - std::sort(result.begin(), result.end(), [&](const auto& a, const auto& b) { return a.time < b.time; }); + std::ranges::sort(result, [&](const auto& a, const auto& b) { return a.time < b.time; }); return result; } @@ -135,4 +135,4 @@ std::string getFromSocket(const std::string& cmd) { close(SERVERSOCKET); return reply; -}
\ No newline at end of file +} diff --git a/hyprtester/src/main.cpp b/hyprtester/src/main.cpp index 2fc78201..0da9a20e 100644 --- a/hyprtester/src/main.cpp +++ b/hyprtester/src/main.cpp @@ -17,14 +17,17 @@ #include "shared.hpp" #include "hyprctlCompat.hpp" #include "tests/main/tests.hpp" +#undef TEST_CASES_STORAGE // Prevent redefinition warning #include "tests/clients/tests.hpp" +#undef TEST_CASES_STORAGE // Prevent redefinition warning #include "tests/plugin/plugin.hpp" +#include "tests/shared.hpp" +#include <algorithm> #include <filesystem> #include <hyprutils/os/Process.hpp> #include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/Casts.hpp> -using namespace Hyprutils::Memory; #include <csignal> #include <cerrno> @@ -38,35 +41,13 @@ using namespace Hyprutils::Memory; using namespace Hyprutils::OS; using namespace Hyprutils::Memory; +using Path = std::filesystem::path; #define SP CSharedPointer -static int ret = 0; -static SP<CProcess> hyprlandProc; -static const std::string cwd = std::filesystem::current_path().string(); - -// -static bool launchHyprland(std::string configPath, std::string binaryPath) { - if (binaryPath == "") { - std::error_code ec; - if (!std::filesystem::exists(cwd + "/../build/Hyprland", ec) || ec) { - NLog::log("{}No Hyprland binary", Colors::RED); - return false; - } - - binaryPath = cwd + "/../build/Hyprland"; - } - - if (configPath == "") { - std::error_code ec; - if (!std::filesystem::exists(cwd + "/test.lua", ec) || ec) { - NLog::log("{}No test config", Colors::RED); - return false; - } - - configPath = cwd + "/test.lua"; - } +static SP<CProcess> hyprlandProc; +static bool launchHyprland(Path configPath, Path binaryPath) { NLog::log("{}Launching Hyprland", Colors::YELLOW); hyprlandProc = makeShared<CProcess>(binaryPath, std::vector<std::string>{"--config", configPath}); hyprlandProc->addEnv("HYPRLAND_HEADLESS_ONLY", "1"); @@ -78,121 +59,147 @@ static bool launchHyprland(std::string configPath, std::string binaryPath) { static bool hyprlandAlive() { NLog::log("{}hyprlandAlive", Colors::YELLOW); - kill(hyprlandProc->pid(), 0); - return errno != ESRCH; + return kill(hyprlandProc->pid(), 0) == 0 || errno != ESRCH; } -static void help() { +[[noreturn]] static void helpAndDie(int exit_code) { NLog::log("usage: hyprtester [arg [...]].\n"); NLog::log(R"(Arguments: --help -h - Show this message again - --config FILE -c FILE - Specify config file to use - --binary FILE -b FILE - Specify Hyprland binary to use - --plugin FILE -p FILE - Specify the location of the test plugin)"); -} - -int main(int argc, char** argv, char** envp) { - - std::string configPath = ""; - std::string binaryPath = ""; - std::string pluginPath = std::filesystem::current_path().string(); - - if (argc > 1) { - std::span<char*> args{argv + 1, sc<std::size_t>(argc - 1)}; + --config FILE -c FILE - Specify config file to use (default: './test.lua') + --binary FILE -b FILE - Specify Hyprland binary to use (default: '../build/Hyprland') + --plugin FILE -p FILE - Specify the location of the test plugin (default: './'))"); - for (auto it = args.begin(); it != args.end(); it++) { - std::string_view value = *it; - - if (value == "--config" || value == "-c") { - if (std::next(it) == args.end()) { - help(); - - return 1; - } - - configPath = *std::next(it); - - try { - configPath = std::filesystem::canonical(configPath); - - if (!std::filesystem::is_regular_file(configPath)) { - throw std::exception(); - } - } catch (...) { - std::println(stderr, "[ ERROR ] Config file '{}' doesn't exist!", configPath); - help(); - - return 1; - } - - it++; + std::exit(exit_code); +} - continue; - } else if (value == "--binary" || value == "-b") { - if (std::next(it) == args.end()) { - help(); +static Path validatePathOrDie(Path path) { + try { + if (!std::filesystem::is_regular_file(path)) { + throw std::exception(); + } + } catch (...) { + std::println(stderr, "[ ERROR ] File '{}' is not accessible or not a regular file", path.string()); + helpAndDie(EXIT_FAILURE); + } + return path; +} - return 1; - } +namespace { + struct SSettings { + Path configPath; + Path binaryPath; + Path pluginPath; + }; +} - binaryPath = *std::next(it); +static SSettings parseSettings(const std::span<const char*> args) { + static const auto cwd = std::filesystem::current_path(); + SSettings settings{}; - try { - binaryPath = std::filesystem::canonical(binaryPath); + for (auto it = args.begin(); it < args.end(); it++) { + std::string_view value = *it; - if (!std::filesystem::is_regular_file(binaryPath)) { - throw std::exception(); - } - } catch (...) { - std::println(stderr, "[ ERROR ] Binary '{}' doesn't exist!", binaryPath); - help(); + if (value == "--config" || value == "-c") { + if (std::next(it) == args.end()) { + helpAndDie(EXIT_FAILURE); + } - return 1; - } + settings.configPath = validatePathOrDie(*std::next(it)); + it++; + } else if (value == "--binary" || value == "-b") { + if (std::next(it) == args.end()) { + helpAndDie(EXIT_FAILURE); + } - it++; + settings.binaryPath = validatePathOrDie(*std::next(it)); + it++; + } else if (value == "--plugin" || value == "-p") { + if (std::next(it) == args.end()) { + helpAndDie(EXIT_FAILURE); + } - continue; - } else if (value == "--plugin" || value == "-p") { - if (std::next(it) == args.end()) { - help(); + settings.pluginPath = validatePathOrDie(*std::next(it)); + it++; + } else if (value == "--help" || value == "-h") { + helpAndDie(EXIT_SUCCESS); + } else { + std::println(stderr, "[ ERROR ] Unknown option '{}' !", *it); + helpAndDie(EXIT_SUCCESS); + } + } - return 1; - } + // Default options + if (settings.configPath.empty()) + settings.configPath = validatePathOrDie(cwd / "test.lua"); + if (settings.binaryPath.empty()) + settings.binaryPath = validatePathOrDie(cwd / "../build/Hyprland"); + if (settings.pluginPath.empty()) + settings.pluginPath = cwd; - pluginPath = *std::next(it); + return settings; +} - try { - pluginPath = std::filesystem::canonical(pluginPath); +static bool preTestCleanup() { + bool failed = false; - if (!std::filesystem::is_regular_file(pluginPath)) { - throw std::exception(); - } - } catch (...) { - std::println(stderr, "[ ERROR ] plugin '{}' doesn't exist!", pluginPath); - help(); + if (!Tests::killAllWindows()) { + NLog::log("{}Internal failure: failed to kill all windows", Colors::RED); + failed = true; + } + if (!Tests::killAllLayers()) { + NLog::log("{}Internal failure: failed to kill all layers", Colors::RED); + failed = true; + } + if (getFromSocket("/reload") != "ok") { + NLog::log("{}Internal failure: failed to reload", Colors::RED); + failed = true; + } + if (!getFromSocket("/activeworkspace").contains("workspace ID 1 (1)")) { + if (getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })") != "ok") { + NLog::log("{}Internal failure: failed to switch to workspace 1", Colors::RED); + failed = true; + } + } + if (getFromSocket("/dispatch hl.dsp.cursor.move({ x = 960, y = 540 })") != "ok") { + NLog::log("{}Internal failure: failed to reset cursor position", Colors::RED); + failed = true; + } - return 1; - } + return !failed; +} - it++; +static void runTests(std::map<const char*, CTestCase&>& testCases) { + for (auto& [name, tc] : testCases) { + // Clean up before every test + NLog::log("{}Cleaning up", Colors::YELLOW); + (void)preTestCleanup(); + + NLog::log("{}Running test {}", Colors::BLUE, name); + tc.test(); + if (tc.failed) + NLog::log("{}Test failed: {}", Colors::RED, name); + else + NLog::log("{}Test passed: {}", Colors::GREEN, name); + } +} - continue; - } else if (value == "--help" || value == "-h") { - help(); +static long long countFailed(const std::map<const char*, CTestCase&>& testCases) { + long long ans = 0; + for (const auto& [_, tc] : testCases) { + if (tc.failed) + ans++; + } + return ans; +} - return 0; - } else { - std::println(stderr, "[ ERROR ] Unknown option '{}' !", *it); - help(); +int main(int argc, char** argv, char** envp) { - return 1; - } - } - } + std::span<const char*> args{const_cast<const char**>(argv + 1), sc<std::size_t>(argc - 1)}; + const SSettings settings = parseSettings(args); NLog::log("{}launching hl", Colors::YELLOW); - if (!launchHyprland(configPath, binaryPath)) { + if (!launchHyprland(settings.configPath, settings.binaryPath)) { NLog::log("{}well it failed", Colors::RED); return 1; } @@ -220,7 +227,7 @@ int main(int argc, char** argv, char** envp) { getFromSocket("/output create headless"); NLog::log("{}trying to load plugin", Colors::YELLOW); - if (const auto R = getFromSocket(std::format("/plugin load {}", pluginPath)); R != "ok") { + if (const auto R = getFromSocket(std::format("/plugin load {}", settings.pluginPath.string())); R != "ok") { NLog::log("{}Failed to load the test plugin: {}", Colors::RED, R); getFromSocket("/dispatch hl.dsp.exit()"); return 1; @@ -228,34 +235,50 @@ int main(int argc, char** argv, char** envp) { NLog::log("{}Loaded plugin", Colors::YELLOW); - NLog::log("{}Running main tests", Colors::YELLOW); + long long failedTests = 0, totalTests = 0; - for (const auto& fn : testFns) { - EXPECT(fn(), true); - } + NLog::log("{}Running main tests", Colors::YELLOW); + runTests(mainTestCases); + failedTests += countFailed(mainTestCases); + totalTests += mainTestCases.size(); NLog::log("{}Running protocol client tests", Colors::YELLOW); + runTests(clientTestCases); + failedTests += countFailed(clientTestCases); + totalTests += clientTestCases.size(); - for (const auto& fn : clientTestFns) { - EXPECT(fn(), true); - } + // TODO: the two tests below should not be hardcoded, include them somewhere NLog::log("{}running plugin test", Colors::YELLOW); - EXPECT(testPlugin(), true); + if (!testPlugin()) { + NLog::log("{}Test failed: plugin test", Colors::RED); + failedTests++; + } else { + NLog::log("{}Test passed: plugin test", Colors::GREEN); + } + totalTests++; NLog::log("{}running vkb test from plugin", Colors::YELLOW); - EXPECT(testVkb(), true); + if (!testVkb()) { + NLog::log("{}Test failed: vkb test from plugin", Colors::RED); + failedTests++; + } else { + NLog::log("{}Test passed: vkb test from plugin", Colors::GREEN); + } + totalTests++; // kill hyprland NLog::log("{}dispatching exit", Colors::YELLOW); getFromSocket("/dispatch hl.dsp.exit()"); - NLog::log("\n{}Summary:\n\tPASSED: {}{}{}/{}\n\tFAILED: {}{}{}/{}\n{}", Colors::RESET, Colors::GREEN, TESTS_PASSED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, Colors::RED, - TESTS_FAILED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, (TESTS_FAILED > 0 ? std::string{Colors::RED} + "\nSome tests failed.\n" : "")); + NLog::log("\nSummary:\n\tPASSED: {}{}{}/{}", Colors::GREEN, totalTests - failedTests, Colors::RESET, totalTests); + NLog::log("\tFAILED: {}{}{}/{}", Colors::RED, failedTests, Colors::RESET, totalTests); + if (failedTests > 0) + NLog::log("{}Some tests failed.", Colors::RED); kill(hyprlandProc->pid(), SIGKILL); hyprlandProc.reset(); - return ret || TESTS_FAILED; + return failedTests > 0; } diff --git a/hyprtester/src/shared.hpp b/hyprtester/src/shared.hpp index 941788fd..d40b2f1f 100644 --- a/hyprtester/src/shared.hpp +++ b/hyprtester/src/shared.hpp @@ -1,12 +1,12 @@ // Stolen from hyprutils #pragma once -#include <iostream> +#include <cmath> +#include <string> -inline std::string HIS = ""; -inline std::string WLDISPLAY = ""; -inline int TESTS_PASSED = 0; -inline int TESTS_FAILED = 0; +// TODO: localize these global variables +inline std::string HIS = ""; +inline std::string WLDISPLAY = ""; namespace Colors { constexpr const char* RED = "\x1b[31m"; @@ -18,94 +18,229 @@ namespace Colors { constexpr const char* RESET = "\x1b[0m"; }; +// ================================= +// TEST CASES DEFINITION +// ================================= + +class CTestCase { + public: + CTestCase() = default; + CTestCase(const CTestCase&) = delete; // Test cases probably should not be copied + bool failed = false; + virtual ~CTestCase() = default; + + // TODO: `test` will be protected + virtual void test() = 0; +}; + +#define TEST_CASE(name) \ + namespace { \ + class TestCase_##name : public CTestCase { \ + public: \ + void test() override; \ + }; \ + } \ + \ + static TestCase_##name test_case_##name{}; \ + static auto register_test_case_##name = [] { \ + /* `TEST_CASES_STORAGE` must be defined by the caller */ \ + TEST_CASES_STORAGE.emplace(#name, test_case_##name); \ + return 1; \ + }(); \ + \ + void TestCase_##name::test() + +#define SUBTEST(name, ...) \ + namespace { \ + class Subtest_##name { \ + public: \ + bool failed = false; \ + \ + void main(__VA_ARGS__); \ + }; \ + } \ + \ + void Subtest_##name::main(__VA_ARGS__) + +#define CALL_SUBTEST(name, ...) \ + do { \ + auto subtest_##name = Subtest_##name{}; \ + subtest_##name.main(__VA_ARGS__); \ + if (subtest_##name.failed) { \ + NLog::log("{}Subtest {}({}) failed", Colors::RED, #name, #__VA_ARGS__); \ + this->failed = true; \ + return; \ + } \ + } while (0) + +// ================================= +// IN-TEST MACROS +// ================================= + +/// Marks the test as failed without terminating it +#define MARK_TEST_FAILED_SILENT() this->failed = true + +/// Prints a failure message and makrs the test as failed without terminating it +#define MARK_TEST_FAILED(fmt, ...) \ + do { \ + NLog::log("{}Failed:{} " fmt ". Source: {}@{}.", Colors::RED, Colors::RESET __VA_OPT__(, ) __VA_ARGS__, __FILE__, __LINE__); \ + MARK_TEST_FAILED_SILENT(); \ + } while (0) + +/// Terminates the test execution and marks it as failed +#define FAIL_TEST_SILENT() \ + do { \ + MARK_TEST_FAILED_SILENT(); \ + return; \ + } while (0) + +/// Prints a failure message, terminates the test execution, and marks it as failed +#define FAIL_TEST(fmt, ...) \ + do { \ + MARK_TEST_FAILED(fmt, __VA_ARGS__); \ + return; \ + } while (0) + +#define LOG_OK(fmt, ...) \ + do { \ + NLog::log("{}OK:{} " fmt ". Source: {}@{}", Colors::GREEN, Colors::RESET, __VA_ARGS__, __FILE__, __LINE__); \ + } while (0) + +// In case of failure: +// - All the `EXPECT*` macros will print a message and mark the test as failed without terminating its execution; +// - All the `ASSERT*` macros will print a message, mark the test as failed, and terminate it. +// +// `OK` acts like `ASSERT_OK`. + #define EXPECT_MAX_DELTA(expr, desired, delta) \ if (const auto RESULT = expr; std::abs(RESULT - (desired)) > delta) { \ - NLog::log("{}Failed: {}{}, expected max delta of {}, got delta {} ({} - {}). Source: {}@{}.", Colors::RED, Colors::RESET, #expr, delta, (RESULT - (desired)), RESULT, \ - desired, __FILE__, __LINE__); \ - ret = 1; \ - TESTS_FAILED++; \ + MARK_TEST_FAILED("{}, expected max delta of {}, got delta {} ({} - {})", #expr, delta, (RESULT - (desired)), RESULT, desired); \ } else { \ - NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, (RESULT - (desired))); \ - TESTS_PASSED++; \ + LOG_OK("{}. Got {}", #expr, (RESULT - (desired))); \ } #define EXPECT(expr, val) \ - if (const auto RESULT = expr; RESULT != (val)) { \ - NLog::log("{}Failed: {}{}, expected {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \ - ret = 1; \ - TESTS_FAILED++; \ - } else { \ - NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \ - TESTS_PASSED++; \ - } + do { \ + if (const auto RESULT = expr; RESULT != (val)) { \ + MARK_TEST_FAILED("{}, expected {}, got {}", #expr, val, RESULT); \ + } else { \ + LOG_OK("{}. Got {}", #expr, val); \ + } \ + } while (0) #define EXPECT_NOT(expr, val) \ if (const auto RESULT = expr; RESULT == (val)) { \ - NLog::log("{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \ - ret = 1; \ - TESTS_FAILED++; \ + MARK_TEST_FAILED("{}, expected not {}, got {}", #expr, val, RESULT); \ } else { \ - NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \ - TESTS_PASSED++; \ + LOG_OK("{}. Got {}", #expr, val); \ } #define EXPECT_VECTOR2D(expr, val) \ do { \ const auto& RESULT = expr; \ - const auto& EXPECTED = val; \ - if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \ - NLog::log("{}Failed: {}{}, expected [{}, {}], got [{}, {}]. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, EXPECTED.x, EXPECTED.y, RESULT.x, RESULT.y, __FILE__, \ - __LINE__); \ - ret = 1; \ - TESTS_FAILED++; \ + const auto& ASSERTED = val; \ + if (!(std::abs(RESULT.x - ASSERTED.x) < 1e-6 && std::abs(RESULT.y - ASSERTED.y) < 1e-6)) { \ + MARK_TEST_FAILED("{}, expected [{}, {}], got [{}, {}]", #expr, ASSERTED.x, ASSERTED.y, RESULT.x, RESULT.y); \ } else { \ - NLog::log("{}Passed: {}{}. Got [{}, {}].", Colors::GREEN, Colors::RESET, #expr, RESULT.x, RESULT.y); \ - TESTS_PASSED++; \ + LOG_OK("{}. Got [{}, {}].", #expr, RESULT.x, RESULT.y); \ } \ } while (0) +// String check macros below use a special error message layout, putting the Source reference before `haystack`, +// since `haystack` may be a large multi-line string. Thus, they use bare `NLog::log` + `MARK_TEST_FAILED_SILENT`. + #define EXPECT_CONTAINS(haystack, needle) \ - if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \ + if (const auto ASSERTED = needle; !std::string{haystack}.contains(ASSERTED)) { \ NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \ std::string{haystack}); \ - ret = 1; \ - TESTS_FAILED++; \ + MARK_TEST_FAILED_SILENT(); \ } else { \ - NLog::log("{}Passed: {}{} contains {}.", Colors::GREEN, Colors::RESET, #haystack, EXPECTED); \ - TESTS_PASSED++; \ + LOG_OK("{} contains {}.", #haystack, ASSERTED); \ } #define EXPECT_NOT_CONTAINS(haystack, needle) \ if (std::string{haystack}.contains(needle)) { \ NLog::log("{}Failed: {}{} shouldn't contain {} but does. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \ std::string{haystack}); \ - ret = 1; \ - TESTS_FAILED++; \ + MARK_TEST_FAILED_SILENT(); \ } else { \ - NLog::log("{}Passed: {}{} doesn't contain {}.", Colors::GREEN, Colors::RESET, #haystack, #needle); \ - TESTS_PASSED++; \ + LOG_OK("{} doesn't contain {}.", #haystack, #needle); \ } #define EXPECT_STARTS_WITH(str, what) \ if (!std::string{str}.starts_with(what)) { \ NLog::log("{}Failed: {}{} should start with {} but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, __FILE__, __LINE__, \ std::string{str}); \ - ret = 1; \ - TESTS_FAILED++; \ + MARK_TEST_FAILED_SILENT(); \ } else { \ - NLog::log("{}Passed: {}{} starts with {}.", Colors::GREEN, Colors::RESET, #str, #what); \ - TESTS_PASSED++; \ + LOG_OK("{} starts with {}.", #str, #what); \ } #define EXPECT_COUNT_STRING(str, what, no) \ if (Tests::countOccurrences(str, what) != no) { \ NLog::log("{}Failed: {}{} should contain {} {} times, but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, no, __FILE__, __LINE__, \ std::string{str}); \ - ret = 1; \ - TESTS_FAILED++; \ + MARK_TEST_FAILED_SILENT(); \ } else { \ - NLog::log("{}Passed: {}{} contains {} {} times.", Colors::GREEN, Colors::RESET, #str, #what, no); \ - TESTS_PASSED++; \ + LOG_OK("{} contains {} {} times.", #str, #what, no); \ } -#define OK(x) EXPECT(x, "ok") +#define EXPECT_OK(x) EXPECT(x, "ok") + +#define ASSERT_MAX_DELTA(expr, desired, delta) \ + do { \ + EXPECT_MAX_DELTA(expr, desired, delta); \ + if (this->failed) \ + return; \ + } while (0) + +#define ASSERT(expr, val) \ + do { \ + EXPECT(expr, val); \ + if (this->failed) \ + return; \ + } while (0) + +#define ASSERT_NOT(expr, val) \ + do { \ + EXPECT_NOT(expr, val); \ + if (this->failed) \ + return; \ + } while (0) + +#define ASSERT_VECTOR2D(expr, val) \ + do { \ + EXPECT_VECTOR2D(expr, val); \ + if (this->failed) \ + return; \ + } while (0) + +#define ASSERT_CONTAINS(haystack, needle) \ + do { \ + EXPECT_CONTAINS(haystack, needle); \ + if (this->failed) \ + return; \ + } while (0) + +#define ASSERT_NOT_CONTAINS(haystack, needle) \ + do { \ + EXPECT_NOT_CONTAINS(haystack, needle); \ + if (this->failed) \ + return; \ + } while (0) + +#define ASSERT_STARTS_WITH(str, what) \ + do { \ + EXPECT_STARTS_WITH(str, what); \ + if (this->failed) \ + return; \ + } while (0) + +#define ASSERT_COUNT_STRING(str, what, no) \ + do { \ + EXPECT_COUNT_STRING(str, what, no); \ + if (this->failed) \ + return; \ + } while (0) + +#define OK(x) ASSERT(x, "ok") diff --git a/hyprtester/src/tests/clients/child-window.cpp b/hyprtester/src/tests/clients/child-window.cpp index 40118c7c..f97ffde1 100644 --- a/hyprtester/src/tests/clients/child-window.cpp +++ b/hyprtester/src/tests/clients/child-window.cpp @@ -1,4 +1,3 @@ -#include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include "../shared.hpp" #include "tests.hpp" @@ -7,6 +6,7 @@ #include <hyprutils/os/FileDescriptor.hpp> #include <hyprutils/os/Process.hpp> +#include <optional> #include <sys/poll.h> #include <unistd.h> #include <csignal> @@ -17,15 +17,6 @@ using namespace Hyprutils::Memory; #define SP CSharedPointer -struct SClient { - SP<CProcess> proc; - std::array<char, 1024> readBuf; - CFileDescriptor readFd, writeFd; - struct pollfd fds; -}; - -static int ret = 0; - static bool waitForWindow(SP<CProcess> proc, int windowsBefore) { int counter = 0; while (Tests::processAlive(proc->pid()) && Tests::windowCount() == windowsBefore) { @@ -40,56 +31,69 @@ static bool waitForWindow(SP<CProcess> proc, int windowsBefore) { return Tests::processAlive(proc->pid()); } -static bool startClient(SClient& client) { +namespace { + class CClient { + SP<CProcess> proc; + std::array<char, 1024> readBuf; + CFileDescriptor readFd, writeFd; + struct pollfd fds; + + public: + CClient(); + ~CClient(); + bool createChild(); + }; +} + +CClient::CClient() { NLog::log("{}Attempting to start child-window client", Colors::YELLOW); - client.proc = makeShared<CProcess>(binaryDir + "/child-window", std::vector<std::string>{}); + this->proc = makeShared<CProcess>(binaryDir + "/child-window", std::vector<std::string>{}); - client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + this->proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); int procInPipeFd[2], procOutPipeFd[2]; if (pipe(procInPipeFd) != 0 || pipe(procOutPipeFd) != 0) { NLog::log("{}Unable to open pipe to client", Colors::RED); - return false; + throw std::exception(); } - client.writeFd = CFileDescriptor(procInPipeFd[1]); - client.proc->setStdinFD(procInPipeFd[0]); + this->writeFd = CFileDescriptor(procInPipeFd[1]); + this->proc->setStdinFD(procInPipeFd[0]); - client.readFd = CFileDescriptor(procOutPipeFd[0]); - client.proc->setStdoutFD(procOutPipeFd[1]); + this->readFd = CFileDescriptor(procOutPipeFd[0]); + this->proc->setStdoutFD(procOutPipeFd[1]); - if (!client.proc->runAsync()) { + if (!this->proc->runAsync()) { NLog::log("{}Failed to run client", Colors::RED); - return false; + throw std::exception(); } close(procInPipeFd[0]); close(procOutPipeFd[1]); - if (!waitForWindow(client.proc, Tests::windowCount())) { + if (!waitForWindow(this->proc, Tests::windowCount())) { NLog::log("{}Window took too long to open", Colors::RED); - return false; + throw std::exception(); } NLog::log("{}Started child-window client", Colors::YELLOW); - return true; } -static void stopClient(SClient& client) { +CClient::~CClient() { std::string cmd = "exit\n"; - write(client.writeFd.get(), cmd.c_str(), cmd.length()); + write(this->writeFd.get(), cmd.c_str(), cmd.length()); - kill(client.proc->pid(), SIGKILL); - client.proc.reset(); + kill(this->proc->pid(), SIGKILL); + this->proc.reset(); } -static bool createChild(SClient& client) { +bool CClient::createChild() { std::string cmd = "toplevel\n"; - if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length()) + if ((size_t)write(this->writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length()) return false; - if (!waitForWindow(client.proc, Tests::windowCount())) + if (!waitForWindow(this->proc, Tests::windowCount())) NLog::log("{}Child window took too long to open", Colors::RED); if (getFromSocket("/dispatch hl.dsp.focus({ window = 'class:child-test-child' })") != "ok") { @@ -100,19 +104,21 @@ static bool createChild(SClient& client) { return true; } -static bool test() { - SClient client; +TEST_CASE(childWindow) { + { + std::optional<CClient> client; + try { + client.emplace(); + } catch (...) { FAIL_TEST("Couldn't start the client"); } - if (!startClient(client)) - return false; - OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:child-test-parent' })")); - OK(getFromSocket("/dispatch hl.dsp.window.pin({ action = 'set', window = 'class:child-test-parent' })")); + OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:child-test-parent' })")); + OK(getFromSocket("/dispatch hl.dsp.window.pin({ action = 'set', window = 'class:child-test-parent' })")); - createChild(client); - EXPECT(Tests::windowCount(), 2) - EXPECT_COUNT_STRING(getFromSocket("/clients"), "pinned: 1", 2); + client->createChild(); + EXPECT(Tests::windowCount(), 2); + EXPECT_COUNT_STRING(getFromSocket("/clients"), "pinned: 1", 2); + } - stopClient(client); NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); Tests::killAllWindows(); @@ -122,30 +128,27 @@ static bool test() { NLog::log("{}Test child windows are not auto-grouped", Colors::GREEN); auto kitty = Tests::spawnKitty(); if (!kitty) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Couldn't spawn kitty"); } // create group and enable auto-grouping OK(getFromSocket("/dispatch hl.dsp.group.toggle()")); OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })")); - SClient client2; - if (!startClient(client2)) - return false; + { + std::optional<CClient> client2; + try { + client2.emplace(); + } catch (...) { FAIL_TEST("Couldn't start the client"); } - EXPECT(Tests::windowCount(), 2); - createChild(client2); - EXPECT(Tests::windowCount(), 3); + EXPECT(Tests::windowCount(), 2); + client2->createChild(); + EXPECT(Tests::windowCount(), 3); - // child has set_parent so shouldBeFloated returns true, it should not be auto-grouped - EXPECT_COUNT_STRING(getFromSocket("/clients"), "grouped: 0", 1); + // child has set_parent so shouldBeFloated returns true, it should not be auto-grouped + EXPECT_COUNT_STRING(getFromSocket("/clients"), "grouped: 0", 1); + } - stopClient(client2); Tests::killAllWindows(); EXPECT(Tests::windowCount(), 0); - - return !ret; } - -REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/clients/pointer-scroll.cpp b/hyprtester/src/tests/clients/pointer-scroll.cpp index 86ce55f0..c468deb3 100644 --- a/hyprtester/src/tests/clients/pointer-scroll.cpp +++ b/hyprtester/src/tests/clients/pointer-scroll.cpp @@ -1,4 +1,3 @@ -#include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include "../shared.hpp" #include "tests.hpp" @@ -7,6 +6,7 @@ #include <hyprutils/os/FileDescriptor.hpp> #include <hyprutils/os/Process.hpp> +#include <optional> #include <sys/poll.h> #include <unistd.h> #include <csignal> @@ -17,100 +17,103 @@ using namespace Hyprutils::Memory; #define SP CSharedPointer -struct SClient { - SP<CProcess> proc; - std::array<char, 1024> readBuf; - CFileDescriptor readFd, writeFd; - struct pollfd fds; -}; - -static int ret = 0; +namespace { + class CClient { + SP<CProcess> proc; + std::array<char, 1024> readBuf; + CFileDescriptor readFd, writeFd; + struct pollfd fds; + + public: + CClient(); + ~CClient(); + int getLastDelta(); + }; +} -static bool startClient(SClient& client) { - client.proc = makeShared<CProcess>(binaryDir + "/pointer-scroll", std::vector<std::string>{}); +CClient::CClient() { + this->proc = makeShared<CProcess>(binaryDir + "/pointer-scroll", std::vector<std::string>{}); - client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + this->proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); int pipeFds1[2], pipeFds2[2]; if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) { NLog::log("{}Unable to open pipe to client", Colors::RED); - return false; + throw std::exception(); } - client.writeFd = CFileDescriptor(pipeFds1[1]); - client.proc->setStdinFD(pipeFds1[0]); + this->writeFd = CFileDescriptor(pipeFds1[1]); + this->proc->setStdinFD(pipeFds1[0]); - client.readFd = CFileDescriptor(pipeFds2[0]); - client.proc->setStdoutFD(pipeFds2[1]); + this->readFd = CFileDescriptor(pipeFds2[0]); + this->proc->setStdoutFD(pipeFds2[1]); const int COUNT_BEFORE = Tests::windowCount(); - client.proc->runAsync(); + this->proc->runAsync(); close(pipeFds1[0]); close(pipeFds2[1]); - client.fds = {.fd = client.readFd.get(), .events = POLLIN}; - if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) - return false; + this->fds = {.fd = this->readFd.get(), .events = POLLIN}; + if (poll(&this->fds, 1, 1000) != 1 || !(this->fds.revents & POLLIN)) + throw std::exception(); - client.readBuf.fill(0); - if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) - return false; + this->readBuf.fill(0); + if (read(this->readFd.get(), this->readBuf.data(), this->readBuf.size() - 1) == -1) + throw std::exception(); - std::string ret = std::string{client.readBuf.data()}; + std::string ret = std::string{this->readBuf.data()}; if (ret.find("started") == std::string::npos) { NLog::log("{}Failed to start pointer-scroll client, read {}", Colors::RED, ret); - return false; + throw std::exception(); } // wait for window to appear int counter = 0; - while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + while (Tests::processAlive(this->proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { counter++; std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (counter > 50) { NLog::log("{}pointer-scroll client took too long to open", Colors::RED); - return false; + throw std::exception(); } } - if (getFromSocket(std::format("/dispatch hl.dsp.window.set_prop({{ window = 'pid:{}', prop = 'no_anim', value = '1' }})", client.proc->pid())) != "ok") { + if (getFromSocket(std::format("/dispatch hl.dsp.window.set_prop({{ window = 'pid:{}', prop = 'no_anim', value = '1' }})", this->proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); - return false; + throw std::exception(); } - if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", client.proc->pid())) != "ok") { + if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", this->proc->pid())) != "ok") { NLog::log("{}Failed to focus pointer-scroll client", Colors::RED, ret); - return false; + throw std::exception(); } NLog::log("{}Started pointer-scroll client", Colors::YELLOW); - - return true; } -static void stopClient(SClient& client) { +CClient::~CClient() { std::string cmd = "exit\n"; - write(client.writeFd.get(), cmd.c_str(), cmd.length()); + write(this->writeFd.get(), cmd.c_str(), cmd.length()); - kill(client.proc->pid(), SIGKILL); - client.proc.reset(); + kill(this->proc->pid(), SIGKILL); + this->proc.reset(); } -static int getLastDelta(SClient& client) { +int CClient::getLastDelta() { std::string cmd = "hypr"; - if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length()) + if ((size_t)write(this->writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length()) return false; - if (poll(&client.fds, 1, 1500) != 1 || !(client.fds.revents & POLLIN)) + if (poll(&this->fds, 1, 1500) != 1 || !(this->fds.revents & POLLIN)) return false; - ssize_t bytesRead = read(client.fds.fd, client.readBuf.data(), 1023); + ssize_t bytesRead = read(this->fds.fd, this->readBuf.data(), 1023); if (bytesRead == -1) return false; - client.readBuf[bytesRead] = 0; - std::string received = std::string{client.readBuf.data()}; + this->readBuf[bytesRead] = 0; + std::string received = std::string{this->readBuf.data()}; received.pop_back(); try { @@ -122,35 +125,29 @@ static bool sendScroll(int delta) { return getFromSocket(std::format("/eval hl.plugin.test.scroll({})", delta)) == "ok"; } -static bool test() { - SClient client; - - if (!startClient(client)) - return false; +TEST_CASE(pointerScroll) { + std::optional<CClient> client; + try { + client.emplace(); + } catch (...) { FAIL_TEST("Couldn't start the client"); } EXPECT(getFromSocket("r/eval hl.config({ input = { emulate_discrete_scroll = 0 } })"), "ok"); EXPECT(sendScroll(10), true); - EXPECT(getLastDelta(client), 10); + EXPECT(client->getLastDelta(), 10); EXPECT(getFromSocket("r/eval hl.config({ input = { scroll_factor = 2 } })"), "ok"); EXPECT(sendScroll(10), true); - EXPECT(getLastDelta(client), 20); + EXPECT(client->getLastDelta(), 20); EXPECT(getFromSocket("r/eval hl.device({ name = 'test-mouse-1', scroll_factor = 3 })"), "ok"); EXPECT(sendScroll(10), true); - EXPECT(getLastDelta(client), 30); + EXPECT(client->getLastDelta(), 30); EXPECT(getFromSocket("r/dispatch hl.dsp.window.set_prop({ window = 'active', prop = 'scroll_mouse', value = '4' })"), "ok"); EXPECT(sendScroll(10), true); - EXPECT(getLastDelta(client), 40); - - stopClient(client); + EXPECT(client->getLastDelta(), 40); NLog::log("{}Reloading the config", Colors::YELLOW); OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/clients/pointer-warp.cpp b/hyprtester/src/tests/clients/pointer-warp.cpp index fa6334d3..e13e03f6 100644 --- a/hyprtester/src/tests/clients/pointer-warp.cpp +++ b/hyprtester/src/tests/clients/pointer-warp.cpp @@ -1,4 +1,3 @@ -#include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include "../shared.hpp" #include "tests.hpp" @@ -7,6 +6,7 @@ #include <hyprutils/os/FileDescriptor.hpp> #include <hyprutils/os/Process.hpp> +#include <optional> #include <sys/poll.h> #include <unistd.h> #include <csignal> @@ -17,102 +17,105 @@ using namespace Hyprutils::Memory; #define SP CSharedPointer -struct SClient { - SP<CProcess> proc; - std::array<char, 1024> readBuf; - CFileDescriptor readFd, writeFd; - struct pollfd fds; -}; - -static int ret = 0; +namespace { + class CClient { + SP<CProcess> proc; + std::array<char, 1024> readBuf; + CFileDescriptor readFd, writeFd; + struct pollfd fds; + + public: + CClient(); + ~CClient(); + bool sendWarp(int x, int y); + }; +} -static bool startClient(SClient& client) { - client.proc = makeShared<CProcess>(binaryDir + "/pointer-warp", std::vector<std::string>{}); +CClient::CClient() { + this->proc = makeShared<CProcess>(binaryDir + "/pointer-warp", std::vector<std::string>{}); - client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + this->proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); int pipeFds1[2], pipeFds2[2]; if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) { NLog::log("{}Unable to open pipe to client", Colors::RED); - return false; + throw std::exception(); } - client.writeFd = CFileDescriptor(pipeFds1[1]); - client.proc->setStdinFD(pipeFds1[0]); + this->writeFd = CFileDescriptor(pipeFds1[1]); + this->proc->setStdinFD(pipeFds1[0]); - client.readFd = CFileDescriptor(pipeFds2[0]); - client.proc->setStdoutFD(pipeFds2[1]); + this->readFd = CFileDescriptor(pipeFds2[0]); + this->proc->setStdoutFD(pipeFds2[1]); const int COUNT_BEFORE = Tests::windowCount(); - client.proc->runAsync(); + this->proc->runAsync(); close(pipeFds1[0]); close(pipeFds2[1]); - client.fds = {.fd = client.readFd.get(), .events = POLLIN}; - if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) - return false; + this->fds = {.fd = this->readFd.get(), .events = POLLIN}; + if (poll(&this->fds, 1, 1000) != 1 || !(this->fds.revents & POLLIN)) + throw std::exception(); - client.readBuf.fill(0); - if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) - return false; + this->readBuf.fill(0); + if (read(this->readFd.get(), this->readBuf.data(), this->readBuf.size() - 1) == -1) + throw std::exception(); - std::string ret = std::string{client.readBuf.data()}; + std::string ret = std::string{this->readBuf.data()}; if (ret.find("started") == std::string::npos) { NLog::log("{}Failed to start pointer-warp client, read {}", Colors::RED, ret); - return false; + throw std::exception(); } // wait for window to appear int counter = 0; - while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + while (Tests::processAlive(this->proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { counter++; std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (counter > 100) { NLog::log("{}pointer-warp client took too long to open, continuing", Colors::YELLOW); - break; + throw std::exception(); } } - if (getFromSocket(std::format("/dispatch hl.dsp.window.set_prop({{ window = 'pid:{}', prop = 'no_anim', value = '1' }})", client.proc->pid())) != "ok") { + if (getFromSocket(std::format("/dispatch hl.dsp.window.set_prop({{ window = 'pid:{}', prop = 'no_anim', value = '1' }})", this->proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); - return false; + throw std::exception(); } - if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", client.proc->pid())) != "ok") { + if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", this->proc->pid())) != "ok") { NLog::log("{}Failed to focus pointer-warp client", Colors::RED, ret); - return false; + throw std::exception(); } NLog::log("{}Started pointer-warp client", Colors::YELLOW); - - return true; } -static void stopClient(SClient& client) { +CClient::~CClient() { std::string cmd = "exit\n"; - write(client.writeFd.get(), cmd.c_str(), cmd.length()); + write(this->writeFd.get(), cmd.c_str(), cmd.length()); - kill(client.proc->pid(), SIGKILL); - client.proc.reset(); + kill(this->proc->pid(), SIGKILL); + this->proc.reset(); } // format is like below // "warp 20 20\n" would ask to warp cursor to x=20,y=20 in surface local coords -static bool sendWarp(SClient& client, int x, int y) { +bool CClient::sendWarp(int x, int y) { std::string cmd = std::format("warp {} {}\n", x, y); - if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length()) + if ((size_t)write(this->writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length()) return false; - if (poll(&client.fds, 1, 1500) != 1 || !(client.fds.revents & POLLIN)) + if (poll(&this->fds, 1, 1500) != 1 || !(this->fds.revents & POLLIN)) return false; - ssize_t bytesRead = read(client.fds.fd, client.readBuf.data(), 1023); + ssize_t bytesRead = read(this->fds.fd, this->readBuf.data(), 1023); if (bytesRead == -1) return false; - client.readBuf[bytesRead] = 0; - std::string recieved = std::string{client.readBuf.data()}; + this->readBuf[bytesRead] = 0; + std::string recieved = std::string{this->readBuf.data()}; recieved.pop_back(); return true; @@ -147,48 +150,40 @@ static bool isCursorPos(int x, int y) { return clientX == x && clientY == y; } -static bool test() { - SClient client; +TEST_CASE(pointerWarp) { + std::optional<CClient> client; - if (!startClient(client)) - return false; + try { + client.emplace(); + } catch (...) { FAIL_TEST("Couldn't start the client"); } - EXPECT(sendWarp(client, 100, 100), true); + EXPECT(client->sendWarp(100, 100), true); EXPECT(isCursorPos(100, 100), true); - EXPECT(sendWarp(client, 0, 0), true); + EXPECT(client->sendWarp(0, 0), true); EXPECT(isCursorPos(0, 0), true); - EXPECT(sendWarp(client, 200, 200), true); + EXPECT(client->sendWarp(200, 200), true); EXPECT(isCursorPos(200, 200), true); - EXPECT(sendWarp(client, 100, -100), true); + EXPECT(client->sendWarp(100, -100), true); EXPECT(isCursorPos(200, 200), true); - EXPECT(sendWarp(client, 234, 345), true); + EXPECT(client->sendWarp(234, 345), true); EXPECT(isCursorPos(234, 345), true); - EXPECT(sendWarp(client, -1, -1), true); + EXPECT(client->sendWarp(-1, -1), true); EXPECT(isCursorPos(234, 345), true); - EXPECT(sendWarp(client, 1, -1), true); + EXPECT(client->sendWarp(1, -1), true); EXPECT(isCursorPos(234, 345), true); - EXPECT(sendWarp(client, 13, 37), true); + EXPECT(client->sendWarp(13, 37), true); EXPECT(isCursorPos(13, 37), true); - EXPECT(sendWarp(client, -100, 100), true); + EXPECT(client->sendWarp(-100, 100), true); EXPECT(isCursorPos(13, 37), true); - EXPECT(sendWarp(client, -1, 1), true); + EXPECT(client->sendWarp(-1, 1), true); EXPECT(isCursorPos(13, 37), true); - - stopClient(client); - - NLog::log("{}Reloading the config", Colors::YELLOW); - OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/clients/shortcut-inhibitor.cpp b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp index 1039916a..491b6f47 100644 --- a/hyprtester/src/tests/clients/shortcut-inhibitor.cpp +++ b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp @@ -1,4 +1,3 @@ -#include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include "../shared.hpp" #include "tests.hpp" @@ -7,6 +6,7 @@ #include <hyprutils/os/FileDescriptor.hpp> #include <hyprutils/os/Process.hpp> +#include <optional> #include <sys/poll.h> #include <csignal> #include <thread> @@ -17,106 +17,109 @@ using namespace Hyprutils::Memory; #define SP CSharedPointer -struct SClient { - SP<CProcess> proc; - std::array<char, 1024> readBuf; - CFileDescriptor readFd, writeFd; - struct pollfd fds; -}; +namespace { -static int ret = 0; + class CClient { + SP<CProcess> proc; + std::array<char, 1024> readBuf; + CFileDescriptor readFd, writeFd; + struct pollfd fds; -static bool startClient(SClient& client) { + public: + CClient(); + ~CClient(); + }; +} + +CClient::CClient() { Tests::killAllWindows(); - client.proc = makeShared<CProcess>(binaryDir + "/shortcut-inhibitor", std::vector<std::string>{}); + this->proc = makeShared<CProcess>(binaryDir + "/shortcut-inhibitor", std::vector<std::string>{}); - client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + this->proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); int pipeFds1[2], pipeFds2[2]; if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) { NLog::log("{}Unable to open pipe to client", Colors::RED); - return false; + throw std::exception(); } - client.writeFd = CFileDescriptor(pipeFds1[1]); - client.proc->setStdinFD(pipeFds1[0]); + this->writeFd = CFileDescriptor(pipeFds1[1]); + this->proc->setStdinFD(pipeFds1[0]); - client.readFd = CFileDescriptor(pipeFds2[0]); - client.proc->setStdoutFD(pipeFds2[1]); + this->readFd = CFileDescriptor(pipeFds2[0]); + this->proc->setStdoutFD(pipeFds2[1]); const int COUNT_BEFORE = Tests::windowCount(); - client.proc->runAsync(); + this->proc->runAsync(); close(pipeFds1[0]); close(pipeFds2[1]); - client.fds = {.fd = client.readFd.get(), .events = POLLIN}; - if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) { + this->fds = {.fd = this->readFd.get(), .events = POLLIN}; + if (poll(&this->fds, 1, 1000) != 1 || !(this->fds.revents & POLLIN)) { NLog::log("{}shortcut-inhibitor client failed poll", Colors::RED); - return false; + throw std::exception(); } - client.readBuf.fill(0); - if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) { + this->readBuf.fill(0); + if (read(this->readFd.get(), this->readBuf.data(), this->readBuf.size() - 1) == -1) { NLog::log("{}shortcut-inhibitor client read failed", Colors::RED); - return false; + throw std::exception(); } - std::string ret = std::string{client.readBuf.data()}; + std::string ret = std::string{this->readBuf.data()}; if (ret.find("started") == std::string::npos) { NLog::log("{}Failed to start shortcut-inhibitor client, read {}", Colors::RED, ret); - return false; + throw std::exception(); } // wait for window to appear int counter = 0; - while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + while (Tests::processAlive(this->proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { counter++; std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (counter > 50) { NLog::log("{}shortcut-inhibitor client took too long to open", Colors::RED); - return false; + throw std::exception(); } } - if (!Tests::processAlive(client.proc->pid())) { + if (!Tests::processAlive(this->proc->pid())) { NLog::log("{}shortcut-inhibitor client not alive", Colors::RED); - return false; + throw std::exception(); } - if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", client.proc->pid())) != "ok") { + if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", this->proc->pid())) != "ok") { NLog::log("{}Failed to focus shortcut-inhibitor client", Colors::RED, ret); - return false; + throw std::exception(); } std::string command = "on\n"; - if (write(client.writeFd.get(), command.c_str(), command.length()) == -1) { + if (write(this->writeFd.get(), command.c_str(), command.length()) == -1) { NLog::log("{}shortcut-inhibitor client write failed", Colors::RED); - return false; + throw std::exception(); } - client.readBuf.fill(0); - if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) - return false; + this->readBuf.fill(0); + if (read(this->readFd.get(), this->readBuf.data(), this->readBuf.size() - 1) == -1) + throw std::exception(); - ret = std::string{client.readBuf.data()}; + ret = std::string{this->readBuf.data()}; if (ret.find("inhibiting") == std::string::npos) { NLog::log("{}shortcut-inhibitor client didn't return inhibiting", Colors::RED); - return false; + throw std::exception(); } NLog::log("{}Started shortcut-inhibitor client", Colors::YELLOW); - - return true; } -static void stopClient(SClient& client) { +CClient::~CClient() { std::string cmd = "off\n"; - write(client.writeFd.get(), cmd.c_str(), cmd.length()); + write(this->writeFd.get(), cmd.c_str(), cmd.length()); - kill(client.proc->pid(), SIGKILL); - client.proc.reset(); + kill(this->proc->pid(), SIGKILL); + this->proc.reset(); } static std::string flagFile = "/tmp/hyprtester-keybinds.txt"; @@ -138,10 +141,12 @@ static bool attemptCheckFlag(int attempts, int intervalMs) { return false; } -static bool test() { - SClient client; - if (!startClient(client)) - return false; +TEST_CASE(shortcutInhibitor) { + std::optional<CClient> client; + + try { + client.emplace(); + } catch (...) { FAIL_TEST("Couldn't start the client"); } NLog::log("{}Testing keybinds", Colors::GREEN); //basic keybind test @@ -168,13 +173,4 @@ static bool test() { //gesture bypass flag test OK(getFromSocket("/eval hl.plugin.test.gesture('right', 2)")); EXPECT_CONTAINS(getFromSocket("/activewindow"), "floating: 1"); - - stopClient(client); - - NLog::log("{}Reloading the config", Colors::YELLOW); - OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/clients/tests.hpp b/hyprtester/src/tests/clients/tests.hpp index 31746fe5..7f10fba7 100644 --- a/hyprtester/src/tests/clients/tests.hpp +++ b/hyprtester/src/tests/clients/tests.hpp @@ -1,12 +1,9 @@ #pragma once +#include <map> -#include <vector> -#include <functional> +#include "../../shared.hpp" -inline std::vector<std::function<bool()>> clientTestFns; +inline std::map<const char*, CTestCase&> clientTestCases; -#define REGISTER_CLIENT_TEST_FN(fn) \ - static auto _register_fn = [] { \ - clientTestFns.emplace_back(fn); \ - return 1; \ - }(); +// Where `TEST_CASE` macros will store generated test cases: +#define TEST_CASES_STORAGE clientTestCases diff --git a/hyprtester/src/tests/main/animations.cpp b/hyprtester/src/tests/main/animations.cpp index e464dcbd..2f918d2b 100644 --- a/hyprtester/src/tests/main/animations.cpp +++ b/hyprtester/src/tests/main/animations.cpp @@ -5,18 +5,11 @@ #include <hyprutils/os/Process.hpp> #include <hyprutils/memory/WeakPtr.hpp> -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; -static bool test() { - NLog::log("{}Testing animations", Colors::GREEN); - +TEST_CASE(animationsTrivial) { auto str = getFromSocket("/animations"); NLog::log("{}Testing bezier curve output from `hyprctl animations`", Colors::YELLOW); - {EXPECT_CONTAINS(str, std::format("beziers:\n\n\tname: quick\n\t\tX0: 0.15\n\t\tY0: 0.00\n\t\tX1: 0.10\n\t\tY1: 1.00"))}; - return !ret; + ASSERT_CONTAINS(str, std::format("beziers:\n\n\tname: quick\n\t\tX0: 0.15\n\t\tY0: 0.00\n\t\tX1: 0.10\n\t\tY1: 1.00")); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/colors.cpp b/hyprtester/src/tests/main/colors.cpp index ee80f24f..93dcbb65 100644 --- a/hyprtester/src/tests/main/colors.cpp +++ b/hyprtester/src/tests/main/colors.cpp @@ -6,32 +6,24 @@ #include <chrono> #include <thread> -static int ret = 0; - -static bool test() { - NLog::log("{}Testing hyprctl monitors", Colors::GREEN); - +TEST_CASE(monitorsColorManagement) { std::string monitorsSpec = getFromSocket("j/monitors"); - EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset")"); + ASSERT_CONTAINS(monitorsSpec, R"("colorManagementPreset")"); - EXPECT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'wide' })"), "ok") + ASSERT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'wide' })"), "ok"); // monitor settings are applied after a frame is pushed. std::this_thread::sleep_for(std::chrono::milliseconds(500)); monitorsSpec = getFromSocket("j/monitors"); - EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")"); + ASSERT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")"); - EXPECT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'srgb', sdrbrightness = 1.2, sdrsaturation = 0.98 })"), "ok") + ASSERT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'srgb', sdrbrightness = 1.2, sdrsaturation = 0.98 })"), "ok"); monitorsSpec = getFromSocket("j/monitors"); std::this_thread::sleep_for(std::chrono::milliseconds(500)); - EXPECT_CONTAINS(monitorsSpec, "colorManagementPreset"); - EXPECT_CONTAINS(monitorsSpec, "sdrBrightness"); - EXPECT_CONTAINS(monitorsSpec, "sdrSaturation"); - - return !ret; + ASSERT_CONTAINS(monitorsSpec, "colorManagementPreset"); + ASSERT_CONTAINS(monitorsSpec, "sdrBrightness"); + ASSERT_CONTAINS(monitorsSpec, "sdrSaturation"); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/dwindle.cpp b/hyprtester/src/tests/main/dwindle.cpp index b0bbe9a9..5641f8ac 100644 --- a/hyprtester/src/tests/main/dwindle.cpp +++ b/hyprtester/src/tests/main/dwindle.cpp @@ -3,15 +3,10 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -static int ret = 0; - -static void testFloatClamp() { +TEST_CASE(dwindleFloatClamp) { for (auto const& win : {"a", "b", "c"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -30,25 +25,16 @@ static void testFloatClamp() { } OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 0 } })")); - - // clean up - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - OK(getFromSocket("/reload")); } -static void test13349() { +TEST_CASE(dwindleIssue13349) { // Test if dwindle properly uses a focal point to place a new window. // exposed by #13349 as a regression from #12890 for (auto const& win : {"a", "b", "c"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -75,13 +61,9 @@ static void test13349() { EXPECT_CONTAINS(str, "at: 967,547"); EXPECT_CONTAINS(str, "size: 931,511"); } - - // clean up - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } -static void testSplit() { +TEST_CASE(dwindleSplit) { // Test various split methods Tests::spawnKitty("a"); @@ -130,22 +112,16 @@ static void testSplit() { EXPECT_CONTAINS(str, "at: 22,859"); EXPECT_CONTAINS(str, "size: 1876,199"); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } -static void testRotatesplit() { +TEST_CASE(dwindleRotateSplit) { OK(getFromSocket("r/eval hl.config({ general = { gaps_in = 0 } })")); OK(getFromSocket("r/eval hl.config({ general = { gaps_out = 0 } })")); OK(getFromSocket("r/eval hl.config({ general = { border_size = 0 } })")); for (auto const& win : {"a", "b"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -220,19 +196,14 @@ static void testRotatesplit() { EXPECT_CONTAINS(str, "at: 960,0"); EXPECT_CONTAINS(str, "size: 960,1080"); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - OK(getFromSocket("/reload")); } -static void testForceSplitOnMoveToWorkspace() { +TEST_CASE(dwindleForceSplitOnMoveToWorkspace) { OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '2' })")); - EXPECT(!!Tests::spawnKitty("kitty"), true); + ASSERT(!!Tests::spawnKitty("kitty"), true); OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); - EXPECT(!!Tests::spawnKitty("kitty"), true); + ASSERT(!!Tests::spawnKitty("kitty"), true); std::string posBefore = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 2 } })")); @@ -242,38 +213,4 @@ static void testForceSplitOnMoveToWorkspace() { // Should be moved to the right, so the position should change std::string activeWindow = getFromSocket("/activewindow"); EXPECT(activeWindow.contains(posBefore), false); - - // clean up - OK(getFromSocket("/reload")); - Tests::killAllWindows(); - Tests::waitUntilWindowsN(0); -} - -static bool test() { - NLog::log("{}Testing Dwindle layout", Colors::GREEN); - - // test - NLog::log("{}Testing float clamp", Colors::GREEN); - testFloatClamp(); - - NLog::log("{}Testing #13349", Colors::GREEN); - test13349(); - - NLog::log("{}Testing splits", Colors::GREEN); - testSplit(); - - NLog::log("{}Testing rotatesplit", Colors::GREEN); - testRotatesplit(); - - NLog::log("{}Testing force_split on move to workspace", Colors::GREEN); - testForceSplitOnMoveToWorkspace(); - - // clean up - NLog::log("Cleaning up", Colors::YELLOW); - getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); - OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/exec.cpp b/hyprtester/src/tests/main/exec.cpp index 099f2cbc..ff19edfe 100644 --- a/hyprtester/src/tests/main/exec.cpp +++ b/hyprtester/src/tests/main/exec.cpp @@ -8,8 +8,6 @@ #include <hyprutils/memory/WeakPtr.hpp> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; @@ -18,9 +16,7 @@ using namespace Hyprutils::Memory; const static auto SLEEP_DURATIONS = std::array{1, 10}; -static bool test() { - NLog::log("{}Testing process spawning", Colors::GREEN); - +TEST_CASE(processSpawning) { for (const auto duration : SLEEP_DURATIONS) { // Note: POSIX sleep does not support fractional seconds, so // can't sleep for less than 1 second. @@ -45,17 +41,9 @@ static bool test() { // Ensure that sleep did not become a zombie EXPECT(Tests::processAlive(sleepPid), false); - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return !ret; + // Test succeeded + return; } - return false; + FAIL_TEST_SILENT(); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/follow_mouse_shrink.cpp b/hyprtester/src/tests/main/follow_mouse_shrink.cpp index b41e9fd6..0e12915b 100644 --- a/hyprtester/src/tests/main/follow_mouse_shrink.cpp +++ b/hyprtester/src/tests/main/follow_mouse_shrink.cpp @@ -7,17 +7,6 @@ #include "../shared.hpp" #include "tests.hpp" -static int ret = 0; - -static bool spawnKitty(const std::string& class_) { - NLog::log("{}Spawning {}", Colors::YELLOW, class_); - if (!Tests::spawnKitty(class_)) { - NLog::log("{}Error: {} did not spawn", Colors::RED, class_); - return false; - } - return true; -} - static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { std::string activeWin = getFromSocket("/activewindow"); auto winClass = Tests::getWindowAttribute(activeWin, "class:"); @@ -43,7 +32,8 @@ static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0' return true; } -static bool test() { +// TODO: split this into multiple test cases +TEST_CASE(followMouseShrink) { NLog::log("{}Testing follow_mouse_shrink", Colors::GREEN); getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:follow_mouse_shrink' })"); @@ -56,15 +46,15 @@ static bool test() { // Spawn two floating windows with a 20px gap // fms_a: position (100,100), size 400x400 -> hitbox [100,499] x [100,499] - if (!spawnKitty("fms_a")) - return false; + if (!Tests::spawnKitty("fms_a")) + FAIL_TEST("Couldn't spawn kitty"); OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })")); OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })")); OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 100, y = 100 })")); // fms_b: position (520,100), size 400x400 -> hitbox [520,919] x [100,499] - if (!spawnKitty("fms_b")) - return false; + if (!Tests::spawnKitty("fms_b")) + FAIL_TEST("Couldn't spawn kitty"); OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })")); OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })")); OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 520, y = 100 })")); @@ -74,58 +64,50 @@ static bool test() { OK(getFromSocket("/eval hl.config({ input = { follow_mouse_shrink = 0 } })")); // Focus A explicitly, then move cursor inside A so follow_mouse tracks it OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_a' })")); - EXPECT(waitForActiveWindow("fms_a"), true); + ASSERT(waitForActiveWindow("fms_a"), true); OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 300, y = 300 })")); std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Move to just inside B's left edge OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 521, y = 300 })")); - EXPECT(waitForActiveWindow("fms_b"), true); + ASSERT(waitForActiveWindow("fms_b"), true); // --- Test 2: Shrink=20, cursor in dead zone does NOT change focus --- NLog::log("{}Test 2: shrink=20, cursor in B's dead zone stays on A", Colors::GREEN); OK(getFromSocket("/eval hl.config({ input = { follow_mouse_shrink = 20 } })")); // Focus A explicitly OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_a' })")); - EXPECT(waitForActiveWindow("fms_a"), true); + ASSERT(waitForActiveWindow("fms_a"), true); OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 300, y = 300 })")); std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Move to 530,300 -- 10px inside B, within 20px shrink zone (B's shrunk hitbox starts at 540) OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 530, y = 300 })")); std::this_thread::sleep_for(std::chrono::milliseconds(500)); - EXPECT(isActiveWindow("fms_a"), true); + ASSERT(isActiveWindow("fms_a"), true); // --- Test 3: Shrink=20, cursor well inside inactive window DOES focus it --- NLog::log("{}Test 3: shrink=20, cursor at B's center focuses B", Colors::GREEN); // Still focused on A from test 2 OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 720, y = 300 })")); - EXPECT(waitForActiveWindow("fms_b"), true); + ASSERT(waitForActiveWindow("fms_b"), true); // --- Test 4: Focused window's hitbox is NOT shrunk --- NLog::log("{}Test 4a: focused window hitbox is not shrunk", Colors::GREEN); // Focus A explicitly, move cursor inside A, then move near A's right edge OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_a' })")); - EXPECT(waitForActiveWindow("fms_a"), true); + ASSERT(waitForActiveWindow("fms_a"), true); OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 300, y = 300 })")); std::this_thread::sleep_for(std::chrono::milliseconds(200)); OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 490, y = 300 })")); std::this_thread::sleep_for(std::chrono::milliseconds(500)); - EXPECT(isActiveWindow("fms_a"), true); + ASSERT(isActiveWindow("fms_a"), true); NLog::log("{}Test 4b: inactive window hitbox IS shrunk at same position", Colors::GREEN); // Focus B explicitly, then move to (490,300). Now A is inactive with shrunk box ending at 479, so 490 is outside A. OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_b' })")); - EXPECT(waitForActiveWindow("fms_b"), true); + ASSERT(waitForActiveWindow("fms_b"), true); OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 720, y = 300 })")); std::this_thread::sleep_for(std::chrono::milliseconds(200)); OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 490, y = 300 })")); std::this_thread::sleep_for(std::chrono::milliseconds(500)); - EXPECT(isActiveWindow("fms_b"), true); - - // Cleanup - OK(getFromSocket("/reload")); - Tests::killAllWindows(); - - return ret == 0; + ASSERT(isActiveWindow("fms_b"), true); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/gestures.cpp b/hyprtester/src/tests/main/gestures.cpp index 131061c4..f8324712 100644 --- a/hyprtester/src/tests/main/gestures.cpp +++ b/hyprtester/src/tests/main/gestures.cpp @@ -10,14 +10,13 @@ #include <cerrno> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer +// TODO: refactor and reuse `Tests::waitUntilWindowsN` static bool waitForWindowCount(int expectedWindowCnt, std::string_view expectation, int waitMillis = 100, int maxWaitCnt = 50) { int counter = 0; while (Tests::windowCount() != expectedWindowCnt) { @@ -32,17 +31,10 @@ static bool waitForWindowCount(int expectedWindowCnt, std::string_view expectati return true; } -static bool test() { - NLog::log("{}Testing gestures", Colors::GREEN); - - EXPECT(Tests::windowCount(), 0); - - // test on workspace "window" - NLog::log("{}Switching to workspace 1", Colors::YELLOW); - getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); // no OK: we might be on 1 already - +// TODO: decompose this into multiple test cases +TEST_CASE(gestures) { Tests::spawnKitty(); - EXPECT(Tests::windowCount(), 1); + ASSERT(Tests::windowCount(), 1); // Give the shell a moment to initialize std::this_thread::sleep_for(std::chrono::milliseconds(500)); @@ -148,7 +140,7 @@ static bool test() { EXPECT(waitForWindowCount(0, "Gesture closed kitty"), true); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); // This test ensures that `movecursortocorner`, which expects // a single-character direction argument, is parsed correctly. @@ -186,18 +178,4 @@ static bool test() { OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); } - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - // reload cfg - OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/groups.cpp b/hyprtester/src/tests/main/groups.cpp index 71b5cf18..7e1c63b6 100644 --- a/hyprtester/src/tests/main/groups.cpp +++ b/hyprtester/src/tests/main/groups.cpp @@ -10,17 +10,14 @@ #include <cerrno> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -static bool test() { - NLog::log("{}Testing groups", Colors::GREEN); - +// TODO: decompose this into multiple test cases +TEST_CASE(groups) { // test on workspace "window" NLog::log("{}Dispatching workspace `groups`", Colors::YELLOW); getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:groups' })"); @@ -28,8 +25,7 @@ static bool test() { NLog::log("{}Testing movewindoworgroup from group to group", Colors::YELLOW); auto kittyA = Tests::spawnKitty("kittyA"); if (!kittyA) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } // check kitty properties. One kitty should take the entire screen, minus the gaps. @@ -43,8 +39,7 @@ static bool test() { auto kittyB = Tests::spawnKitty("kittyB"); if (!kittyB) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kittyB' })")); @@ -61,8 +56,7 @@ static bool test() { auto kittyC = Tests::spawnKitty("kittyC"); if (!kittyC) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Check kittyC dimensions", Colors::YELLOW); @@ -87,12 +81,11 @@ static bool test() { NLog::log("{}Spawning kittyProcA", Colors::YELLOW); auto kittyProcA = Tests::spawnKitty(); if (!kittyProcA) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Expecting 1 window", Colors::YELLOW); - EXPECT(Tests::windowCount(), 1); + ASSERT(Tests::windowCount(), 1); // check kitty properties. One kitty should take the entire screen, minus the gaps. NLog::log("{}Check kitty dimensions", Colors::YELLOW); @@ -127,8 +120,7 @@ static bool test() { NLog::log("{}Spawn kitty again", Colors::YELLOW); kittyProcA = Tests::spawnKitty(); if (!kittyProcA) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Group kitty", Colors::YELLOW); @@ -145,12 +137,11 @@ static bool test() { NLog::log("{}Spawn kittyProcB", Colors::YELLOW); auto kittyProcB = Tests::spawnKitty(); if (!kittyProcB) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Expecting 2 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); size_t lastActiveKittyIdx = 0; @@ -158,10 +149,7 @@ static bool test() { try { auto str = getFromSocket("/activewindow"); lastActiveKittyIdx = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); - } catch (...) { - NLog::log("{}Fail at getting prop", Colors::RED); - ret = 1; - } + } catch (...) { FAIL_TEST("Could not extract the active window id"); } // test cycling through @@ -170,21 +158,15 @@ static bool test() { try { auto str = getFromSocket("/activewindow"); - EXPECT(lastActiveKittyIdx != std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16), true); - } catch (...) { - NLog::log("{}Fail at getting prop", Colors::RED); - ret = 1; - } + ASSERT(lastActiveKittyIdx != std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16), true); + } catch (...) { FAIL_TEST("Could not extract the active window id"); } getFromSocket("/dispatch hl.dsp.group.next()"); try { auto str = getFromSocket("/activewindow"); - EXPECT(lastActiveKittyIdx, std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16)); - } catch (...) { - NLog::log("{}Fail at getting prop", Colors::RED); - ret = 1; - } + ASSERT(lastActiveKittyIdx, std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16)); + } catch (...) { FAIL_TEST("Could not extract the active window id"); } // test movegroupwindow: focus should follow the moved window NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW); @@ -195,10 +177,7 @@ static bool test() { str = getFromSocket("/activewindow"); auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); EXPECT(activeAfterMove, activeBeforeMove); - } catch (...) { - NLog::log("{}Fail at getting prop", Colors::RED); - ret = 1; - } + } catch (...) { FAIL_TEST("Could not extract the active window id"); } // and backwards NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW); @@ -209,10 +188,7 @@ static bool test() { str = getFromSocket("/activewindow"); auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); EXPECT(activeAfterMove, activeBeforeMove); - } catch (...) { - NLog::log("{}Fail at getting prop", Colors::RED); - ret = 1; - } + } catch (...) { FAIL_TEST("Could not extract the active window id"); } NLog::log("{}Disable autogrouping", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ group = { auto_group = false } })")); @@ -220,12 +196,11 @@ static bool test() { NLog::log("{}Spawn kittyProcC", Colors::YELLOW); auto kittyProcC = Tests::spawnKitty(); if (!kittyProcC) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Expecting 3 windows 2", Colors::YELLOW); - EXPECT(Tests::windowCount(), 3); + ASSERT(Tests::windowCount(), 3); { auto str = getFromSocket("/clients"); EXPECT_COUNT_STRING(str, "at: 22,22", 2); @@ -239,12 +214,11 @@ static bool test() { NLog::log("{}Spawn kittyProcD", Colors::YELLOW); auto kittyProcD = Tests::spawnKitty(); if (!kittyProcD) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Expecting 4 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 4); + ASSERT(Tests::windowCount(), 4); OK(getFromSocket("/dispatch hl.dsp.group.active({ index = 3 })")); @@ -258,7 +232,7 @@ static bool test() { Tests::killAllWindows(); NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); // test movewindoworgroup: direction should be respected when extracting from group NLog::log("{}Test movewindoworgroup respects direction out of group", Colors::YELLOW); @@ -266,8 +240,7 @@ static bool test() { { auto kittyE = Tests::spawnKitty(); if (!kittyE) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } // group kitty, and new windows should be auto-grouped @@ -275,10 +248,9 @@ static bool test() { auto kittyF = Tests::spawnKitty(); if (!kittyF) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); // both windows should be grouped at the same position { @@ -310,7 +282,7 @@ static bool test() { } Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); } // test that we deny a floated window getting auto-grouped into a tiled group. @@ -320,8 +292,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-tiled', tile = true })")); auto kittyProcE = Tests::spawnKitty("kitty_tiled"); if (!kittyProcE) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } OK(getFromSocket("/dispatch hl.dsp.group.toggle()")); @@ -329,18 +300,16 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-floated', float = true })")); auto kittyProcF = Tests::spawnKitty("kitty_floated"); if (!kittyProcF) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); { auto clients = getFromSocket("/clients"); auto classPos = clients.find("class: kitty_floated"); if (classPos == std::string::npos) { - NLog::log("{}Could not find kitty_floated in clients output", Colors::RED); - ret = 1; + FAIL_TEST("Could not find kitty_floated in clients output"); } else { auto entryStart = clients.rfind("Window ", classPos); auto entryEnd = clients.find("\n\n", classPos); @@ -351,27 +320,25 @@ static bool test() { } Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); // Tests for grouping/merging logic NLog::log("{}Testing locked groups w/ invade", Colors::GREEN); Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); // Test normal, unlocked groups { auto winA = Tests::spawnKitty("unlocked"); if (!winA) { - NLog::log("{}Error: unlocked kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn unlocked kitty"); } OK(getFromSocket("/dispatch hl.dsp.group.toggle()")); auto winB = Tests::spawnKitty("top"); if (!winB) { - NLog::log("{}Error: top kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn top kitty"); } // Verify it DID merge into a group @@ -382,14 +349,13 @@ static bool test() { } Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); // Test locked groups { auto lockedWin = Tests::spawnKitty("locked"); if (!lockedWin) { - NLog::log("{}Error: locked kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn locked kitty"); } OK(getFromSocket("/dispatch hl.dsp.group.toggle()")); OK(getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", lockedWin->pid()))); @@ -397,8 +363,7 @@ static bool test() { auto winB = Tests::spawnKitty("top"); if (!winB) { - NLog::log("{}Error: top kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn top kitty"); } // Verify it did NOT merge into the locked group @@ -409,7 +374,7 @@ static bool test() { } Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); // Test locked groups WITH invade rule { @@ -418,14 +383,12 @@ static bool test() { auto lockedWin = Tests::spawnKitty("locked"); if (!lockedWin) { - NLog::log("{}Error: locked kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn locked kitty"); } auto invadingWin = Tests::spawnKitty("invade"); if (!invadingWin) { - NLog::log("{}Error: invading kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn invading kitty"); } // Verify it DID merge into the locked group @@ -434,9 +397,5 @@ static bool test() { } Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - - return !ret; + ASSERT(Tests::windowCount(), 0); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/hyprctl.cpp b/hyprtester/src/tests/main/hyprctl.cpp index c6d6a9bf..c67c8af9 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -12,8 +12,6 @@ #include <cerrno> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; @@ -35,9 +33,7 @@ static void setWindowProp(const std::string& selector, const std::string& prop, getFromSocket("/dispatch hl.dsp.window.set_prop({ window = '" + selector + "', prop = '" + prop + "', value = '" + value + "' })"); } -static bool testDevicesActiveLayoutIndex() { - NLog::log("{}Testing hyprctl devices active_layout_index", Colors::GREEN); - +TEST_CASE(hyprctlDevicesActiveLayoutIndex) { // configure layouts OK(getFromSocket("r/eval hl.config({ input = { kb_layout = \"us,pl,ua\" } })")); @@ -49,15 +45,11 @@ static bool testDevicesActiveLayoutIndex() { // check layout index EXPECT_CONTAINS(devicesJson, expected); } - - return true; } -static bool testGetprop() { - NLog::log("{}Testing hyprctl getprop", Colors::GREEN); +TEST_CASE(hyprctlGetprop) { if (!Tests::spawnKitty()) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } // animation @@ -166,41 +158,16 @@ static bool testGetprop() { EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args"); EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animation"), "window not found"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found"); - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return true; } -static void testSubmap() { - NLog::log("{}Testing hyprctl submap", Colors::GREEN); - +TEST_CASE(hyprctlSubmap) { EXPECT(getCommandStdOut("hyprctl submap"), "default\n"); EXPECT(getCommandStdOut("hyprctl submap -j | jq -r \".\""), "default"); } -static bool test() { - NLog::log("{}Testing hyprctl", Colors::GREEN); - - { - NLog::log("{}Testing hyprctl descriptions for any json errors", Colors::GREEN); - CProcess jqProc("bash", {"-c", "hyprctl descriptions | jq"}); - jqProc.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS); - jqProc.runSync(); - EXPECT(jqProc.exitCode(), 0); - } - - testGetprop(); - testDevicesActiveLayoutIndex(); - testSubmap(); - getFromSocket("/reload"); - - return !ret; +TEST_CASE(hyprctlJsonErrors) { + CProcess jqProc("bash", {"-c", "hyprctl descriptions | jq"}); + jqProc.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS); + jqProc.runSync(); + EXPECT(jqProc.exitCode(), 0); } - -REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/keybinds.cpp b/hyprtester/src/tests/main/keybinds.cpp index c9a7786c..a1b5288c 100644 --- a/hyprtester/src/tests/main/keybinds.cpp +++ b/hyprtester/src/tests/main/keybinds.cpp @@ -8,8 +8,6 @@ using namespace Hyprutils::OS; using namespace Hyprutils::Memory; - -static int ret = 0; static std::string flagFile = "/tmp/hyprtester-keybinds.txt"; static std::string pluginKeybindCmd(bool pressed, uint32_t modifier, uint32_t key) { @@ -88,7 +86,11 @@ static CUniquePointer<CProcess> spawnRemoteControlKitty() { return kittyProc; } -static void testBind() { +// All the `SUBTEST`s below are supposed to be independent `TEST_CASE`s. +// But if isolated trivially, some of them fail. +// TODO: investigate and isolate tests by turning `SUBTEST`s into `TEST_CASE`s. + +SUBTEST(bind) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'))"), "ok"); // press keybind @@ -100,7 +102,7 @@ static void testBind() { EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } -static void testBindKey() { +SUBTEST(bindKey) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'))"), "ok"); // press keybind @@ -112,7 +114,7 @@ static void testBindKey() { EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok"); } -static void testLongPress() { +SUBTEST(longPress) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok"); EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok"); @@ -128,8 +130,7 @@ static void testLongPress() { OK(getFromSocket(pluginKeybindCmd(false, 0, 29))); EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } - -static void testKeyLongPress() { +SUBTEST(keyLongPress) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok"); EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok"); @@ -146,7 +147,7 @@ static void testKeyLongPress() { EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok"); } -static void testLongPressRelease() { +SUBTEST(longPressRelease) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok"); EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok"); @@ -162,8 +163,7 @@ static void testLongPressRelease() { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } - -static void testLongPressOnlyKeyRelease() { +SUBTEST(longPressOnlyKeyRelease) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok"); EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok"); @@ -181,7 +181,7 @@ static void testLongPressOnlyKeyRelease() { EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } -static void testRepeat() { +SUBTEST(repeat) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok"); EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok"); @@ -201,7 +201,7 @@ static void testRepeat() { EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } -static void testKeyRepeat() { +SUBTEST(keyRepeat) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok"); EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok"); @@ -221,7 +221,7 @@ static void testKeyRepeat() { EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok"); } -static void testRepeatRelease() { +SUBTEST(repeatRelease) { // wait until flag becomes false (CI timing can vary) bool ok = false; for (int i = 0; i < 20; ++i) { @@ -253,7 +253,7 @@ static void testRepeatRelease() { EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } -static void testRepeatOnlyKeyRelease() { +SUBTEST(repeatOnlyKeyRelease) { EXPECT(checkFlag(), false); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok"); EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok"); @@ -276,12 +276,10 @@ static void testRepeatOnlyKeyRelease() { EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } -static void testShortcutBind() { +SUBTEST(shortcutBind) { auto kittyProc = spawnRemoteControlKitty(); if (!kittyProc) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok"); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }))"), "ok"); @@ -298,12 +296,10 @@ static void testShortcutBind() { Tests::killAllWindows(); } -static void testShortcutBindKey() { +SUBTEST(shortcutBindKey) { auto kittyProc = spawnRemoteControlKitty(); if (!kittyProc) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok"); EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }))"), "ok"); @@ -321,12 +317,10 @@ static void testShortcutBindKey() { Tests::killAllWindows(); } -static void testShortcutLongPress() { +SUBTEST(shortcutLongPress) { auto kittyProc = spawnRemoteControlKitty(); if (!kittyProc) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok"); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { long_press = true })"), "ok"); @@ -350,12 +344,10 @@ static void testShortcutLongPress() { Tests::killAllWindows(); } -static void testShortcutLongPressKeyRelease() { +SUBTEST(shortcutLongPressKeyRelease) { auto kittyProc = spawnRemoteControlKitty(); if (!kittyProc) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok"); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { long_press = true })"), "ok"); @@ -377,12 +369,10 @@ static void testShortcutLongPressKeyRelease() { Tests::killAllWindows(); } -static void testShortcutRepeat() { +SUBTEST(shortcutRepeat) { auto kittyProc = spawnRemoteControlKitty(); if (!kittyProc) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok"); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { repeating = true })"), "ok"); @@ -407,12 +397,10 @@ static void testShortcutRepeat() { Tests::killAllWindows(); } -static void testShortcutRepeatKeyRelease() { +SUBTEST(shortcutRepeatKeyRelease) { auto kittyProc = spawnRemoteControlKitty(); if (!kittyProc) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok"); EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { repeating = true })"), "ok"); @@ -439,7 +427,7 @@ static void testShortcutRepeatKeyRelease() { Tests::killAllWindows(); } -static void testSubmap() { +SUBTEST(submap) { const auto press = [](const uint32_t key, const uint32_t mod = 0) { // +8 because udev -> XKB keycode. getFromSocket(pluginKeybindCmd(true, mod, key + 8)); @@ -474,7 +462,7 @@ static void testSubmap() { Tests::killAllWindows(); } -static void testBindsAfterScroll() { +SUBTEST(bindsAfterScroll) { NLog::log("{}Testing binds after scroll", Colors::GREEN); clearFlag(); @@ -503,7 +491,7 @@ static void testBindsAfterScroll() { OK(getFromSocket("/eval hl.unbind('ALT', 'w')")); } -static void testSubmapUniversal() { +SUBTEST(submapUniversal) { NLog::log("{}Testing submap universal", Colors::GREEN); EXPECT(checkFlag(), false); @@ -531,7 +519,7 @@ static void testSubmapUniversal() { EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } -static void testPerDeviceKeybind() { +SUBTEST(perDeviceKeybind) { NLog::log("{}Testing per-device binds", Colors::GREEN); // Inclusive @@ -561,34 +549,26 @@ static void testPerDeviceKeybind() { EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok"); } -static bool test() { - NLog::log("{}Testing keybinds", Colors::GREEN); - - clearFlag(); - - testBind(); - testBindKey(); - testLongPress(); - testKeyLongPress(); - testLongPressRelease(); - testLongPressOnlyKeyRelease(); - testRepeat(); - testKeyRepeat(); - testRepeatRelease(); - testRepeatOnlyKeyRelease(); - testShortcutBind(); - testShortcutBindKey(); - testShortcutLongPress(); - testShortcutLongPressKeyRelease(); - testShortcutRepeat(); - testShortcutRepeatKeyRelease(); - testSubmap(); - testSubmapUniversal(); - testBindsAfterScroll(); - testPerDeviceKeybind(); - - clearFlag(); - return !ret; +// TODO: remove this test after subtests above are properly isolated into independent tests +TEST_CASE(keybinds) { + CALL_SUBTEST(bind); + CALL_SUBTEST(bindKey); + CALL_SUBTEST(longPress); + CALL_SUBTEST(keyLongPress); + CALL_SUBTEST(longPressRelease); + CALL_SUBTEST(longPressOnlyKeyRelease); + CALL_SUBTEST(repeat); + CALL_SUBTEST(keyRepeat); + CALL_SUBTEST(repeatRelease); + CALL_SUBTEST(repeatOnlyKeyRelease); + CALL_SUBTEST(shortcutBind); + CALL_SUBTEST(shortcutBindKey); + CALL_SUBTEST(shortcutLongPress); + CALL_SUBTEST(shortcutLongPressKeyRelease); + CALL_SUBTEST(shortcutRepeat); + CALL_SUBTEST(shortcutRepeatKeyRelease); + CALL_SUBTEST(submap); + CALL_SUBTEST(submapUniversal); + CALL_SUBTEST(bindsAfterScroll); + CALL_SUBTEST(perDeviceKeybind); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/layer.cpp b/hyprtester/src/tests/main/layer.cpp index df74f8b7..f39c007c 100644 --- a/hyprtester/src/tests/main/layer.cpp +++ b/hyprtester/src/tests/main/layer.cpp @@ -6,8 +6,6 @@ #include <hyprutils/os/Process.hpp> #include <hyprutils/memory/WeakPtr.hpp> -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; @@ -20,34 +18,18 @@ static bool spawnLayer(const std::string& namespace_) { return true; } -static bool test() { - NLog::log("{}Testing plugin layerrules", Colors::GREEN); +TEST_CASE(plugin_layerrules) { - if (!spawnLayer("rule-layer")) - return false; + EXPECT(spawnLayer("rule-layer"), true); OK(getFromSocket("/eval hl.plugin.test.add_layer_rule()")); OK(getFromSocket("/reload")); OK(getFromSocket("/eval hl.layer_rule({ match = { namespace = 'rule-layer' }, plugin_rule = 'effect' })")); - if (!spawnLayer("rule-layer")) - return false; + EXPECT(spawnLayer("rule-layer"), true); - if (!spawnLayer("norule-layer")) - return false; + EXPECT(spawnLayer("norule-layer"), true); OK(getFromSocket("/eval hl.plugin.test.check_layer_rule()")); - - OK(getFromSocket("/reload")); - - NLog::log("{}Killing all layers", Colors::YELLOW); - Tests::killAllLayers(); - - NLog::log("{}Expecting 0 layers", Colors::YELLOW); - EXPECT(Tests::layerCount(), 0); - - return !ret; } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/layout.cpp b/hyprtester/src/tests/main/layout.cpp index e2336add..4d604fa4 100644 --- a/hyprtester/src/tests/main/layout.cpp +++ b/hyprtester/src/tests/main/layout.cpp @@ -3,9 +3,7 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -static int ret = 0; - -static void swar() { +TEST_CASE(single_window_aspect_ratio) { OK(getFromSocket("/eval hl.config({ layout = { single_window_aspect_ratio = '1 1' } })")); Tests::spawnKitty(); @@ -36,14 +34,10 @@ static void swar() { EXPECT_CONTAINS(str, "at: 22,22"); EXPECT_CONTAINS(str, "size: 1876,1036"); } - - // clean up - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } // Don't crash when focus after global geometry changes -static void testCrashOnGeomUpdate() { +TEST_CASE(crashOnGeomUpdate) { Tests::spawnKitty(); Tests::spawnKitty(); Tests::spawnKitty(); @@ -53,15 +47,10 @@ static void testCrashOnGeomUpdate() { // shouldnt crash OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })")); - - OK(getFromSocket("/reload")); - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } // Test if size + pos is preserved after fs cycle -static void testPosPreserve() { +TEST_CASE(posPreserve) { Tests::spawnKitty(); OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:kitty' })")); @@ -98,21 +87,18 @@ static void testPosPreserve() { EXPECT_CONTAINS(str, "at: 581,420"); EXPECT_CONTAINS(str, "size: 1337,69"); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } -static bool testFocusMRUAfterClose() { +TEST_CASE(focusMRUAfterClose) { NLog::log("{}Testing focus after close (MRU order)", Colors::GREEN); OK(getFromSocket("/reload")); OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 1.25 } })")); OK(getFromSocket("/eval hl.config({ input = { focus_on_close = 2 } })")); - EXPECT(!!Tests::spawnKitty("kitty_A"), true); - EXPECT(!!Tests::spawnKitty("kitty_B"), true); - EXPECT(!!Tests::spawnKitty("kitty_C"), true); + ASSERT(!!Tests::spawnKitty("kitty_A"), true); + ASSERT(!!Tests::spawnKitty("kitty_B"), true); + ASSERT(!!Tests::spawnKitty("kitty_C"), true); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })")); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })")); @@ -133,22 +119,15 @@ static bool testFocusMRUAfterClose() { auto str = getFromSocket("/activewindow"); EXPECT(str.contains("class: kitty_A"), true); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - return true; } -static bool testFocusPreservedLayoutChange() { - NLog::log("{}Testing focus is preserved on layout change", Colors::GREEN); - +TEST_CASE(focusPreservedLayoutChange) { OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })")); - EXPECT(!!Tests::spawnKitty("kitty_A"), true); - EXPECT(!!Tests::spawnKitty("kitty_B"), true); - EXPECT(!!Tests::spawnKitty("kitty_C"), true); - EXPECT(!!Tests::spawnKitty("kitty_D"), true); + ASSERT(!!Tests::spawnKitty("kitty_A"), true); + ASSERT(!!Tests::spawnKitty("kitty_B"), true); + ASSERT(!!Tests::spawnKitty("kitty_C"), true); + ASSERT(!!Tests::spawnKitty("kitty_D"), true); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_C' })")); @@ -158,34 +137,4 @@ static bool testFocusPreservedLayoutChange() { auto str = getFromSocket("/activewindow"); EXPECT(str.contains("class: kitty_C"), true); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - return true; } - -static bool test() { - NLog::log("{}Testing layout generic", Colors::GREEN); - - // setup - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '10' })")); - - // test - NLog::log("{}Testing `single_window_aspect_ratio`", Colors::GREEN); - swar(); - - testCrashOnGeomUpdate(); - testPosPreserve(); - testFocusMRUAfterClose(); - testFocusPreservedLayoutChange(); - - // clean up - NLog::log("Cleaning up", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); - OK(getFromSocket("/reload")); - - return !ret; -} - -REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/master.cpp b/hyprtester/src/tests/main/master.cpp index 02792631..b9364c45 100644 --- a/hyprtester/src/tests/main/master.cpp +++ b/hyprtester/src/tests/main/master.cpp @@ -3,10 +3,44 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -static int ret = 0; +TEST_CASE(focusMasterPrevious) { + OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })")); + + // setup + NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW); + // order of windows set according to new_status = master (set in test.lua) + for (auto const& win : {"slave1", "slave2", "slave3", "master"}) { + if (!Tests::spawnKitty(win)) { + FAIL_TEST("Could not spawn kitty with win class `{}`", win); + } + } + NLog::log("{}Ensuring focus is on master before testing", Colors::YELLOW); + OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster master')")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + + // test + NLog::log("{}Testing fallback to focusmaster auto", Colors::YELLOW); + + OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: slave1"); + + NLog::log("{}Testing focusing from slave to master", Colors::YELLOW); + + OK(getFromSocket("/dispatch hl.dsp.layout('cyclenext noloop')")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: slave2"); + OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + + NLog::log("{}Testing focusing on previous window", Colors::YELLOW); + + OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: slave2"); + + NLog::log("{}Testing focusing back to master", Colors::YELLOW); + + OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: master"); -// reqs 1 master 3 slaves -static void testOrientations() { OK(getFromSocket("r/eval hl.config({ master = { orientation = 'top' } })")); // top @@ -51,62 +85,15 @@ static void testOrientations() { } } -static void focusMasterPrevious() { - // setup - NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW); - // order of windows set according to new_status = master (set in test.lua) - for (auto const& win : {"slave1", "slave2", "slave3", "master"}) { - if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; - } - } - NLog::log("{}Ensuring focus is on master before testing", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster master')")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); - - // test - NLog::log("{}Testing fallback to focusmaster auto", Colors::YELLOW); - - OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave1"); - - NLog::log("{}Testing focusing from slave to master", Colors::YELLOW); - - OK(getFromSocket("/dispatch hl.dsp.layout('cyclenext noloop')")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2"); - OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); - - NLog::log("{}Testing focusing on previous window", Colors::YELLOW); - - OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2"); - - NLog::log("{}Testing focusing back to master", Colors::YELLOW); - - OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); - - testOrientations(); - - // clean up - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); -} - -static void testFsBehavior() { +TEST_CASE(fsBehavior) { // Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen // check that it doesn't. + OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })")); + for (auto const& win : {"master", "slave1", "slave2"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -153,31 +140,4 @@ static void testFsBehavior() { EXPECT_CONTAINS(str, "class: vaxwashere"); EXPECT_CONTAINS(str, "fullscreen: 0"); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); -} - -static bool test() { - NLog::log("{}Testing Master layout", Colors::GREEN); - - // setup - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:master' })")); - OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })")); - - // test - NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN); - focusMasterPrevious(); - - NLog::log("{}Testing fs behavior", Colors::GREEN); - testFsBehavior(); - - // clean up - NLog::log("Cleaning up", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); - OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/misc.cpp b/hyprtester/src/tests/main/misc.cpp index 704ba9d1..24cc4097 100644 --- a/hyprtester/src/tests/main/misc.cpp +++ b/hyprtester/src/tests/main/misc.cpp @@ -10,8 +10,6 @@ #include <cerrno> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; @@ -38,7 +36,7 @@ using namespace Hyprutils::Memory; // // { // auto str = getFromSocket("/activewindow"); -// EXPECT_CONTAINS(str, "workspace: 2"); +// ASSERT_CONTAINS(str, "workspace: 2"); // } // // OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); @@ -48,13 +46,13 @@ using namespace Hyprutils::Memory; // // { // auto str = getFromSocket("/activeworkspace"); -// EXPECT_CONTAINS(str, "windows: 0"); +// ASSERT_CONTAINS(str, "windows: 0"); // } // // { // OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:hyprland-dialog' })")) // auto str = getFromSocket("/activewindow"); -// EXPECT_CONTAINS(str, "workspace: 2"); +// ASSERT_CONTAINS(str, "workspace: 2"); // } // } // @@ -73,7 +71,7 @@ using namespace Hyprutils::Memory; // // { // auto str = getFromSocket("/activewindow"); -// EXPECT_CONTAINS(str, "yummy"); +// ASSERT_CONTAINS(str, "yummy"); // } // // OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); @@ -83,13 +81,13 @@ using namespace Hyprutils::Memory; // // { // auto str = getFromSocket("/activeworkspace"); -// EXPECT_CONTAINS(str, "windows: 0"); +// ASSERT_CONTAINS(str, "windows: 0"); // } // // { // OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:hyprland-dialog' })")) // auto str = getFromSocket("/activewindow"); -// EXPECT_CONTAINS(str, "yummy"); +// ASSERT_CONTAINS(str, "yummy"); // } // } // @@ -108,7 +106,7 @@ using namespace Hyprutils::Memory; // // { // auto str = getFromSocket("/activewindow"); -// EXPECT_CONTAINS(str, "special:apple"); +// ASSERT_CONTAINS(str, "special:apple"); // } // // OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('apple')")); @@ -119,13 +117,13 @@ using namespace Hyprutils::Memory; // // { // auto str = getFromSocket("/activeworkspace"); -// EXPECT_CONTAINS(str, "windows: 0"); +// ASSERT_CONTAINS(str, "windows: 0"); // } // // { // OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:hyprland-dialog' })")) // auto str = getFromSocket("/activewindow"); -// EXPECT_CONTAINS(str, "special:apple"); +// ASSERT_CONTAINS(str, "special:apple"); // } // } // @@ -133,9 +131,8 @@ using namespace Hyprutils::Memory; // Tests::killAllWindows(); // } -static bool test() { - NLog::log("{}Testing config: misc:", Colors::GREEN); - +// TODO: decompose this into multiple test cases +TEST_CASE(misc) { NLog::log("{}Testing close_special_on_empty", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ misc = { close_special_on_empty = false } })")); @@ -145,14 +142,14 @@ static bool test() { { auto str = getFromSocket("/monitors"); - EXPECT_CONTAINS(str, "special workspace: -"); + ASSERT_CONTAINS(str, "special workspace: -"); } Tests::killAllWindows(); { auto str = getFromSocket("/monitors"); - EXPECT_CONTAINS(str, "special workspace: -"); + ASSERT_CONTAINS(str, "special workspace: -"); } Tests::spawnKitty(); @@ -163,7 +160,7 @@ static bool test() { { auto str = getFromSocket("/monitors"); - EXPECT_NOT_CONTAINS(str, "special workspace: -"); + ASSERT_NOT_CONTAINS(str, "special workspace: -"); } NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW); @@ -350,19 +347,10 @@ static bool test() { EXPECT_CONTAINS(str, "fullscreen: 2"); EXPECT_CONTAINS(str, "fullscreenClient: 2"); } +} +TEST_CASE(processesThatDieEarlyAreReaped) { // Ensure that the process autostarted in the config does not // become a zombie even if it terminates very quickly. - EXPECT(Tests::execAndGet("pgrep -f 'sleep 0'").empty(), true); - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return !ret; + ASSERT(Tests::execAndGet("pgrep -f 'sleep 0'").empty(), true); } - -REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/moveintoorcreategroup.cpp b/hyprtester/src/tests/main/moveintoorcreategroup.cpp index 2d47661b..8c2f3aae 100644 --- a/hyprtester/src/tests/main/moveintoorcreategroup.cpp +++ b/hyprtester/src/tests/main/moveintoorcreategroup.cpp @@ -10,17 +10,13 @@ #include <cerrno> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -static bool test() { - NLog::log("{}Testing moveintoorcreategroup", Colors::GREEN); - +TEST_CASE(moveIntoOrCreateGroup) { NLog::log("{}Dispatching workspace `moveintoorcreategroup`", Colors::YELLOW); getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:moveintoorcreategroup' })"); @@ -29,19 +25,17 @@ static bool test() { NLog::log("{}Spawning kittyA", Colors::YELLOW); auto kittyA = Tests::spawnKitty("kitty_A"); if (!kittyA) { - NLog::log("{}Error: kittyA did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty_A"); } NLog::log("{}Spawning kittyB", Colors::YELLOW); auto kittyB = Tests::spawnKitty("kitty_B"); if (!kittyB) { - NLog::log("{}Error: kittyB did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty_B"); } NLog::log("{}Expecting 2 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); { auto str = getFromSocket("/clients"); @@ -68,7 +62,7 @@ static bool test() { Tests::killAllWindows(); NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); NLog::log("{}Testing moveintoorcreategroup into existing group", Colors::YELLOW); @@ -80,7 +74,7 @@ static bool test() { auto kittyE = Tests::spawnKitty("kitty_E"); NLog::log("{}Expecting 3 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 3); + ASSERT(Tests::windowCount(), 3); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_D' })")); OK(getFromSocket("/dispatch hl.dsp.group.toggle()")); @@ -100,12 +94,4 @@ static bool test() { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "kitty_E"); } - - NLog::log("{}Kill windows", Colors::YELLOW); - Tests::killAllWindows(); - OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })")); - - return !ret; } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/persistent.cpp b/hyprtester/src/tests/main/persistent.cpp index 0a2a7f2e..a203b1f2 100644 --- a/hyprtester/src/tests/main/persistent.cpp +++ b/hyprtester/src/tests/main/persistent.cpp @@ -10,19 +10,13 @@ #include <cerrno> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -static bool test() { - NLog::log("{}Testing persistent workspaces", Colors::GREEN); - - EXPECT(Tests::windowCount(), 0); - +TEST_CASE(persistentWorkspaces) { // test on workspace "window" NLog::log("{}Switching to workspace 1", Colors::YELLOW); getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); // no OK: we might be on 1 already @@ -42,7 +36,7 @@ static bool test() { { auto str = getFromSocket("/monitors"); - EXPECT_CONTAINS(str, "HEADLESS-PERSISTENT-TEST"); + ASSERT_CONTAINS(str, "HEADLESS-PERSISTENT-TEST"); } OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-PERSISTENT-TEST' })")); @@ -68,18 +62,4 @@ static bool test() { } OK(getFromSocket("/output remove HEADLESS-PERSISTENT-TEST")); - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - // reload cfg - OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/scroll.cpp b/hyprtester/src/tests/main/scroll.cpp index 3b73b906..bd33e345 100644 --- a/hyprtester/src/tests/main/scroll.cpp +++ b/hyprtester/src/tests/main/scroll.cpp @@ -3,15 +3,12 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -static int ret = 0; +TEST_CASE(scrollFocusCycling) { + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); -static void testFocusCycling() { for (auto const& win : {"a", "b", "c", "d"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -21,49 +18,44 @@ static void testFocusCycling() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: b"); + ASSERT_CONTAINS(str, "class: b"); } OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: c"); + ASSERT_CONTAINS(str, "class: c"); } OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: d"); + ASSERT_CONTAINS(str, "class: d"); } OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'left' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: d"); + ASSERT_CONTAINS(str, "class: d"); } OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'up' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: c"); + ASSERT_CONTAINS(str, "class: c"); } - - // clean up - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } -static void testFocusWrapping() { +TEST_CASE(scrollFocusWrapping) { + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + for (auto const& win : {"a", "b", "c", "d"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -76,14 +68,14 @@ static void testFocusWrapping() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: d"); + ASSERT_CONTAINS(str, "class: d"); } OK(getFromSocket("/dispatch hl.dsp.layout('focus r')")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: a"); + ASSERT_CONTAINS(str, "class: a"); } // set wrap_focus to false @@ -95,7 +87,7 @@ static void testFocusWrapping() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: a"); + ASSERT_CONTAINS(str, "class: a"); } OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:d' })")); @@ -104,21 +96,16 @@ static void testFocusWrapping() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: d"); + ASSERT_CONTAINS(str, "class: d"); } - - // clean up - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } -static void testSwapcolWrapping() { +TEST_CASE(scrollSwapcolWrapping) { + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + for (auto const& win : {"a", "b", "c", "d"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -132,7 +119,7 @@ static void testSwapcolWrapping() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: c"); + ASSERT_CONTAINS(str, "class: c"); } // clean up @@ -141,10 +128,7 @@ static void testSwapcolWrapping() { for (auto const& win : {"a", "b", "c", "d"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -154,7 +138,7 @@ static void testSwapcolWrapping() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: b"); + ASSERT_CONTAINS(str, "class: b"); } // clean up @@ -163,10 +147,7 @@ static void testSwapcolWrapping() { for (auto const& win : {"a", "b", "c", "d"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -180,7 +161,7 @@ static void testSwapcolWrapping() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: b"); + ASSERT_CONTAINS(str, "class: b"); } OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:d' })")); @@ -190,15 +171,13 @@ static void testSwapcolWrapping() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: c"); + ASSERT_CONTAINS(str, "class: c"); } - - // clean up - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); } -static bool testWindowRule() { +TEST_CASE(scrollWindowRule) { + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + NLog::log("{}Testing Scrolling Width", Colors::GREEN); // inject a new rule. @@ -206,53 +185,15 @@ static bool testWindowRule() { OK(getFromSocket("/eval hl.window_rule({ name = 'scrolling-width', scrolling_width = 0.1 })")); if (!Tests::spawnKitty("kitty_scroll")) { - NLog::log("{}Failed to spawn kitty with win class `kitty_scroll`", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty with win class `kitty_scroll`"); } if (!Tests::spawnKitty("kitty_scroll")) { - NLog::log("{}Failed to spawn kitty with win class `kitty_scroll`", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty with win class `kitty_scroll`"); } - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); // not the greatest test, but as long as res and gaps don't change, we good. EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 174,1036"); - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - return true; } - -static bool test() { - NLog::log("{}Testing Scroll layout", Colors::GREEN); - - // setup - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:scroll' })")); - OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); - - // test - NLog::log("{}Testing focus cycling", Colors::GREEN); - testFocusCycling(); - - // test - NLog::log("{}Testing focus wrap", Colors::GREEN); - testFocusWrapping(); - - // test - NLog::log("{}Testing swapcol wrap", Colors::GREEN); - testSwapcolWrapping(); - - testWindowRule(); - - // clean up - NLog::log("Cleaning up", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); - OK(getFromSocket("/reload")); - - return !ret; -} - -REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/snap.cpp b/hyprtester/src/tests/main/snap.cpp index ca103fa0..0e0c4604 100644 --- a/hyprtester/src/tests/main/snap.cpp +++ b/hyprtester/src/tests/main/snap.cpp @@ -9,43 +9,35 @@ using Hyprutils::Math::Vector2D; -static int ret = 0; - static bool spawnFloatingKitty() { if (!Tests::spawnKitty()) { NLog::log("{}Error: kitty did not spawn", Colors::RED); return false; } - OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })")); - OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 100, y = 100 })")); - return true; -} - -static void expectSocket(const std::string& CMD) { - if (const auto RESULT = getFromSocket(CMD); RESULT != "ok") { - NLog::log("{}Failed: {}getFromSocket({}), expected ok, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, CMD, RESULT, __FILE__, __LINE__); - ret = 1; - TESTS_FAILED++; - } else { - NLog::log("{}Passed: {}getFromSocket({}). Got ok", Colors::GREEN, Colors::RESET, CMD); - TESTS_PASSED++; - } + bool ok = true; + ok &= getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })") == "ok"; + ok &= getFromSocket("/dispatch hl.dsp.window.resize({ x = 100, y = 100 })") == "ok"; + return ok; } -static void expectSnapMove(const Vector2D FROM, const Vector2D* TO) { - const Vector2D& A = FROM; - const Vector2D& B = TO ? *TO : FROM; - if (TO) - NLog::log("{}Expecting snap to ({},{}) when window is moved to ({},{})", Colors::YELLOW, B.x, B.y, A.x, A.y); +SUBTEST(expectSnapMove, double fromX, double fromY, double toX, double toY) { + const Vector2D FROM = {fromX, fromY}; + const Vector2D TO = {toX, toY}; + if (FROM == TO) + NLog::log("{}Expecting no snap when window is moved to ({},{})", Colors::YELLOW, FROM.x, FROM.y); else - NLog::log("{}Expecting no snap when window is moved to ({},{})", Colors::YELLOW, A.x, A.y); + NLog::log("{}Expecting snap to ({},{}) when window is moved to ({},{})", Colors::YELLOW, TO.x, TO.y, FROM.x, FROM.y); - expectSocket(std::format("/dispatch hl.dsp.window.move({{ x = {}, y = {} }})", A.x, A.y)); - expectSocket("/eval hl.plugin.test.snapmove()"); - EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", B.x, B.y)); + OK(getFromSocket(std::format("/dispatch hl.dsp.window.move({{ x = {}, y = {} }})", FROM.x, FROM.y))); + OK(getFromSocket("/eval hl.plugin.test.snapmove()")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", TO.x, TO.y)); } -static void testWindowSnap(const bool RESPECTGAPS) { +SUBTEST(expectNoSnapMove, double x, double y) { + CALL_SUBTEST(expectSnapMove, x, y, x, y); +} + +SUBTEST(testWindowSnap, const bool RESPECTGAPS) { const int BORDERSIZE = 2; const int WINDOWSIZE = 100; @@ -55,22 +47,19 @@ static void testWindowSnap(const bool RESPECTGAPS) { const int GAP = (RESPECTGAPS ? 2 * GAPSIN : 0) + (2 * BORDERSIZE); const int END = GAP + WINDOWSIZE; - int x; - Vector2D predict; - - x = WINDOWGAP + END; - expectSnapMove({OTHER + x, OTHER}, nullptr); - expectSnapMove({OTHER - x, OTHER}, nullptr); - expectSnapMove({OTHER, OTHER + x}, nullptr); - expectSnapMove({OTHER, OTHER - x}, nullptr); + int x = WINDOWGAP + END; + CALL_SUBTEST(expectNoSnapMove, OTHER + x, OTHER); + CALL_SUBTEST(expectNoSnapMove, OTHER - x, OTHER); + CALL_SUBTEST(expectNoSnapMove, OTHER, OTHER + x); + CALL_SUBTEST(expectNoSnapMove, OTHER, OTHER - x); x -= 1; - expectSnapMove({OTHER + x, OTHER}, &(predict = {OTHER + END, OTHER})); - expectSnapMove({OTHER - x, OTHER}, &(predict = {OTHER - END, OTHER})); - expectSnapMove({OTHER, OTHER + x}, &(predict = {OTHER, OTHER + END})); - expectSnapMove({OTHER, OTHER - x}, &(predict = {OTHER, OTHER - END})); + CALL_SUBTEST(expectSnapMove, OTHER + x, OTHER, OTHER + END, OTHER); + CALL_SUBTEST(expectSnapMove, OTHER - x, OTHER, OTHER - END, OTHER); + CALL_SUBTEST(expectSnapMove, OTHER, OTHER + x, OTHER, OTHER + END); + CALL_SUBTEST(expectSnapMove, OTHER, OTHER - x, OTHER, OTHER - END); } -static void testMonitorSnap(const bool RESPECTGAPS, const bool OVERLAP) { +SUBTEST(testMonitorSnap, const bool RESPECTGAPS, const bool OVERLAP) { const int BORDERSIZE = 2; const int WINDOWSIZE = 100; @@ -84,14 +73,14 @@ static void testMonitorSnap(const bool RESPECTGAPS, const bool OVERLAP) { Vector2D predict; x = MONITORGAP + GAP; - expectSnapMove({x, x}, nullptr); + CALL_SUBTEST(expectNoSnapMove, x, x); x -= 1; - expectSnapMove({x, x}, &(predict = {GAP, GAP})); + CALL_SUBTEST(expectSnapMove, x, x, GAP, GAP); x = MONITORGAP + END; - expectSnapMove({1920 - x, 1080 - x}, nullptr); + CALL_SUBTEST(expectNoSnapMove, 1920 - x, 1080 - x); x -= 1; - expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - END, 1080 - END})); + CALL_SUBTEST(expectSnapMove, 1920 - x, 1080 - x, 1920 - END, 1080 - END); // test reserved area const int RESERVED = 200; @@ -99,17 +88,18 @@ static void testMonitorSnap(const bool RESPECTGAPS, const bool OVERLAP) { const int REND = RGAP + WINDOWSIZE; x = MONITORGAP + RGAP; - expectSnapMove({x, x}, nullptr); + CALL_SUBTEST(expectNoSnapMove, x, x); x -= 1; - expectSnapMove({x, x}, &(predict = {RGAP, RGAP})); + CALL_SUBTEST(expectSnapMove, x, x, RGAP, RGAP); x = MONITORGAP + REND; - expectSnapMove({1920 - x, 1080 - x}, nullptr); + CALL_SUBTEST(expectNoSnapMove, 1920 - x, 1080 - x); x -= 1; - expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - REND, 1080 - REND})); + CALL_SUBTEST(expectSnapMove, 1920 - x, 1080 - x, 1920 - REND, 1080 - REND); } -static bool test() { +// TODO: decompose this into multiple test cases +TEST_CASE(snap) { NLog::log("{}Testing snap", Colors::GREEN); // move to monitor HEADLESS-2 @@ -125,10 +115,10 @@ static bool test() { // spawn a kitty terminal and move to (500,500) NLog::log("{}Spawning kittyProcA", Colors::YELLOW); if (!spawnFloatingKitty()) - return false; + FAIL_TEST("Could not spawn kitty"); NLog::log("{}Expecting 1 window", Colors::YELLOW); - EXPECT(Tests::windowCount(), 1); + ASSERT(Tests::windowCount(), 1); NLog::log("{}Move the kitty window to (500,500)", Colors::YELLOW); OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 500, y = 500 })")); @@ -136,41 +126,26 @@ static bool test() { // spawn a second kitty terminal NLog::log("{}Spawning kittyProcB", Colors::YELLOW); if (!spawnFloatingKitty()) - return false; + FAIL_TEST("Could not spawn kitty"); NLog::log("{}Expecting 2 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); NLog::log(""); - testWindowSnap(false); - testMonitorSnap(false, false); + CALL_SUBTEST(testWindowSnap, false); + CALL_SUBTEST(testMonitorSnap, false, false); NLog::log("\n{}Turning on respect_gaps", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ general = { snap = { respect_gaps = true } } })")); - testWindowSnap(true); - testMonitorSnap(true, false); + CALL_SUBTEST(testWindowSnap, true); + CALL_SUBTEST(testMonitorSnap, true, false); NLog::log("\n{}Turning on border_overlap", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ general = { snap = { respect_gaps = false } } })")); OK(getFromSocket("/eval hl.config({ general = { snap = { border_overlap = true } } })")); - testMonitorSnap(false, true); + CALL_SUBTEST(testMonitorSnap, false, true); NLog::log("\n{}Turning on both border_overlap and respect_gaps", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ general = { snap = { respect_gaps = true } } })")); - testMonitorSnap(true, true); - - // kill all - NLog::log("\n{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - NLog::log("{}Reloading the config", Colors::YELLOW); - OK(getFromSocket("/reload")); - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); - - return !ret; + CALL_SUBTEST(testMonitorSnap, true, true); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/solitary.cpp b/hyprtester/src/tests/main/solitary.cpp index b89c1090..3e124a1e 100644 --- a/hyprtester/src/tests/main/solitary.cpp +++ b/hyprtester/src/tests/main/solitary.cpp @@ -7,17 +7,13 @@ #include <hyprutils/memory/WeakPtr.hpp> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -static bool test() { - NLog::log("{}Testing solitary clients", Colors::GREEN); - +TEST_CASE(solitaryClients) { OK(getFromSocket("/eval hl.config({ general = { allow_tearing = false } })")); OK(getFromSocket("/eval hl.config({ render = { direct_scanout = 0 } })")); OK(getFromSocket("/eval hl.config({ cursor = { no_hardware_cursors = 1 } })")); @@ -50,27 +46,16 @@ static bool test() { // NLog::log("{}Expecting kitty to almost pass for solitary/DS/tearing", Colors::YELLOW); // { // auto str = getFromSocket("/monitors"); - // EXPECT_NOT_CONTAINS(str, "solitary: 0\n"); - // EXPECT_CONTAINS(str, "solitaryBlockedBy: null"); - // EXPECT_CONTAINS(str, "activelyTearing: false"); - // EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor,window settings"); + // ASSERT_NOT_CONTAINS(str, "solitary: 0\n"); + // ASSERT_CONTAINS(str, "solitaryBlockedBy: null"); + // ASSERT_CONTAINS(str, "activelyTearing: false"); + // ASSERT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor,window settings"); // } // OK(getFromSocket("/dispatch hl.dsp.window.set_prop({ window = 'active', prop = 'immediate', value = '1' })")); // NLog::log("{}Expecting kitty to almost pass for tearing", Colors::YELLOW); // { // auto str = getFromSocket("/monitors"); - // EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor\n"); + // ASSERT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor\n"); // } - - // // kill all - // NLog::log("{}Killing all windows", Colors::YELLOW); - // Tests::killAllWindows(); - - NLog::log("{}Reloading the config", Colors::YELLOW); - OK(getFromSocket("/reload")); - - return !ret; } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/tags.cpp b/hyprtester/src/tests/main/tags.cpp index 45ee4088..d83f00fa 100644 --- a/hyprtester/src/tests/main/tags.cpp +++ b/hyprtester/src/tests/main/tags.cpp @@ -3,20 +3,13 @@ #include "../shared.hpp" #include "tests.hpp" -static int ret = 0; - -static bool testTags() { - NLog::log("{}Testing tags", Colors::GREEN); - - EXPECT(Tests::windowCount(), 0); - +TEST_CASE(tags) { NLog::log("{}Spawning kittyProcA&B on ws 1", Colors::YELLOW); auto kittyProcA = Tests::spawnKitty("tagged"); auto kittyProcB = Tests::spawnKitty("untagged"); if (!kittyProcA || !kittyProcB) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Testing testTag tags", Colors::YELLOW); @@ -28,7 +21,7 @@ static bool testTags() { OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-3', match = { tag = 'testTag' } })")); OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-3', no_dim = true })")); - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:tagged' })")); NLog::log("{}Testing tagged window for no_dim 0 & no_shadow", Colors::YELLOW); EXPECT_CONTAINS(getFromSocket("/activewindow"), "testTag"); @@ -39,13 +32,4 @@ static bool testTags() { EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "testTag"); EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "true"); EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "false"); - - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - - OK(getFromSocket("/reload")); - - return ret == 0; } - -REGISTER_TEST_FN(testTags) diff --git a/hyprtester/src/tests/main/tests.hpp b/hyprtester/src/tests/main/tests.hpp index 757052b6..7ee4fd4a 100644 --- a/hyprtester/src/tests/main/tests.hpp +++ b/hyprtester/src/tests/main/tests.hpp @@ -1,12 +1,9 @@ #pragma once +#include <map> -#include <vector> -#include <functional> +#include "../shared.hpp" -inline std::vector<std::function<bool()>> testFns; +inline std::map<const char*, CTestCase&> mainTestCases; -#define REGISTER_TEST_FN(fn) \ - static auto _register_fn = [] { \ - testFns.emplace_back(fn); \ - return 1; \ - }(); +// Where `TEST_CASE` macros will store generated test cases: +#define TEST_CASES_STORAGE mainTestCases diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index f8e7c499..730c51be 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -14,8 +14,7 @@ #include "../shared.hpp" #include "tests.hpp" -static int ret = 0; - +// TODO: seems redundant, can just use `Tests::spawnKitty`? static bool spawnKitty(const std::string& class_, const std::vector<std::string>& args = {}) { NLog::log("{}Spawning {}", Colors::YELLOW, class_); if (!Tests::spawnKitty(class_, args)) { @@ -52,31 +51,26 @@ static std::string getWindowAddress(const std::string& winInfo) { auto pos2 = winInfo.find(" -> "); if (pos == std::string::npos || pos2 == std::string::npos) { NLog::log("{}Wrong window info", Colors::RED); - ret = 1; return "Wrong window info"; } return winInfo.substr(pos + 7, pos2 - pos - 7); } -static void testSwapWindow() { - NLog::log("{}Testing swapwindow", Colors::GREEN); - +TEST_CASE(swapWindow) { // test on workspace "swapwindow" NLog::log("{}Switching to workspace \"swapwindow\"", Colors::YELLOW); getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:swapwindow' })"); if (!Tests::spawnKitty("kitty_A")) { - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } if (!Tests::spawnKitty("kitty_B")) { - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } NLog::log("{}Expecting 2 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 2); + ASSERT(Tests::windowCount(), 2); // Test swapwindow by direction { @@ -138,18 +132,9 @@ static void testSwapWindow() { getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", ws)); } - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); } -static void testGroupRules() { - NLog::log("{}Testing group window rules", Colors::YELLOW); - +TEST_CASE(windowGroupRules) { OK(getFromSocket("/eval hl.config({ general = { border_size = 8 } })")); OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'w[tv1]', border_size = 0 })")); OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'f[1]', border_size = 0 })")); @@ -157,8 +142,7 @@ static void testGroupRules() { OK(getFromSocket("/eval hl.window_rule({ match = { workspace = 'f[1]' }, border_size = 0 })")); if (!Tests::spawnKitty("kitty_A")) { - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } { @@ -167,8 +151,7 @@ static void testGroupRules() { } if (!Tests::spawnKitty("kitty_B")) { - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } { @@ -194,8 +177,7 @@ static void testGroupRules() { } if (!Tests::spawnKitty("kitty_C")) { - ret = 1; - return; + FAIL_TEST("Could not spawn kitty"); } OK(getFromSocket("/dispatch hl.dsp.window.move({ out_of_group = 'right' })")); @@ -204,9 +186,6 @@ static void testGroupRules() { auto str = getFromSocket("/getprop active border_size"); EXPECT_CONTAINS(str, "8"); } - - OK(getFromSocket("/reload")); - Tests::killAllWindows(); } static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { @@ -236,11 +215,11 @@ static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0' /// Tests behavior of a window being focused when on that window's workspace /// another fullscreen window exists. -static bool testWindowFocusOnFullscreenConflict() { +TEST_CASE(windowFocusOnFullscreenConflict) { if (!spawnKitty("kitty_A")) - return false; + FAIL_TEST("Could not spawn kitty"); if (!spawnKitty("kitty_B")) - return false; + FAIL_TEST("Could not spawn kitty"); OK(getFromSocket("/eval hl.config({ misc = { focus_on_activate = true } })")); @@ -263,7 +242,7 @@ static bool testWindowFocusOnFullscreenConflict() { // Make a window that will request focus const std::string removeToActivate = spawnKittyActivating(); if (removeToActivate.empty()) - return false; + FAIL_TEST("Could not spawn kitty_activating"); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })")); OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })")); EXPECT(isActiveWindow("kitty_A", '2'), true); @@ -293,7 +272,7 @@ static bool testWindowFocusOnFullscreenConflict() { // Make a window that will request focus const std::string removeToActivate = spawnKittyActivating(); if (removeToActivate.empty()) - return false; + FAIL_TEST("Could not spawn kitty_activating"); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })")); OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })")); EXPECT(isActiveWindow("kitty_A", '2'), true); @@ -318,30 +297,17 @@ static bool testWindowFocusOnFullscreenConflict() { // Make a window that will request focus - the setting is treated normally const std::string removeToActivate = spawnKittyActivating(); if (removeToActivate.empty()) - return false; + FAIL_TEST("Could not spawn kitty_activating"); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })")); OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })")); EXPECT(isActiveWindow("kitty_A", '2'), true); std::filesystem::remove(removeToActivate); EXPECT(waitForActiveWindow("kitty_A", '2'), true); } - - NLog::log("{}Reloading config", Colors::YELLOW); - OK(getFromSocket("/reload")); - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return true; } -static void testMaximizeSize() { - NLog::log("{}Testing maximize size", Colors::GREEN); - - EXPECT(spawnKitty("kitty_A"), true); +TEST_CASE(windowMaximizeSize) { + ASSERT(spawnKitty("kitty_A"), true); // check kitty properties. Maximizing shouldnt change its size { @@ -359,44 +325,28 @@ static void testMaximizeSize() { EXPECT(str.contains("size: 1876,1036"), true); EXPECT(str.contains("fullscreen: 1"), true); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); } -static void testFloatingFocusOnFullscreen() { - NLog::log("{}Testing floating focus on fullscreen", Colors::GREEN); - - EXPECT(spawnKitty("kitty_A"), true); +TEST_CASE(floatingFocusOnFullscreen) { + ASSERT(spawnKitty("kitty_A"), true); OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle' })")); - EXPECT(spawnKitty("kitty_B"), true); + ASSERT(spawnKitty("kitty_B"), true); OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized' })")); OK(getFromSocket("/dispatch hl.dsp.window.cycle_next()")); OK(getFromSocket("/eval hl.plugin.test.floating_focus_on_fullscreen()")); - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); } -static void testGroupFallbackFocus() { - NLog::log("{}Testing group fallback focus", Colors::GREEN); - - EXPECT(spawnKitty("kitty_A"), true); +TEST_CASE(groupFallbackFocus) { + ASSERT(spawnKitty("kitty_A"), true); OK(getFromSocket("/dispatch hl.dsp.group.toggle()")); - EXPECT(spawnKitty("kitty_B"), true); - EXPECT(spawnKitty("kitty_C"), true); - EXPECT(spawnKitty("kitty_D"), true); + ASSERT(spawnKitty("kitty_B"), true); + ASSERT(spawnKitty("kitty_C"), true); + ASSERT(spawnKitty("kitty_D"), true); { auto str = getFromSocket("/activewindow"); @@ -414,27 +364,18 @@ static void testGroupFallbackFocus() { auto str = getFromSocket("/activewindow"); EXPECT(str.contains("class: kitty_B"), true); } - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); } -static void testBringActiveToTopMouseMovement() { - NLog::log("{}Testing bringactivetotop mouse movement", Colors::GREEN); - - Tests::killAllWindows(); +TEST_CASE(bringActiveToTopMouseMovement) { OK(getFromSocket("/eval hl.config({ input = { follow_mouse = 2 } })")); OK(getFromSocket("/eval hl.config({ input = { float_switch_override_focus = 0 } })")); - EXPECT(spawnKitty("a"), true); + ASSERT(spawnKitty("a"), true); OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })")); OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 500, y = 300 })")); OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })")); - EXPECT(spawnKitty("b"), true); + ASSERT(spawnKitty("b"), true); OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })")); OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 500, y = 300 })")); OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })")); @@ -444,31 +385,26 @@ static void testBringActiveToTopMouseMovement() { return (clients.rfind("class: a") > clients.rfind("class: b")) ? "a" : "b"; }; - EXPECT(getTopWindow(), std::string("b")); + ASSERT(getTopWindow(), std::string("b")); OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 700, y = 500 })")); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: a"); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: a"); OK(getFromSocket("/dispatch hl.dsp.window.bring_to_top()")); - EXPECT(getTopWindow(), std::string("a")); + ASSERT(getTopWindow(), std::string("a")); OK(getFromSocket("/eval hl.plugin.test.click(272, 1)")); OK(getFromSocket("/eval hl.plugin.test.click(272, 0)")); - EXPECT(getTopWindow(), std::string("a")); - - Tests::killAllWindows(); + ASSERT(getTopWindow(), std::string("a")); } -static void testInitialFloatSize() { - NLog::log("{}Testing initial float size", Colors::GREEN); - - Tests::killAllWindows(); +TEST_CASE(initialFloatSize) { OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty' }, float = true })")); OK(getFromSocket("/eval hl.config({ input = { float_switch_override_focus = 0 } })")); - EXPECT(spawnKitty("kitty"), true); + ASSERT(spawnKitty("kitty"), true); { // Kitty by default opens as 640x400, if this changes this test will break @@ -490,21 +426,17 @@ static void testInitialFloatSize() { EXPECT(str.contains("size: 640,400"), true); EXPECT(str.contains("floating: 1"), true); } - - Tests::killAllWindows(); } /// Tests that the `focus_on_activate` effect of window rules always overrides /// the `misc:focus_on_activate` variable. -static bool testWindowRuleFocusOnActivate() { - OK(getFromSocket("/reload")); - +TEST_CASE(windowRuleFocusOnActivate) { if (!spawnKitty("kitty_default")) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } // Do not focus anyone automatically + // TODO: this looks like a bug: the following line should not be commented out ///////////OK(getFromSocket("/eval hl.window_rule({ match = { class = '.*' }, no_initial_focus = true })")); // `focus_on_activate off` takes over @@ -514,15 +446,15 @@ static bool testWindowRuleFocusOnActivate() { const std::string removeToActivate = spawnKittyActivating("kitty_antifocus"); if (removeToActivate.empty()) { - return false; + FAIL_TEST("Could not spawn kitty_antifocus"); } - EXPECT(waitForActiveWindow("kitty_antifocus"), true); + ASSERT(waitForActiveWindow("kitty_antifocus"), true); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_default' })")); - EXPECT(isActiveWindow("kitty_default"), true); + ASSERT(isActiveWindow("kitty_default"), true); std::filesystem::remove(removeToActivate); // The focus should NOT transition, since the window rule explicitly forbids that - EXPECT(waitForActiveWindow("kitty_antifocus", '0', false), false); + ASSERT(waitForActiveWindow("kitty_antifocus", '0', false), false); } // `focus_on_activate on` takes over @@ -532,37 +464,24 @@ static bool testWindowRuleFocusOnActivate() { const std::string removeToActivate = spawnKittyActivating("kitty_superfocus"); if (removeToActivate.empty()) { - return false; + FAIL_TEST("Could not spawn kitty_superfocus"); } - EXPECT(waitForActiveWindow("kitty_superfocus"), true); + ASSERT(waitForActiveWindow("kitty_superfocus"), true); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_default' })")); - EXPECT(isActiveWindow("kitty_default"), true); + ASSERT(isActiveWindow("kitty_default"), true); std::filesystem::remove(removeToActivate); // Now that we requested activation, the focus SHOULD transition to kitty_superfocus, according to the window rule - EXPECT(waitForActiveWindow("kitty_superfocus"), true); + ASSERT(waitForActiveWindow("kitty_superfocus"), true); } - - NLog::log("{}Reloading config", Colors::YELLOW); - OK(getFromSocket("/reload")); - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return true; } // tests if a pinned window contains the valid workspace after change -static bool testPinnedWorkspacesValid() { - OK(getFromSocket("/reload")); +TEST_CASE(pinnedWorkspacesValid) { getFromSocket("/dispatch hl.dsp.focus({ workspace = '1337' })"); if (!spawnKitty("kitty")) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:kitty' })")); @@ -582,38 +501,23 @@ static bool testPinnedWorkspacesValid() { EXPECT(str.contains("pinned: 1"), true); } - OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset', window = 'class:kitty' })")) + OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset', window = 'class:kitty' })")); { auto str = getFromSocket("/activewindow"); EXPECT(str.contains("workspace: 1338"), true); EXPECT(str.contains("pinned: 0"), true); } - - NLog::log("{}Reloading config", Colors::YELLOW); - OK(getFromSocket("/reload")); - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return true; } -static bool testWindowRuleWorkspaceEmpty() { - NLog::log("{}Testing windowrule workspace empty", Colors::YELLOW); - OK(getFromSocket("/reload")); - +TEST_CASE(windowruleWorkspaceEmpty) { OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_A' }, workspace = 'empty' })")); OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_B' }, workspace = 'emptyn' })")); getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"); if (!spawnKitty("kitty")) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } { @@ -622,8 +526,7 @@ static bool testWindowRuleWorkspaceEmpty() { } if (!spawnKitty("kitty_A")) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } { @@ -633,23 +536,16 @@ static bool testWindowRuleWorkspaceEmpty() { getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"); if (!spawnKitty("kitty_B")) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return false; + FAIL_TEST("Could not spawn kitty"); } { auto str = getFromSocket("/activewindow"); EXPECT(str.contains("workspace: 4"), true); } - - Tests::killAllWindows(); - - return true; } -static bool testContentRules() { - NLog::log("{}Testing content window rules", Colors::YELLOW); - +TEST_CASE(contentWindowRules) { // kill me PLEASE OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_content_string' }, content = 'game' })")); @@ -657,33 +553,32 @@ static bool testContentRules() { OK(getFromSocket("/eval hl.window_rule({ match = { content = 'game' }, border_size = 10 })")); OK(getFromSocket("/eval hl.window_rule({ match = { content = '3' }, opacity = '0.5' })")); - const auto testProps = []() { - EXPECT_CONTAINS(getFromSocket("/getprop active border_size"), "10"); - EXPECT_CONTAINS(getFromSocket("/getprop active opacity"), "0.5"); - }; +#define TEST_PROPS() \ + EXPECT_CONTAINS(getFromSocket("/getprop active border_size"), "10"); \ + EXPECT_CONTAINS(getFromSocket("/getprop active opacity"), "0.5"); + if (!spawnKitty("kitty_content_string")) - return false; + FAIL_TEST("Could not spawn kitty_content_string"); waitForActiveWindow("kitty_content_string"); - testProps(); + TEST_PROPS(); Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); if (!spawnKitty("kitty_content_numbers")) - return false; + FAIL_TEST("Could not spawn kitty_content_numbers"); waitForActiveWindow("kitty_content_numbers"); - testProps(); + TEST_PROPS(); Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - return true; -} + ASSERT(Tests::windowCount(), 0); -static bool test14038() { - NLog::log("{}Testing #14038 crash", Colors::YELLOW); +#undef TEST_PROPS +} +TEST_CASE(issue14038) { if (!spawnKitty("kitty_14038")) - return false; + FAIL_TEST("Could not spawn kitty"); OK(getFromSocket("/dispatch hl.dsp.window.move({ workspace = 'special:a', follow = false, window = 'class:kitty_14038' })")); OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle', window = 'class:kitty_14038' })")); @@ -691,21 +586,16 @@ static bool test14038() { OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle', window = 'class:kitty_14038' })")); // this should not crash hyprland. If we are alive, we good. - - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - return true; } -static bool test() { - NLog::log("{}Testing windows", Colors::GREEN); - +// TODO: decompose this into multiple test cases +TEST_CASE(windows) { // test on workspace "window" NLog::log("{}Switching to workspace `window`", Colors::YELLOW); getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:window' })"); if (!spawnKitty("kitty_A")) - return false; + FAIL_TEST("Could not spawn kitty"); // check kitty properties. One kitty should take the entire screen, as this is smart gaps NLog::log("{}Expecting kitty_A to take up the whole screen", Colors::YELLOW); @@ -749,7 +639,7 @@ static bool test() { OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 1.25 } })")); if (!spawnKitty("kitty_B")) - return false; + FAIL_TEST("Could not spawn kitty"); NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT)); @@ -761,7 +651,7 @@ static bool test() { OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 0.75 } })")); if (!spawnKitty("kitty_B")) - return false; + FAIL_TEST("Could not spawn kitty"); try { NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT); @@ -791,10 +681,7 @@ static bool test() { EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2); } - } catch (...) { - NLog::log("{}Exception thrown", Colors::RED); - EXPECT(false, true); - } + } catch (...) { FAIL_TEST("Exception thrown"); } OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 1 } })")); } @@ -807,7 +694,7 @@ static bool test() { Tests::waitUntilWindowsN(3); NLog::log("{}Expecting 3 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 3); + ASSERT(Tests::windowCount(), 3); NLog::log("{}Checking props of xeyes", Colors::YELLOW); // check some window props of xeyes, try to float it @@ -825,23 +712,16 @@ static bool test() { Tests::killAllWindows(); NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - testSwapWindow(); + ASSERT(Tests::windowCount(), 0); getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); - if (!testWindowFocusOnFullscreenConflict()) { - ret = 1; - return false; - } - NLog::log("{}Testing spawning a floating window over a fullscreen window", Colors::YELLOW); { if (!spawnKitty("kitty_A")) - return false; + FAIL_TEST("Could not spawn kitty"); OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })")); - EXPECT(Tests::windowCount(), 1); + ASSERT(Tests::windowCount(), 1); OK(getFromSocket("/dispatch hl.dsp.exec_cmd('kitty', { float = true })")); Tests::waitUntilWindowsN(2); @@ -869,7 +749,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', max_size = '1500 500' })")); OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', min_size = '1200 500' })")); if (!spawnKitty("kitty_maxsize")) - return false; + FAIL_TEST("Could not spawn kitty"); auto dwindle = getFromSocket("/activewindow"); EXPECT_CONTAINS(dwindle, "size: 1500,500"); @@ -881,19 +761,19 @@ static bool test() { // EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500"); Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })")); if (!spawnKitty("kitty_maxsize")) - return false; + FAIL_TEST("Could not spawn kitty"); auto master = getFromSocket("/activewindow"); EXPECT_CONTAINS(master, "size: 1500,500"); EXPECT_CONTAINS(master, "at: 210,290"); if (!spawnKitty("kitty_maxsize")) - return false; + FAIL_TEST("Could not spawn kitty"); // FIXME: I can't be arsed. OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_maxsize' })")); @@ -902,7 +782,7 @@ static bool test() { NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); } NLog::log("{}Testing minsize/maxsize rules", Colors::YELLOW); @@ -913,7 +793,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', max_size = '1500 500' })")); OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', min_size = '1200 500' })")); if (!spawnKitty("kitty_maxsize")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto res = getFromSocket("/getprop active max_size"); @@ -930,7 +810,7 @@ static bool test() { NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); } { @@ -940,7 +820,7 @@ static bool test() { OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', min_size = '1200 500' })")); OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', float = true })")); if (!spawnKitty("kitty_maxsize")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto res = getFromSocket("/getprop active max_size"); @@ -962,12 +842,12 @@ static bool test() { NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); } NLog::log("{}Testing window rules", Colors::YELLOW); if (!spawnKitty("wr_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/activewindow"); const int SIZE = 200; @@ -987,7 +867,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ name = 'special-magic-kitty', workspace = 'special:magic' })")); if (!spawnKitty("magic_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/activewindow"); @@ -1005,7 +885,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ name = 'border-magic-kitty', border_color = 'rgba(c6ff00ff) rgba(ff0000ee) 45deg' })")); if (!spawnKitty("border_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:border_kitty' })")); @@ -1019,7 +899,7 @@ static bool test() { Tests::killAllWindows(); if (!spawnKitty("tag_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/activewindow"); @@ -1034,7 +914,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ match = { fullscreen = false }, border_size = 10 })")); if (!spawnKitty("overlap_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/getprop active border_size"); @@ -1048,9 +928,9 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ match = { class = 'persistent_size_kitty' }, persistent_size = true, float = true })")); if (!spawnKitty("persistent_size_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); - OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 600, y = 400 })")) + OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 600, y = 400 })")); { auto str = getFromSocket("/activewindow"); @@ -1061,7 +941,7 @@ static bool test() { Tests::killAllWindows(); if (!spawnKitty("persistent_size_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/activewindow"); @@ -1076,7 +956,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ match = { float = true }, border_size = 10 })")); if (!spawnKitty("border_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/getprop active border_size"); @@ -1105,7 +985,7 @@ static bool test() { "max_size = 'monitor_w*0.75 monitor_h*0.75', move = '20+(monitor_w*0.1) monitor_h*0.5' })")); if (!spawnKitty("expr_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/activewindow"); @@ -1131,7 +1011,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ match = { class = 'plugin_kitty' }, plugin_rule = 'effect' })")); if (!spawnKitty("plugin_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); OK(getFromSocket("/eval hl.plugin.test.check_window_rule()")); @@ -1145,35 +1025,7 @@ static bool test() { OK(getFromSocket("/eval hl.window_rule({ name = 'test-plugin-rule', plugin_rule = 'effect' })")); if (!spawnKitty("plugin_kitty")) - return false; + FAIL_TEST("Could not spawn kitty"); OK(getFromSocket("/eval hl.plugin.test.check_window_rule()")); - - OK(getFromSocket("/reload")); - Tests::killAllWindows(); - - testGroupRules(); - testMaximizeSize(); - testFloatingFocusOnFullscreen(); - testBringActiveToTopMouseMovement(); - testGroupFallbackFocus(); - testInitialFloatSize(); - testWindowRuleFocusOnActivate(); - testPinnedWorkspacesValid(); - testWindowRuleWorkspaceEmpty(); - testContentRules(); - test14038(); - - NLog::log("{}Reloading config", Colors::YELLOW); - OK(getFromSocket("/reload")); - - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return !ret; } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/workspaces.cpp b/hyprtester/src/tests/main/workspaces.cpp index 2975b568..8cde4914 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -11,8 +11,6 @@ #include <cerrno> #include "../shared.hpp" -static int ret = 0; - using namespace Hyprutils::OS; using namespace Hyprutils::Memory; using namespace Hyprutils::Utils; @@ -20,7 +18,11 @@ using namespace Hyprutils::Utils; #define UP CUniquePointer #define SP CSharedPointer -static bool testSpecialWorkspaceFullscreen() { +// All the `SUBTEST`s below are supposed to be independent `TEST_CASE`s. +// But if isolated trivially, some of them fail. +// TODO: investigate and isolate tests by turning `SUBTEST`s into `TEST_CASE`s. + +SUBTEST(specialWorkspaceFullscreen) { NLog::log("{}Testing special workspace fullscreen detection", Colors::YELLOW); CScopeGuard guard = {[&]() { @@ -34,14 +36,14 @@ static bool testSpecialWorkspaceFullscreen() { }}; getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); - EXPECT(Tests::windowCount(), 0); + ASSERT(Tests::windowCount(), 0); NLog::log("{}Test 1: Fullscreen detection on special workspace", Colors::YELLOW); OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'special:test_fs_special' })")); if (!Tests::spawnKitty("kitty_special")) - return false; + FAIL_TEST("Could not spawn kitty"); { auto str = getFromSocket("/activewindow"); @@ -68,7 +70,7 @@ static bool testSpecialWorkspaceFullscreen() { getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); if (!Tests::spawnKitty("kitty_regular")) - return false; + FAIL_TEST("Could not spawn kitty"); OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen' })")); @@ -101,99 +103,91 @@ static bool testSpecialWorkspaceFullscreen() { auto str = getFromSocket("/monitors"); EXPECT_CONTAINS(str, "special workspace: 0 ()"); } - - return true; } -static bool testAsymmetricGaps() { +SUBTEST(asymmetricGaps) { NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW); - { - CScopeGuard guard = {[&]() { - NLog::log("{}Cleaning up asymmetric gap test", Colors::YELLOW); - Tests::killAllWindows(); - OK(getFromSocket("/reload")); - }}; + ASSERT(Tests::windowCount(), 0); + getFromSocket("/dispatch workspace 1"); + getFromSocket("/reload"); - OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:gap_split_test' })")); - OK(getFromSocket("r/eval hl.config({ general = { gaps_in = 0 } })")); - OK(getFromSocket("r/eval hl.config({ general = { border_size = 0 } })")); - OK(getFromSocket("r/eval hl.config({ dwindle = { split_width_multiplier = 1.0 } })")); - OK(getFromSocket("r/eval hl.workspace_rule({ workspace = 'name:gap_split_test', gaps_out = { top = 0, right = 1000, bottom = 0, left = 0 } })")); + OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:gap_split_test' })")); + OK(getFromSocket("r/eval hl.config({ general = { gaps_in = 0 } })")); + OK(getFromSocket("r/eval hl.config({ general = { border_size = 0 } })")); + OK(getFromSocket("r/eval hl.config({ dwindle = { split_width_multiplier = 1.0 } })")); + OK(getFromSocket("r/eval hl.workspace_rule({ workspace = 'name:gap_split_test', gaps_out = { top = 0, right = 1000, bottom = 0, left = 0 } })")); - NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW); - OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 0 } })")); + NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW); + OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 0 } })")); - if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) - return false; + if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) + FAIL_TEST("Could not spawn kitty"); - NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); + NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); - NLog::log("{}Testing force_split = 1", Colors::YELLOW); - OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 1 } })")); + NLog::log("{}Testing force_split = 1", Colors::YELLOW); + OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 1 } })")); - if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) - return false; + if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) + FAIL_TEST("Could not spawn kitty"); - NLog::log("{}Expecting vertical split (B above A)", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); + NLog::log("{}Expecting vertical split (B above A)", Colors::YELLOW); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); - NLog::log("{}Expecting horizontal split (C left of B)", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); + NLog::log("{}Expecting horizontal split (C left of B)", Colors::YELLOW); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); - if (!Tests::spawnKitty("gaps_kitty_C")) - return false; + if (!Tests::spawnKitty("gaps_kitty_C")) + FAIL_TEST("Could not spawn kitty"); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_C' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_C' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); - NLog::log("{}Testing force_split = 2", Colors::YELLOW); - OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 2 } })")); + NLog::log("{}Testing force_split = 2", Colors::YELLOW); + OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 2 } })")); - if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) - return false; + if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) + FAIL_TEST("Could not spawn kitty"); - NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); + NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); - NLog::log("{}Expecting horizontal split (C right of A)", Colors::YELLOW); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); + NLog::log("{}Expecting horizontal split (C right of A)", Colors::YELLOW); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); - if (!Tests::spawnKitty("gaps_kitty_C")) - return false; + if (!Tests::spawnKitty("gaps_kitty_C")) + FAIL_TEST("Could not spawn kitty"); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_C' })")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); - } + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_C' })")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); // kill all NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); - - return true; } -static void testWorkspaceHistoryMultiMon() { +SUBTEST(workspaceHistoryMultiMon) { NLog::log("{}Testing multimon workspace history tracker", Colors::YELLOW); // Initial state: @@ -222,7 +216,7 @@ static void testWorkspaceHistoryMultiMon() { Tests::killAllWindows(); } -static void testMultimonBAF() { +SUBTEST(multimonBAF) { NLog::log("{}Testing multimon back and forth", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ binds = { workspace_back_and_forth = 1 } })")); @@ -284,7 +278,7 @@ static void testMultimonBAF() { Tests::killAllWindows(); } -static void testMultimonFocus() { +SUBTEST(multimonFocus) { NLog::log("{}Testing multimon focus and move", Colors::YELLOW); OK(getFromSocket("/eval hl.config({ input = { follow_mouse = 0 } })")); @@ -295,10 +289,7 @@ static void testMultimonFocus() { for (auto const& win : {"a", "b"}) { if (!Tests::spawnKitty(win)) { - NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); - ++TESTS_FAILED; - ret = 1; - return; + FAIL_TEST("Could not spawn kitty with win class `{}`", win); } } @@ -350,24 +341,24 @@ static void testMultimonFocus() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: b"); + ASSERT_CONTAINS(str, "class: b"); } { auto str = getFromSocket("/activeworkspace"); - EXPECT_CONTAINS(str, "workspace ID 8 "); + ASSERT_CONTAINS(str, "workspace ID 8 "); } OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: c"); + ASSERT_CONTAINS(str, "class: c"); } { auto str = getFromSocket("/activeworkspace"); - EXPECT_CONTAINS(str, "workspace ID 8 "); + ASSERT_CONTAINS(str, "workspace ID 8 "); } OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })")); @@ -375,28 +366,28 @@ static void testMultimonFocus() { OK(getFromSocket("/eval hl.config({ general = { no_focus_fallback = true } })")); OK(getFromSocket("/eval hl.config({ binds = { window_direction_monitor_fallback = false } })")); - EXPECT_NOT(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })"), "ok"); + ASSERT_NOT(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })"), "ok"); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: b"); + ASSERT_CONTAINS(str, "class: b"); } { auto str = getFromSocket("/activeworkspace"); - EXPECT_CONTAINS(str, "workspace ID 8 "); + ASSERT_CONTAINS(str, "workspace ID 8 "); } OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'left' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: b"); + ASSERT_CONTAINS(str, "class: b"); } { auto str = getFromSocket("/activeworkspace"); - EXPECT_CONTAINS(str, "workspace ID 8 "); + ASSERT_CONTAINS(str, "workspace ID 8 "); } OK(getFromSocket("/reload")); @@ -404,7 +395,7 @@ static void testMultimonFocus() { Tests::killAllWindows(); } -static void testDynamicWsEffects() { +SUBTEST(dynamicWsEffects) { // test dynamic workspace effects, they shouldn't lag OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '69' })")); @@ -414,29 +405,22 @@ static void testDynamicWsEffects() { OK(getFromSocket("r/eval hl.workspace_rule({ workspace = '69', border_size = 20 })")); OK(getFromSocket("r/eval hl.workspace_rule({ workspace = '69', no_rounding = true })")); - EXPECT(getFromSocket("/getprop class:bitch border_size"), "20"); - EXPECT(getFromSocket("/getprop class:bitch rounding"), "0"); + ASSERT(getFromSocket("/getprop class:bitch border_size"), "20"); + ASSERT(getFromSocket("/getprop class:bitch rounding"), "0"); OK(getFromSocket("/reload")); Tests::killAllWindows(); } -static bool test() { - NLog::log("{}Testing workspaces", Colors::GREEN); - - EXPECT(Tests::windowCount(), 0); - - // test on workspace "window" - NLog::log("{}Switching to workspace 1", Colors::YELLOW); - getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); - +// TODO: decompose this into multiple test cases +TEST_CASE(workspacesCombined) { NLog::log("{}Checking persistent no-mon", Colors::YELLOW); OK(getFromSocket("r/eval hl.workspace_rule({ workspace = '966', persistent = true })")); { auto str = getFromSocket("/workspaces"); - EXPECT_CONTAINS(str, "workspace ID 966 (966)"); + ASSERT_CONTAINS(str, "workspace ID 966 (966)"); } OK(getFromSocket("/reload")); @@ -444,10 +428,8 @@ static bool test() { NLog::log("{}Spawning kittyProc on ws 1", Colors::YELLOW); auto kittyProcA = Tests::spawnKitty(); - if (!kittyProcA) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; - } + if (!kittyProcA) + FAIL_TEST("Could not spawn kitty"); NLog::log("{}Switching to workspace 3", Colors::YELLOW); OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })")); @@ -455,10 +437,8 @@ static bool test() { NLog::log("{}Spawning kittyProc on ws 3", Colors::YELLOW); auto kittyProcB = Tests::spawnKitty(); - if (!kittyProcB) { - NLog::log("{}Error: kitty did not spawn", Colors::RED); - return false; - } + if (!kittyProcB) + FAIL_TEST("Could not spawn kitty"); NLog::log("{}Switching to workspace 1", Colors::YELLOW); OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })")); @@ -468,14 +448,14 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 2 (2)"); + ASSERT_STARTS_WITH(str, "workspace ID 2 (2)"); } // check if the other workspaces are alive { auto str = getFromSocket("/workspaces"); - EXPECT_CONTAINS(str, "workspace ID 3 (3)"); - EXPECT_CONTAINS(str, "workspace ID 1 (1)"); + ASSERT_CONTAINS(str, "workspace ID 3 (3)"); + ASSERT_CONTAINS(str, "workspace ID 1 (1)"); } NLog::log("{}Switching to workspace 1", Colors::YELLOW); @@ -483,7 +463,7 @@ static bool test() { { auto str = getFromSocket("/workspaces"); - EXPECT_NOT_CONTAINS(str, "workspace ID 2 (2)"); + ASSERT_NOT_CONTAINS(str, "workspace ID 2 (2)"); } NLog::log("{}Switching to workspace m+1", Colors::YELLOW); @@ -491,7 +471,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 3 (3)"); + ASSERT_STARTS_WITH(str, "workspace ID 3 (3)"); } NLog::log("{}Switching to workspace -1", Colors::YELLOW); @@ -499,7 +479,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 2 (2)"); + ASSERT_STARTS_WITH(str, "workspace ID 2 (2)"); } NLog::log("{}Switching to workspace 1", Colors::YELLOW); @@ -510,7 +490,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 2 (2)"); + ASSERT_STARTS_WITH(str, "workspace ID 2 (2)"); } NLog::log("{}Switching to workspace r+1", Colors::YELLOW); @@ -518,7 +498,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 3 (3)"); + ASSERT_STARTS_WITH(str, "workspace ID 3 (3)"); } NLog::log("{}Switching to workspace r~1", Colors::YELLOW); @@ -526,7 +506,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 1 (1)"); + ASSERT_STARTS_WITH(str, "workspace ID 1 (1)"); } NLog::log("{}Switching to workspace empty", Colors::YELLOW); @@ -534,7 +514,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 2 (2)"); + ASSERT_STARTS_WITH(str, "workspace ID 2 (2)"); } NLog::log("{}Switching to workspace previous", Colors::YELLOW); @@ -542,7 +522,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 1 (1)"); + ASSERT_STARTS_WITH(str, "workspace ID 1 (1)"); } NLog::log("{}Switching to workspace name:TEST_WORKSPACE_NULL", Colors::YELLOW); @@ -550,7 +530,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID -1337 (TEST_WORKSPACE_NULL)"); + ASSERT_STARTS_WITH(str, "workspace ID -1337 (TEST_WORKSPACE_NULL)"); } NLog::log("{}Switching to workspace 1", Colors::YELLOW); @@ -558,14 +538,14 @@ static bool test() { // add a new monitor NLog::log("{}Adding a new monitor", Colors::YELLOW); - EXPECT(getFromSocket("/output create headless"), "ok") + ASSERT(getFromSocket("/output create headless"), "ok"); // should take workspace 2 { auto str = getFromSocket("/monitors"); - EXPECT_CONTAINS(str, "active workspace: 2 (2)"); - EXPECT_CONTAINS(str, "active workspace: 1 (1)"); - EXPECT_CONTAINS(str, "HEADLESS-3"); + ASSERT_CONTAINS(str, "active workspace: 2 (2)"); + ASSERT_CONTAINS(str, "active workspace: 1 (1)"); + ASSERT_CONTAINS(str, "HEADLESS-3"); } // focus the first monitor @@ -573,7 +553,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 1 (1)"); + ASSERT_STARTS_WITH(str, "workspace ID 1 (1)"); } NLog::log("{}Switching to workspace r+1", Colors::YELLOW); @@ -581,7 +561,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 3 (3)"); + ASSERT_STARTS_WITH(str, "workspace ID 3 (3)"); } NLog::log("{}Switching to workspace r~2", Colors::YELLOW); @@ -590,7 +570,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 3 (3)"); + ASSERT_STARTS_WITH(str, "workspace ID 3 (3)"); } NLog::log("{}Switching to workspace m+1", Colors::YELLOW); @@ -598,7 +578,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 1 (1)"); + ASSERT_STARTS_WITH(str, "workspace ID 1 (1)"); } NLog::log("{}Switching to workspace 1", Colors::YELLOW); @@ -607,7 +587,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 1 (1)"); + ASSERT_STARTS_WITH(str, "workspace ID 1 (1)"); } NLog::log("{}Testing back_and_forth", Colors::YELLOW); @@ -616,7 +596,7 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 3 (3)"); + ASSERT_STARTS_WITH(str, "workspace ID 3 (3)"); } OK(getFromSocket("/eval hl.config({ binds = { workspace_back_and_forth = false } })")); @@ -627,8 +607,8 @@ static bool test() { { auto str = getFromSocket("/monitors"); - EXPECT_CONTAINS(str, "special workspace: -"); - EXPECT_CONTAINS(str, "special:HELLO"); + ASSERT_CONTAINS(str, "special workspace: -"); + ASSERT_CONTAINS(str, "special:HELLO"); } // no OK: will err (it shouldn't prolly but oh well) @@ -636,7 +616,7 @@ static bool test() { { auto str = getFromSocket("/monitors"); - EXPECT_COUNT_STRING(str, "special workspace: 0 ()", 2); + ASSERT_COUNT_STRING(str, "special workspace: 0 ()", 2); } OK(getFromSocket("/eval hl.config({ binds = { hide_special_on_workspace_change = false } })")); @@ -652,21 +632,21 @@ static bool test() { { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 3 (3)"); + ASSERT_STARTS_WITH(str, "workspace ID 3 (3)"); } OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })")); { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 1 (1)"); + ASSERT_STARTS_WITH(str, "workspace ID 1 (1)"); } OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })")); { auto str = getFromSocket("/activeworkspace"); - EXPECT_STARTS_WITH(str, "workspace ID 3 (3)"); + ASSERT_STARTS_WITH(str, "workspace ID 3 (3)"); } OK(getFromSocket("/eval hl.config({ binds = { allow_workspace_cycles = false } })")); @@ -700,7 +680,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: kitty_C"); + ASSERT_CONTAINS(str, "class: kitty_C"); } OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })")); @@ -711,7 +691,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: kitty_B"); + ASSERT_CONTAINS(str, "class: kitty_B"); } NLog::log("{}Testing movefocus_cycles_fullscreen", Colors::YELLOW); @@ -720,14 +700,14 @@ static bool test() { Tests::spawnKitty("kitty_D"); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: kitty_D"); + ASSERT_CONTAINS(str, "class: kitty_D"); } OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'l' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: kitty_A"); + ASSERT_CONTAINS(str, "class: kitty_A"); } OK(getFromSocket("/eval hl.config({ binds = { movefocus_cycles_fullscreen = false } })")); @@ -737,14 +717,14 @@ static bool test() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: kitty_D"); + ASSERT_CONTAINS(str, "class: kitty_D"); } OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'l' })")); { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: kitty_A"); + ASSERT_CONTAINS(str, "class: kitty_A"); } OK(getFromSocket("/eval hl.config({ binds = { movefocus_cycles_fullscreen = true } })")); @@ -753,28 +733,24 @@ static bool test() { { auto str = getFromSocket("/activewindow"); - EXPECT_CONTAINS(str, "class: kitty_B"); + ASSERT_CONTAINS(str, "class: kitty_B"); } // kill all NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); - testMultimonBAF(); - testMultimonFocus(); - testWorkspaceHistoryMultiMon(); + CALL_SUBTEST(multimonBAF); + CALL_SUBTEST(multimonFocus); + CALL_SUBTEST(workspaceHistoryMultiMon); // destroy the headless output OK(getFromSocket("/output remove HEADLESS-3")); - testSpecialWorkspaceFullscreen(); - testAsymmetricGaps(); - testDynamicWsEffects(); + CALL_SUBTEST(specialWorkspaceFullscreen); + CALL_SUBTEST(asymmetricGaps); + CALL_SUBTEST(dynamicWsEffects); NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return !ret; + ASSERT(Tests::windowCount(), 0); } - -REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/shared.cpp b/hyprtester/src/tests/shared.cpp index fd158a3e..03a64751 100644 --- a/hyprtester/src/tests/shared.cpp +++ b/hyprtester/src/tests/shared.cpp @@ -10,6 +10,10 @@ using namespace Hyprutils::OS; using namespace Hyprutils::Memory; +// Almost everywhere `Tests::spawnKitty` is used, the return value is immediately tested against `nullptr` +// and the test fails if it is. +// TODO: add a test macro for that. + CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std::vector<std::string> args) { const auto COUNT_BEFORE = windowCount(); |
