mirror of
https://github.com/hyprwm/Hyprland.git
synced 2025-08-09 08:31:54 -07:00
241 lines
6.9 KiB
C++
241 lines
6.9 KiB
C++
|
|
// 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);
|
|
|
|
for (const auto& fn : testFns) {
|
|
EXPECT(fn(), 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;
|
|
}
|