Skip to content

Commit f8f49df

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

2 files changed

Lines changed: 297 additions & 0 deletions

File tree

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: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
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 12
31+
#define NOCLIENT_ATTEMPTS 8
32+
#define NONBLOCK_BACKOFF_USECS 1000
33+
#define SHORT_ACCEPT_TIMEOUT 250
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+
pthread_barrier_wait(&state->start_barrier);
64+
65+
while (1) {
66+
pthread_mutex_lock(&state->lock);
67+
done = state->accepted_count >= state->client_count;
68+
pthread_mutex_unlock(&state->lock);
69+
if (done) {
70+
break;
71+
}
72+
73+
msgtype = nc_accept(state->accept_timeout, state->test_ctx->ctx, &session);
74+
if (msgtype == NC_MSG_HELLO) {
75+
assert_non_null(session);
76+
nc_session_free(session, NULL);
77+
78+
pthread_mutex_lock(&state->lock);
79+
++state->accepted_count;
80+
pthread_mutex_unlock(&state->lock);
81+
} else if (msgtype == NC_MSG_WOULDBLOCK) {
82+
assert_null(session);
83+
84+
pthread_mutex_lock(&state->lock);
85+
++state->timeout_count;
86+
pthread_mutex_unlock(&state->lock);
87+
88+
if (!state->accept_timeout) {
89+
usleep(NONBLOCK_BACKOFF_USECS);
90+
}
91+
} else {
92+
fail_msg("Unexpected nc_accept return code %d", msgtype);
93+
}
94+
}
95+
96+
return NULL;
97+
}
98+
99+
static void *
100+
client_thread_connect(void *arg)
101+
{
102+
struct nc_session *session = NULL;
103+
struct accept_state *state = arg;
104+
105+
nc_client_set_thread_context(state->client_thr_ctx);
106+
107+
pthread_barrier_wait(&state->start_barrier);
108+
109+
session = nc_connect_ssh("127.0.0.1", TEST_PORT, NULL);
110+
assert_non_null(session);
111+
nc_session_free(session, NULL);
112+
113+
return NULL;
114+
}
115+
116+
static void *
117+
server_thread_timeout_only(void *arg)
118+
{
119+
int i;
120+
NC_MSG_TYPE msgtype;
121+
struct nc_session *session = NULL;
122+
struct no_client_state *state = arg;
123+
124+
pthread_barrier_wait(&state->start_barrier);
125+
126+
for (i = 0; i < NOCLIENT_ATTEMPTS; ++i) {
127+
msgtype = nc_accept(state->accept_timeout, state->test_ctx->ctx, &session);
128+
assert_int_equal(msgtype, NC_MSG_WOULDBLOCK);
129+
assert_null(session);
130+
131+
if (!state->accept_timeout) {
132+
usleep(NONBLOCK_BACKOFF_USECS);
133+
}
134+
}
135+
136+
return NULL;
137+
}
138+
139+
static void
140+
run_parallel_accept(void **state, int accept_timeout)
141+
{
142+
int i, ret;
143+
pthread_t server_tids[PARALLEL_SERVER_THREADS];
144+
pthread_t client_tids[PARALLEL_CLIENT_THREADS];
145+
struct ln2_test_ctx *test_ctx = *state;
146+
struct accept_state accept_state;
147+
148+
accept_state.accept_timeout = accept_timeout;
149+
accept_state.client_count = PARALLEL_CLIENT_THREADS;
150+
accept_state.accepted_count = 0;
151+
accept_state.timeout_count = 0;
152+
accept_state.client_thr_ctx = nc_client_get_thread_context();
153+
assert_non_null(accept_state.client_thr_ctx);
154+
accept_state.test_ctx = test_ctx;
155+
156+
ret = pthread_barrier_init(&accept_state.start_barrier, NULL,
157+
PARALLEL_SERVER_THREADS + PARALLEL_CLIENT_THREADS + 1);
158+
assert_int_equal(ret, 0);
159+
ret = pthread_mutex_init(&accept_state.lock, NULL);
160+
assert_int_equal(ret, 0);
161+
162+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
163+
ret = pthread_create(&server_tids[i], NULL, server_thread_accept_all, &accept_state);
164+
assert_int_equal(ret, 0);
165+
}
166+
167+
for (i = 0; i < PARALLEL_CLIENT_THREADS; ++i) {
168+
ret = pthread_create(&client_tids[i], NULL, client_thread_connect, &accept_state);
169+
assert_int_equal(ret, 0);
170+
}
171+
172+
pthread_barrier_wait(&accept_state.start_barrier);
173+
174+
for (i = 0; i < PARALLEL_CLIENT_THREADS; ++i) {
175+
pthread_join(client_tids[i], NULL);
176+
}
177+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
178+
pthread_join(server_tids[i], NULL);
179+
}
180+
181+
assert_int_equal(accept_state.accepted_count, PARALLEL_CLIENT_THREADS);
182+
183+
pthread_mutex_destroy(&accept_state.lock);
184+
pthread_barrier_destroy(&accept_state.start_barrier);
185+
}
186+
187+
static void
188+
run_timeout_only(void **state, int accept_timeout)
189+
{
190+
int i, ret;
191+
pthread_t server_tids[PARALLEL_SERVER_THREADS];
192+
struct ln2_test_ctx *test_ctx = *state;
193+
struct no_client_state no_client_state;
194+
195+
no_client_state.accept_timeout = accept_timeout;
196+
no_client_state.test_ctx = test_ctx;
197+
198+
ret = pthread_barrier_init(&no_client_state.start_barrier, NULL, PARALLEL_SERVER_THREADS + 1);
199+
assert_int_equal(ret, 0);
200+
201+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
202+
ret = pthread_create(&server_tids[i], NULL, server_thread_timeout_only, &no_client_state);
203+
assert_int_equal(ret, 0);
204+
}
205+
206+
pthread_barrier_wait(&no_client_state.start_barrier);
207+
208+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
209+
pthread_join(server_tids[i], NULL);
210+
}
211+
212+
pthread_barrier_destroy(&no_client_state.start_barrier);
213+
}
214+
215+
static void
216+
test_parallel_accept_nonblocking(void **state)
217+
{
218+
run_parallel_accept(state, 0);
219+
}
220+
221+
static void
222+
test_parallel_accept_timed(void **state)
223+
{
224+
run_parallel_accept(state, NC_ACCEPT_TIMEOUT);
225+
}
226+
227+
static void
228+
test_parallel_accept_timeout_only_nonblocking(void **state)
229+
{
230+
run_timeout_only(state, 0);
231+
}
232+
233+
static void
234+
test_parallel_accept_timeout_only_timed(void **state)
235+
{
236+
run_timeout_only(state, SHORT_ACCEPT_TIMEOUT);
237+
}
238+
239+
static int
240+
setup_ssh(void **state)
241+
{
242+
int ret;
243+
struct lyd_node *tree = NULL;
244+
struct ln2_test_ctx *test_ctx;
245+
246+
ret = ln2_glob_test_setup(&test_ctx);
247+
assert_int_equal(ret, 0);
248+
249+
*state = test_ctx;
250+
251+
ret = nc_server_config_add_address_port(test_ctx->ctx, "endpt", NC_TI_SSH, "127.0.0.1", TEST_PORT, &tree);
252+
assert_int_equal(ret, 0);
253+
254+
ret = nc_server_config_add_ssh_hostkey(test_ctx->ctx, "endpt", "hostkey", TESTS_DIR "/data/key_ecdsa", NULL, &tree);
255+
assert_int_equal(ret, 0);
256+
257+
ret = nc_server_config_add_ssh_user_pubkey(test_ctx->ctx, "endpt", "parallel_client", "pubkey",
258+
TESTS_DIR "/data/id_ed25519.pub", &tree);
259+
assert_int_equal(ret, 0);
260+
261+
ret = nc_server_config_setup_data(tree);
262+
assert_int_equal(ret, 0);
263+
264+
lyd_free_all(tree);
265+
266+
nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP);
267+
268+
ret = nc_client_set_schema_searchpath(MODULES_DIR);
269+
assert_int_equal(ret, 0);
270+
271+
ret = nc_client_ssh_set_username("parallel_client");
272+
assert_int_equal(ret, 0);
273+
274+
ret = nc_client_ssh_add_keypair(TESTS_DIR "/data/id_ed25519.pub", TESTS_DIR "/data/id_ed25519");
275+
assert_int_equal(ret, 0);
276+
277+
return 0;
278+
}
279+
280+
int
281+
main(void)
282+
{
283+
const struct CMUnitTest tests[] = {
284+
cmocka_unit_test_setup_teardown(test_parallel_accept_nonblocking, setup_ssh, ln2_glob_test_teardown),
285+
cmocka_unit_test_setup_teardown(test_parallel_accept_timed, setup_ssh, ln2_glob_test_teardown),
286+
cmocka_unit_test_setup_teardown(test_parallel_accept_timeout_only_nonblocking, setup_ssh, ln2_glob_test_teardown),
287+
cmocka_unit_test_setup_teardown(test_parallel_accept_timeout_only_timed, setup_ssh, ln2_glob_test_teardown),
288+
};
289+
290+
if (ln2_glob_test_get_ports(1, &TEST_PORT, &TEST_PORT_STR)) {
291+
return 1;
292+
}
293+
294+
setenv("CMOCKA_TEST_ABORT", "1", 1);
295+
return cmocka_run_group_tests(tests, NULL, NULL);
296+
}

0 commit comments

Comments
 (0)