Skip to content

Commit d3410d7

Browse files
committed
tests: add unit tests
Adds a bunch of unit tests for the entire library
1 parent 86012ee commit d3410d7

34 files changed

Lines changed: 2430 additions & 43 deletions

CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ if(BUILD_TESTING)
111111
message(STATUS "building tests is enabled")
112112

113113
enable_testing()
114+
115+
# Server + Client tests
114116
add_custom_target(tests)
115117

116118
message(STATUS "generating protocols")
@@ -133,6 +135,29 @@ if(BUILD_TESTING)
133135
"tests/generated/test_protocol_v1-server.cpp")
134136
target_link_libraries(fork PRIVATE PkgConfig::deps hyprwire)
135137
add_dependencies(tests fork)
138+
139+
# GTests
140+
find_package(GTest CONFIG REQUIRED)
141+
include(GoogleTest)
142+
file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/unit/*.cpp")
143+
add_executable(hyprwire_tests ${TESTFILES})
144+
145+
target_compile_options(hyprwire_tests PRIVATE --coverage -fsanitize=address)
146+
target_link_options(hyprwire_tests PRIVATE --coverage)
147+
148+
target_include_directories(
149+
hyprwire_tests
150+
PUBLIC "./include"
151+
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
152+
target_link_libraries(hyprwire_tests PRIVATE asan hyprwire GTest::gtest_main
153+
PkgConfig::deps)
154+
gtest_discover_tests(hyprwire_tests
155+
PROPERTIES ENVIRONMENT "ASAN_OPTIONS=detect_leaks=0"
156+
)
157+
158+
# Add coverage to hyprwire for test builds
159+
target_compile_options(hyprwire PRIVATE --coverage)
160+
target_link_options(hyprwire PRIVATE --coverage)
136161
else()
137162
message(STATUS "building tests is disabled")
138163
endif()

include/hyprwire/core/implementation/Types.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ namespace Hyprwire {
99
struct SMethod {
1010
uint32_t idx = 0;
1111
std::vector<uint8_t> params;
12-
std::string returnsType = "";
13-
uint32_t since = 0;
12+
std::string returnsType = "";
13+
uint32_t since = 0;
14+
bool isDestructor = false;
1415
};
1516

1617
class IProtocolObjectSpec {
@@ -26,4 +27,4 @@ namespace Hyprwire {
2627
IProtocolObjectSpec() = default;
2728
};
2829

29-
};
30+
};

scanner/main.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,9 @@ Hyprwire::SMethod{{
359359
.params = {{ {} }},
360360
.returnsType = "{}",
361361
.since = {},
362+
.isDestructor = {},
362363
}},)#",
363-
m.idx, argArrayStr, m.returns, m.since);
364+
m.idx, argArrayStr, m.returns, m.since, m.destructor ? "true" : "false");
364365
}
365366

366367
if (!object.c2s.empty())
@@ -391,8 +392,9 @@ Hyprwire::SMethod{{
391392
.idx = {},
392393
.params = {{ {} }},
393394
.since = {},
395+
.isDestructor = {},
394396
}},)#",
395-
m.idx, argArrayStr, m.since);
397+
m.idx, argArrayStr, m.since, m.destructor ? "true" : "false");
396398
}
397399

398400
if (!object.s2c.empty())

src/core/client/ClientObject.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@ CClientObject::CClientObject(SP<CClientSocket> client) : m_client(client) {
1818
}
1919

2020
CClientObject::~CClientObject() {
21+
if (!m_destroyed && m_id != 0 && m_spec && m_client && m_client->m_fd.isValid()) {
22+
const auto methods = methodsOut();
23+
for (const auto& method : methods) {
24+
if (!method.isDestructor)
25+
continue;
26+
27+
if (method.since > m_version)
28+
continue;
29+
30+
if (!method.returnsType.empty()) {
31+
Debug::log(WARN, "can't auto-call destructor for object {}: method {} has returns type", m_id, method.idx);
32+
break;
33+
}
34+
35+
if (!method.params.empty()) {
36+
Debug::log(WARN, "can't auto-call destructor for object {}: method {} has params", m_id, method.idx);
37+
break;
38+
}
39+
40+
TRACE(Debug::log(TRACE, "auto-calling protocol destructor {} for object {}", method.idx, m_id));
41+
call(method.idx);
42+
break;
43+
}
44+
}
45+
2146
TRACE(Debug::log(TRACE, "destroying object {}", m_id));
2247
}
2348

src/core/client/ClientSocket.cpp

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "ClientSocket.hpp"
22
#include "../../helpers/Memory.hpp"
33
#include "../../helpers/Log.hpp"
4+
#include "../../helpers/Syscalls.hpp"
45
#include "../../Macros.hpp"
56
#include "../message/MessageParser.hpp"
67
#include "../message/messages/IMessage.hpp"
@@ -30,6 +31,10 @@ using namespace Hyprwire;
3031
using namespace Hyprutils::OS;
3132
using namespace Hyprutils::Utils;
3233

34+
namespace {
35+
std::chrono::milliseconds g_handshakeMax = std::chrono::milliseconds(5000);
36+
}
37+
3338
SP<IClientSocket> IClientSocket::open(const std::string& path) {
3439
SP<CClientSocket> sock = makeShared<CClientSocket>();
3540
sock->m_self = sock;
@@ -101,18 +106,34 @@ void CClientSocket::addImplementation(SP<IProtocolClientImplementation>&& x) {
101106
m_impls.emplace_back(std::move(x));
102107
}
103108

104-
constexpr const size_t HANDSHAKE_MAX_MS = 5000;
109+
void CClientSocket::setHandshakeTimeoutForTests(std::chrono::milliseconds timeout) {
110+
g_handshakeMax = timeout;
111+
}
112+
113+
void CClientSocket::resetHandshakeTimeoutForTests() {
114+
g_handshakeMax = std::chrono::milliseconds(5000);
115+
}
105116

106117
//
107118
bool CClientSocket::dispatchEvents(bool block) {
108119

109120
if (m_error)
110121
return false;
111122

123+
collectOrphanedObjects();
124+
112125
if (!m_handshakeDone) {
113-
const auto MAX_MS =
114-
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::milliseconds(HANDSHAKE_MAX_MS) - (std::chrono::steady_clock::now() - m_handshakeBegin)).count();
115-
int ret = poll(m_pollfds.data(), m_pollfds.size(), block ? MAX_MS : 0);
126+
const auto elapsed = std::chrono::steady_clock::now() - m_handshakeBegin;
127+
const auto maxMs = g_handshakeMax;
128+
129+
if (block && elapsed >= maxMs) {
130+
Debug::log(ERR, "handshake error: timed out");
131+
disconnectOnError();
132+
return false;
133+
}
134+
135+
const auto timeout = block ? std::chrono::duration_cast<std::chrono::milliseconds>(maxMs - elapsed).count() : 0;
136+
int ret = Syscalls::poll(m_pollfds.data(), m_pollfds.size(), static_cast<int>(timeout));
116137
if (block && !ret) {
117138
Debug::log(ERR, "handshake error: timed out");
118139
disconnectOnError();
@@ -121,13 +142,15 @@ bool CClientSocket::dispatchEvents(bool block) {
121142
}
122143

123144
if (m_handshakeDone)
124-
poll(m_pollfds.data(), m_pollfds.size(), block ? -1 : 0);
145+
Syscalls::poll(m_pollfds.data(), m_pollfds.size(), block ? -1 : 0);
125146

126147
if (m_pollfds[0].revents & POLLHUP)
127148
return false;
128149

129-
if (!(m_pollfds[0].revents & POLLIN))
150+
if (!(m_pollfds[0].revents & POLLIN)) {
151+
collectOrphanedObjects();
130152
return true;
153+
}
131154

132155
// dispatch
133156

@@ -165,6 +188,8 @@ bool CClientSocket::dispatchEvents(bool block) {
165188
return true;
166189
});
167190

191+
collectOrphanedObjects();
192+
168193
return !m_error;
169194
}
170195

@@ -203,13 +228,13 @@ void CClientSocket::sendMessage(const IMessage& message) {
203228
}
204229

205230
while (m_fd.isValid()) {
206-
int ret = sendmsg(m_fd.get(), &msg, 0);
231+
int ret = Syscalls::sendmsg(m_fd.get(), &msg, 0);
207232
if (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) {
208233
pollfd pfd = {
209234
.fd = m_fd.get(),
210235
.events = POLLOUT | POLLWRBAND,
211236
};
212-
poll(&pfd, 1, -1);
237+
Syscalls::poll(&pfd, 1, -1);
213238
} else
214239
break;
215240
}
@@ -327,14 +352,38 @@ void CClientSocket::waitForObject(SP<IWireObject> x) {
327352
}
328353

329354
void CClientSocket::onGeneric(const CGenericProtocolMessage& msg) {
355+
SP<CClientObject> object;
356+
330357
for (const auto& o : m_objects) {
331-
if (o->m_id == msg.m_object) {
332-
o->called(msg.m_method, msg.m_dataSpan, msg.m_fds);
333-
return;
358+
if (o && o->m_id == msg.m_object) {
359+
object = o;
360+
break;
334361
}
335362
}
336363

337-
Debug::log(WARN, "[{} @ {:.3f}] -> Generic message not handled. No object with id {}!", m_fd.get(), steadyMillis(), msg.m_object);
364+
if (!object) {
365+
Debug::log(ERR, "[{} @ {:.3f}] -> Generic message references unknown object {}", m_fd.get(), steadyMillis(), msg.m_object);
366+
disconnectOnError();
367+
return;
368+
}
369+
370+
object->called(msg.m_method, msg.m_dataSpan, msg.m_fds);
371+
}
372+
373+
void CClientSocket::destroyObject(uint32_t id) {
374+
std::erase_if(m_objects, [id](const auto& obj) { return obj && obj->m_id == id; });
375+
}
376+
377+
void CClientSocket::collectOrphanedObjects() {
378+
std::erase_if(m_objects, [](const auto& obj) {
379+
if (!obj)
380+
return true;
381+
382+
if (obj->m_id == 0)
383+
return false;
384+
385+
return obj.strongRef() == 1;
386+
});
338387
}
339388

340389
SP<IObject> CClientSocket::objectForId(uint32_t id) {

src/core/client/ClientSocket.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <vector>
1010
#include <sys/poll.h>
11+
#include <chrono>
1112

1213
namespace Hyprwire {
1314
class IMessage;
@@ -33,11 +34,16 @@ namespace Hyprwire {
3334
virtual void roundtrip();
3435
virtual bool isHandshakeDone();
3536

37+
static void setHandshakeTimeoutForTests(std::chrono::milliseconds timeout);
38+
static void resetHandshakeTimeoutForTests();
39+
3640
void sendMessage(const IMessage& message);
3741
void serverSpecs(const std::vector<std::string>& s);
3842
void recheckPollFds();
3943
void onSeq(uint32_t seq, uint32_t id);
4044
void onGeneric(const CGenericProtocolMessage& msg);
45+
void destroyObject(uint32_t id);
46+
void collectOrphanedObjects();
4147
SP<CClientObject> makeObject(const std::string& protocolName, const std::string& objectName, uint32_t seq);
4248
void waitForObject(SP<IWireObject>);
4349

@@ -65,4 +71,4 @@ namespace Hyprwire {
6571
uint32_t m_lastAckdRoundtripSeq = 0;
6672
uint32_t m_lastSentRoundtripSeq = 0;
6773
};
68-
};
74+
};

src/core/message/messages/FatalProtocolError.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,19 @@ CFatalErrorMessage::CFatalErrorMessage(const std::vector<uint8_t>& data, size_t
5353
}
5454

5555
CFatalErrorMessage::CFatalErrorMessage(SP<IWireObject> obj, uint32_t errorId, const std::string_view& msg) {
56+
uint32_t objectId = 0;
57+
if (obj)
58+
objectId = obj->m_id;
59+
60+
*this = CFatalErrorMessage(objectId, errorId, msg);
61+
}
62+
63+
CFatalErrorMessage::CFatalErrorMessage(uint32_t objectId, uint32_t errorId, const std::string_view& msg) {
5664
m_type = HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR;
5765

5866
m_data = {HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_TYPE_VARCHAR};
5967

60-
if (obj)
61-
std::memcpy(&m_data[2], &obj->m_id, sizeof(obj->m_id));
68+
std::memcpy(&m_data[2], &objectId, sizeof(objectId));
6269
std::memcpy(&m_data[7], &errorId, sizeof(errorId));
6370

6471
m_data.append_range(g_messageParser->encodeVarInt(msg.size()));

src/core/message/messages/FatalProtocolError.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ namespace Hyprwire {
1313
public:
1414
CFatalErrorMessage(const std::vector<uint8_t>& data, size_t offset);
1515
CFatalErrorMessage(SP<IWireObject> obj, uint32_t errorId, const std::string_view& msg);
16+
CFatalErrorMessage(uint32_t objectId, uint32_t errorId, const std::string_view& msg);
1617

1718
virtual ~CFatalErrorMessage() = default;
1819

1920
uint32_t m_objectId = 0;
2021
uint32_t m_errorId = 0;
2122
std::string m_errorMsg;
2223
};
23-
};
24+
};

src/core/server/ServerClient.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#include "../message/messages/IMessage.hpp"
55
#include "../message/messages/NewObject.hpp"
66
#include "../message/messages/GenericProtocolMessage.hpp"
7+
#include "../message/messages/FatalProtocolError.hpp"
78
#include "../../helpers/Log.hpp"
9+
#include "../../helpers/Syscalls.hpp"
810
#include "../../Macros.hpp"
911

1012
#include <hyprwire/core/implementation/ServerImpl.hpp>
@@ -80,13 +82,13 @@ void CServerClient::sendMessage(const IMessage& message) {
8082
}
8183

8284
while (m_fd.isValid()) {
83-
int ret = sendmsg(m_fd.get(), &msg, 0);
85+
int ret = Syscalls::sendmsg(m_fd.get(), &msg, 0);
8486
if (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) {
8587
pollfd pfd = {
8688
.fd = m_fd.get(),
8789
.events = POLLOUT | POLLWRBAND,
8890
};
89-
poll(&pfd, 1, -1);
91+
Syscalls::poll(&pfd, 1, -1);
9092
} else
9193
break;
9294
}
@@ -143,6 +145,10 @@ SP<CServerObject> CServerClient::createObject(const std::string& protocol, const
143145
return obj;
144146
}
145147

148+
void CServerClient::destroyObject(uint32_t id) {
149+
std::erase_if(m_objects, [id](const auto& obj) { return obj && obj->m_id == id; });
150+
}
151+
146152
void CServerClient::onBind(SP<CServerObject> obj) {
147153
for (const auto& p : m_server->m_impls) {
148154
if (p->protocol()->specName() != obj->m_protocolName)
@@ -162,14 +168,24 @@ void CServerClient::onBind(SP<CServerObject> obj) {
162168
}
163169

164170
void CServerClient::onGeneric(const CGenericProtocolMessage& msg) {
171+
SP<CServerObject> object;
172+
165173
for (const auto& o : m_objects) {
166-
if (o->m_id == msg.m_object) {
167-
o->called(msg.m_method, msg.m_dataSpan, msg.m_fds);
168-
return;
174+
if (o && o->m_id == msg.m_object) {
175+
object = o;
176+
break;
169177
}
170178
}
171179

172-
Debug::log(WARN, "[{} @ {:.3f}] -> Generic message not handled. No object with id {}!", m_fd.get(), steadyMillis(), msg.m_object);
180+
if (!object) {
181+
const auto error = std::format("generic message references unknown object {}", msg.m_object);
182+
Debug::log(ERR, "[{} @ {:.3f}] -> {}", m_fd.get(), steadyMillis(), error);
183+
sendMessage(CFatalErrorMessage(msg.m_object, static_cast<uint32_t>(-1), error));
184+
m_error = true;
185+
return;
186+
}
187+
188+
object->called(msg.m_method, msg.m_dataSpan, msg.m_fds);
173189
}
174190

175191
int CServerClient::getPID() {

0 commit comments

Comments
 (0)