From fa17173e5c21ef156f78fc7ab119fa8086cdce12 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Tue, 13 Jan 2026 17:46:03 +0300 Subject: [PATCH] feat(tests): enable parallel execution of functional tests --- Makefile.am | 17 +++ ci-build.sh | 2 +- tests/functionaltests/functionaltests.c | 168 +++++++++++++++--------- tests/functionaltests/proftest.c | 29 +++- tests/functionaltests/proftest.h | 9 +- 5 files changed, 151 insertions(+), 74 deletions(-) diff --git a/Makefile.am b/Makefile.am index acd005bf..d1f2f8a7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -305,6 +305,23 @@ tests_functionaltests_functionaltests_SOURCES = $(functionaltest_sources) tests_functionaltests_functionaltests_CPPFLAGS = -Itests/ tests_functionaltests_functionaltests_CFLAGS = $(AM_CFLAGS) tests_functionaltests_functionaltests_LDADD = -lcmocka -lstabber @FORKPTY_LIB@ + +# Parallel functional tests target (~3x faster than sequential) +# Usage: make check-functional-parallel +check-functional-parallel: tests/functionaltests/functionaltests + @echo "Running functional tests in parallel (4 groups)..." + @mkdir -p $(builddir)/test-logs + @( \ + ./tests/functionaltests/functionaltests 1 > $(builddir)/test-logs/group1.log 2>&1 & \ + ./tests/functionaltests/functionaltests 2 > $(builddir)/test-logs/group2.log 2>&1 & \ + ./tests/functionaltests/functionaltests 3 > $(builddir)/test-logs/group3.log 2>&1 & \ + ./tests/functionaltests/functionaltests 4 > $(builddir)/test-logs/group4.log 2>&1 & \ + wait \ + ) && ( \ + echo "=== Test Results ==="; \ + grep -E 'PASSED|FAILED|Running' $(builddir)/test-logs/group*.log; \ + ! grep -q FAILED $(builddir)/test-logs/group*.log \ + ) endif endif diff --git a/ci-build.sh b/ci-build.sh index 0a3defb8..439fb2e0 100755 --- a/ci-build.sh +++ b/ci-build.sh @@ -139,7 +139,7 @@ do ./configure $features $* $MAKE CC="${CC}" - $MAKE check + $MAKE check-functional-parallel ./profanity -v $MAKE clean diff --git a/tests/functionaltests/functionaltests.c b/tests/functionaltests/functionaltests.c index 8e33ffd1..cd2a34bc 100644 --- a/tests/functionaltests/functionaltests.c +++ b/tests/functionaltests/functionaltests.c @@ -14,16 +14,28 @@ * flaky tests caused by leftover state. The overhead is acceptable since * functional tests run less frequently than unit tests. * - * Tests are organized into groups for better maintainability: - * Group 1: Connection, Ping, Rooms, Presence - * Group 2: Messages, Receipts, Roster management - * Group 3: MUC (Multi-User Chat) functionality - * Group 4: Carbons, Chat sessions, Software version, Disconnect + * Tests are organized into groups for better maintainability and parallel execution: + * Group 1: Connect, Ping, Rooms, Software (17 tests) + * Group 2: Message, Receipts, Roster, Chat Session (17 tests) + * Group 3: Presence (16 tests) + * Group 4: MUC, Carbons (19 tests) + * + * Parallel execution: + * ./functionaltests - run all tests sequentially + * ./functionaltests 1 - run group 1 only + * ./functionaltests 2 - run group 2 only + * ./functionaltests 3 - run group 3 only + * ./functionaltests 4 - run group 4 only + * + * For parallel execution, run multiple groups simultaneously: + * ./functionaltests 1 & ./functionaltests 2 & ./functionaltests 3 & ./functionaltests 4 & wait */ #include +#include #include #include +#include #include "prof_cmocka.h" #include @@ -49,14 +61,21 @@ int main(int argc, char* argv[]) { - const struct CMUnitTest all_tests[] = { + int group = 0; /* 0 = all groups */ + if (argc > 1) { + group = atoi(argv[1]); + if (group < 1 || group > 4) { + fprintf(stderr, "Usage: %s [group]\n", argv[0]); + fprintf(stderr, " group: 1-4 to run specific group, or omit for all\n"); + return 1; + } + } - /* ============================================================ - * GROUP 1: Connect, Ping, Rooms, Presence - * Basic XMPP session establishment and presence management - * ============================================================ */ + /* GROUP 1: Connect, Ping, Rooms, Software (17 tests) + * Basic XMPP session establishment and server queries */ - /* Connection tests - verify login, roster, bookmarks */ + /* Connection tests - verify login, roster, bookmarks */ + const struct CMUnitTest group1_tests[] = { PROF_FUNC_TEST(connect_jid_requests_roster), PROF_FUNC_TEST(connect_jid_sends_presence_after_receiving_roster), PROF_FUNC_TEST(connect_jid_requests_bookmarks), @@ -73,27 +92,18 @@ main(int argc, char* argv[]) /* Room discovery - XEP-0045 */ PROF_FUNC_TEST(rooms_query), - /* Presence tests - online/away/xa/dnd/chat status */ - PROF_FUNC_TEST(presence_online), - PROF_FUNC_TEST(presence_online_with_message), - PROF_FUNC_TEST(presence_away), - PROF_FUNC_TEST(presence_away_with_message), - PROF_FUNC_TEST(presence_xa), - PROF_FUNC_TEST(presence_xa_with_message), - PROF_FUNC_TEST(presence_dnd), - PROF_FUNC_TEST(presence_dnd_with_message), - PROF_FUNC_TEST(presence_chat), - PROF_FUNC_TEST(presence_chat_with_message), - PROF_FUNC_TEST(presence_set_priority), - PROF_FUNC_TEST(presence_includes_priority), - PROF_FUNC_TEST(presence_keeps_status), - PROF_FUNC_TEST(presence_received), - PROF_FUNC_TEST(presence_missing_resource_defaults), + /* Software Version - XEP-0092 */ + PROF_FUNC_TEST(send_software_version_request), + PROF_FUNC_TEST(display_software_version_result), + PROF_FUNC_TEST(shows_message_when_software_version_error), + PROF_FUNC_TEST(display_software_version_result_when_from_domainpart), + PROF_FUNC_TEST(show_message_in_chat_window_when_no_resource), + PROF_FUNC_TEST(display_software_version_result_in_chat), + }; - /* ============================================================ - * GROUP 2: Message, Receipts, Roster - * Core messaging and contact management - * ============================================================ */ + /* GROUP 2: Message, Receipts, Roster, Chat Session (17 tests) + * Core messaging and contact management */ + const struct CMUnitTest group2_tests[] = { /* Basic message send/receive */ PROF_FUNC_TEST(message_send), @@ -112,21 +122,51 @@ main(int argc, char* argv[]) PROF_FUNC_TEST(sends_remove_item_nick), PROF_FUNC_TEST(sends_nick_change), - /* ============================================================ - * GROUP 3: MUC (Multi-User Chat) - * XEP-0045 conference room functionality - * ============================================================ */ + /* Chat session management - bare/full JID routing */ + PROF_FUNC_TEST(sends_message_to_barejid_when_contact_offline), + PROF_FUNC_TEST(sends_message_to_barejid_when_contact_online), + PROF_FUNC_TEST(sends_message_to_fulljid_when_received_from_fulljid), + PROF_FUNC_TEST(sends_subsequent_messages_to_fulljid), + PROF_FUNC_TEST(resets_to_barejid_after_presence_received), + PROF_FUNC_TEST(new_session_when_message_received_from_different_fulljid), + }; - /* Room join with various options */ + /* GROUP 3: Presence (16 tests) + * Online/away/xa/dnd/chat status management */ + const struct CMUnitTest group3_tests[] = { + PROF_FUNC_TEST(presence_online), + PROF_FUNC_TEST(presence_online_with_message), + PROF_FUNC_TEST(presence_away), + PROF_FUNC_TEST(presence_away_with_message), + PROF_FUNC_TEST(presence_xa), + PROF_FUNC_TEST(presence_xa_with_message), + PROF_FUNC_TEST(presence_dnd), + PROF_FUNC_TEST(presence_dnd_with_message), + PROF_FUNC_TEST(presence_chat), + PROF_FUNC_TEST(presence_chat_with_message), + PROF_FUNC_TEST(presence_set_priority), + PROF_FUNC_TEST(presence_includes_priority), + PROF_FUNC_TEST(presence_keeps_status), + PROF_FUNC_TEST(presence_received), + PROF_FUNC_TEST(presence_missing_resource_defaults), + + /* Disconnect - clean session termination */ + PROF_FUNC_TEST(disconnect_ends_session), + }; + + /* GROUP 4: MUC, Carbons (19 tests) + * Multi-user chat and message synchronization */ + const struct CMUnitTest group4_tests[] = { + + /* MUC room join with various options - XEP-0045 */ PROF_FUNC_TEST(sends_room_join), PROF_FUNC_TEST(sends_room_join_with_nick), PROF_FUNC_TEST(sends_room_join_with_password), PROF_FUNC_TEST(sends_room_join_with_nick_and_password), - /* Room information display */ + /* MUC room information display */ PROF_FUNC_TEST(shows_role_and_affiliation_on_join), PROF_FUNC_TEST(shows_subject_on_join), - // PROF_FUNC_TEST(shows_history_message), // temporarily disabled due to timing issues in CI PROF_FUNC_TEST(shows_occupant_join), /* MUC messaging */ @@ -134,16 +174,11 @@ main(int argc, char* argv[]) PROF_FUNC_TEST(shows_me_message_from_occupant), PROF_FUNC_TEST(shows_me_message_from_self), - /* Console notification settings for MUC */ + /* MUC console notification settings */ PROF_FUNC_TEST(shows_all_messages_in_console_when_window_not_focussed), PROF_FUNC_TEST(shows_first_message_in_console_when_window_not_focussed), PROF_FUNC_TEST(shows_no_message_in_console_when_window_not_focussed), - /* ============================================================ - * GROUP 4: Carbons, Chat Session, Software, Disconnect - * Message synchronization and session management - * ============================================================ */ - /* Message Carbons - XEP-0280 (message sync across devices) */ PROF_FUNC_TEST(send_enable_carbons), PROF_FUNC_TEST(connect_with_carbons_enabled), @@ -151,26 +186,31 @@ main(int argc, char* argv[]) PROF_FUNC_TEST(receive_carbon), PROF_FUNC_TEST(receive_self_carbon), PROF_FUNC_TEST(receive_private_carbon), - - /* Chat session management - bare/full JID routing */ - PROF_FUNC_TEST(sends_message_to_barejid_when_contact_offline), - PROF_FUNC_TEST(sends_message_to_barejid_when_contact_online), - PROF_FUNC_TEST(sends_message_to_fulljid_when_received_from_fulljid), - PROF_FUNC_TEST(sends_subsequent_messages_to_fulljid), - PROF_FUNC_TEST(resets_to_barejid_after_presence_received), - PROF_FUNC_TEST(new_session_when_message_received_from_different_fulljid), - - /* Software Version - XEP-0092 */ - PROF_FUNC_TEST(send_software_version_request), - PROF_FUNC_TEST(display_software_version_result), - PROF_FUNC_TEST(shows_message_when_software_version_error), - PROF_FUNC_TEST(display_software_version_result_when_from_domainpart), - PROF_FUNC_TEST(show_message_in_chat_window_when_no_resource), - PROF_FUNC_TEST(display_software_version_result_in_chat), - - /* Disconnect - clean session termination */ - PROF_FUNC_TEST(disconnect_ends_session), }; - return cmocka_run_group_tests(all_tests, NULL, NULL); + int result = 0; + + switch (group) { + case 1: + result = cmocka_run_group_tests_name("Group 1: Connect/Ping/Rooms/Software", group1_tests, NULL, NULL); + break; + case 2: + result = cmocka_run_group_tests_name("Group 2: Message/Receipts/Roster/Session", group2_tests, NULL, NULL); + break; + case 3: + result = cmocka_run_group_tests_name("Group 3: Presence", group3_tests, NULL, NULL); + break; + case 4: + result = cmocka_run_group_tests_name("Group 4: MUC/Carbons", group4_tests, NULL, NULL); + break; + default: + /* Run all groups sequentially */ + result |= cmocka_run_group_tests_name("Group 1: Connect/Ping/Rooms/Software", group1_tests, NULL, NULL); + result |= cmocka_run_group_tests_name("Group 2: Message/Receipts/Roster/Session", group2_tests, NULL, NULL); + result |= cmocka_run_group_tests_name("Group 3: Presence", group3_tests, NULL, NULL); + result |= cmocka_run_group_tests_name("Group 4: MUC/Carbons", group4_tests, NULL, NULL); + break; + } + + return result; } diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index 3eb0e193..4ece33d6 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -24,6 +24,13 @@ int fd = 0; int stub_port = 5230; pid_t child_pid = 0; +/* + * Dynamic XDG paths based on stub_port for parallel test execution. + * Each test instance gets unique directories to avoid file conflicts. + */ +char xdg_config_home[256]; +char xdg_data_home[256]; + /* * Buffer for accumulating output from profanity. * 64KB is sufficient for typical test output while keeping memory usage @@ -77,7 +84,7 @@ _mkdir_recursive(const char *dir) void _create_config_dir(void) { - GString *profanity_dir = g_string_new(XDG_CONFIG_HOME); + GString *profanity_dir = g_string_new(xdg_config_home); g_string_append(profanity_dir, "/profanity"); if (!_mkdir_recursive(profanity_dir->str)) { @@ -90,7 +97,7 @@ _create_config_dir(void) void _create_data_dir(void) { - GString *profanity_dir = g_string_new(XDG_DATA_HOME); + GString *profanity_dir = g_string_new(xdg_data_home); g_string_append(profanity_dir, "/profanity"); if (!_mkdir_recursive(profanity_dir->str)) { @@ -103,7 +110,7 @@ _create_data_dir(void) void _create_chatlogs_dir(void) { - GString *chatlogs_dir = g_string_new(XDG_DATA_HOME); + GString *chatlogs_dir = g_string_new(xdg_data_home); g_string_append(chatlogs_dir, "/profanity/chatlogs"); if (!_mkdir_recursive(chatlogs_dir->str)) { @@ -116,7 +123,7 @@ _create_chatlogs_dir(void) void _create_logs_dir(void) { - GString *logs_dir = g_string_new(XDG_DATA_HOME); + GString *logs_dir = g_string_new(xdg_data_home); g_string_append(logs_dir, "/profanity/logs"); if (!_mkdir_recursive(logs_dir->str)) { @@ -129,7 +136,9 @@ _create_logs_dir(void) void _cleanup_dirs(void) { - int res = system("rm -rf ./tests/functionaltests/files"); + char cmd[512]; + snprintf(cmd, sizeof(cmd), "rm -rf ./tests/functionaltests/files/%d", stub_port); + int res = system(cmd); if (res == -1) { assert_true(FALSE); } @@ -231,14 +240,20 @@ init_prof_test(void **state) return -1; } + // Generate unique XDG paths based on stub_port for parallel execution + snprintf(xdg_config_home, sizeof(xdg_config_home), + "./tests/functionaltests/files/%d/xdg_config_home", stub_port); + snprintf(xdg_data_home, sizeof(xdg_data_home), + "./tests/functionaltests/files/%d/xdg_data_home", stub_port); + // Give stabber server thread time to start listening usleep(100000); // 100ms config_orig = getenv("XDG_CONFIG_HOME"); data_orig = getenv("XDG_DATA_HOME"); - setenv("XDG_CONFIG_HOME", XDG_CONFIG_HOME, 1); - setenv("XDG_DATA_HOME", XDG_DATA_HOME, 1); + setenv("XDG_CONFIG_HOME", xdg_config_home, 1); + setenv("XDG_DATA_HOME", xdg_data_home, 1); _cleanup_dirs(); diff --git a/tests/functionaltests/proftest.h b/tests/functionaltests/proftest.h index 5fd5a0d7..cafa7056 100644 --- a/tests/functionaltests/proftest.h +++ b/tests/functionaltests/proftest.h @@ -1,8 +1,13 @@ #ifndef __H_PROFTEST #define __H_PROFTEST -#define XDG_CONFIG_HOME "./tests/functionaltests/files/xdg_config_home" -#define XDG_DATA_HOME "./tests/functionaltests/files/xdg_data_home" +/* + * XDG paths are now dynamic, generated per-test based on stub_port. + * This allows parallel test execution without file conflicts. + * Use xdg_config_home and xdg_data_home variables instead of macros. + */ +extern char xdg_config_home[256]; +extern char xdg_data_home[256]; extern int stub_port;