Skip to content

Commit 7cc53bb

Browse files
committed
tests UPDATE add server thread test
Tests accepting connections with multiple threads with different timeouts.
1 parent c6527ef commit 7cc53bb

File tree

2 files changed

+304
-0
lines changed

2 files changed

+304
-0
lines changed

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ if(ENABLE_SSH_TLS)
8181
endif()
8282
libnetconf2_test(NAME test_replace)
8383
libnetconf2_test(NAME test_runtime_changes PORT_COUNT 2)
84+
libnetconf2_test(NAME test_server_thread)
8485
libnetconf2_test(NAME test_ssh)
8586
libnetconf2_test(NAME test_tls)
8687
libnetconf2_test(NAME test_two_channels)

tests/test_server_thread.c

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
/**
2+
* @file test_server_thread.c
3+
* @author Roman Janota <janota@cesnet.cz>
4+
* @brief libnetconf2 parallel server accept thread test.
5+
*
6+
* @copyright
7+
* Copyright (c) 2026 CESNET, z.s.p.o.
8+
*
9+
* This source code is licensed under BSD 3-Clause License (the "License").
10+
* You may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* https://opensource.org/licenses/BSD-3-Clause
14+
*/
15+
16+
#define _GNU_SOURCE
17+
18+
#include <pthread.h>
19+
#include <setjmp.h>
20+
#include <stdarg.h>
21+
#include <stdio.h>
22+
#include <stdlib.h>
23+
#include <unistd.h>
24+
25+
#include <cmocka.h>
26+
27+
#include "ln2_test.h"
28+
29+
#define PARALLEL_SERVER_THREADS 4
30+
#define PARALLEL_CLIENT_THREADS 4
31+
#define NOCLIENT_ATTEMPTS 8
32+
#define NONBLOCK_BACKOFF_USECS 1000
33+
#define SHORT_ACCEPT_TIMEOUT 400
34+
35+
int TEST_PORT = 10050;
36+
const char *TEST_PORT_STR = "10050";
37+
38+
struct accept_state {
39+
pthread_barrier_t start_barrier;
40+
pthread_mutex_t lock;
41+
int accept_timeout;
42+
int client_count;
43+
int accepted_count;
44+
int timeout_count;
45+
void *client_thr_ctx;
46+
struct ln2_test_ctx *test_ctx;
47+
};
48+
49+
struct no_client_state {
50+
pthread_barrier_t start_barrier;
51+
int accept_timeout;
52+
struct ln2_test_ctx *test_ctx;
53+
};
54+
55+
static void *
56+
server_thread_accept_all(void *arg)
57+
{
58+
int done;
59+
NC_MSG_TYPE msgtype;
60+
struct nc_session *session = NULL;
61+
struct accept_state *state = arg;
62+
63+
/* wait until all server and client threads are ready to start the test */
64+
pthread_barrier_wait(&state->start_barrier);
65+
66+
while (1) {
67+
pthread_mutex_lock(&state->lock);
68+
done = state->accepted_count >= state->client_count;
69+
pthread_mutex_unlock(&state->lock);
70+
if (done) {
71+
break;
72+
}
73+
74+
msgtype = nc_accept(state->accept_timeout, state->test_ctx->ctx, &session);
75+
if (msgtype == NC_MSG_HELLO) {
76+
assert_non_null(session);
77+
nc_session_free(session, NULL);
78+
79+
pthread_mutex_lock(&state->lock);
80+
++state->accepted_count;
81+
pthread_mutex_unlock(&state->lock);
82+
} else if (msgtype == NC_MSG_WOULDBLOCK) {
83+
assert_null(session);
84+
85+
pthread_mutex_lock(&state->lock);
86+
++state->timeout_count;
87+
pthread_mutex_unlock(&state->lock);
88+
89+
usleep(NONBLOCK_BACKOFF_USECS);
90+
} else {
91+
fail_msg("Unexpected nc_accept return code %d", msgtype);
92+
}
93+
}
94+
95+
return NULL;
96+
}
97+
98+
static void *
99+
client_thread_connect(void *arg)
100+
{
101+
int ret = 0;
102+
struct nc_session *session = NULL;
103+
struct accept_state *state = arg;
104+
105+
ret = nc_client_set_schema_searchpath(MODULES_DIR);
106+
assert_int_equal(ret, 0);
107+
108+
ret = nc_client_ssh_set_username("parallel_client");
109+
assert_int_equal(ret, 0);
110+
111+
ret = nc_client_ssh_add_keypair(TESTS_DIR "/data/id_ed25519.pub", TESTS_DIR "/data/id_ed25519");
112+
assert_int_equal(ret, 0);
113+
114+
nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP);
115+
116+
/* wait until the server threads are ready to accept connections */
117+
pthread_barrier_wait(&state->start_barrier);
118+
119+
session = nc_connect_ssh("127.0.0.1", TEST_PORT, NULL);
120+
assert_non_null(session);
121+
nc_session_free(session, NULL);
122+
123+
return NULL;
124+
}
125+
126+
static void *
127+
server_thread_timeout_only(void *arg)
128+
{
129+
int i;
130+
NC_MSG_TYPE msgtype;
131+
struct nc_session *session = NULL;
132+
struct no_client_state *state = arg;
133+
134+
/* wait until all server threads are ready to start the test */
135+
pthread_barrier_wait(&state->start_barrier);
136+
137+
for (i = 0; i < NOCLIENT_ATTEMPTS; ++i) {
138+
msgtype = nc_accept(state->accept_timeout, state->test_ctx->ctx, &session);
139+
assert_int_equal(msgtype, NC_MSG_WOULDBLOCK);
140+
assert_null(session);
141+
142+
usleep(NONBLOCK_BACKOFF_USECS);
143+
}
144+
145+
return NULL;
146+
}
147+
148+
static void
149+
run_parallel_accept(void **state, int accept_timeout)
150+
{
151+
int i, ret;
152+
pthread_t server_tids[PARALLEL_SERVER_THREADS];
153+
pthread_t client_tids[PARALLEL_CLIENT_THREADS];
154+
struct ln2_test_ctx *test_ctx = *state;
155+
struct accept_state accept_state;
156+
157+
accept_state.accept_timeout = accept_timeout;
158+
accept_state.client_count = PARALLEL_CLIENT_THREADS;
159+
accept_state.accepted_count = 0;
160+
accept_state.timeout_count = 0;
161+
accept_state.client_thr_ctx = nc_client_get_thread_context();
162+
assert_non_null(accept_state.client_thr_ctx);
163+
accept_state.test_ctx = test_ctx;
164+
165+
/* sync all threads to start at the same time */
166+
ret = pthread_barrier_init(&accept_state.start_barrier, NULL,
167+
PARALLEL_SERVER_THREADS + PARALLEL_CLIENT_THREADS + 1);
168+
assert_int_equal(ret, 0);
169+
ret = pthread_mutex_init(&accept_state.lock, NULL);
170+
assert_int_equal(ret, 0);
171+
172+
/* start server threads */
173+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
174+
ret = pthread_create(&server_tids[i], NULL, server_thread_accept_all, &accept_state);
175+
assert_int_equal(ret, 0);
176+
}
177+
178+
/* start client threads */
179+
for (i = 0; i < PARALLEL_CLIENT_THREADS; ++i) {
180+
ret = pthread_create(&client_tids[i], NULL, client_thread_connect, &accept_state);
181+
assert_int_equal(ret, 0);
182+
}
183+
184+
/* wait until all threads are ready to start the test */
185+
pthread_barrier_wait(&accept_state.start_barrier);
186+
187+
for (i = 0; i < PARALLEL_CLIENT_THREADS; ++i) {
188+
pthread_join(client_tids[i], NULL);
189+
}
190+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
191+
pthread_join(server_tids[i], NULL);
192+
}
193+
194+
/* all clients should have been accepted */
195+
assert_int_equal(accept_state.accepted_count, PARALLEL_CLIENT_THREADS);
196+
197+
pthread_mutex_destroy(&accept_state.lock);
198+
pthread_barrier_destroy(&accept_state.start_barrier);
199+
}
200+
201+
static void
202+
run_timeout_only(void **state, int accept_timeout)
203+
{
204+
int i, ret;
205+
pthread_t server_tids[PARALLEL_SERVER_THREADS];
206+
struct ln2_test_ctx *test_ctx = *state;
207+
struct no_client_state no_client_state;
208+
209+
no_client_state.accept_timeout = accept_timeout;
210+
no_client_state.test_ctx = test_ctx;
211+
212+
/* sync all threads to start at the same time */
213+
ret = pthread_barrier_init(&no_client_state.start_barrier, NULL, PARALLEL_SERVER_THREADS + 1);
214+
assert_int_equal(ret, 0);
215+
216+
/* start server threads, no client threads will be started, so all threads should only experience timeouts */
217+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
218+
ret = pthread_create(&server_tids[i], NULL, server_thread_timeout_only, &no_client_state);
219+
assert_int_equal(ret, 0);
220+
}
221+
222+
/* wait until all threads are ready to start the test */
223+
pthread_barrier_wait(&no_client_state.start_barrier);
224+
225+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
226+
pthread_join(server_tids[i], NULL);
227+
}
228+
229+
pthread_barrier_destroy(&no_client_state.start_barrier);
230+
}
231+
232+
static void
233+
test_parallel_accept_nonblocking(void **state)
234+
{
235+
run_parallel_accept(state, 0);
236+
}
237+
238+
static void
239+
test_parallel_accept_timed(void **state)
240+
{
241+
run_parallel_accept(state, NC_ACCEPT_TIMEOUT);
242+
}
243+
244+
static void
245+
test_parallel_accept_timeout_only_nonblocking(void **state)
246+
{
247+
run_timeout_only(state, 0);
248+
}
249+
250+
static void
251+
test_parallel_accept_timeout_only_timed(void **state)
252+
{
253+
run_timeout_only(state, SHORT_ACCEPT_TIMEOUT);
254+
}
255+
256+
static int
257+
setup_ssh(void **state)
258+
{
259+
int ret;
260+
struct lyd_node *tree = NULL;
261+
struct ln2_test_ctx *test_ctx;
262+
263+
ret = ln2_glob_test_setup(&test_ctx);
264+
assert_int_equal(ret, 0);
265+
266+
*state = test_ctx;
267+
268+
/* setup server with single SSH endpoint and one user with public key authentication */
269+
ret = nc_server_config_add_address_port(test_ctx->ctx, "endpt", NC_TI_SSH, "127.0.0.1", TEST_PORT, &tree);
270+
assert_int_equal(ret, 0);
271+
272+
ret = nc_server_config_add_ssh_hostkey(test_ctx->ctx, "endpt", "hostkey", TESTS_DIR "/data/key_ecdsa", NULL, &tree);
273+
assert_int_equal(ret, 0);
274+
275+
ret = nc_server_config_add_ssh_user_pubkey(test_ctx->ctx, "endpt", "parallel_client", "pubkey",
276+
TESTS_DIR "/data/id_ed25519.pub", &tree);
277+
assert_int_equal(ret, 0);
278+
279+
ret = nc_server_config_setup_data(tree);
280+
assert_int_equal(ret, 0);
281+
282+
lyd_free_all(tree);
283+
284+
return 0;
285+
}
286+
287+
int
288+
main(void)
289+
{
290+
const struct CMUnitTest tests[] = {
291+
cmocka_unit_test_setup_teardown(test_parallel_accept_nonblocking, setup_ssh, ln2_glob_test_teardown),
292+
cmocka_unit_test_setup_teardown(test_parallel_accept_timed, setup_ssh, ln2_glob_test_teardown),
293+
cmocka_unit_test_setup_teardown(test_parallel_accept_timeout_only_nonblocking, setup_ssh, ln2_glob_test_teardown),
294+
cmocka_unit_test_setup_teardown(test_parallel_accept_timeout_only_timed, setup_ssh, ln2_glob_test_teardown),
295+
};
296+
297+
if (ln2_glob_test_get_ports(1, &TEST_PORT, &TEST_PORT_STR)) {
298+
return 1;
299+
}
300+
301+
setenv("CMOCKA_TEST_ABORT", "1", 1);
302+
return cmocka_run_group_tests(tests, NULL, NULL);
303+
}

0 commit comments

Comments
 (0)