Skip to content

Commit 55b7aa1

Browse files
committed
tests/ipc: Add Simple Multi-Client test
This test shows the basic behavior of a call with this library. If it breaks, the changes before testing are the likely cause.
1 parent f8a62a5 commit 55b7aa1

File tree

3 files changed

+369
-0
lines changed

3 files changed

+369
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(test_ipc_simple-multi-client)
3+
4+
ADD_SUBDIRECTORY("../../../" "lib-streamlabs-ipc")
5+
6+
################################################################################
7+
# System & Utilities
8+
################################################################################
9+
# Detect Libary Suffix
10+
IF(WIN32)
11+
SET(libExtension ".dll")
12+
ELSEIF(APPLE)
13+
SET(libExtension ".dylib")
14+
ELSE()
15+
SET(libExtension ".so")
16+
ENDIF()
17+
18+
# Detect Architecture (Bitness)
19+
math(EXPR BITS "8*${CMAKE_SIZEOF_VOID_P}")
20+
21+
################################################################################
22+
# Code
23+
################################################################################
24+
25+
# File List
26+
SET(ipc-test_SOURCES
27+
"${PROJECT_SOURCE_DIR}/test.cpp"
28+
)
29+
SET(ipc-test_LIBRARIES
30+
)
31+
32+
# Project
33+
source_group("Data Files" FILES $ipc-test_DATA)
34+
35+
################################################################################
36+
# Platform Dependencies
37+
################################################################################
38+
IF(WIN32)
39+
# Windows
40+
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
41+
42+
LIST(APPEND ipc-test_SOURCES
43+
)
44+
LIST(APPEND ipc-test_DEPS
45+
)
46+
ELSEIF(APPLE)
47+
# MacOSX
48+
49+
LIST(APPEND ipc-test_SOURCES
50+
)
51+
LIST(APPEND ipc-test_DEPS
52+
)
53+
ELSEIF("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
54+
# Linux
55+
56+
LIST(APPEND ipc-test_SOURCES
57+
)
58+
LIST(APPEND ipc-test_DEPS
59+
)
60+
ELSEIF("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD")
61+
# FreeBSD
62+
63+
LIST(APPEND ipc-test_SOURCES
64+
)
65+
LIST(APPEND ipc-test_DEPS
66+
)
67+
ENDIF()
68+
69+
################################################################################
70+
# Building
71+
################################################################################
72+
# Includes
73+
include_directories(
74+
${PROJECT_SOURCE_DIR}
75+
${PROJECT_SOURCE_DIR}
76+
${lib-streamlabs-ipc_SOURCE_DIR}/include
77+
)
78+
79+
# Building
80+
ADD_EXECUTABLE(${PROJECT_NAME}
81+
${ipc-test_SOURCES}
82+
)
83+
84+
# Linking
85+
TARGET_LINK_LIBRARIES(${PROJECT_NAME}
86+
lib-streamlabs-ipc
87+
${ipc-test_LIBRARIES}
88+
)
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
#include "ipc-server.hpp"
2+
#include "ipc-client.hpp"
3+
#include <iostream>
4+
#include <thread>
5+
#include <sstream>
6+
#include <chrono>
7+
#include <ctime>
8+
#include <mutex>
9+
#include <vector>
10+
#include <cstdarg>
11+
12+
#pragma region Logging
13+
std::chrono::high_resolution_clock hrc;
14+
std::chrono::high_resolution_clock::time_point tp = std::chrono::high_resolution_clock::now();
15+
16+
inline std::string varlog(const char* format, va_list& args) {
17+
size_t length = _vscprintf(format, args);
18+
std::vector<char> buf = std::vector<char>(length + 1, '\0');
19+
size_t written = vsprintf_s(buf.data(), buf.size(), format, args);
20+
return std::string(buf.begin(), buf.begin() + length);
21+
}
22+
23+
static void blog(const char* format, ...) {
24+
va_list args;
25+
va_start(args, format);
26+
std::string text = varlog(format, args);
27+
va_end(args);
28+
29+
auto timeSinceStart = (std::chrono::high_resolution_clock::now() - tp);
30+
auto hours = std::chrono::duration_cast<std::chrono::hours>(timeSinceStart);
31+
timeSinceStart -= hours;
32+
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(timeSinceStart);
33+
timeSinceStart -= minutes;
34+
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(timeSinceStart);
35+
timeSinceStart -= seconds;
36+
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(timeSinceStart);
37+
timeSinceStart -= milliseconds;
38+
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(timeSinceStart);
39+
timeSinceStart -= microseconds;
40+
auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(timeSinceStart);
41+
42+
std::vector<char> timebuf(65535, '\0');
43+
std::string timeformat = "%.2d:%.2d:%.2d.%.3d.%.3d.%.3d: %*s\n";// "%*s";
44+
sprintf_s(
45+
timebuf.data(),
46+
timebuf.size(),
47+
timeformat.c_str(),
48+
hours.count(),
49+
minutes.count(),
50+
seconds.count(),
51+
milliseconds.count(),
52+
microseconds.count(),
53+
nanoseconds.count(),
54+
text.length(), text.c_str());
55+
std::cout << timebuf.data();
56+
}
57+
#pragma endregion Logging
58+
59+
#pragma region Windows
60+
#ifdef _WIN32
61+
#include <windows.h>
62+
#include <direct.h>
63+
#include <wchar.h>
64+
65+
bool spawn(std::string program, std::string commandLine, std::string workingDirectory) {
66+
PROCESS_INFORMATION m_win32_processInformation;
67+
STARTUPINFOW m_win32_startupInfo;
68+
69+
// Buffers
70+
std::vector<wchar_t> programBuf;
71+
std::vector<wchar_t> commandLineBuf;
72+
std::vector<wchar_t> workingDirectoryBuf;
73+
74+
// Convert to WideChar
75+
DWORD wr;
76+
programBuf.resize(MultiByteToWideChar(CP_UTF8, 0,
77+
program.data(), (int)program.size(),
78+
nullptr, 0) + 1);
79+
wr = MultiByteToWideChar(CP_UTF8, 0,
80+
program.data(), (int)program.size(),
81+
programBuf.data(), (int)programBuf.size());
82+
if (wr == 0) {
83+
// Conversion failed.
84+
DWORD errorCode = GetLastError();
85+
return false;
86+
}
87+
88+
commandLineBuf.resize(MultiByteToWideChar(CP_UTF8, 0,
89+
commandLine.data(), (int)commandLine.size(),
90+
nullptr, 0) + 1);
91+
wr = MultiByteToWideChar(CP_UTF8, 0,
92+
commandLine.data(), (int)commandLine.size(),
93+
commandLineBuf.data(), (int)commandLineBuf.size());
94+
if (wr == 0) {
95+
// Conversion failed.
96+
DWORD errorCode = GetLastError();
97+
return false;
98+
}
99+
100+
if (workingDirectory.length() > 1) {
101+
workingDirectoryBuf.resize(MultiByteToWideChar(CP_UTF8, 0,
102+
workingDirectory.data(), (int)workingDirectory.size(),
103+
nullptr, 0) + 1);
104+
if (workingDirectoryBuf.size() > 0) {
105+
wr = MultiByteToWideChar(CP_UTF8, 0,
106+
workingDirectory.data(), (int)workingDirectory.size(),
107+
workingDirectoryBuf.data(), (int)workingDirectoryBuf.size());
108+
if (wr == 0) {
109+
// Conversion failed.
110+
DWORD errorCode = GetLastError();
111+
return false;
112+
}
113+
}
114+
}
115+
116+
// Build information
117+
memset(&m_win32_startupInfo, 0, sizeof(m_win32_startupInfo));
118+
memset(&m_win32_processInformation, 0, sizeof(m_win32_processInformation));
119+
120+
// Launch process
121+
size_t attempts = 0;
122+
while (!CreateProcessW(
123+
programBuf.data(),
124+
commandLineBuf.data(),
125+
nullptr,
126+
nullptr,
127+
false,
128+
CREATE_NEW_CONSOLE,
129+
nullptr,
130+
workingDirectory.length() > 0 ? workingDirectoryBuf.data() : nullptr,
131+
&m_win32_startupInfo,
132+
&m_win32_processInformation)) {
133+
if (attempts >= 5) {
134+
break;
135+
}
136+
attempts++;
137+
std::cerr << "Attempt " << attempts << ": Creating client failed." << std::endl;
138+
std::this_thread::sleep_for(std::chrono::milliseconds(500));
139+
}
140+
141+
if (attempts >= 5) {
142+
DWORD errorCode = GetLastError();
143+
return false;
144+
}
145+
}
146+
147+
std::string get_working_directory() {
148+
std::vector<wchar_t> bufUTF16 = std::vector<wchar_t>(65535);
149+
std::vector<char> bufUTF8;
150+
151+
_wgetcwd(bufUTF16.data(), bufUTF16.size());
152+
153+
// Convert from Wide-char to UTF8
154+
DWORD bufferSize = WideCharToMultiByte(CP_UTF8, 0,
155+
bufUTF16.data(), bufUTF16.size(),
156+
nullptr, 0,
157+
NULL, NULL);
158+
bufUTF8.resize(bufferSize + 1);
159+
DWORD finalSize = WideCharToMultiByte(CP_UTF8, 0,
160+
bufUTF16.data(), bufUTF16.size(),
161+
bufUTF8.data(), bufUTF8.size(),
162+
NULL, NULL);
163+
if (finalSize == 0) {
164+
// Conversion failed.
165+
DWORD errorCode = GetLastError();
166+
return false;
167+
}
168+
169+
return bufUTF8.data();
170+
}
171+
172+
#endif
173+
#pragma endregion Windows
174+
175+
#define CONN "HelloWorldIPC2"
176+
#define CLIENTCOUNT 4ull
177+
178+
static int server(int argc, char* argv[]);
179+
static int client(int argc, char* argv[]);
180+
181+
int main(int argc, char* argv[]) {
182+
if ((argc >= 2) || (strcmp(argv[0], "client") == 0)) {
183+
client(argc, argv);
184+
} else {
185+
server(argc, argv);
186+
}
187+
}
188+
189+
static void function1(void* data, const int64_t id, const std::vector<ipc::value>& args, std::vector<ipc::value>& rval) {
190+
rval.resize(args.size());
191+
for (size_t idx = 0; idx < args.size(); idx++) {
192+
rval[idx] = args[idx];
193+
}
194+
}
195+
196+
int server(int argc, char* argv[]) {
197+
blog("Starting server...");
198+
199+
ipc::server socket;
200+
201+
std::shared_ptr<ipc::collection> collection = std::make_shared<ipc::collection>("Default");
202+
collection->register_function(std::make_shared<ipc::function>("Function1", function1));
203+
socket.register_collection(collection);
204+
205+
try {
206+
socket.initialize(CONN);
207+
} catch (...) {
208+
blog("Unable to start server.");
209+
std::cin.get();
210+
return -1;
211+
}
212+
213+
blog("Spawning %llu clients...", CLIENTCOUNT);
214+
for (size_t idx = 0; idx < CLIENTCOUNT; idx++) {
215+
spawn(std::string(argv[0]), '"' + std::string(argv[0]) + '"' + " client", get_working_directory());
216+
}
217+
218+
blog("Hit Enter to shut down server.");
219+
std::cin.get();
220+
221+
blog("Shutting down server...");
222+
socket.finalize();
223+
224+
return 0;
225+
}
226+
227+
void client_call_handler(const void* data, const std::vector<ipc::value>& rval) {
228+
size_t* mydata = static_cast<size_t*>(const_cast<void*>(data));
229+
(*mydata)++;
230+
}
231+
232+
int client(int argc, char* argv[]) {
233+
blog("Starting client...");
234+
235+
std::shared_ptr<ipc::client> socket;
236+
237+
try {
238+
socket = std::make_shared<ipc::client>(CONN);
239+
} catch (...) {
240+
blog("Unable to start client.");
241+
std::cin.get();
242+
return -1;
243+
}
244+
245+
socket->authenticate();
246+
247+
size_t inbox = 0, outbox = 0, total = 10000;
248+
blog("Attempting to make %llu calls...", total);
249+
250+
auto tpstart = std::chrono::high_resolution_clock::now();
251+
while ((inbox < total) || (outbox < total)) {
252+
if (outbox < total) {
253+
if (!socket->call("Default", "Function1", {}, client_call_handler, &inbox)) {
254+
blog("Critical Failure: Could not call function.");
255+
break;
256+
}
257+
outbox++;
258+
}
259+
//blog("Called %llu times with %llu replies.", outbox, inbox);
260+
}
261+
262+
while (inbox < outbox) {
263+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
264+
}
265+
auto tpend = std::chrono::high_resolution_clock::now();
266+
267+
auto tpdurns = std::chrono::duration_cast<std::chrono::nanoseconds>(tpend - tpstart);
268+
auto tpdurms = std::chrono::duration_cast<std::chrono::milliseconds>(tpend - tpstart);
269+
270+
blog("Sent %llu & Received %llu messages in %llu milliseconds.", outbox, inbox, tpdurms.count());
271+
blog("Average %llu ns per message.", tpdurns.count() / total);
272+
273+
blog("Hit Enter to shut down client.");
274+
std::cin.get();
275+
276+
blog("Shutting down client...");
277+
socket = nullptr;
278+
279+
return 0;
280+
}

0 commit comments

Comments
 (0)