Skip to content

Commit 8a34236

Browse files
committed
tests UPDATE add server thread test
Tests accepting connections with multiple threads with different timeouts.
1 parent 125a9d3 commit 8a34236

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-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: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
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+
struct ln2_test_ctx *test_ctx;
46+
};
47+
48+
struct no_client_state {
49+
pthread_barrier_t start_barrier;
50+
int accept_timeout;
51+
struct ln2_test_ctx *test_ctx;
52+
};
53+
54+
static void *
55+
server_thread_accept_all(void *arg)
56+
{
57+
int done;
58+
NC_MSG_TYPE msgtype;
59+
struct nc_session *session = NULL;
60+
struct accept_state *state = arg;
61+
62+
/* wait until all server and client threads are ready to start the test */
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+
usleep(NONBLOCK_BACKOFF_USECS);
89+
} else {
90+
fail_msg("Unexpected nc_accept return code %d", msgtype);
91+
}
92+
}
93+
94+
return NULL;
95+
}
96+
97+
static void *
98+
client_thread_connect(void *arg)
99+
{
100+
int ret = 0;
101+
struct nc_session *session = NULL;
102+
struct accept_state *state = arg;
103+
104+
ret = nc_client_set_schema_searchpath(MODULES_DIR);
105+
assert_int_equal(ret, 0);
106+
107+
ret = nc_client_ssh_set_username("parallel_client");
108+
assert_int_equal(ret, 0);
109+
110+
ret = nc_client_ssh_add_keypair(TESTS_DIR "/data/id_ed25519.pub", TESTS_DIR "/data/id_ed25519");
111+
assert_int_equal(ret, 0);
112+
113+
nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP);
114+
115+
/* wait until the server threads are ready to accept connections */
116+
pthread_barrier_wait(&state->start_barrier);
117+
118+
session = nc_connect_ssh("127.0.0.1", TEST_PORT, NULL);
119+
assert_non_null(session);
120+
nc_session_free(session, NULL);
121+
122+
return NULL;
123+
}
124+
125+
static void *
126+
server_thread_timeout_only(void *arg)
127+
{
128+
int i;
129+
NC_MSG_TYPE msgtype;
130+
struct nc_session *session = NULL;
131+
struct no_client_state *state = arg;
132+
133+
/* wait until all server threads are ready to start the test */
134+
pthread_barrier_wait(&state->start_barrier);
135+
136+
for (i = 0; i < NOCLIENT_ATTEMPTS; ++i) {
137+
msgtype = nc_accept(state->accept_timeout, state->test_ctx->ctx, &session);
138+
assert_int_equal(msgtype, NC_MSG_WOULDBLOCK);
139+
assert_null(session);
140+
141+
usleep(NONBLOCK_BACKOFF_USECS);
142+
}
143+
144+
return NULL;
145+
}
146+
147+
static void
148+
run_parallel_accept(void **state, int accept_timeout)
149+
{
150+
int i, ret;
151+
pthread_t server_tids[PARALLEL_SERVER_THREADS];
152+
pthread_t client_tids[PARALLEL_CLIENT_THREADS];
153+
struct ln2_test_ctx *test_ctx = *state;
154+
struct accept_state accept_state;
155+
156+
accept_state.accept_timeout = accept_timeout;
157+
accept_state.client_count = PARALLEL_CLIENT_THREADS;
158+
accept_state.accepted_count = 0;
159+
accept_state.timeout_count = 0;
160+
accept_state.test_ctx = test_ctx;
161+
162+
/* sync all threads to start at the same time */
163+
ret = pthread_barrier_init(&accept_state.start_barrier, NULL,
164+
PARALLEL_SERVER_THREADS + PARALLEL_CLIENT_THREADS + 1);
165+
assert_int_equal(ret, 0);
166+
ret = pthread_mutex_init(&accept_state.lock, NULL);
167+
assert_int_equal(ret, 0);
168+
169+
/* start server threads */
170+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
171+
ret = pthread_create(&server_tids[i], NULL, server_thread_accept_all, &accept_state);
172+
assert_int_equal(ret, 0);
173+
}
174+
175+
/* start client threads */
176+
for (i = 0; i < PARALLEL_CLIENT_THREADS; ++i) {
177+
ret = pthread_create(&client_tids[i], NULL, client_thread_connect, &accept_state);
178+
assert_int_equal(ret, 0);
179+
}
180+
181+
/* wait until all threads are ready to start the test */
182+
pthread_barrier_wait(&accept_state.start_barrier);
183+
184+
for (i = 0; i < PARALLEL_CLIENT_THREADS; ++i) {
185+
pthread_join(client_tids[i], NULL);
186+
}
187+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
188+
pthread_join(server_tids[i], NULL);
189+
}
190+
191+
/* all clients should have been accepted */
192+
assert_int_equal(accept_state.accepted_count, PARALLEL_CLIENT_THREADS);
193+
194+
pthread_mutex_destroy(&accept_state.lock);
195+
pthread_barrier_destroy(&accept_state.start_barrier);
196+
}
197+
198+
static void
199+
run_timeout_only(void **state, int accept_timeout)
200+
{
201+
int i, ret;
202+
pthread_t server_tids[PARALLEL_SERVER_THREADS];
203+
struct ln2_test_ctx *test_ctx = *state;
204+
struct no_client_state no_client_state;
205+
206+
no_client_state.accept_timeout = accept_timeout;
207+
no_client_state.test_ctx = test_ctx;
208+
209+
/* sync all threads to start at the same time */
210+
ret = pthread_barrier_init(&no_client_state.start_barrier, NULL, PARALLEL_SERVER_THREADS + 1);
211+
assert_int_equal(ret, 0);
212+
213+
/* start server threads, no client threads will be started, so all threads should only experience timeouts */
214+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
215+
ret = pthread_create(&server_tids[i], NULL, server_thread_timeout_only, &no_client_state);
216+
assert_int_equal(ret, 0);
217+
}
218+
219+
/* wait until all threads are ready to start the test */
220+
pthread_barrier_wait(&no_client_state.start_barrier);
221+
222+
for (i = 0; i < PARALLEL_SERVER_THREADS; ++i) {
223+
pthread_join(server_tids[i], NULL);
224+
}
225+
226+
pthread_barrier_destroy(&no_client_state.start_barrier);
227+
}
228+
229+
static void
230+
test_parallel_accept_nonblocking(void **state)
231+
{
232+
run_parallel_accept(state, 0);
233+
}
234+
235+
static void
236+
test_parallel_accept_timed(void **state)
237+
{
238+
run_parallel_accept(state, NC_ACCEPT_TIMEOUT);
239+
}
240+
241+
static void
242+
test_parallel_accept_timeout_only_nonblocking(void **state)
243+
{
244+
run_timeout_only(state, 0);
245+
}
246+
247+
static void
248+
test_parallel_accept_timeout_only_timed(void **state)
249+
{
250+
run_timeout_only(state, SHORT_ACCEPT_TIMEOUT);
251+
}
252+
253+
static int
254+
setup_ssh(void **state)
255+
{
256+
int ret;
257+
struct lyd_node *tree = NULL;
258+
struct ln2_test_ctx *test_ctx;
259+
260+
ret = ln2_glob_test_setup(&test_ctx);
261+
assert_int_equal(ret, 0);
262+
263+
*state = test_ctx;
264+
265+
/* setup server with single SSH endpoint and one user with public key authentication */
266+
ret = nc_server_config_add_address_port(test_ctx->ctx, "endpt", NC_TI_SSH, "127.0.0.1", TEST_PORT, &tree);
267+
assert_int_equal(ret, 0);
268+
269+
ret = nc_server_config_add_ssh_hostkey(test_ctx->ctx, "endpt", "hostkey", TESTS_DIR "/data/key_ecdsa", NULL, &tree);
270+
assert_int_equal(ret, 0);
271+
272+
ret = nc_server_config_add_ssh_user_pubkey(test_ctx->ctx, "endpt", "parallel_client", "pubkey",
273+
TESTS_DIR "/data/id_ed25519.pub", &tree);
274+
assert_int_equal(ret, 0);
275+
276+
ret = nc_server_config_setup_data(tree);
277+
assert_int_equal(ret, 0);
278+
279+
lyd_free_all(tree);
280+
281+
return 0;
282+
}
283+
284+
int
285+
main(void)
286+
{
287+
const struct CMUnitTest tests[] = {
288+
cmocka_unit_test_setup_teardown(test_parallel_accept_nonblocking, setup_ssh, ln2_glob_test_teardown),
289+
cmocka_unit_test_setup_teardown(test_parallel_accept_timed, setup_ssh, ln2_glob_test_teardown),
290+
cmocka_unit_test_setup_teardown(test_parallel_accept_timeout_only_nonblocking, setup_ssh, ln2_glob_test_teardown),
291+
cmocka_unit_test_setup_teardown(test_parallel_accept_timeout_only_timed, setup_ssh, ln2_glob_test_teardown),
292+
};
293+
294+
if (ln2_glob_test_get_ports(1, &TEST_PORT, &TEST_PORT_STR)) {
295+
return 1;
296+
}
297+
298+
setenv("CMOCKA_TEST_ABORT", "1", 1);
299+
return cmocka_run_group_tests(tests, NULL, NULL);
300+
}

0 commit comments

Comments
 (0)