1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
//
// This file is part of j4-dmenu-desktop.
//
// j4-dmenu-desktop is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// j4-dmenu-desktop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with j4-dmenu-desktop. If not, see <http://www.gnu.org/licenses/>.
//
#include <catch2/catch_message.hpp>
#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <chrono>
#include <cstring>
#include <errno.h>
#include <exception>
#include <future>
#include <signal.h>
#include <stdexcept>
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include "FSUtils.hh"
#include "I3Exec.hh"
#include "Utilities.hh"
// NOTE: Meaningful tests cannot be done on i3 IPC without i3 itself. The most
// important of i3 IPC implementation, I3Interface::exec(), requires manual
// testing.
using std::string;
#define SUCCESS_MESSAGE "[{\"success\":true}]"
// This is a simplified version that doesn't handle escaping.
static string construct_i3_message(const string &message) {
string result(14 + message.size(), '\0');
char *ptr = result.data();
std::memcpy(ptr, "i3-ipc", 6);
uint32_t length = message.size();
std::memcpy(ptr + 6, &length, 4);
std::memset(ptr + 10, 0, 4);
std::memcpy(ptr + 14, message.c_str(), message.size());
return result;
}
static string read_request_server(int sfd) {
int cfd = accept(sfd, NULL, NULL);
if (cfd == -1)
throw std::runtime_error((string) "accept: " + strerror(errno));
OnExit cfd_close = [cfd]() { close(cfd); };
char buf[512];
ssize_t size = read(cfd, buf, sizeof buf);
if (size == -1)
throw std::runtime_error((string) "read: " + strerror(errno));
string result = string(buf, size);
while ((size = recv(cfd, buf, sizeof buf, MSG_DONTWAIT)) > 0)
result.append(buf, size);
if (size == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
throw std::runtime_error((string) "read: " + strerror(errno));
}
string success_message = construct_i3_message(
string(SUCCESS_MESSAGE, sizeof SUCCESS_MESSAGE - 1));
if (writen(cfd, success_message.c_str(), success_message.size()) == -1)
throw std::runtime_error((string) "writen: " + strerror(errno));
return result;
}
static std::string path;
static void rmdir() {
if (!path.empty())
FSUtils::rmdir_recursive(path.c_str());
}
static struct sigaction old;
static void sighandler(int signal) {
rmdir();
sigaction(signal, &old, NULL);
raise(signal);
}
TEST_CASE("Test I3Exec", "[I3Exec]") {
char tmpdirname[] = "/tmp/j4dd-i3-unit-test-XXXXXX";
if (mkdtemp(tmpdirname) == NULL) {
SKIP("mkdtemp: " << strerror(errno));
}
path = tmpdirname;
// We want to make reaaaly sure that there will be no leftovers.
if (atexit(rmdir) == -1) {
WARN("atexit: " << strerror(errno));
}
std::set_terminate([]() {
rmdir();
abort();
});
struct sigaction act;
std::memset(&act, 0, sizeof(struct sigaction));
act.sa_handler = sighandler;
if (sigaction(SIGINT, &act, &old) == -1) {
WARN("sigaction: " << strerror(errno));
}
OnExit rmdir_handler = []() {
rmdir();
path.clear();
};
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1)
SKIP("socket: " << strerror(errno));
OnExit sfd_close = [sfd]() { close(sfd); };
struct sockaddr_un addr;
std::memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
fmt::format_to(addr.sun_path, "{}/socket", tmpdirname);
if (bind(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
SKIP("bind: " << strerror(errno));
}
if (listen(sfd, 2) == -1) {
SKIP("listen: " << strerror(errno));
}
auto result = std::async(std::launch::async, read_request_server, sfd);
I3Interface::exec("true", (string)tmpdirname + "/socket");
using namespace std::chrono_literals;
if (result.wait_for(2s) == std::future_status::timeout) {
FAIL("I3 dummy server is taking too long to respond!");
}
std::string query = result.get();
string check1 = construct_i3_message("exec true");
string check2 = construct_i3_message("exec \"true\"");
REQUIRE(!query.empty());
REQUIRE((query == check1 || query == check2));
}
|