perf(functests): speedup — profrc pre-baking, pty-close shutdown, parallel port pools
All checks were successful
CI Code / Check spelling (pull_request) Successful in 18s
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Code Coverage (pull_request) Successful in 3m2s
CI Code / Linux (debian) (pull_request) Successful in 4m31s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m32s
CI Code / Linux (arch) (pull_request) Successful in 4m48s

Replace 16 interactive UI setup commands with pre-written profrc file
containing [ui] and [notifications] sections (~1800ms saved per test).

Replace sleep(1) + blocking waitpid with close(pty fd) → SIGHUP →
polling waitpid(WNOHANG) → SIGTERM/SIGKILL fallback chain (~4900ms
saved per test).

Remove post-stbbr_start() and post-stbbr_stop() sleeps — bind+listen
completes synchronously before stbbr_start() returns, and
pthread_join() in stbbr_stop() guarantees socket cleanup (~200ms saved).

Add PORTS_PER_GROUP=50 isolated port ranges per test group to enable
safe parallel execution of 4 groups without port conflicts.
This commit is contained in:
2026-03-12 13:06:56 +03:00
parent 9ec01fa8cc
commit d98f270cb6

View File

@@ -12,6 +12,7 @@
#include <fcntl.h>
#include <sys/select.h>
#include <regex.h>
#include <signal.h>
#include <stabber.h>
@@ -20,6 +21,14 @@
/* Number of parallel test groups for CI builds */
#define TEST_GROUPS 4
/* Number of ports reserved per test group; allows skipping TIME_WAIT ports */
#define PORTS_PER_GROUP 50
/* Shutdown polling: interval and maximum attempts before escalating signals */
#define SHUTDOWN_POLL_MS 50
#define SHUTDOWN_POLL_MAX 100 /* 100 x 50ms = 5s */
#define SIGTERM_POLL_MAX 20 /* 20 x 50ms = 1s */
char *config_orig;
char *data_orig;
@@ -246,38 +255,41 @@ init_prof_test(void **state)
const char *build_env = getenv("PROF_BUILD_INDEX");
int build_idx = build_env ? atoi(build_env) : 0;
/* Calculate port base: each build uses a different range of TEST_GROUPS ports.
* Build 0 (local/default): 5230-5233, Full: 5230-5233, Minimal: 5234-5237, etc.
* Build 0 and Full share the same range because build 0 is for local runs or sequential run (no parallel builds),
* while Full/Minimal/NoEncrypt/Default are used in CI where they run in parallel. */
int port_base = 5230 + ((build_idx > 0 ? build_idx - 1 : 0) * TEST_GROUPS);
/* Calculate port base: each build uses a different range of
* TEST_GROUPS * PORTS_PER_GROUP ports.
* Build 0 (local/default): 5230-5429, Full: 5230-5429, Minimal: 5430-5629, etc.
* Build 0 and Full share the same range because build 0 is for local runs
* or sequential run (no parallel builds), while Full/Minimal/NoEncrypt/Default
* are used in CI where they run in parallel. */
int port_base = 5230 + ((build_idx > 0 ? build_idx - 1 : 0) * TEST_GROUPS * PORTS_PER_GROUP);
/* Static resource allocation to avoid conflicts in parallel execution.
* Group 1-4: use static port assignment.
* Group 0 (all groups): use dynamic allocation as fallback. */
/* Each group gets a dedicated range of ports for parallel execution.
* Group 1: port_base..port_base+PORTS_PER_GROUP-1
* Group 2: port_base+PORTS_PER_GROUP..port_base+2*PORTS_PER_GROUP-1, etc.
* Ports in TIME_WAIT from previous tests are skipped automatically. */
gboolean started = FALSE;
if (group >= 1 && group <= TEST_GROUPS) {
/* Static allocation: each group gets a dedicated port */
stub_port = port_base + group - 1;
printf("[PROF_TEST] Build %d, Group %d: trying port %d\n", build_idx, group, stub_port);
if (stbbr_start(STBBR_LOGDEBUG, stub_port, 0) == 0) {
started = TRUE;
printf("[PROF_TEST] Started stabber on port %d\n", stub_port);
} else {
printf("[PROF_TEST] Failed to start stabber on port %d\n", stub_port);
}
}
/* Fallback to dynamic allocation if static failed or group=0 */
if (!started) {
printf("[PROF_TEST] Using dynamic port allocation\n");
for (int p = port_base; p < port_base + 20; ++p) {
int group_port_base = port_base + (group - 1) * PORTS_PER_GROUP;
for (int p = group_port_base; p < group_port_base + PORTS_PER_GROUP && !started; p++) {
if (stbbr_start(STBBR_LOGDEBUG, p, 0) == 0) {
stub_port = p;
started = TRUE;
}
}
if (!started) {
fprintf(stderr, "[PROF_TEST] Failed to start stabber on ports %d-%d\n",
group_port_base, group_port_base + PORTS_PER_GROUP - 1);
}
}
/* Fallback to dynamic allocation if static failed or group=0 */
if (!started) {
for (int p = port_base; p < port_base + 200; ++p) {
if (stbbr_start(STBBR_LOGDEBUG, p, 0) == 0) {
stub_port = p;
started = TRUE;
printf("[PROF_TEST] Started stabber on port %d\n", stub_port);
break;
}
}
@@ -299,8 +311,8 @@ init_prof_test(void **state)
printf("[PROF_TEST] Group %d using directories: config=%s, data=%s\n",
group, xdg_config_home, xdg_data_home);
// Give stabber server thread time to start listening
usleep(100000); // 100ms
/* No sleep needed: server_run() completes bind()+listen() before returning.
* Incoming connections queue in kernel backlog until accept() runs. */
config_orig = getenv("XDG_CONFIG_HOME");
data_orig = getenv("XDG_DATA_HOME");
@@ -315,38 +327,37 @@ init_prof_test(void **state)
_create_chatlogs_dir();
_create_logs_dir();
/* Pre-write profrc with UI/notification defaults to ensure consistent,
* deterministic output across tests (no timestamps, no roster/occupants
* panels, low input-blocking delay for fast command processing). */
char profrc_path[512];
snprintf(profrc_path, sizeof(profrc_path),
"%s/profanity/profrc", xdg_config_home);
FILE* prc = fopen(profrc_path, "w");
if (prc) {
fprintf(prc,
"[ui]\n"
"inpblock=5\n"
"inpblock.dynamic=false\n"
"wrap=false\n"
"roster=false\n"
"occupants=false\n"
"time.console=off\n"
"time.chat=off\n"
"time.muc=off\n"
"time.config=off\n"
"time.private=off\n"
"time.xmlconsole=off\n"
"[notifications]\n"
"message=false\n"
"room=false\n");
fclose(prc);
}
prof_start();
int prof_started = prof_output_regex("CProof\\. Type /help for help information\\.");
assert_true(prof_started);
// set UI options to make expect assertions faster and more reliable
prof_input("/inpblock timeout 5");
assert_true(prof_output_regex("Input blocking set to 5 milliseconds"));
prof_input("/inpblock dynamic off");
assert_true(prof_output_regex("Dynamic input blocking disabled"));
prof_input("/notify chat off");
assert_true(prof_output_regex("Chat notifications disabled"));
prof_input("/notify room off");
assert_true(prof_output_regex("Room notifications disabled"));
prof_input("/wrap off");
assert_true(prof_output_regex("Word wrap disabled"));
prof_input("/roster hide");
assert_true(prof_output_regex("Roster disabled"));
prof_input("/occupants default hide");
assert_true(prof_output_regex("Occupant list disabled"));
prof_input("/time console off");
prof_input("/time console off");
assert_true(prof_output_regex("Console time display disabled\\."));
prof_input("/time chat off");
assert_true(prof_output_regex("Chat time display disabled\\."));
prof_input("/time muc off");
assert_true(prof_output_regex("MUC time display disabled\\."));
prof_input("/time config off");
assert_true(prof_output_regex("Config time display disabled\\."));
prof_input("/time private off");
assert_true(prof_output_regex("Private chat time display disabled\\."));
prof_input("/time xml off");
assert_true(prof_output_regex("XML Console time display disabled\\."));
return 0;
}
@@ -355,11 +366,31 @@ close_prof_test(void **state)
{
if (fd > 0 && child_pid > 0) {
prof_input("/quit");
// Give profanity time to process quit command
sleep(1);
waitpid(child_pid, NULL, 0);
/* Close pty master after brief grace — child gets SIGHUP which
* accelerates shutdown instead of waiting for XMPP disconnect
* timeout (~5s). */
usleep(100000); /* 100ms grace for /quit to be read from pty buffer */
close(fd);
fd = 0;
int exited = 0;
for (int i = 0; i < SHUTDOWN_POLL_MAX; i++) {
if (waitpid(child_pid, NULL, WNOHANG) != 0) {
exited = 1;
break;
}
usleep(SHUTDOWN_POLL_MS * 1000);
}
if (!exited) {
kill(child_pid, SIGTERM);
for (int i = 0; i < SIGTERM_POLL_MAX; i++) {
if (waitpid(child_pid, NULL, WNOHANG) != 0) { exited = 1; break; }
usleep(SHUTDOWN_POLL_MS * 1000);
}
if (!exited) {
kill(child_pid, SIGKILL);
waitpid(child_pid, NULL, 0);
}
}
child_pid = 0;
}
@@ -371,13 +402,6 @@ close_prof_test(void **state)
}
stbbr_stop();
/*
* TODO: Replace with proper synchronization.
* stabber doesn't provide wait_stopped() API yet, so we use delay
* to ensure the port is released before the next test starts.
* See: https://git.jabber.space/devs/stabber/issues/3
*/
usleep(100000); // 100ms
return 0;
}