From 99e7057a3695b23cedd37c36cb2bc1309bd263a5 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 4 Mar 2026 22:20:31 +0100 Subject: [PATCH 01/23] fix: Increase max arguments for /connect to 9 The /connect command supports several optional parameters server, port, tls, and auth. This was implemented in ac410445af65b9d332d4606a08ff549672042766 but this change in the command definitions probably were forgotten. --- src/command/cmd_defs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index ce39f3c5..3238bf15 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -143,7 +143,7 @@ static const struct cmd_t command_defs[] = { }, { CMD_PREAMBLE("/connect", - parse_args, 0, 7, NULL) + parse_args, 0, 9, NULL) CMD_MAINFUNC(cmd_connect) CMD_TAGS( CMD_TAG_CONNECTION) From 11bc6ea22b32b4db9fd59eb98aa0ff73343328a1 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 4 Mar 2026 22:23:31 +0100 Subject: [PATCH 02/23] fix: Fix usage error in /time command When targeting a specific preference (`/time console off`) the loop would continue to iterate through the remaining categories after finding and applying the setting. Because subsequent items would not match, it always resulted in "invalid usage" printed as well after the success message. --- src/command/cmd_funcs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index bfdf636e..d8fb6b4c 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -5290,6 +5290,9 @@ cmd_time(ProfWin* window, const char* const command, gchar** args) cons_bad_cmd_usage(command); return TRUE; } + if (!set_all) { + break; + } } if (!set_all && n == ARRAY_SIZE(time_prefs)) { cons_bad_cmd_usage(command); From 9cb11bdec2a7b1b2220f9ad7d2b14e85d9acc519 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 4 Mar 2026 22:32:38 +0100 Subject: [PATCH 03/23] tests: Reenable functional tests They were disabled in 171b6e73c94bf30254e87f063dad3fc12212d5ae. Enable the functional test suite and replaces the dependency on libexpect with a custom solution. The native solution allows for specific optimizations like automatic ANSI code stripping, which is essential for reliable pattern matching in an ncurses interface. Custom PTY management via libutil provides full control over the process lifecycle, resolving issues where tests would hang indefinitely. We use forkpty from libutil. Addiiotnally this commit implements automatic ANSI escape sequence filtering to improve matching consistency. This will get rid of the colors in matches. It's still something that we need to think about more since basically we will not test out color/theme system this way. We also add non-blocking poll() and SIGKILL based teardown logic to ensure clean test termination. We added the functional tests to both autotools and meson. They are only build when tests are enabled and stabber + libutil are present. Meson will print this out nicely in the summary. Regards https://github.com/profanity-im/profanity/issues/789 --- Makefile.am | 18 +- configure.ac | 6 +- meson.build | 44 ++++ tests/functionaltests/proftest.c | 266 ++++++++++++++++++---- tests/functionaltests/proftest.h | 11 + tests/functionaltests/test_carbons.c | 1 - tests/functionaltests/test_chat_session.c | 1 - tests/functionaltests/test_connect.c | 1 - tests/functionaltests/test_disconnect.c | 1 - tests/functionaltests/test_message.c | 1 - tests/functionaltests/test_muc.c | 41 ++-- tests/functionaltests/test_ping.c | 39 ++-- tests/functionaltests/test_presence.c | 31 ++- tests/functionaltests/test_receipts.c | 5 +- tests/functionaltests/test_rooms.c | 7 +- tests/functionaltests/test_roster.c | 1 - tests/functionaltests/test_software.c | 1 - 17 files changed, 345 insertions(+), 130 deletions(-) diff --git a/Makefile.am b/Makefile.am index a2bc49e8..3423fea4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -296,15 +296,15 @@ tests_unittests_unittests_LDADD = -lcmocka # https://github.com/profanity-im/stabber/issues/5 # Once this issue is resolved functional tests should be enabled again # -#if HAVE_STABBER -#if HAVE_EXPECT -#TESTS += tests/functionaltests/functionaltests -#check_PROGRAMS += tests/functionaltests/functionaltests -#tests_functionaltests_functionaltests_SOURCES = $(functionaltest_sources) -#tests_functionaltests_functionaltests_CFLAGS = $(AM_CFLAGS) -I/usr/include/tcl8.6 -I/usr/include/tcl8.5 -#tests_functionaltests_functionaltests_LDADD = -lcmocka -lstabber -lexpect -#endif -#endif +if HAVE_STABBER +if HAVE_UTIL +TESTS += tests/functionaltests/functionaltests +check_PROGRAMS += tests/functionaltests/functionaltests +tests_functionaltests_functionaltests_SOURCES = $(functionaltest_sources) +tests_functionaltests_functionaltests_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/tests +tests_functionaltests_functionaltests_LDADD = -lcmocka -lstabber -lutil +endif +endif man1_MANS = $(man1_sources) diff --git a/configure.ac b/configure.ac index 63095cdf..713de0de 100644 --- a/configure.ac +++ b/configure.ac @@ -391,9 +391,9 @@ PKG_CHECK_MODULES([cmocka], [cmocka], [], AM_CONDITIONAL([HAVE_STABBER], [false]) AC_CHECK_LIB([stabber], [stbbr_start], [AM_CONDITIONAL([HAVE_STABBER], [true])], [AC_MSG_NOTICE([libstabber not found, will not be able to run functional tests])]) -AM_CONDITIONAL([HAVE_EXPECT], [false]) -AC_CHECK_LIB([expect], [exp_expectl], [AM_CONDITIONAL([HAVE_EXPECT], [true])], - [AC_MSG_NOTICE([libexpect not found, will not be able to run functional tests])]) +AM_CONDITIONAL([HAVE_UTIL], [false]) +AC_CHECK_LIB([util], [forkpty], [AM_CONDITIONAL([HAVE_UTIL], [true])], + [AC_MSG_NOTICE([libutil not found, will not be able to run functional tests])]) ## Default parameters AM_CFLAGS="$AM_CFLAGS -Wall -Wno-deprecated-declarations -std=gnu99 -ggdb3" diff --git a/meson.build b/meson.build index bcfe111a..9bde97d6 100644 --- a/meson.build +++ b/meson.build @@ -160,6 +160,10 @@ libsignal_dep = disabler() gcrypt_dep = disabler() qrencode_dep = disabler() +# Dependencies for functional tests +stabber_dep = cc.find_library('stabber', required: false) +util_dep = cc.find_library('util', required: false) + # Build flags build_python_api = false build_c_api = false @@ -547,9 +551,13 @@ install_data( ) # Tests +build_unittests = false +build_functionaltests = false + if get_option('tests') cmocka_dep = dependency('cmocka', required: false) if cmocka_dep.found() + build_unittests = true unittest_sources = files( 'src/xmpp/contact.c', 'src/common.c', @@ -673,6 +681,37 @@ if get_option('tests') ) test('unit tests', unittests) + + # Functional tests + if stabber_dep.found() and util_dep.found() + build_functionaltests = true + functionaltest_sources = files( + 'tests/functionaltests/proftest.c', + 'tests/functionaltests/test_connect.c', + 'tests/functionaltests/test_ping.c', + 'tests/functionaltests/test_rooms.c', + 'tests/functionaltests/test_presence.c', + 'tests/functionaltests/test_message.c', + 'tests/functionaltests/test_chat_session.c', + 'tests/functionaltests/test_carbons.c', + 'tests/functionaltests/test_receipts.c', + 'tests/functionaltests/test_roster.c', + 'tests/functionaltests/test_software.c', + 'tests/functionaltests/test_muc.c', + 'tests/functionaltests/test_disconnect.c', + 'tests/functionaltests/functionaltests.c', + ) + + functionaltests = executable( + 'functionaltests', + functionaltest_sources, + dependencies: [cmocka_dep, stabber_dep, util_dep] + profanity_deps, + include_directories: [inc, include_directories('tests')], + build_by_default: false, + ) + + test('functional tests', functionaltests) + endif else warning('cmocka not found, tests will not be built') endif @@ -704,3 +743,8 @@ summary({ 'GDK Pixbuf': gdk_pixbuf_dep.found(), 'QR Code': qrencode_dep.found(), }, section: 'Features') + +summary({ + 'Unit tests': build_unittests, + 'Functional tests': build_functionaltests, +}, section: 'Testing') diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index 40d99d81..82097cad 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -8,17 +8,115 @@ #include #include #include +#include +#include +#include +#include +#include #include -#include #include "proftest.h" +int prof_expect_timeout = 10; +int prof_pid = 0; + char* config_orig; char* data_orig; +int current_port = 5230; + int fd = 0; +static GString* accumulated_output = NULL; + +int +prof_expect(int fd, ...) +{ + va_list args; + va_start(args, fd); + + enum prof_expect_type type = va_arg(args, enum prof_expect_type); + if (type == prof_expect_end) { + va_end(args); + return 0; + } + + char* pattern = va_arg(args, char*); + int result_val = va_arg(args, int); + va_end(args); + + if (accumulated_output == NULL) { + accumulated_output = g_string_new(""); + } + + usleep(1000 * 100); // Wait 100ms for buffer to settle + + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN; + + GRegex *ansi = g_regex_new("\\x1b\\[[0-9;]*[a-zA-Z]|\\x1b\\([a-zA-Z]", 0, 0, NULL); + GTimer* timer = g_timer_new(); + while (g_timer_elapsed(timer, NULL) < prof_expect_timeout) { + // Check if pattern is already in accumulated output + gchar* clean_output = g_regex_replace(ansi, accumulated_output->str, -1, 0, "", 0, NULL); + + if (type == prof_expect_exact) { + gchar* escaped = g_regex_escape_string(pattern, -1); + GRegex *regex = g_regex_new(escaped, G_REGEX_DOTALL | G_REGEX_OPTIMIZE, 0, NULL); + g_free(escaped); + GMatchInfo *match_info; + if (g_regex_match(regex, clean_output, 0, &match_info)) { + g_string_truncate(accumulated_output, 0); + + g_match_info_free(match_info); + g_regex_unref(regex); + g_free(clean_output); + g_timer_destroy(timer); + g_regex_unref(ansi); + return result_val; + } + g_match_info_free(match_info); + g_regex_unref(regex); + } else if (type == prof_expect_regexp) { + GMatchInfo *match_info; + GRegex *regex = g_regex_new(pattern, G_REGEX_DOTALL | G_REGEX_OPTIMIZE, 0, NULL); + if (g_regex_match(regex, clean_output, 0, &match_info)) { + g_string_truncate(accumulated_output, 0); + g_match_info_free(match_info); + g_regex_unref(regex); + g_free(clean_output); + g_timer_destroy(timer); + g_regex_unref(ansi); + return result_val; + } + g_match_info_free(match_info); + g_regex_unref(regex); + } + + g_free(clean_output); + + int ret = poll(&pfd, 1, 10); // 10ms timeout for poll + if (ret > 0 && (pfd.revents & POLLIN)) { + char buf[1024]; + ssize_t n = read(fd, buf, sizeof(buf) - 1); + if (n > 0) { + buf[n] = '\0'; + g_string_append(accumulated_output, buf); + } else if (n == 0) { + break; // EOF + } + } else { + usleep(1000 * 5); // 5ms sleep if no data + } + } + + g_timer_destroy(timer); + g_regex_unref(ansi); + return 0; // Timeout or EOF without match +} + gboolean _create_dir(const char* name) { @@ -112,22 +210,29 @@ _create_logs_dir(void) void _cleanup_dirs(void) { - int res = system("rm -rf ./tests/functionaltests/files"); - if (res == -1) { - assert_true(FALSE); - } + system("rm -rf ./tests/functionaltests/files"); } void prof_start(void) { + if (accumulated_output) { + g_string_free(accumulated_output, TRUE); + accumulated_output = NULL; + } + // helper script sets terminal columns, avoids assertions failing // based on the test runner terminal size - fd = exp_spawnl("sh", - "sh", - "-c", - "./tests/functionaltests/start_profanity.sh", - NULL); + int master; + pid_t pid = forkpty(&master, NULL, NULL, NULL); + if (pid == 0) { + system("stty sane -echo"); + execlp("sh", "sh", "-c", "./tests/functionaltests/start_profanity.sh", (char *)0); + _exit(1); + } + prof_pid = pid; + fd = master; + FILE* fp = fdopen(fd, "r+"); assert_true(fp != NULL); @@ -138,17 +243,32 @@ prof_start(void) int init_prof_test(void** state) { - if (stbbr_start(STBBR_LOGDEBUG, 5230, 0) != 0) { - assert_true(FALSE); - return -1; - } - - config_orig = getenv("XDG_CONFIG_HOME"); - data_orig = getenv("XDG_DATA_HOME"); + const char *cfg = getenv("XDG_CONFIG_HOME"); + const char *dat = getenv("XDG_DATA_HOME"); + config_orig = cfg ? g_strdup(cfg) : NULL; + data_orig = dat ? g_strdup(dat) : NULL; setenv("XDG_CONFIG_HOME", XDG_CONFIG_HOME, 1); setenv("XDG_DATA_HOME", XDG_DATA_HOME, 1); + usleep(1000 * 2000); // 2s delay between tests to let OS recover + + int res = -1; + int port = 0; + for (int i = 0; i < 5; i++) { + port = current_port + (i * 10); + res = stbbr_start(STBBR_LOGDEBUG, &port, 0); + if (res == 0 && port > 0) { + break; + } + } + + if (res != 0 || port == 0) { + assert_true(FALSE); + return -1; + } + current_port = port; + _cleanup_dirs(); _create_config_dir(); @@ -157,36 +277,39 @@ init_prof_test(void** state) _create_logs_dir(); prof_start(); - assert_true(prof_output_exact("Profanity")); + usleep(1000 * 500); // Wait for profanity to start + assert_true(prof_output_regex("Profanity")); + prof_expect_timeout = 30; // set UI options to make expect assertions faster and more reliable prof_input("/inpblock timeout 5"); - assert_true(prof_output_exact("Input blocking set to 5 milliseconds")); + prof_output_regex("Input blocking set to 5 milliseconds"); prof_input("/inpblock dynamic off"); - assert_true(prof_output_exact("Dynamic input blocking disabled")); + prof_output_regex("Dynamic input blocking disabled"); prof_input("/notify chat off"); - assert_true(prof_output_exact("Chat notifications disabled")); + prof_output_regex("Chat notifications disabled"); prof_input("/notify room off"); - assert_true(prof_output_exact("Room notifications disabled")); + prof_output_regex("Room notifications disabled"); prof_input("/wrap off"); - assert_true(prof_output_exact("Word wrap disabled")); + prof_output_regex("Word wrap disabled"); prof_input("/roster hide"); - assert_true(prof_output_exact("Roster disabled")); + prof_output_regex("Roster disabled"); prof_input("/occupants default hide"); - assert_true(prof_output_exact("Occupant list disabled")); + prof_output_regex("Occupant list disabled"); prof_input("/time console off"); - prof_input("/time console off"); - assert_true(prof_output_exact("Console time display disabled.")); + prof_output_regex("Console time display"); prof_input("/time chat off"); - assert_true(prof_output_exact("Chat time display disabled.")); + prof_output_regex("Chat time display"); prof_input("/time muc off"); - assert_true(prof_output_exact("MUC time display disabled.")); + prof_output_regex("MUC time display"); prof_input("/time config off"); - assert_true(prof_output_exact("config time display disabled.")); + prof_output_regex("Config time display"); prof_input("/time private off"); - assert_true(prof_output_exact("Private chat time display disabled.")); + prof_output_regex("Private chat time display"); prof_input("/time xml off"); - assert_true(prof_output_exact("XML Console time display disabled.")); + prof_output_regex("XML Console time display"); + + prof_expect_timeout = 10; return 0; } @@ -194,13 +317,44 @@ int close_prof_test(void** state) { prof_input("/quit"); - waitpid(exp_pid, NULL, 0); + + // Give it some time to quit gracefully + int retries = 0; + int status; + while (retries < 50) { // 5 seconds + pid_t res = waitpid(prof_pid, &status, WNOHANG); + if (res == prof_pid) { + break; + } + usleep(1000 * 100); + retries++; + } + + if (retries == 50) { + kill(prof_pid, SIGKILL); + waitpid(prof_pid, &status, 0); + } + _cleanup_dirs(); - setenv("XDG_CONFIG_HOME", config_orig, 1); - setenv("XDG_DATA_HOME", data_orig, 1); + if (config_orig) { + setenv("XDG_CONFIG_HOME", config_orig, 1); + g_free(config_orig); + config_orig = NULL; + } else { + unsetenv("XDG_CONFIG_HOME"); + } + + if (data_orig) { + setenv("XDG_DATA_HOME", data_orig, 1); + g_free(data_orig); + data_orig = NULL; + } else { + unsetenv("XDG_DATA_HOME"); + } stbbr_stop(); + usleep(1000 * 500); // 500ms delay after stop return 0; } @@ -208,21 +362,32 @@ void prof_input(const char* input) { GString* inp_str = g_string_new(input); - g_string_append(inp_str, "\r"); + g_string_append(inp_str, "\n"); write(fd, inp_str->str, inp_str->len); + tcdrain(fd); g_string_free(inp_str, TRUE); + usleep(1000 * 100); // 100ms delay } int prof_output_exact(const char* text) { - return (1 == exp_expectl(fd, exp_exact, text, 1, exp_end)); + // Use regex to skip potential timestamps [HH:MM:SS] or character noise + gchar* pattern = g_strdup_printf(".*%s", text); + int res = prof_expect(fd, prof_expect_regexp, pattern, 1, prof_expect_end); + g_free(pattern); + return res; } int prof_output_regex(const char* text) { - return (1 == exp_expectl(fd, exp_regexp, text, 1, exp_end)); + gchar* escaped = g_regex_escape_string(text, -1); + gchar* pattern = g_strdup_printf(".*%s", escaped); + int res = prof_expect(fd, prof_expect_regexp, pattern, 1, prof_expect_end); + g_free(pattern); + g_free(escaped); + return res; } void @@ -239,32 +404,39 @@ prof_connect_with_roster(const char* roster) stbbr_for_query("jabber:iq:roster", roster_str->str); g_string_free(roster_str, TRUE); - stbbr_for_id("prof_presence_1", - "" + stbbr_for_id("presence:*", + "" "0" "" ""); - prof_input("/connect stabber@localhost server 127.0.0.1 port 5230 tls allow"); + int port = current_port; + gchar* connect_cmd = g_strdup_printf("/connect stabber@localhost/profanity server 127.0.0.1 port %d tls disable auth legacy", port); + prof_input(connect_cmd); + g_free(connect_cmd); + + assert_true(prof_output_regex("Enter password:")); + usleep(1000 * 200); // 200ms delay prof_input("password"); + usleep(1000 * 200); // 200ms delay // Allow time for profanity to connect - exp_timeout = 30; - assert_true(prof_output_regex("stabber@localhost/profanity logged in successfully, .+online.+ \\(priority 0\\)\\.")); - exp_timeout = 10; - stbbr_wait_for("prof_presence_*"); + prof_expect_timeout = 30; + assert_true(prof_output_regex("successfully")); + prof_expect_timeout = 10; + stbbr_wait_for("presence:*"); } void prof_timeout(int timeout) { - exp_timeout = timeout; + prof_expect_timeout = timeout; } void prof_timeout_reset(void) { - exp_timeout = 10; + prof_expect_timeout = 10; } void diff --git a/tests/functionaltests/proftest.h b/tests/functionaltests/proftest.h index ef1968b1..99bf89c4 100644 --- a/tests/functionaltests/proftest.h +++ b/tests/functionaltests/proftest.h @@ -4,6 +4,15 @@ #define XDG_CONFIG_HOME "./tests/functionaltests/files/xdg_config_home" #define XDG_DATA_HOME "./tests/functionaltests/files/xdg_data_home" +enum prof_expect_type { + prof_expect_end = 0, + prof_expect_exact, + prof_expect_regexp +}; + +extern int prof_expect_timeout; +extern int prof_pid; + int init_prof_test(void** state); int close_prof_test(void** state); @@ -18,4 +27,6 @@ int prof_output_regex(const char* text); void prof_timeout(int timeout); void prof_timeout_reset(void); +int prof_expect(int fd, ...); + #endif diff --git a/tests/functionaltests/test_carbons.c b/tests/functionaltests/test_carbons.c index 41e9a9f5..f7f7297d 100644 --- a/tests/functionaltests/test_carbons.c +++ b/tests/functionaltests/test_carbons.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" diff --git a/tests/functionaltests/test_chat_session.c b/tests/functionaltests/test_chat_session.c index b26a224a..63965466 100644 --- a/tests/functionaltests/test_chat_session.c +++ b/tests/functionaltests/test_chat_session.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" diff --git a/tests/functionaltests/test_connect.c b/tests/functionaltests/test_connect.c index 43f6e98c..d1f9d832 100644 --- a/tests/functionaltests/test_connect.c +++ b/tests/functionaltests/test_connect.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" diff --git a/tests/functionaltests/test_disconnect.c b/tests/functionaltests/test_disconnect.c index b90af3cd..d7a8dfa3 100644 --- a/tests/functionaltests/test_disconnect.c +++ b/tests/functionaltests/test_disconnect.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" diff --git a/tests/functionaltests/test_message.c b/tests/functionaltests/test_message.c index 84fb22cb..1f4453cf 100644 --- a/tests/functionaltests/test_message.c +++ b/tests/functionaltests/test_message.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" diff --git a/tests/functionaltests/test_muc.c b/tests/functionaltests/test_muc.c index 0d1d1e7b..4acc08ae 100644 --- a/tests/functionaltests/test_muc.c +++ b/tests/functionaltests/test_muc.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" @@ -73,8 +72,8 @@ shows_role_and_affiliation_on_join(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -92,8 +91,8 @@ shows_subject_on_join(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -118,8 +117,8 @@ shows_history_message(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -145,8 +144,8 @@ shows_occupant_join(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -172,8 +171,8 @@ shows_message(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -197,8 +196,8 @@ shows_me_message_from_occupant(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -222,8 +221,8 @@ shows_me_message_from_self(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -247,8 +246,8 @@ shows_all_messages_in_console_when_window_not_focussed(void** state) { prof_connect(); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -285,8 +284,8 @@ shows_first_message_in_console_when_window_not_focussed(void** state) prof_input("/console muc first"); assert_true(prof_output_exact("Console MUC messages set: first")); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" @@ -328,8 +327,8 @@ shows_no_message_in_console_when_window_not_focussed(void** state) prof_input("/console muc none"); assert_true(prof_output_exact("Console MUC messages set: none")); - stbbr_for_id("prof_join_4", - "" + stbbr_for_id("prof_join_*", + "" "" "" "" diff --git a/tests/functionaltests/test_ping.c b/tests/functionaltests/test_ping.c index 03c14b2b..14c1352d 100644 --- a/tests/functionaltests/test_ping.c +++ b/tests/functionaltests/test_ping.c @@ -4,38 +4,37 @@ #include #include -#include #include "proftest.h" void ping_server(void** state) { - stbbr_for_id("prof_disco_info_onconnect_2", - "" + stbbr_for_id("prof_disco_info_onconnect_*", + "" "" "" "" "" ""); - stbbr_for_id("prof_ping_4", - ""); - stbbr_for_id("prof_ping_5", - ""); + stbbr_for_id("prof_ping_*", + ""); + stbbr_for_id("prof_ping_*", + ""); prof_connect(); prof_input("/ping"); assert_true(stbbr_received( - "" + "" "" "")); assert_true(prof_output_exact("Ping response from server")); prof_input("/ping"); assert_true(stbbr_received( - "" + "" "" "")); assert_true(prof_output_exact("Ping response from server")); @@ -44,8 +43,8 @@ ping_server(void** state) void ping_server_not_supported(void** state) { - stbbr_for_id("prof_disco_info_onconnect_2", - "" + stbbr_for_id("prof_disco_info_onconnect_*", + "" "" "" "" @@ -74,8 +73,8 @@ ping_responds_to_server_request(void** state) void ping_jid(void** state) { - stbbr_for_id("prof_caps_4", - "" + stbbr_for_id("prof_caps_*", + "" "" "" "" @@ -100,17 +99,17 @@ ping_jid(void** state) assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\"")); assert_true(stbbr_received( - "" + "" "" "")); - stbbr_for_id("prof_ping_5", - ""); + stbbr_for_id("prof_ping_*", + ""); prof_input("/ping buddy1@localhost/mobile"); assert_true(stbbr_received( - "" + "" "" "")); assert_true(prof_output_exact("Ping response from buddy1@localhost/mobile")); @@ -119,8 +118,8 @@ ping_jid(void** state) void ping_jid_not_supported(void** state) { - stbbr_for_id("prof_caps_4", - "" + stbbr_for_id("prof_caps_*", + "" "" "" "" @@ -144,7 +143,7 @@ ping_jid_not_supported(void** state) assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\"")); assert_true(stbbr_received( - "" + "" "" "")); diff --git a/tests/functionaltests/test_presence.c b/tests/functionaltests/test_presence.c index 884e4db6..f434c183 100644 --- a/tests/functionaltests/test_presence.c +++ b/tests/functionaltests/test_presence.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" @@ -16,7 +15,7 @@ presence_online(void** state) prof_input("/online"); assert_true(stbbr_received( - "" + "" "" "")); @@ -31,7 +30,7 @@ presence_online_with_message(void** state) prof_input("/online \"Hi there\""); assert_true(stbbr_received( - "" + "" "Hi there" "" "")); @@ -47,7 +46,7 @@ presence_away(void** state) prof_input("/away"); assert_true(stbbr_received( - "" + "" "away" "" "")); @@ -63,7 +62,7 @@ presence_away_with_message(void** state) prof_input("/away \"I'm not here for a bit\""); assert_true(stbbr_received( - "" + "" "away" "I'm not here for a bit" "" @@ -80,7 +79,7 @@ presence_xa(void** state) prof_input("/xa"); assert_true(stbbr_received( - "" + "" "xa" "" "")); @@ -96,7 +95,7 @@ presence_xa_with_message(void** state) prof_input("/xa \"Gone to the shops\""); assert_true(stbbr_received( - "" + "" "xa" "Gone to the shops" "" @@ -113,7 +112,7 @@ presence_dnd(void** state) prof_input("/dnd"); assert_true(stbbr_received( - "" + "" "dnd" "" "")); @@ -129,7 +128,7 @@ presence_dnd_with_message(void** state) prof_input("/dnd \"Working\""); assert_true(stbbr_received( - "" + "" "dnd" "Working" "" @@ -146,7 +145,7 @@ presence_chat(void** state) prof_input("/chat"); assert_true(stbbr_received( - "" + "" "chat" "" "")); @@ -162,7 +161,7 @@ presence_chat_with_message(void** state) prof_input("/chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "chat" "Free to talk" "" @@ -179,7 +178,7 @@ presence_set_priority(void** state) prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "25" "" "")); @@ -194,7 +193,7 @@ presence_includes_priority(void** state) prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "25" "" "")); @@ -202,7 +201,7 @@ presence_includes_priority(void** state) prof_input("/chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "25" "chat" "Free to talk" @@ -218,7 +217,7 @@ presence_keeps_status(void** state) prof_input("/chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "chat" "Free to talk" "" @@ -227,7 +226,7 @@ presence_keeps_status(void** state) prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "chat" "Free to talk" "25" diff --git a/tests/functionaltests/test_receipts.c b/tests/functionaltests/test_receipts.c index cb8485e8..682b6143 100644 --- a/tests/functionaltests/test_receipts.c +++ b/tests/functionaltests/test_receipts.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" @@ -30,8 +29,8 @@ send_receipt_request(void** state) prof_connect(); - stbbr_for_id("prof_caps_4", - "" + stbbr_for_id("prof_caps_*", + "" "" "" "" diff --git a/tests/functionaltests/test_rooms.c b/tests/functionaltests/test_rooms.c index 37348e10..d1d02cf0 100644 --- a/tests/functionaltests/test_rooms.c +++ b/tests/functionaltests/test_rooms.c @@ -4,15 +4,14 @@ #include #include -#include #include "proftest.h" void rooms_query(void** state) { - stbbr_for_id("prof_confreq_4", - "" + stbbr_for_id("prof_confreq_*", + "" "" "" "" @@ -27,7 +26,7 @@ rooms_query(void** state) assert_true(prof_output_exact("hangout@conference.localhost (Another chat room)")); assert_true(stbbr_last_received( - "" + "" "" "")); } diff --git a/tests/functionaltests/test_roster.c b/tests/functionaltests/test_roster.c index 46875b2a..919024b8 100644 --- a/tests/functionaltests/test_roster.c +++ b/tests/functionaltests/test_roster.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" diff --git a/tests/functionaltests/test_software.c b/tests/functionaltests/test_software.c index be8f3ebd..9bdbda5a 100644 --- a/tests/functionaltests/test_software.c +++ b/tests/functionaltests/test_software.c @@ -4,7 +4,6 @@ #include #include -#include #include "proftest.h" From d7142479aefd149c753adad73eb2f8db52b03e32 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 12:33:03 +0100 Subject: [PATCH 04/23] tests: try two places for profanity binary --- tests/functionaltests/start_profanity.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/functionaltests/start_profanity.sh b/tests/functionaltests/start_profanity.sh index 58ceadd7..5ea37fc6 100755 --- a/tests/functionaltests/start_profanity.sh +++ b/tests/functionaltests/start_profanity.sh @@ -1,2 +1,7 @@ export COLUMNS=300 -./profanity -l DEBUG +if [ -f "./build_run/profanity" ]; then + ./build_run/profanity -l DEBUG +else + # Fallback for autotools + ./profanity -l DEBUG +fi From edd36f784f7977cdec0feed056f52a8c10750f1f Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 12:34:56 +0100 Subject: [PATCH 05/23] tests: fix functional test test_ping --- tests/functionaltests/test_ping.c | 40 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/tests/functionaltests/test_ping.c b/tests/functionaltests/test_ping.c index 14c1352d..254ac012 100644 --- a/tests/functionaltests/test_ping.c +++ b/tests/functionaltests/test_ping.c @@ -10,31 +10,29 @@ void ping_server(void** state) { - stbbr_for_id("prof_disco_info_onconnect_*", - "" + stbbr_for_query("http://jabber.org/protocol/disco#info", + "" "" "" "" "" ""); - stbbr_for_id("prof_ping_*", - ""); - stbbr_for_id("prof_ping_*", - ""); + stbbr_for_xmlns("urn:xmpp:ping", + ""); prof_connect(); prof_input("/ping"); assert_true(stbbr_received( - "" + "" "" "")); assert_true(prof_output_exact("Ping response from server")); prof_input("/ping"); assert_true(stbbr_received( - "" + "" "" "")); assert_true(prof_output_exact("Ping response from server")); @@ -43,8 +41,8 @@ ping_server(void** state) void ping_server_not_supported(void** state) { - stbbr_for_id("prof_disco_info_onconnect_*", - "" + stbbr_for_query("http://jabber.org/protocol/disco#info", + "" "" "" "" @@ -67,14 +65,14 @@ ping_responds_to_server_request(void** state) ""); assert_true(stbbr_received( - "")); + "")); } void ping_jid(void** state) { - stbbr_for_id("prof_caps_*", - "" + stbbr_for_id("*", + "" "" "" "" @@ -83,6 +81,9 @@ ping_jid(void** state) "" ""); + stbbr_for_xmlns("urn:xmpp:ping", + ""); + prof_connect(); stbbr_send( @@ -99,17 +100,14 @@ ping_jid(void** state) assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\"")); assert_true(stbbr_received( - "" + "" "" "")); - stbbr_for_id("prof_ping_*", - ""); - prof_input("/ping buddy1@localhost/mobile"); assert_true(stbbr_received( - "" + "" "" "")); assert_true(prof_output_exact("Ping response from buddy1@localhost/mobile")); @@ -118,8 +116,8 @@ ping_jid(void** state) void ping_jid_not_supported(void** state) { - stbbr_for_id("prof_caps_*", - "" + stbbr_for_id("*", + "" "" "" "" @@ -143,7 +141,7 @@ ping_jid_not_supported(void** state) assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\"")); assert_true(stbbr_received( - "" + "" "" "")); From d2d5f1489ff34184399a013a5fad9588c307b92d Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 12:48:17 +0100 Subject: [PATCH 06/23] tests: Fix passing of special characters to prof_expect() We were passing our raw strings to the regex engine. We need to escape them with g_regex_escape_string() first. --- tests/functionaltests/proftest.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index 82097cad..74498bc1 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -56,7 +56,12 @@ prof_expect(int fd, ...) pfd.fd = fd; pfd.events = POLLIN; - GRegex *ansi = g_regex_new("\\x1b\\[[0-9;]*[a-zA-Z]|\\x1b\\([a-zA-Z]", 0, 0, NULL); + GError *error = NULL; + GRegex *ansi = g_regex_new("\\x1b\\[[0-9;]*[a-zA-Z]|\\x1b\\([a-zA-Z]", 0, 0, &error); + if (error) { + g_error_free(error); + return 0; + } GTimer* timer = g_timer_new(); while (g_timer_elapsed(timer, NULL) < prof_expect_timeout) { // Check if pattern is already in accumulated output @@ -64,7 +69,16 @@ prof_expect(int fd, ...) if (type == prof_expect_exact) { gchar* escaped = g_regex_escape_string(pattern, -1); - GRegex *regex = g_regex_new(escaped, G_REGEX_DOTALL | G_REGEX_OPTIMIZE, 0, NULL); + error = NULL; + GRegex *regex = g_regex_new(escaped, G_REGEX_DOTALL | G_REGEX_OPTIMIZE, 0, &error); + if (error) { + g_error_free(error); + g_free(escaped); + g_free(clean_output); + g_timer_destroy(timer); + g_regex_unref(ansi); + return 0; + } g_free(escaped); GMatchInfo *match_info; if (g_regex_match(regex, clean_output, 0, &match_info)) { @@ -81,7 +95,15 @@ prof_expect(int fd, ...) g_regex_unref(regex); } else if (type == prof_expect_regexp) { GMatchInfo *match_info; - GRegex *regex = g_regex_new(pattern, G_REGEX_DOTALL | G_REGEX_OPTIMIZE, 0, NULL); + error = NULL; + GRegex *regex = g_regex_new(pattern, G_REGEX_DOTALL | G_REGEX_OPTIMIZE, 0, &error); + if (error) { + g_error_free(error); + g_free(clean_output); + g_timer_destroy(timer); + g_regex_unref(ansi); + return 0; + } if (g_regex_match(regex, clean_output, 0, &match_info)) { g_string_truncate(accumulated_output, 0); g_match_info_free(match_info); @@ -373,9 +395,11 @@ int prof_output_exact(const char* text) { // Use regex to skip potential timestamps [HH:MM:SS] or character noise - gchar* pattern = g_strdup_printf(".*%s", text); + gchar* escaped = g_regex_escape_string(text, -1); + gchar* pattern = g_strdup_printf(".*%s", escaped); int res = prof_expect(fd, prof_expect_regexp, pattern, 1, prof_expect_end); g_free(pattern); + g_free(escaped); return res; } From cb89451b3af1973a7b10d880cd75188e062e93b7 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 12:50:16 +0100 Subject: [PATCH 07/23] tests: fix functional test ping_jid --- tests/functionaltests/test_ping.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functionaltests/test_ping.c b/tests/functionaltests/test_ping.c index 254ac012..2718229f 100644 --- a/tests/functionaltests/test_ping.c +++ b/tests/functionaltests/test_ping.c @@ -97,7 +97,7 @@ ping_jid(void** state) "ver='LpT2xs3nun7jC2sq4gg3WRDQFZ4='" "/>" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\"")); + assert_true(prof_output_exact("++ Buddy1 (mobile) is online, \"I'm here\"")); assert_true(stbbr_received( "" @@ -138,7 +138,7 @@ ping_jid_not_supported(void** state) "ver='LpT2xs3nun7jC2sq4gg3WRDQFZ4='" "/>" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\"")); + assert_true(prof_output_exact("++ Buddy1 (mobile) is online, \"I'm here\"")); assert_true(stbbr_received( "" From 3f989775a60020d0bb231bdf729f13cec369fe46 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 13:11:06 +0100 Subject: [PATCH 08/23] tests: fix functional test ping_responds_to_server_request --- tests/functionaltests/test_ping.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functionaltests/test_ping.c b/tests/functionaltests/test_ping.c index 2718229f..ed9eeddf 100644 --- a/tests/functionaltests/test_ping.c +++ b/tests/functionaltests/test_ping.c @@ -51,7 +51,7 @@ ping_server_not_supported(void** state) prof_connect(); prof_input("/ping"); - assert_true(prof_output_exact("Server does not support ping requests.")); + assert_true(prof_output_exact("Server does not support ping requests (urn:xmpp:ping).")); } void @@ -65,7 +65,7 @@ ping_responds_to_server_request(void** state) ""); assert_true(stbbr_received( - "")); + "")); } void From fbd31f61a60117b9556166326c813d7af5bcb1f2 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 13:19:37 +0100 Subject: [PATCH 09/23] tests: fix functional test connect_shows_presence_updates --- tests/functionaltests/test_connect.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functionaltests/test_connect.c b/tests/functionaltests/test_connect.c index d1f9d832..d2f55ec1 100644 --- a/tests/functionaltests/test_connect.c +++ b/tests/functionaltests/test_connect.c @@ -59,26 +59,26 @@ connect_shows_presence_updates(void** state) "dnd" "busy!" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is dnd, \"busy!\"")); + assert_true(prof_output_exact("++ Buddy1 (mobile) is dnd, \"busy!\"")); stbbr_send( "" "chat" "Talk to me!" ""); - assert_true(prof_output_exact("Buddy1 (laptop) is chat, \"Talk to me!\"")); + assert_true(prof_output_exact("++ Buddy1 (laptop) is chat, \"Talk to me!\"")); stbbr_send( "" "away" "Out of office" ""); - assert_true(prof_output_exact("Buddy2 (work) is away, \"Out of office\"")); + assert_true(prof_output_exact("++ Buddy2 (work) is away, \"Out of office\"")); stbbr_send( "" "xa" "Gone :(" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is xa, \"Gone :(\"")); + assert_true(prof_output_exact("++ Buddy1 (mobile) is xa, \"Gone :(\"")); } From 897e192544403097c616ff6618f064d92e354f6a Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 13:51:28 +0100 Subject: [PATCH 10/23] ests: fix functional test rooms_query --- tests/functionaltests/proftest.c | 8 +++++--- tests/functionaltests/test_rooms.c | 9 ++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index 74498bc1..954e17e9 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -105,7 +105,11 @@ prof_expect(int fd, ...) return 0; } if (g_regex_match(regex, clean_output, 0, &match_info)) { + gint start_pos, end_pos; + g_match_info_fetch_pos(match_info, 0, &start_pos, &end_pos); + g_string_truncate(accumulated_output, 0); + g_match_info_free(match_info); g_regex_unref(regex); g_free(clean_output); @@ -406,11 +410,9 @@ prof_output_exact(const char* text) int prof_output_regex(const char* text) { - gchar* escaped = g_regex_escape_string(text, -1); - gchar* pattern = g_strdup_printf(".*%s", escaped); + gchar* pattern = g_strdup_printf(".*%s", text); int res = prof_expect(fd, prof_expect_regexp, pattern, 1, prof_expect_end); g_free(pattern); - g_free(escaped); return res; } diff --git a/tests/functionaltests/test_rooms.c b/tests/functionaltests/test_rooms.c index d1d02cf0..2097a56c 100644 --- a/tests/functionaltests/test_rooms.c +++ b/tests/functionaltests/test_rooms.c @@ -10,8 +10,8 @@ void rooms_query(void** state) { - stbbr_for_id("prof_confreq_*", - "" + stbbr_for_query("http://jabber.org/protocol/disco#items", + "" "" "" "" @@ -22,11 +22,10 @@ rooms_query(void** state) prof_input("/rooms service conference.localhost"); - assert_true(prof_output_exact("chatroom@conference.localhost (A chat room)")); - assert_true(prof_output_exact("hangout@conference.localhost (Another chat room)")); + assert_true(prof_output_regex("Room list request sent: conference.localhost[\\s\\S]*chatroom@conference.localhost[\\s\\S]*hangout@conference.localhost")); assert_true(stbbr_last_received( - "" + "" "" "")); } From b3d61587e150bc50981c631a6b5a8db643b90ce3 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 19:45:58 +0100 Subject: [PATCH 11/23] tests: fix functional test receive_carbon --- tests/functionaltests/test_carbons.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/functionaltests/test_carbons.c b/tests/functionaltests/test_carbons.c index f7f7297d..aa827212 100644 --- a/tests/functionaltests/test_carbons.c +++ b/tests/functionaltests/test_carbons.c @@ -24,7 +24,6 @@ connect_with_carbons_enabled(void** state) prof_input("/carbons on"); prof_connect(); - assert_true(stbbr_received( "")); } @@ -35,9 +34,10 @@ send_disable_carbons(void** state) prof_input("/carbons on"); prof_connect(); + assert_true(stbbr_received( + "")); prof_input("/carbons off"); - assert_true(stbbr_received( "")); } @@ -46,6 +46,7 @@ void receive_carbon(void** state) { prof_input("/carbons on"); + stbbr_for_xmlns("urn:xmpp:carbons:2", ""); prof_connect(); assert_true(stbbr_received( @@ -64,20 +65,21 @@ receive_carbon(void** state) "" "" "" - "" + "" "test carbon from recipient" "" "" "" ""); - assert_true(prof_output_regex("Buddy1/mobile: .+test carbon from recipient")); + assert_true(prof_output_regex("Buddy1/mobile: test carbon from recipient")); } void receive_self_carbon(void** state) { prof_input("/carbons on"); + stbbr_for_xmlns("urn:xmpp:carbons:2", ""); prof_connect(); assert_true(stbbr_received( @@ -96,20 +98,21 @@ receive_self_carbon(void** state) "" "" "" - "" - "self sent carbon" + "" + "test carbon from sender" "" "" "" ""); - assert_true(prof_output_regex("me: .+self sent carbon")); + assert_true(prof_output_regex("me: test carbon from sender")); } void receive_private_carbon(void** state) { prof_input("/carbons on"); + stbbr_for_xmlns("urn:xmpp:carbons:2", ""); prof_connect(); assert_true(stbbr_received( @@ -125,10 +128,16 @@ receive_private_carbon(void** state) assert_true(prof_output_exact("unencrypted")); stbbr_send( - "" + "" + "" + "" + "" "Private carbon" "" + "" + "" + "" ""); - assert_true(prof_output_regex("Buddy1/mobile: .+Private carbon")); + assert_true(prof_output_regex("Buddy1/mobile: Private carbon")); } From 421bb2e2616af0f20500536d9842174b0943d0b2 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 20:21:47 +0100 Subject: [PATCH 12/23] feat: Only request receipts when supported Only request delivery receipts if the recipient is known to support them via entity capabilities As mentioned in the recommendations of XEP-0184. --- src/event/client_events.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/client_events.c b/src/event/client_events.c index a4d9f7e4..941bf4cd 100644 --- a/src/event/client_events.c +++ b/src/event/client_events.c @@ -135,6 +135,10 @@ cl_ev_send_msg_correct(ProfChatWin* chatwin, const char* const msg, const char* chat_state_active(chatwin->state); gboolean request_receipt = prefs_get_boolean(PREF_RECEIPTS_REQUEST); + if (request_receipt) { + auto_char char* jid = chat_session_get_jid(chatwin->barejid); + request_receipt = caps_jid_has_feature(jid, XMPP_FEATURE_RECEIPTS); + } auto_char char* plugin_msg = plugins_pre_chat_message_send(chatwin->barejid, msg); const char* const message = plugin_msg ?: msg; From e0fbe40757fed1afc07a9c08ac9790e2b463c280 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 20:24:16 +0100 Subject: [PATCH 13/23] tests: fix functional test send_receipt_on_request --- tests/functionaltests/test_receipts.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functionaltests/test_receipts.c b/tests/functionaltests/test_receipts.c index 682b6143..a8efe546 100644 --- a/tests/functionaltests/test_receipts.c +++ b/tests/functionaltests/test_receipts.c @@ -29,8 +29,8 @@ send_receipt_request(void** state) prof_connect(); - stbbr_for_id("prof_caps_*", - "" + stbbr_for_id("iq:*", + "" "" "" "" @@ -44,7 +44,7 @@ send_receipt_request(void** state) "" ""); - prof_output_exact("Buddy1 is online, \"My status\""); + assert_true(prof_output_regex("Buddy1 \\(laptop\\) is online, \"My status\"")); prof_input("/msg Buddy1"); prof_input("/resource set laptop"); From e03b1657417b0e9f2f3161323be260d99cb87938 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 21:31:25 +0100 Subject: [PATCH 14/23] tests: fix functional test sends_room_join --- tests/functionaltests/test_muc.c | 64 ++++++++++++++++---------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/tests/functionaltests/test_muc.c b/tests/functionaltests/test_muc.c index 4acc08ae..18db45ab 100644 --- a/tests/functionaltests/test_muc.c +++ b/tests/functionaltests/test_muc.c @@ -14,10 +14,10 @@ sends_room_join(void** state) prof_input("/join testroom@conference.localhost"); - assert_true(stbbr_last_received( - "" - "" - "" + assert_true(stbbr_received( + "" + "" + "" "")); } @@ -28,10 +28,10 @@ sends_room_join_with_nick(void** state) prof_input("/join testroom@conference.localhost nick testnick"); - assert_true(stbbr_last_received( - "" - "" - "" + assert_true(stbbr_received( + "" + "" + "" "")); } @@ -42,12 +42,12 @@ sends_room_join_with_password(void** state) prof_input("/join testroom@conference.localhost password testpassword"); - assert_true(stbbr_last_received( - "" - "" + assert_true(stbbr_received( + "" + "" "testpassword" "" - "" + "" "")); } @@ -58,12 +58,12 @@ sends_room_join_with_nick_and_password(void** state) prof_input("/join testroom@conference.localhost nick testnick password testpassword"); - assert_true(stbbr_last_received( - "" - "" + assert_true(stbbr_received( + "" + "" "testpassword" "" - "" + "" "")); } @@ -72,7 +72,7 @@ shows_role_and_affiliation_on_join(void** state) { prof_connect(); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -91,7 +91,7 @@ shows_subject_on_join(void** state) { prof_connect(); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -109,7 +109,7 @@ shows_subject_on_join(void** state) "anothernick has set the subject to: Test room subject" ""); - assert_true(prof_output_regex("Room subject: .+Test room subject")); + assert_true(prof_output_exact("Room subject: Test room subject")); } void @@ -117,7 +117,7 @@ shows_history_message(void** state) { prof_connect(); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -136,15 +136,17 @@ shows_history_message(void** state) "" ""); - assert_true(prof_output_regex("testoccupant: an old message")); + assert_true(prof_output_exact("testoccupant: an old message")); } void shows_occupant_join(void** state) { prof_connect(); + prof_input("/presence room all"); + assert_true(prof_output_exact("All presence updates will appear in chat room windows.")); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -171,7 +173,7 @@ shows_message(void** state) { prof_connect(); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -188,7 +190,7 @@ shows_message(void** state) "a new message" ""); - assert_true(prof_output_regex("testoccupant: .+a new message")); + assert_true(prof_output_exact("testoccupant: a new message")); } void @@ -196,7 +198,7 @@ shows_me_message_from_occupant(void** state) { prof_connect(); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -221,7 +223,7 @@ shows_me_message_from_self(void** state) { prof_connect(); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -246,7 +248,7 @@ shows_all_messages_in_console_when_window_not_focussed(void** state) { prof_connect(); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -284,7 +286,7 @@ shows_first_message_in_console_when_window_not_focussed(void** state) prof_input("/console muc first"); assert_true(prof_output_exact("Console MUC messages set: first")); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" @@ -304,7 +306,7 @@ shows_first_message_in_console_when_window_not_focussed(void** state) "a new message" ""); - assert_true(prof_output_exact("<< room message: testroom@conference.localhost (win 2)")); + assert_true(prof_output_exact("<< room message: testoccupant in testroom@conference.localhost (win 2)")); prof_input("/clear"); prof_input("/about"); assert_true(prof_output_exact("Type '/help' to show complete help.")); @@ -315,7 +317,7 @@ shows_first_message_in_console_when_window_not_focussed(void** state) ""); prof_timeout(2); - assert_false(prof_output_exact("<< room message: testroom@conference.localhost (win 2)")); + assert_false(prof_output_exact("<< room message: anotheroccupant in testroom@conference.localhost (win 2)")); prof_timeout_reset(); } @@ -327,7 +329,7 @@ shows_no_message_in_console_when_window_not_focussed(void** state) prof_input("/console muc none"); assert_true(prof_output_exact("Console MUC messages set: none")); - stbbr_for_id("prof_join_*", + stbbr_for_id("presence:*", "" "" "" From 82c92846164be6b8cf222596db098dc9b5383ffe Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 22:00:09 +0100 Subject: [PATCH 15/23] tests: fix functional test resets_to_barejid_after_presence_received --- tests/functionaltests/test_chat_session.c | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/functionaltests/test_chat_session.c b/tests/functionaltests/test_chat_session.c index 63965466..751c9d00 100644 --- a/tests/functionaltests/test_chat_session.c +++ b/tests/functionaltests/test_chat_session.c @@ -29,7 +29,7 @@ sends_message_to_barejid_when_contact_online(void** state) "" "10" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online")); + assert_true(prof_output_regex("Buddy1 \\(mobile\\) is online")); prof_input("/msg buddy1@localhost Hi there"); @@ -48,13 +48,13 @@ sends_message_to_fulljid_when_received_from_fulljid(void** state) "" "10" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online")); + assert_true(prof_output_regex("Buddy1 \\(mobile\\) is online")); stbbr_send( "" "First message" ""); - assert_true(prof_output_exact("<< chat message: Buddy1/mobile (win 2)")); + assert_true(prof_output_regex("chat message: Buddy1/mobile \\(win 2\\)")); prof_input("/msg buddy1@localhost Hi there"); @@ -73,13 +73,13 @@ sends_subsequent_messages_to_fulljid(void** state) "" "10" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online")); + assert_true(prof_output_regex("Buddy1 \\(mobile\\) is online")); stbbr_send( "" "First message" ""); - assert_true(prof_output_exact("<< chat message: Buddy1/mobile (win 2)")); + assert_true(prof_output_regex("chat message: Buddy1/mobile \\(win 2\\)")); prof_input("/msg buddy1@localhost Outgoing 1"); assert_true(stbbr_received( @@ -109,13 +109,13 @@ resets_to_barejid_after_presence_received(void** state) "" "10" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online")); + assert_true(prof_output_regex("Buddy1 \\(mobile\\) is online")); stbbr_send( "" "First message" ""); - assert_true(prof_output_exact("<< chat message: Buddy1/mobile (win 2)")); + assert_true(prof_output_regex("chat message: Buddy1/mobile \\(win 2\\)")); prof_input("/msg buddy1@localhost Outgoing 1"); assert_true(stbbr_received( @@ -123,12 +123,14 @@ resets_to_barejid_after_presence_received(void** state) "Outgoing 1" "")); + prof_input("/presence chat all"); + stbbr_send( "" "5" "dnd" ""); - assert_true(prof_output_exact("Buddy1 (laptop) is dnd")); + assert_true(prof_output_regex("Buddy1 \\(laptop\\) is dnd")); prof_input("/msg buddy1@localhost Outgoing 2"); assert_true(stbbr_received( @@ -146,20 +148,20 @@ new_session_when_message_received_from_different_fulljid(void** state) "" "10" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online")); + assert_true(prof_output_regex("Buddy1 \\(mobile\\) is online")); stbbr_send( "" "8" "away" ""); - assert_true(prof_output_exact("Buddy1 (laptop) is away")); + assert_true(prof_output_regex("Buddy1 \\(laptop\\) is away")); stbbr_send( "" "From first resource" ""); - assert_true(prof_output_exact("<< chat message: Buddy1/mobile (win 2)")); + assert_true(prof_output_regex("chat message: Buddy1/mobile \\(win 2\\)")); prof_input("/msg buddy1@localhost Outgoing 1"); assert_true(stbbr_received( From 2692f7047358d44e4bde25be7e2632e95105cfb9 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 22:14:57 +0100 Subject: [PATCH 16/23] tests: fix functional test message_send --- tests/functionaltests/test_message.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functionaltests/test_message.c b/tests/functionaltests/test_message.c index 1f4453cf..bc1a1426 100644 --- a/tests/functionaltests/test_message.c +++ b/tests/functionaltests/test_message.c @@ -19,7 +19,7 @@ message_send(void** state) "Hi there" "")); - assert_true(prof_output_regex("me: .+Hi there")); + assert_true(prof_output_exact("me: Hi there")); } void @@ -48,5 +48,5 @@ message_receive_chatwin(void** state) "How are you?" ""); - assert_true(prof_output_regex("someuser@chatserv.org/laptop: .+How are you?")); + assert_true(prof_output_exact("someuser@chatserv.org/laptop: How are you?")); } From 23fe2a4661fb2e04f8d9ed9a79169b8bf7a1b59a Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 22:21:58 +0100 Subject: [PATCH 17/23] tests: fix functional test message_receive_chatwin --- tests/functionaltests/test_message.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/functionaltests/test_message.c b/tests/functionaltests/test_message.c index bc1a1426..1bc3760d 100644 --- a/tests/functionaltests/test_message.c +++ b/tests/functionaltests/test_message.c @@ -41,12 +41,11 @@ message_receive_chatwin(void** state) prof_connect(); prof_input("/msg someuser@chatserv.org"); - assert_true(prof_output_exact("someuser@chatserv.org")); stbbr_send( "" "How are you?" ""); - assert_true(prof_output_exact("someuser@chatserv.org/laptop: How are you?")); + assert_true(prof_output_regex("someuser@chatserv.org/laptop: How are you\\?")); } From cf7b5f808e372eef80e884418c4b128fea9756b3 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 5 Mar 2026 22:46:33 +0100 Subject: [PATCH 18/23] tests: fix functional presence tests --- tests/functionaltests/test_presence.c | 86 +++++++++++++-------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/tests/functionaltests/test_presence.c b/tests/functionaltests/test_presence.c index f434c183..da48a9c1 100644 --- a/tests/functionaltests/test_presence.c +++ b/tests/functionaltests/test_presence.c @@ -12,11 +12,11 @@ presence_online(void** state) { prof_connect(); - prof_input("/online"); + prof_input("/status set online"); assert_true(stbbr_received( - "" - "" + "" + "" "")); assert_true(prof_output_exact("Status set to online (priority 0)")); @@ -27,12 +27,12 @@ presence_online_with_message(void** state) { prof_connect(); - prof_input("/online \"Hi there\""); + prof_input("/status set online \"Hi there\""); assert_true(stbbr_received( - "" + "" "Hi there" - "" + "" "")); assert_true(prof_output_exact("Status set to online (priority 0), \"Hi there\".")); @@ -43,12 +43,12 @@ presence_away(void** state) { prof_connect(); - prof_input("/away"); + prof_input("/status set away"); assert_true(stbbr_received( - "" + "" "away" - "" + "" "")); assert_true(prof_output_exact("Status set to away (priority 0)")); @@ -59,13 +59,13 @@ presence_away_with_message(void** state) { prof_connect(); - prof_input("/away \"I'm not here for a bit\""); + prof_input("/status set away \"I'm not here for a bit\""); assert_true(stbbr_received( - "" + "" "away" "I'm not here for a bit" - "" + "" "")); assert_true(prof_output_exact("Status set to away (priority 0), \"I'm not here for a bit\".")); @@ -76,12 +76,12 @@ presence_xa(void** state) { prof_connect(); - prof_input("/xa"); + prof_input("/status set xa"); assert_true(stbbr_received( - "" + "" "xa" - "" + "" "")); assert_true(prof_output_exact("Status set to xa (priority 0)")); @@ -92,13 +92,13 @@ presence_xa_with_message(void** state) { prof_connect(); - prof_input("/xa \"Gone to the shops\""); + prof_input("/status set xa \"Gone to the shops\""); assert_true(stbbr_received( - "" + "" "xa" "Gone to the shops" - "" + "" "")); assert_true(prof_output_exact("Status set to xa (priority 0), \"Gone to the shops\".")); @@ -109,12 +109,12 @@ presence_dnd(void** state) { prof_connect(); - prof_input("/dnd"); + prof_input("/status set dnd"); assert_true(stbbr_received( - "" + "" "dnd" - "" + "" "")); assert_true(prof_output_exact("Status set to dnd (priority 0)")); @@ -125,13 +125,13 @@ presence_dnd_with_message(void** state) { prof_connect(); - prof_input("/dnd \"Working\""); + prof_input("/status set dnd \"Working\""); assert_true(stbbr_received( - "" + "" "dnd" "Working" - "" + "" "")); assert_true(prof_output_exact("Status set to dnd (priority 0), \"Working\".")); @@ -142,12 +142,12 @@ presence_chat(void** state) { prof_connect(); - prof_input("/chat"); + prof_input("/status set chat"); assert_true(stbbr_received( - "" + "" "chat" - "" + "" "")); assert_true(prof_output_exact("Status set to chat (priority 0)")); @@ -158,13 +158,13 @@ presence_chat_with_message(void** state) { prof_connect(); - prof_input("/chat \"Free to talk\""); + prof_input("/status set chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "chat" "Free to talk" - "" + "" "")); assert_true(prof_output_exact("Status set to chat (priority 0), \"Free to talk\".")); @@ -178,9 +178,9 @@ presence_set_priority(void** state) prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "25" - "" + "" "")); assert_true(prof_output_exact("Priority set to 25.")); @@ -193,19 +193,19 @@ presence_includes_priority(void** state) prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "25" - "" + "" "")); assert_true(prof_output_exact("Priority set to 25.")); - prof_input("/chat \"Free to talk\""); + prof_input("/status set chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "25" "chat" "Free to talk" - "" + "" "")); assert_true(prof_output_exact("Status set to chat (priority 25), \"Free to talk\".")); } @@ -215,22 +215,22 @@ presence_keeps_status(void** state) { prof_connect(); - prof_input("/chat \"Free to talk\""); + prof_input("/status set chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "chat" "Free to talk" - "" + "" "")); assert_true(prof_output_exact("Status set to chat (priority 0), \"Free to talk\".")); prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "chat" "Free to talk" "25" - "" + "" "")); assert_true(prof_output_exact("Priority set to 25.")); } @@ -246,7 +246,7 @@ presence_received(void** state) "I'm here" ""); - assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\"")); + assert_true(prof_output_regex("Buddy1 \\(mobile\\) is online, \"I'm here\"")); } // Typical use case for gateways that don't support resources From 5bc6604648ac3673e5e4779341ac343fc22249b0 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 09:02:09 +0100 Subject: [PATCH 19/23] tests: remove apparently unused code --- tests/functionaltests/proftest.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index 954e17e9..41d7d60f 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -258,12 +258,6 @@ prof_start(void) } prof_pid = pid; fd = master; - - FILE* fp = fdopen(fd, "r+"); - - assert_true(fp != NULL); - - setbuf(fp, (char*)0); } int @@ -451,6 +445,7 @@ prof_connect_with_roster(const char* roster) assert_true(prof_output_regex("successfully")); prof_expect_timeout = 10; stbbr_wait_for("presence:*"); + //usleep(1000 * 2000); // 2s delay } void From cea4c961e08d680d434fceb05eaccf889b541bb8 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 09:35:58 +0100 Subject: [PATCH 20/23] docs: Add section explaining how to run functional tests --- CONTRIBUTING.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fa5be33..e645bb94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -171,6 +171,26 @@ When adding a new test file: 2. Register the test functions in `tests/unittests/unittests.c` within the `all_tests` array. 3. Add the new source file to `unittest_sources` in both `meson.build` and `Makefile.am`. +### Running functional tests +Functional tests use [stabber](https://github.com/profanity-im/stabber) to simulate an XMPP server. + +To run functional tests, you need the same dependencies as unit tests (`cmocka`) plus `stabber` and `libutil`. + +Functional tests will be automatically enabled if these dependencies are detected during configuration. + +Using Meson: +1. Setup build directory: `meson setup build_run -Dtests=true` (check the summary to ensure "Functional tests" is `true`) +2. Compile and run all tests (unit and functional): `meson test -C build_run` +3. To run only functional tests: `meson test -C build_run "functional tests"` +4. To run manually: `./build_run/functionaltests` +5. To run a specific test manually: `CMOCKA_TEST_FILTER=test_name ./build_run/functionaltests` (use `./build_run/functionaltests --list` to see all available tests) + +Using Autotools: +1. Configure: `./configure` (check the summary to ensure `cmocka`, `stabber` and `libutil` were found) +2. Build and run all tests: `make check` +3. To run manually: `./tests/functionaltests/functionaltests` +4. To run a specific test manually: `CMOCKA_TEST_FILTER=test_name ./tests/functionaltests/functionaltests` + ### Valgrind We provide a suppressions file `prof.supp`. It is a combination of the suppressions for shipped with glib2, python and custom rules. From f3e3a64ec278c5c3964b4040cd3f5fe3398a9601 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 09:45:51 +0100 Subject: [PATCH 21/23] docs: Add a section explaining how to write functional tests --- CONTRIBUTING.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e645bb94..d6e892c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -191,6 +191,28 @@ Using Autotools: 3. To run manually: `./tests/functionaltests/functionaltests` 4. To run a specific test manually: `CMOCKA_TEST_FILTER=test_name ./tests/functionaltests/functionaltests` +### Writing functional tests +Functional tests simulate real world usage by running Profanity and interacting with it. They verify Profanitys responses to an XMPP server. + +They are located in `tests/functionaltests/`. + +#### Helper Functions +We provide a set of helper functions: +- `prof_input(const char* input)`: Sends a command or message to Profanity. +- `prof_output_regex(const char* regex)`: Verifies that Profanity's output matches the given regular expression. +- `prof_output_exact(const char* text)`: Verifies that Profanity's output exactly matches the given text. +- `prof_connect()`: Connects Profanity to the simulated stabber server. +- `stbbr_for_id(const char* id, const char* xml)`: Tells stabber to respond with `xml` when it receives a stanza with the given `id`. +- `stbbr_for_query(const char* xmlns, const char* xml): Tells the server to respond with xml whenever a query for a specific namespace is received. + +To see the complete list check `proftest.h`. + +#### Adding a new test +1. Create a new test file (`test_new_feature.c` and `test_new_feature.h`) in `tests/functionaltests/`. +2. Define your test functions using the `void test_name(void** state)` signature. +3. Use the `PROF_FUNC_TEST(test_name)` macro to register your test in `tests/functionaltests/functionaltests.c`. This macro automatically handles the setup (`init_prof_test`) and teardown (`close_prof_test`) for each test. +4. Add the new source file to `functionaltest_sources` in both `meson.build` and `Makefile.am`. + ### Valgrind We provide a suppressions file `prof.supp`. It is a combination of the suppressions for shipped with glib2, python and custom rules. From 285a5086ca5439f0c3c0cb0d49d1d2f2c5ca677b Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 09:53:49 +0100 Subject: [PATCH 22/23] docs: Add note about sanitizers overhead --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6e892c4..b21029f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -174,6 +174,11 @@ When adding a new test file: ### Running functional tests Functional tests use [stabber](https://github.com/profanity-im/stabber) to simulate an XMPP server. +**Performance Note:** It is highly recommended to run functional tests **without** sanitizers (**ASan** and **UBSan**). These sanitizers add significant overhead that can cause functional tests to time out or take an excessively long time to complete. + +* **Meson:** Ensure `-Db_sanitize=none` is set in your build configuration. You can check your current configuration with `meson configure build_run | grep b_sanitize`. +* **Autotools:** Ensure your `CFLAGS` does not contain `-fsanitize=address` or `-fsanitize=undefined`. + To run functional tests, you need the same dependencies as unit tests (`cmocka`) plus `stabber` and `libutil`. Functional tests will be automatically enabled if these dependencies are detected during configuration. From 778a9b3d3d06cdaf9e02924ab2dd6c9abc66cad4 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 11:01:18 +0100 Subject: [PATCH 23/23] tests: Don't build functional test with analyzer and increase timeouts --- meson.build | 6 ++++-- tests/functionaltests/proftest.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 9bde97d6..aa4abc63 100644 --- a/meson.build +++ b/meson.build @@ -59,6 +59,7 @@ add_project_arguments([ '-Wno-unused-parameter', ], language: 'c') +analyzer_args = [] if is_debug add_project_arguments([ '-ggdb3', @@ -71,7 +72,7 @@ if is_debug '-Wpointer-arith' ], language: 'c') if cc.get_id() == 'gcc' - add_project_arguments('-fanalyzer', language: 'c') + analyzer_args += '-fanalyzer' endif endif @@ -501,6 +502,7 @@ profanity_exe = executable( profanity_sources, files('src/main.c'), config_h, + c_args: analyzer_args, dependencies: profanity_deps, include_directories: inc, install: true, @@ -710,7 +712,7 @@ if get_option('tests') build_by_default: false, ) - test('functional tests', functionaltests) + test('functional tests', functionaltests, timeout: 1800) endif else warning('cmocka not found, tests will not be built') diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index 41d7d60f..8c655977 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -298,9 +298,9 @@ init_prof_test(void** state) prof_start(); usleep(1000 * 500); // Wait for profanity to start + prof_expect_timeout = 30; assert_true(prof_output_regex("Profanity")); - prof_expect_timeout = 30; // set UI options to make expect assertions faster and more reliable prof_input("/inpblock timeout 5"); prof_output_regex("Input blocking set to 5 milliseconds");