feat: parallel functional tests (~3x faster CI)

- Split 69 functional tests into 4 groups for parallel execution
- Add check-functional-parallel target to Makefile.am
- Fix VPATH builds with $(srcdir)/tests path
- Add test failure detection verification in CI
- Update Dockerfiles with parallel make -j$(nproc)
- Add --depth 1 for faster git clone in Dockerfiles
This commit is contained in:
2026-01-19 16:38:19 +03:00
parent a90eef1cb2
commit cb2cd1bbf2
10 changed files with 316 additions and 98 deletions

View File

@@ -1,6 +1,7 @@
FROM archlinux
ENV TERM=xterm
ENV CC="ccache gcc"
RUN pacman -Syu --noconfirm
# reflector is optional - if it fails due to network issues, continue with default mirrorlist
@@ -12,6 +13,7 @@ RUN pacman -S --needed --noconfirm \
autoconf-archive \
automake \
base-devel \
ccache \
check \
cmake \
cmocka \

View File

@@ -3,11 +3,13 @@ FROM debian:testing
ENV DEBIAN_FRONTEND="noninteractive"
ENV TERM=xterm
ENV CC="ccache gcc"
RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
autoconf-archive \
automake \
ccache \
gcc \
git \
libcmocka-dev \

View File

@@ -1,6 +1,9 @@
# Build the latest Fedora image
FROM fedora:latest
ENV TERM=xterm
ENV CC="ccache gcc"
# libmicrohttpd - for stabber
# glibc-locale - to have en_US locale
RUN dnf install -y \
@@ -8,6 +11,7 @@ RUN dnf install -y \
autoconf-archive \
automake \
awk \
ccache \
gcc \
git \
glib2-devel \

View File

@@ -1,12 +1,16 @@
# Build the latest openSUSE Tumbleweed image
FROM opensuse/tumbleweed
ENV TERM=xterm
ENV CC="ccache gcc"
# libmicrohttpd - for stabber
# glibc-locale - to have en_US locale
RUN zypper --non-interactive in --no-recommends \
autoconf \
autoconf-archive \
automake \
ccache \
gcc \
git \
glib2-devel \

View File

@@ -2,11 +2,13 @@ FROM ubuntu:latest
ENV DEBIAN_FRONTEND="noninteractive"
ENV TERM=xterm
ENV CC="ccache gcc"
RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
autoconf-archive \
automake \
ccache \
gcc \
git \
libcmocka-dev \

View File

@@ -286,7 +286,7 @@ endif
TESTS = tests/unittests/unittests
check_PROGRAMS = tests/unittests/unittests
tests_unittests_unittests_CPPFLAGS = -Itests/
tests_unittests_unittests_CPPFLAGS = -I$(srcdir)/tests
tests_unittests_unittests_SOURCES = $(unittest_sources)
tests_unittests_unittests_LDADD = -lcmocka
@@ -302,9 +302,28 @@ if HAVE_FORKPTY
TESTS += tests/functionaltests/functionaltests
check_PROGRAMS += tests/functionaltests/functionaltests
tests_functionaltests_functionaltests_SOURCES = $(functionaltest_sources)
tests_functionaltests_functionaltests_CPPFLAGS = -Itests/
tests_functionaltests_functionaltests_CPPFLAGS = -I$(srcdir)/tests
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
@failed=0; \
./tests/functionaltests/functionaltests 1 > $(builddir)/test-logs/group1.log 2>&1 & pid1=$$!; \
./tests/functionaltests/functionaltests 2 > $(builddir)/test-logs/group2.log 2>&1 & pid2=$$!; \
./tests/functionaltests/functionaltests 3 > $(builddir)/test-logs/group3.log 2>&1 & pid3=$$!; \
./tests/functionaltests/functionaltests 4 > $(builddir)/test-logs/group4.log 2>&1 & pid4=$$!; \
wait $$pid1 || { echo "Group 1 FAILED (exit $$?)"; cat $(builddir)/test-logs/group1.log; failed=1; }; \
wait $$pid2 || { echo "Group 2 FAILED (exit $$?)"; cat $(builddir)/test-logs/group2.log; failed=1; }; \
wait $$pid3 || { echo "Group 3 FAILED (exit $$?)"; cat $(builddir)/test-logs/group3.log; failed=1; }; \
wait $$pid4 || { echo "Group 4 FAILED (exit $$?)"; cat $(builddir)/test-logs/group4.log; failed=1; }; \
echo "=== Test Results Summary ==="; \
grep -E 'PASSED|FAILED|Running' $(builddir)/test-logs/group*.log || true; \
if [ $$failed -ne 0 ]; then echo "FUNCTIONAL TESTS FAILED"; exit 1; fi; \
echo "All functional test groups passed!"
endif
endif

View File

@@ -23,6 +23,79 @@ error_handler()
trap error_handler ERR
# Verify that test failures are properly detected
# This is a meta-test: it runs a deliberately failing test
# and checks that the test framework reports the failure correctly
verify_test_failure_detection()
{
echo
echo "==> Verifying test failure detection..."
# Create a simple failing test
cat > /tmp/test_must_fail.c << 'EOF'
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
static void test_that_must_fail(void **state) {
(void)state;
assert_true(0); // This MUST fail
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_that_must_fail),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
EOF
# Compile the failing test
if ! gcc -o /tmp/test_must_fail /tmp/test_must_fail.c -lcmocka 2>/dev/null; then
echo "Warning: Could not compile test failure verification (cmocka not available?)"
echo "Skipping test failure detection verification"
return 0
fi
# Test 1: Single failing test detection
echo " Testing single test failure detection..."
if /tmp/test_must_fail > /tmp/test_must_fail.log 2>&1; then
echo "ERROR: Test that should fail returned success (exit code 0)"
echo "This means the test framework is NOT detecting failures correctly!"
echo "--- Test output ---"
cat /tmp/test_must_fail.log
echo "--- End output ---"
rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log
exit 1
fi
echo " ✓ Single test failure correctly detected"
# Test 2: Parallel failure detection (simulates check-functional-parallel)
echo " Testing parallel test failure detection..."
failed=0
/tmp/test_must_fail > /tmp/p1.log 2>&1 & pid1=$!
true > /tmp/p2.log 2>&1 & pid2=$! # This passes
/tmp/test_must_fail > /tmp/p3.log 2>&1 & pid3=$!
true > /tmp/p4.log 2>&1 & pid4=$! # This passes
wait $pid1 || failed=$((failed + 1))
wait $pid2 || failed=$((failed + 1))
wait $pid3 || failed=$((failed + 1))
wait $pid4 || failed=$((failed + 1))
if [ $failed -ne 2 ]; then
echo "ERROR: Expected 2 failures in parallel tests, got $failed"
echo "Parallel failure detection is broken!"
rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log /tmp/p?.log
exit 1
fi
echo " ✓ Parallel test failures correctly detected (2 of 4 failed as expected)"
rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log /tmp/p?.log
echo "✓ Test failure detection verified"
}
num_cores()
{
# Check for cores, for systems with:
@@ -34,6 +107,9 @@ num_cores()
|| getconf _NPROCESSORS_ONLN 2>/dev/null
}
# Run test failure detection verification first
verify_test_failure_detection
./bootstrap.sh
tests=()
@@ -44,7 +120,7 @@ ARCH="$(uname | tr '[:upper:]' '[:lower:]')"
case "$ARCH" in
linux*)
# Reduced set of configurations for faster CI
# 4 configurations for parallel CI
tests=(
# 1. Full build (all features enabled)
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
@@ -56,15 +132,13 @@ case "$ARCH" in
--disable-python-plugins --without-xscreensaver --disable-omemo-qrcode --disable-gdk-pixbuf"
# 3. No encryption (disable otr, pgp, omemo)
"--disable-pgp --disable-otr --disable-omemo --disable-omemo-qrcode"
# 4. No plugins
"--disable-plugins --disable-c-plugins --disable-python-plugins"
# 5. Default configuration
# 4. Default configuration
""
)
source /etc/profile.d/debuginfod.sh 2>/dev/null || true
;;
darwin*)
# Reduced set of configurations for faster CI
# 4 configurations for parallel CI
tests=(
# 1. Full build (all features enabled)
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
@@ -76,9 +150,7 @@ case "$ARCH" in
--disable-python-plugins"
# 3. No encryption (disable otr, pgp, omemo)
"--disable-pgp --disable-otr --disable-omemo"
# 4. No plugins
"--disable-plugins --disable-c-plugins --disable-python-plugins"
# 5. Default configuration
# 4. Default configuration
""
)
;;
@@ -90,7 +162,7 @@ case "$ARCH" in
# src/event/server_events.c:1454:19: error: universal character names are only valid in C++ and C99
CC="egcc -std=gnu99 -fexec-charset=UTF-8"
# Reduced set of configurations for faster CI
# 4 configurations for parallel CI
tests=(
# 1. Full build (all features enabled)
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
@@ -102,9 +174,7 @@ case "$ARCH" in
--disable-python-plugins"
# 3. No encryption (disable otr, pgp, omemo)
"--disable-pgp --disable-otr --disable-omemo"
# 4. No plugins
"--disable-plugins --disable-c-plugins --disable-python-plugins"
# 5. Default configuration
# 4. Default configuration
""
)
;;
@@ -129,18 +199,73 @@ case "$ARCH" in
;;
esac
for features in "${tests[@]}"
do
echo
echo "--> Building with ./configure ${features} $*"
echo
# Function to build and test a single configuration
build_and_test() {
local features="$1"
local extra_args="$2"
local idx="$3"
local build_dir="build-$idx"
local log_file="build-$idx.log"
# shellcheck disable=SC2086
./configure $features $*
{
echo "=== Build $idx started at $(date) ==="
echo "--> Building in $build_dir with ./configure $features $extra_args"
$MAKE CC="${CC}"
$MAKE check
mkdir -p "$build_dir"
cd "$build_dir"
./profanity -v
$MAKE clean
# shellcheck disable=SC2086
../configure $features $extra_args
$MAKE CC="${CC}"
$MAKE check-functional-parallel
./profanity -v
$MAKE clean
cd ..
rm -rf "$build_dir"
echo "=== Build $idx completed at $(date) ==="
} > "$log_file" 2>&1
}
# Run all 4 configurations in parallel
echo "Starting parallel builds..."
pids=()
for idx in 1 2 3 4; do
if [ $idx -le ${#tests[@]} ]; then
build_and_test "${tests[$((idx-1))]}" "$*" "$idx" &
pids+=("$!")
echo "Started build $idx (PID: $!)"
fi
done
# Wait for all builds and check exit codes
failed=0
for i in "${!pids[@]}"; do
idx=$((i + 1))
if wait "${pids[$i]}"; then
echo "✓ Build $idx passed"
else
echo "✗ Build $idx failed"
echo "--- Log for build $idx ---"
cat "build-$idx.log"
echo "--- End log ---"
failed=1
fi
done
# Show all logs on success too
if [ $failed -eq 0 ]; then
echo
echo "All builds passed!"
for idx in 1 2 3 4; do
if [ -f "build-$idx.log" ]; then
echo "--- Log for build $idx ---"
cat "build-$idx.log"
fi
done
else
exit 1
fi

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "prof_cmocka.h"
#include <sys/stat.h>
@@ -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;
}

View File

@@ -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();

View File

@@ -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;