Core: Add a test suite (#9297)
Adds a test suite for testing hyprland's features with a runtime tester --------- Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
parent
9a67e0421b
commit
3d6476c902
29 changed files with 2096 additions and 15 deletions
17
hyprtester/src/Log.hpp
Normal file
17
hyprtester/src/Log.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <format>
|
||||
#include <print>
|
||||
|
||||
namespace NLog {
|
||||
template <typename... Args>
|
||||
//NOLINTNEXTLINE
|
||||
void log(std::format_string<Args...> fmt, Args&&... args) {
|
||||
std::string logMsg = "";
|
||||
|
||||
logMsg += std::vformat(fmt.get(), std::make_format_args(args...));
|
||||
|
||||
std::println("{}", logMsg);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
136
hyprtester/src/hyprctlCompat.cpp
Normal file
136
hyprtester/src/hyprctlCompat.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include "hyprctlCompat.hpp"
|
||||
#include "shared.hpp"
|
||||
|
||||
#include <pwd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include <print>
|
||||
|
||||
static int getUID() {
|
||||
const auto UID = getuid();
|
||||
const auto PWUID = getpwuid(UID);
|
||||
return PWUID ? PWUID->pw_uid : UID;
|
||||
}
|
||||
|
||||
static std::string getRuntimeDir() {
|
||||
const auto XDG = getenv("XDG_RUNTIME_DIR");
|
||||
|
||||
if (!XDG) {
|
||||
const std::string USERID = std::to_string(getUID());
|
||||
return "/run/user/" + USERID + "/hypr";
|
||||
}
|
||||
|
||||
return std::string{XDG} + "/hypr";
|
||||
}
|
||||
|
||||
std::vector<SInstanceData> instances() {
|
||||
std::vector<SInstanceData> result;
|
||||
|
||||
try {
|
||||
if (!std::filesystem::exists(getRuntimeDir()))
|
||||
return {};
|
||||
} 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"))
|
||||
continue;
|
||||
|
||||
// read lock
|
||||
SInstanceData* data = &result.emplace_back();
|
||||
data->id = el.path().filename().string();
|
||||
|
||||
try {
|
||||
data->time = std::stoull(data->id.substr(data->id.find_first_of('_') + 1, data->id.find_last_of('_') - (data->id.find_first_of('_') + 1)));
|
||||
} catch (std::exception& e) { continue; }
|
||||
|
||||
// read file
|
||||
std::ifstream ifs(el.path().string() + "/hyprland.lock");
|
||||
|
||||
int i = 0;
|
||||
for (std::string line; std::getline(ifs, line); ++i) {
|
||||
if (i == 0) {
|
||||
try {
|
||||
data->pid = std::stoull(line);
|
||||
} catch (std::exception& e) { continue; }
|
||||
} else if (i == 1) {
|
||||
data->wlSocket = line;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
ifs.close();
|
||||
}
|
||||
|
||||
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; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string getFromSocket(const std::string& cmd) {
|
||||
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
auto t = timeval{.tv_sec = 5, .tv_usec = 0};
|
||||
setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval));
|
||||
|
||||
if (SERVERSOCKET < 0) {
|
||||
std::println("socket: Couldn't open a socket (1)");
|
||||
return "";
|
||||
}
|
||||
|
||||
sockaddr_un serverAddress = {0};
|
||||
serverAddress.sun_family = AF_UNIX;
|
||||
|
||||
std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock";
|
||||
|
||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||
|
||||
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
|
||||
std::println("Couldn't connect to {}. (3)", socketPath);
|
||||
return "";
|
||||
}
|
||||
|
||||
auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
std::println("Couldn't write (4)");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string reply = "";
|
||||
char buffer[8192] = {0};
|
||||
|
||||
sizeWritten = read(SERVERSOCKET, buffer, 8192);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
std::println("Hyprland IPC didn't respond in time");
|
||||
std::println("Couldn't read (5)");
|
||||
return "";
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
|
||||
while (sizeWritten == 8192) {
|
||||
sizeWritten = read(SERVERSOCKET, buffer, 8192);
|
||||
if (sizeWritten < 0) {
|
||||
std::println("Couldn't read (5)");
|
||||
return "";
|
||||
}
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
}
|
||||
|
||||
close(SERVERSOCKET);
|
||||
|
||||
return reply;
|
||||
}
|
||||
16
hyprtester/src/hyprctlCompat.hpp
Normal file
16
hyprtester/src/hyprctlCompat.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
struct SInstanceData {
|
||||
std::string id;
|
||||
uint64_t time;
|
||||
uint64_t pid;
|
||||
std::string wlSocket;
|
||||
bool valid = true;
|
||||
};
|
||||
|
||||
std::vector<SInstanceData> instances();
|
||||
std::string getFromSocket(const std::string& cmd);
|
||||
251
hyprtester/src/main.cpp
Normal file
251
hyprtester/src/main.cpp
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
|
||||
// This is a tester for Hyprland. It will launch the built binary in ./build/Hyprland
|
||||
// in headless mode and test various things.
|
||||
// for now it's quite basic and limited, but will be expanded in the future.
|
||||
|
||||
// NOTE: This tester has to be ran from its directory!!
|
||||
|
||||
// Some TODO:
|
||||
// - Add a plugin built alongside so that we can do more detailed tests (e.g. simulating keystrokes)
|
||||
// - test coverage
|
||||
// - maybe figure out a way to do some visual tests too?
|
||||
|
||||
// Required runtime deps for checks:
|
||||
// - kitty
|
||||
// - xeyes
|
||||
|
||||
#include "shared.hpp"
|
||||
#include "hyprctlCompat.hpp"
|
||||
#include "tests/main/tests.hpp"
|
||||
#include "tests/plugin/plugin.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <print>
|
||||
|
||||
#include "Log.hpp"
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#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.conf", ec) || ec) {
|
||||
NLog::log("{}No test config", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
configPath = cwd + "/test.conf";
|
||||
}
|
||||
|
||||
NLog::log("{}Launching Hyprland", Colors::YELLOW);
|
||||
hyprlandProc = makeShared<CProcess>(binaryPath, std::vector<std::string>{"--config", configPath});
|
||||
hyprlandProc->addEnv("HYPRLAND_HEADLESS_ONLY", "1");
|
||||
|
||||
NLog::log("{}Launched async process", Colors::YELLOW);
|
||||
|
||||
return hyprlandProc->runAsync();
|
||||
}
|
||||
|
||||
static bool hyprlandAlive() {
|
||||
NLog::log("{}hyprlandAlive", Colors::YELLOW);
|
||||
kill(hyprlandProc->pid(), 0);
|
||||
return errno != ESRCH;
|
||||
}
|
||||
|
||||
static void help() {
|
||||
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();
|
||||
|
||||
std::vector<std::string> args{argv + 1, argv + argc};
|
||||
|
||||
for (auto it = args.begin(); it != args.end(); it++) {
|
||||
if (*it == "--config" || *it == "-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++;
|
||||
|
||||
continue;
|
||||
} else if (*it == "--binary" || *it == "-b") {
|
||||
if (std::next(it) == args.end()) {
|
||||
help();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
binaryPath = *std::next(it);
|
||||
|
||||
try {
|
||||
binaryPath = std::filesystem::canonical(binaryPath);
|
||||
|
||||
if (!std::filesystem::is_regular_file(binaryPath)) {
|
||||
throw std::exception();
|
||||
}
|
||||
} catch (...) {
|
||||
std::println(stderr, "[ ERROR ] Binary '{}' doesn't exist!", binaryPath);
|
||||
help();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
it++;
|
||||
|
||||
continue;
|
||||
} else if (*it == "--plugin" || *it == "-p") {
|
||||
if (std::next(it) == args.end()) {
|
||||
help();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
pluginPath = *std::next(it);
|
||||
|
||||
try {
|
||||
pluginPath = std::filesystem::canonical(pluginPath);
|
||||
|
||||
if (!std::filesystem::is_regular_file(pluginPath)) {
|
||||
throw std::exception();
|
||||
}
|
||||
} catch (...) {
|
||||
std::println(stderr, "[ ERROR ] plugin '{}' doesn't exist!", pluginPath);
|
||||
help();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
it++;
|
||||
|
||||
continue;
|
||||
} else if (*it == "--help" || *it == "-h") {
|
||||
help();
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
std::println(stderr, "[ ERROR ] Unknown option '{}' !", *it);
|
||||
help();
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
NLog::log("{}launching hl", Colors::YELLOW);
|
||||
if (!launchHyprland(configPath, binaryPath)) {
|
||||
NLog::log("{}well it failed", Colors::RED);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// hyprland has launched, let's check if it's alive after 10s
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
|
||||
NLog::log("{}slept for 10s", Colors::YELLOW);
|
||||
if (!hyprlandAlive()) {
|
||||
NLog::log("{}Hyprland failed to launch", Colors::RED);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// wonderful, we are in. Let's get the instance signature.
|
||||
NLog::log("{}trying to get INSTANCES", Colors::YELLOW);
|
||||
const auto INSTANCES = instances();
|
||||
if (INSTANCES.empty()) {
|
||||
NLog::log("{}Hyprland failed to launch (2)", Colors::RED);
|
||||
return 1;
|
||||
}
|
||||
|
||||
HIS = INSTANCES.back().id;
|
||||
WLDISPLAY = INSTANCES.back().wlSocket;
|
||||
|
||||
NLog::log("{}trying to get create headless output", Colors::YELLOW);
|
||||
getFromSocket("/output create headless");
|
||||
|
||||
NLog::log("{}trying to load plugin", Colors::YELLOW);
|
||||
if (const auto R = getFromSocket(std::format("/plugin load {}", pluginPath)); R != "ok") {
|
||||
NLog::log("{}Failed to load the test plugin: {}", Colors::RED, R);
|
||||
getFromSocket("/dispatch exit 1");
|
||||
return 1;
|
||||
}
|
||||
|
||||
NLog::log("{}Loaded plugin", Colors::YELLOW);
|
||||
|
||||
// now we can start issuing stuff.
|
||||
NLog::log("{}testing windows", Colors::YELLOW);
|
||||
EXPECT(testWindows(), true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
||||
|
||||
NLog::log("{}testing groups", Colors::YELLOW);
|
||||
EXPECT(testGroups(), true);
|
||||
|
||||
NLog::log("{}testing workspaces", Colors::YELLOW);
|
||||
EXPECT(testWorkspaces(), true);
|
||||
|
||||
NLog::log("{}testing misc variables", Colors::YELLOW);
|
||||
EXPECT(testMisc(), true);
|
||||
|
||||
NLog::log("{}running plugin test", Colors::YELLOW);
|
||||
EXPECT(testPlugin(), true);
|
||||
|
||||
// kill hyprland
|
||||
NLog::log("{}dispatching exit", Colors::YELLOW);
|
||||
getFromSocket("/dispatch 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" : ""));
|
||||
|
||||
kill(hyprlandProc->pid(), SIGKILL);
|
||||
|
||||
hyprlandProc.reset();
|
||||
|
||||
return ret || TESTS_FAILED;
|
||||
}
|
||||
90
hyprtester/src/shared.hpp
Normal file
90
hyprtester/src/shared.hpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Stolen from hyprutils
|
||||
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
|
||||
inline std::string HIS = "";
|
||||
inline std::string WLDISPLAY = "";
|
||||
inline int TESTS_PASSED = 0;
|
||||
inline int TESTS_FAILED = 0;
|
||||
|
||||
namespace Colors {
|
||||
constexpr const char* RED = "\x1b[31m";
|
||||
constexpr const char* GREEN = "\x1b[32m";
|
||||
constexpr const char* YELLOW = "\x1b[33m";
|
||||
constexpr const char* BLUE = "\x1b[34m";
|
||||
constexpr const char* MAGENTA = "\x1b[35m";
|
||||
constexpr const char* CYAN = "\x1b[36m";
|
||||
constexpr const char* RESET = "\x1b[0m";
|
||||
};
|
||||
|
||||
#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++; \
|
||||
}
|
||||
|
||||
#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++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{}. Got [{}, {}].", Colors::GREEN, Colors::RESET, #expr, RESULT.x, RESULT.y); \
|
||||
TESTS_PASSED++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EXPECT_CONTAINS(haystack, needle) \
|
||||
if (!std::string{haystack}.contains(needle)) { \
|
||||
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++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{} contains {}.", Colors::GREEN, Colors::RESET, #haystack, #needle); \
|
||||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#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++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{} doesn't contain {}.", Colors::GREEN, Colors::RESET, #haystack, #needle); \
|
||||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#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++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{} starts with {}.", Colors::GREEN, Colors::RESET, #str, #what); \
|
||||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#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++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{} contains {} {} times.", Colors::GREEN, Colors::RESET, #str, #what, no); \
|
||||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#define OK(x) EXPECT(x, "ok")
|
||||
177
hyprtester/src/tests/main/groups.cpp
Normal file
177
hyprtester/src/tests/main/groups.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <print>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include "../shared.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
bool testGroups() {
|
||||
NLog::log("{}Testing groups", Colors::GREEN);
|
||||
|
||||
// test on workspace "window"
|
||||
NLog::log("{}Dispatching workspace `groups`", Colors::YELLOW);
|
||||
getFromSocket("/dispatch workspace name:groups");
|
||||
|
||||
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
|
||||
auto kittyProcA = Tests::spawnKitty();
|
||||
if (!kittyProcA) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 1 window", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 1);
|
||||
|
||||
// check kitty properties. One kitty should take the entire screen, minus the gaps.
|
||||
NLog::log("{}Check kitty dimensions", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
|
||||
EXPECT_COUNT_STRING(str, "size: 1876,1036", 1);
|
||||
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
|
||||
}
|
||||
|
||||
// group the kitty
|
||||
NLog::log("{}Enable group and groupbar", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
OK(getFromSocket("/keyword group:groupbar:enabled 1"));
|
||||
|
||||
// check the height of the window now
|
||||
NLog::log("{}Recheck kitty dimensions", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 22,43");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1015");
|
||||
}
|
||||
|
||||
// disable the groupbar for ease of testing for now
|
||||
NLog::log("{}Disable groupbar", Colors::YELLOW);
|
||||
OK(getFromSocket("r/keyword group:groupbar:enabled 0"));
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Kill windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Spawn kitty again", Colors::YELLOW);
|
||||
kittyProcA = Tests::spawnKitty();
|
||||
if (!kittyProcA) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Group kitty", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
|
||||
// check the height of the window now
|
||||
NLog::log("{}Check kitty dimensions 2", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
}
|
||||
|
||||
NLog::log("{}Spawn kittyProcB", Colors::YELLOW);
|
||||
auto kittyProcB = Tests::spawnKitty();
|
||||
if (!kittyProcB) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 2);
|
||||
|
||||
size_t lastActiveKittyIdx = 0;
|
||||
|
||||
NLog::log("{}Get last active kitty id", Colors::YELLOW);
|
||||
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;
|
||||
}
|
||||
|
||||
// test cycling through
|
||||
|
||||
NLog::log("{}Test cycling through grouped windows", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch changegroupactive f"));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
getFromSocket("/dispatch changegroupactive f");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
NLog::log("{}Disable autogrouping", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword group:auto_group false"));
|
||||
|
||||
NLog::log("{}Spawn kittyProcC", Colors::YELLOW);
|
||||
auto kittyProcC = Tests::spawnKitty();
|
||||
if (!kittyProcC) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 3 windows 2", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 3);
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus l"));
|
||||
OK(getFromSocket("/dispatch changegroupactive 1"));
|
||||
OK(getFromSocket("/keyword group:auto_group true"));
|
||||
OK(getFromSocket("/keyword group:insert_after_current false"));
|
||||
|
||||
NLog::log("{}Spawn kittyProcD", Colors::YELLOW);
|
||||
auto kittyProcD = Tests::spawnKitty();
|
||||
if (!kittyProcD) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 4 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 4);
|
||||
|
||||
OK(getFromSocket("/dispatch changegroupactive 3"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, std::format("pid: {}", kittyProcD->pid()));
|
||||
}
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Kill windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
142
hyprtester/src/tests/main/misc.cpp
Normal file
142
hyprtester/src/tests/main/misc.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <print>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include "../shared.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
bool testMisc() {
|
||||
NLog::log("{}Testing config: misc:", Colors::GREEN);
|
||||
|
||||
NLog::log("{}Testing close_special_on_empty", Colors::YELLOW);
|
||||
|
||||
OK(getFromSocket("/keyword misc:close_special_on_empty false"));
|
||||
OK(getFromSocket("/dispatch workspace special:test"));
|
||||
|
||||
Tests::spawnKitty();
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/monitors");
|
||||
EXPECT_CONTAINS(str, "special workspace: -");
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/monitors");
|
||||
EXPECT_CONTAINS(str, "special workspace: -");
|
||||
}
|
||||
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/keyword misc:close_special_on_empty true"));
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/monitors");
|
||||
EXPECT_NOT_CONTAINS(str, "special workspace: -");
|
||||
}
|
||||
|
||||
NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW);
|
||||
|
||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
|
||||
|
||||
Tests::spawnKitty("kitty_A");
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "kitty_A");
|
||||
}
|
||||
|
||||
Tests::spawnKitty("kitty_B");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "kitty_A");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 1"));
|
||||
|
||||
Tests::spawnKitty("kitty_C");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "kitty_C");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 2"));
|
||||
|
||||
Tests::spawnKitty("kitty_D");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
EXPECT_CONTAINS(str, "kitty_D");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Testing exit_window_retains_fullscreen", Colors::YELLOW);
|
||||
|
||||
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen false"));
|
||||
|
||||
Tests::spawnKitty("kitty_A");
|
||||
Tests::spawnKitty("kitty_B");
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch killwindow activewindow"));
|
||||
Tests::waitUntilWindowsN(1);
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
}
|
||||
|
||||
Tests::spawnKitty("kitty_B");
|
||||
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen true"));
|
||||
|
||||
OK(getFromSocket("/dispatch killwindow activewindow"));
|
||||
Tests::waitUntilWindowsN(1);
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
}
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
6
hyprtester/src/tests/main/tests.hpp
Normal file
6
hyprtester/src/tests/main/tests.hpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
bool testGroups();
|
||||
bool testWindows();
|
||||
bool testWorkspaces();
|
||||
bool testMisc();
|
||||
96
hyprtester/src/tests/main/window.cpp
Normal file
96
hyprtester/src/tests/main/window.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <print>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include "../shared.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
bool testWindows() {
|
||||
NLog::log("{}Testing windows", Colors::GREEN);
|
||||
|
||||
// test on workspace "window"
|
||||
NLog::log("{}Switching to workspace `window`", Colors::YELLOW);
|
||||
getFromSocket("/dispatch workspace name:window");
|
||||
|
||||
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
|
||||
auto kittyProcA = Tests::spawnKitty();
|
||||
|
||||
if (!kittyProcA) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 1 window", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 1);
|
||||
|
||||
// check kitty properties. One kitty should take the entire screen, as this is smart gaps
|
||||
NLog::log("{}Expecting kitty to take up the whole screen", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT(str.contains("at: 0,0"), true);
|
||||
EXPECT(str.contains("size: 1920,1080"), true);
|
||||
EXPECT(str.contains("fullscreen: 0"), true);
|
||||
}
|
||||
|
||||
NLog::log("{}Spawning kittyProcB", Colors::YELLOW);
|
||||
auto kittyProcB = Tests::spawnKitty();
|
||||
if (!kittyProcB) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 2);
|
||||
|
||||
// open xeyes
|
||||
NLog::log("{}Spawning xeyes", Colors::YELLOW);
|
||||
getFromSocket("/dispatch exec xeyes");
|
||||
|
||||
NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW);
|
||||
int counter = 0;
|
||||
while (Tests::windowCount() != 3) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
EXPECT(Tests::windowCount(), 3);
|
||||
return !ret;
|
||||
}
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 3);
|
||||
|
||||
NLog::log("{}Checking props of xeyes", Colors::YELLOW);
|
||||
// check some window props of xeyes, try to tile them
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "floating: 1");
|
||||
getFromSocket("/dispatch settiled class:XEyes");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
str = getFromSocket("/clients");
|
||||
EXPECT_NOT_CONTAINS(str, "floating: 1");
|
||||
}
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
349
hyprtester/src/tests/main/workspaces.cpp
Normal file
349
hyprtester/src/tests/main/workspaces.cpp
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <print>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include "../shared.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
bool testWorkspaces() {
|
||||
NLog::log("{}Testing workspaces", Colors::GREEN);
|
||||
|
||||
// test on workspace "window"
|
||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace 3", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
NLog::log("{}Switching to workspace +1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace +1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_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)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace m+1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace -1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace -1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace r+1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace r+1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace r~1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace r~1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace empty", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace empty"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace previous", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace previous"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace name:TEST_WORKSPACE_NULL", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace name:TEST_WORKSPACE_NULL"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID -1337 (TEST_WORKSPACE_NULL)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
// add a new monitor
|
||||
NLog::log("{}Adding a new monitor", Colors::YELLOW);
|
||||
EXPECT(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");
|
||||
}
|
||||
|
||||
// focus the first monitor
|
||||
OK(getFromSocket("/dispatch focusmonitor 0"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace r+1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace r~2", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/dispatch workspace r~2"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace m+1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
|
||||
}
|
||||
|
||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||
// no OK: this will throw an error as it should
|
||||
getFromSocket("/dispatch workspace 1");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
|
||||
}
|
||||
|
||||
NLog::log("{}Testing back_and_forth", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword binds:workspace_back_and_forth true"));
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword binds:workspace_back_and_forth false"));
|
||||
|
||||
NLog::log("{}Testing hide_special_on_workspace_change", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change true"));
|
||||
OK(getFromSocket("/dispatch workspace special:HELLO"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/monitors");
|
||||
EXPECT_CONTAINS(str, "special workspace: -");
|
||||
EXPECT_CONTAINS(str, "special:HELLO");
|
||||
}
|
||||
|
||||
// no OK: will err (it shouldnt prolly but oh well)
|
||||
getFromSocket("/dispatch workspace 3");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/monitors");
|
||||
EXPECT_COUNT_STRING(str, "special workspace: 0 ()", 2);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change false"));
|
||||
|
||||
NLog::log("{}Testing allow_workspace_cycles", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword binds:allow_workspace_cycles true"));
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
OK(getFromSocket("/dispatch workspace previous"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch workspace previous"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch workspace previous"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword binds:allow_workspace_cycles false"));
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
// spawn 3 kitties
|
||||
NLog::log("{}Testing focus_preferred_method", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword dwindle:force_split 2"));
|
||||
Tests::spawnKitty("kitty_A");
|
||||
Tests::spawnKitty("kitty_B");
|
||||
Tests::spawnKitty("kitty_C");
|
||||
OK(getFromSocket("/keyword dwindle:force_split 0"));
|
||||
|
||||
// focus kitty 2: will be top right (dwindle)
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||
|
||||
// resize it to be a bit taller
|
||||
OK(getFromSocket("/dispatch resizeactive +20 +20"));
|
||||
|
||||
// now we test focus methods.
|
||||
OK(getFromSocket("/keyword binds:focus_preferred_method 0"));
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_C");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||
|
||||
OK(getFromSocket("/keyword binds:focus_preferred_method 1"));
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_B");
|
||||
}
|
||||
|
||||
NLog::log("{}Testing movefocus_cycles_fullscreen", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
|
||||
Tests::spawnKitty("kitty_D");
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_D");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focusmonitor l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_A");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen false"));
|
||||
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_D");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focusmonitor l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_A");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen true"));
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_B");
|
||||
}
|
||||
|
||||
// destroy the headless output
|
||||
OK(getFromSocket("/output remove HEADLESS-3"));
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
21
hyprtester/src/tests/plugin/plugin.cpp
Normal file
21
hyprtester/src/tests/plugin/plugin.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include "plugin.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <print>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include "../shared.hpp"
|
||||
|
||||
bool testPlugin() {
|
||||
const auto RESPONSE = getFromSocket("/dispatch plugin:test:test");
|
||||
|
||||
if (RESPONSE != "ok") {
|
||||
NLog::log("{}Plugin tests failed, plugin returned:\n{}{}", Colors::RED, Colors::RESET, RESPONSE);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
3
hyprtester/src/tests/plugin/plugin.hpp
Normal file
3
hyprtester/src/tests/plugin/plugin.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
bool testPlugin();
|
||||
92
hyprtester/src/tests/shared.cpp
Normal file
92
hyprtester/src/tests/shared.cpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#include "shared.hpp"
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include <thread>
|
||||
#include <print>
|
||||
#include "../shared.hpp"
|
||||
#include "../hyprctlCompat.hpp"
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_) {
|
||||
const auto COUNT_BEFORE = windowCount();
|
||||
|
||||
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", class_.empty() ? std::vector<std::string>{} : std::vector<std::string>{"--class", class_});
|
||||
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||
kitty->runAsync();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// wait while kitty spawns
|
||||
int counter = 0;
|
||||
while (processAlive(kitty->pid()) && windowCount() == COUNT_BEFORE) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!processAlive(kitty->pid()))
|
||||
return nullptr;
|
||||
|
||||
return kitty;
|
||||
}
|
||||
|
||||
bool Tests::processAlive(pid_t pid) {
|
||||
errno = 0;
|
||||
int ret = kill(pid, 0);
|
||||
return ret != -1 || errno != ESRCH;
|
||||
}
|
||||
|
||||
int Tests::windowCount() {
|
||||
return countOccurrences(getFromSocket("/clients"), "focusHistoryID: ");
|
||||
}
|
||||
|
||||
int Tests::countOccurrences(const std::string& in, const std::string& what) {
|
||||
int cnt = 0;
|
||||
auto pos = in.find(what);
|
||||
while (pos != std::string::npos) {
|
||||
cnt++;
|
||||
pos = in.find(what, pos + what.length() - 1);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
bool Tests::killAllWindows() {
|
||||
auto str = getFromSocket("/clients");
|
||||
auto pos = str.find("Window ");
|
||||
while (pos != std::string::npos) {
|
||||
auto pos2 = str.find(" -> ", pos);
|
||||
getFromSocket("/dispatch killwindow address:0x" + str.substr(pos + 7, pos2 - pos - 7));
|
||||
pos = str.find("Window ", pos + 5);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
while (Tests::windowCount() != 0) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
std::println("{}Timed out waiting for windows to close", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Tests::waitUntilWindowsN(int n) {
|
||||
int counter = 0;
|
||||
while (Tests::windowCount() != n) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
std::println("{}Timed out waiting for windows", Colors::RED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
hyprtester/src/tests/shared.hpp
Normal file
17
hyprtester/src/tests/shared.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../Log.hpp"
|
||||
|
||||
//NOLINTNEXTLINE
|
||||
namespace Tests {
|
||||
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "");
|
||||
bool processAlive(pid_t pid);
|
||||
int windowCount();
|
||||
int countOccurrences(const std::string& in, const std::string& what);
|
||||
bool killAllWindows();
|
||||
void waitUntilWindowsN(int n);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue