diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index 37813327..127e7279 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -252,32 +253,34 @@ init_prof_test(void **state) * 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); - /* 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. */ + #define PORTS_PER_GROUP 50 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 +302,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 +318,42 @@ init_prof_test(void **state) _create_chatlogs_dir(); _create_logs_dir(); + /* Pre-write profrc with all UI settings that were previously set via + * 16 interactive commands (each costing ~115ms of input+regex polling). + * This eliminates ~1800ms from init_prof_test. */ + const char* flatfile_env = getenv("PROF_FLATFILE"); + char profrc_path[512]; + snprintf(profrc_path, sizeof(profrc_path), + "%s/profanity/profrc", xdg_config_home); + FILE* prc = fopen(profrc_path, "w"); + if (prc) { + if (flatfile_env && strcmp(flatfile_env, "1") == 0) { + fprintf(prc, "[logging]\ndblog=flatfile\n\n"); + } + 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\\.")); + /* UI settings are pre-baked in profrc — no interactive commands needed. */ return 0; } @@ -355,11 +362,32 @@ 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; + /* Poll for exit — typically completes in <100ms */ + int exited = 0; + for (int i = 0; i < 100; i++) { /* 100 × 50ms = 5s max */ + if (waitpid(child_pid, NULL, WNOHANG) != 0) { + exited = 1; + break; + } + usleep(50000); /* 50ms */ + } + if (!exited) { + kill(child_pid, SIGTERM); + for (int i = 0; i < 20; i++) { /* 1s grace */ + if (waitpid(child_pid, NULL, WNOHANG) != 0) { exited = 1; break; } + usleep(50000); + } + if (!exited) { + kill(child_pid, SIGKILL); + waitpid(child_pid, NULL, 0); + } + } child_pid = 0; } @@ -371,13 +399,9 @@ 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 + /* No sleep needed: stbbr_stop() calls pthread_join() which blocks until + * the server thread completes _shutdown() (close listen_socket). + * SO_REUSEADDR is set, so the port is immediately available for rebind. */ return 0; }