Compare commits

..

15 Commits

Author SHA1 Message Date
01c3205f5d revert: remove cmocka 2.0 compatibility workaround
Some checks failed
CI Code / Code Coverage (pull_request) Has been cancelled
CI Code / Linux (arch) (pull_request) Has been cancelled
CI Code / Linux (ubuntu) (pull_request) Has been cancelled
CI Code / Check coding style (pull_request) Has been cancelled
CI Code / Check spelling (pull_request) Has been cancelled
CI Code / Linux (debian) (pull_request) Has been cancelled
Restore original prof_cmocka.h - the CMOCKA_DISABLE_DEPRECATION_WARNINGS
workaround is not needed for this project.
2026-01-19 16:33:22 +03:00
4789ada834 ci: fix deprecated lcov option
All checks were successful
CI Code / Check spelling (pull_request) Successful in 21s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Code Coverage (pull_request) Successful in 15m29s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m0s
CI Code / Linux (debian) (pull_request) Successful in 18m19s
CI Code / Linux (arch) (pull_request) Successful in 20m21s
Change lcov_branch_coverage to branch_coverage (new syntax)
2026-01-19 16:29:38 +03:00
f6b621ad40 ci: add branch coverage to lcov
All checks were successful
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Check coding style (pull_request) Successful in 37s
CI Code / Code Coverage (pull_request) Successful in 15m56s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m20s
CI Code / Linux (debian) (pull_request) Successful in 18m42s
CI Code / Linux (arch) (pull_request) Successful in 20m9s
Enable branch coverage with --rc lcov_branch_coverage=1.
This provides more detailed coverage metrics:
- Line coverage: % of code lines executed
- Function coverage: % of functions called
- Branch coverage: % of if/else/switch branches taken

Branch coverage is a step towards MC/DC (Modified Condition/Decision Coverage)
used in safety-critical systems.

Also add coverage/ to .gitignore.
2026-01-19 15:53:52 +03:00
d80092821b ci: add functional tests to coverage + fix lcov errors
All checks were successful
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Code Coverage (pull_request) Successful in 15m28s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m1s
CI Code / Linux (debian) (pull_request) Successful in 18m16s
CI Code / Linux (arch) (pull_request) Successful in 20m20s
1. Add 'make check-functional-parallel' to coverage job
   - Unit tests alone cover ~27% (mostly utilities)
   - Functional tests exercise UI, XMPP, and full application flow
   - Should significantly increase coverage

2. Fix lcov errors:
   - Remove /usr/lib/* pattern (unused, causes error)
   - Add 'unused' to --ignore-errors
2026-01-17 19:10:24 +03:00
ca5835c58e ci: fix coverage lcov exclude pattern
Some checks failed
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Code Coverage (pull_request) Failing after 14m46s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m1s
CI Code / Linux (debian) (pull_request) Successful in 18m16s
CI Code / Linux (arch) (pull_request) Successful in 20m5s
The code is built in /usr/src/profanity/ inside Docker, so
excluding '/usr/*' removes all source files!

Fix: exclude only system headers/libs (/usr/include/*, /usr/lib/*),
not the entire /usr/ tree.

Also add --ignore-errors empty to handle edge cases.
2026-01-17 18:37:01 +03:00
1d113c4d64 ci: run coverage on PRs too, not just master push
Some checks failed
CI Code / Check spelling (pull_request) Successful in 18s
CI Code / Check coding style (pull_request) Successful in 30s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m7s
CI Code / Linux (debian) (pull_request) Successful in 18m25s
CI Code / Linux (arch) (pull_request) Successful in 20m32s
CI Code / Code Coverage (pull_request) Failing after 12m34s
2026-01-17 18:13:47 +03:00
e40b89ad92 ci: improve test failure detection in parallel tests
All checks were successful
CI Code / Check spelling (pull_request) Successful in 18s
CI Code / Code Coverage (pull_request) Has been skipped
CI Code / Check coding style (pull_request) Successful in 30s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m10s
CI Code / Linux (debian) (pull_request) Successful in 18m23s
CI Code / Linux (arch) (pull_request) Successful in 20m2s
1. ci-build.sh: Add verify_test_failure_detection() function
   - Tests that single test failures are properly detected
   - Tests that parallel test failures are properly detected
   - Runs at CI start to catch framework issues early

2. Makefile.am: Fix check-functional-parallel exit code handling
   - OLD: used 'wait' without checking individual exit codes
   - NEW: capture each PID and check exit code with 'wait $pid'
   - On failure: print the log file and set failed flag
   - Exit with error if any group failed

This ensures CI correctly fails when:
- Any unit test fails
- Any of the 4 functional test groups fails
- Any parallel build configuration fails
2026-01-17 18:03:07 +03:00
f41888b7a5 feat: add code coverage support
All checks were successful
CI Code / Code Coverage (pull_request) Has been skipped
CI Code / Check spelling (pull_request) Successful in 20s
CI Code / Check coding style (pull_request) Successful in 33s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m16s
CI Code / Linux (debian) (pull_request) Successful in 18m27s
CI Code / Linux (arch) (pull_request) Successful in 20m58s
- Add --enable-coverage option to configure.ac
- Add coverage and coverage-html targets to Makefile.am
- Add coverage CI job with Codecov upload
- Add lcov to all Dockerfiles (arch, debian, fedora, tumbleweed, ubuntu)

Usage:
  ./configure --enable-coverage
  make check
  make coverage-html
2026-01-17 17:45:34 +03:00
c125746f6e perf: optimize build with configure cache and parallel make
All checks were successful
CI Code / Check spelling (pull_request) Successful in 18s
CI Code / Check coding style (pull_request) Successful in 36s
CI Code / Linux (ubuntu) (pull_request) Successful in 17m55s
CI Code / Linux (debian) (pull_request) Successful in 18m8s
CI Code / Linux (arch) (pull_request) Successful in 20m12s
- Add -C flag to ./configure for caching results
- Use --depth 1 for git clone (faster)
- Use make -j$(nproc) for stabber/libstrophe builds

Borrowed optimizations from build/multicore branch.
2026-01-16 19:33:56 +03:00
16d4efbccc ci: disable Docker cache to test cmocka 2.0 breakage
Add --no-cache to docker build to force fresh image build with
latest cmocka 2.0.1 from debian:testing.
2026-01-16 18:15:45 +03:00
e1a9f55a32 test: remove cmocka 2.0 fix to verify CI failure
All checks were successful
CI Code / Check coding style (pull_request) Successful in 32s
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Linux (debian) (pull_request) Successful in 15m51s
CI Code / Linux (ubuntu) (pull_request) Successful in 15m58s
CI Code / Linux (arch) (pull_request) Successful in 16m8s
Temporarily removing CMOCKA_DISABLE_DEPRECATION_WARNINGS to demonstrate
that tests fail on debian:testing (cmocka 2.0.1) without this fix.
2026-01-16 17:38:21 +03:00
7765f65be1 fix(build): use $(srcdir)/tests for VPATH builds
All checks were successful
CI Code / Linux (arch) (pull_request) Successful in 16m40s
CI Code / Check coding style (pull_request) Successful in 36s
CI Code / Check spelling (pull_request) Successful in 21s
CI Code / Linux (ubuntu) (pull_request) Successful in 18m20s
CI Code / Linux (debian) (pull_request) Successful in 18m41s
Fix include paths for out-of-tree builds (e.g., build-1/, build-2/)
to correctly find tests/prof_cmocka.h header file.
2026-01-16 17:11:51 +03:00
5153e04f96 feat: update compatibility header for cmocka 2.0 deprecation handling
All checks were successful
CI Code / Linux (debian) (pull_request) Successful in 12m52s
CI Code / Check coding style (pull_request) Successful in 32s
CI Code / Check spelling (pull_request) Successful in 17s
CI Code / Linux (ubuntu) (pull_request) Successful in 15m26s
CI Code / Linux (arch) (pull_request) Successful in 18m7s
2026-01-16 16:47:48 +03:00
bed046e7ed feat(tests): update Dockerfile and CI script for parallel builds and improved caching
All checks were successful
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Check coding style (pull_request) Successful in 33s
CI Code / Linux (ubuntu) (pull_request) Successful in 15m23s
CI Code / Linux (debian) (pull_request) Successful in 15m35s
CI Code / Linux (arch) (pull_request) Successful in 18m11s
2026-01-15 17:20:19 +03:00
fa17173e5c feat(tests): enable parallel execution of functional tests
All checks were successful
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Check coding style (pull_request) Successful in 34s
CI Code / Linux (ubuntu) (pull_request) Successful in 30m9s
CI Code / Linux (arch) (pull_request) Successful in 33m47s
CI Code / Linux (debian) (pull_request) Successful in 33m9s
2026-01-13 17:46:03 +03:00
14 changed files with 123 additions and 369 deletions

View File

@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
- name: Run tests
run: |
docker build -f Dockerfile.${{ matrix.flavor }} -t profanity .
docker build --no-cache -f Dockerfile.${{ matrix.flavor }} -t profanity .
docker run profanity ./ci-build.sh
code-style:
@@ -121,5 +121,10 @@ jobs:
--output-file /coverage/coverage.info \
--rc branch_coverage=1 \
--ignore-errors inconsistent,empty,unused
lcov --summary /coverage/coverage.info
'
'
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage/coverage.info
fail_ci_if_error: false
verbose: true

6
.gitignore vendored
View File

@@ -62,8 +62,6 @@ tests/unittests/unittests
tests/unittests/unittests.log
tests/unittests/unittests.trs
test-suite.log
test-files/
test-logs/
# valgrind output
profval*
@@ -110,7 +108,3 @@ breaks
*.zip
*.log*
coverage/
*.gcno
*.gcda
*.gcov
coverage.info

View File

@@ -90,15 +90,6 @@ set -e
```
This will run the same tests that the CI runs and refuse the push if it fails.
The CI script runs 4 parallel builds with different configurations, each with Valgrind and functional tests.
Build 1 also collects code coverage (lines, functions, branches).
Output shows test results per build:
```
✓ Build 1 passed (unit: 437/0, func: 69/0, cov: L:27.5% F:36.2% B:18.1%, 5m39s)
✓ Build 2 passed (unit: 398/0, func: 69/0, 5m00s)
```
Note that it will run on the actual content of the repository directory and not
what may have been staged/committed.
@@ -156,28 +147,9 @@ The functional test suite uses [stabber](https://git.jabber.space/devs/stabber)
Functional tests require stabber to be installed. Once installed, tests run as part of `make check`:
```bash
make check # Run all tests (unit + functional)
make check-functional-parallel # Run functional tests in parallel (~3x faster)
./tests/functionaltests/functionaltests # Run all functional tests sequentially
./tests/functionaltests/functionaltests 1 # Run specific group (1-4)
make check
```
#### Test groups
Tests are organized into 4 groups for parallel execution:
| Group | Description |
|-------|-------------|
| 1 | Connect, Ping, Rooms, Software |
| 2 | Message, Receipts, Roster, Chat Session |
| 3 | Presence, Disconnect |
| 4 | MUC, Carbons |
To add a new group:
1. Define the test array in `functionaltests.c`
2. Add entry to `groups[]` array
3. Update `FUNC_TEST_GROUPS` in `Makefile.am`
#### Writing functional tests
Use content-based stubbing with stabber:
@@ -196,5 +168,4 @@ Guidelines:
2. Use `stbbr_send(xml)` for presence, message, and other push-style stanzas.
3. Keep assertions tolerant of ordering when possible; use `prof_output_regex()` for flexible matching.
4. If timing issues appear, use `prof_timeout()` around critical expectations and reset afterwards.
5. When adding new tests, place them in the appropriate group based on functionality.

View File

@@ -13,7 +13,6 @@ RUN pacman -S --needed --noconfirm \
autoconf-archive \
automake \
base-devel \
ca-certificates \
ccache \
check \
cmake \
@@ -65,7 +64,7 @@ USER root
RUN pacman -U --noconfirm libstrophe-git/libstrophe-git-*.pkg.tar.zst
WORKDIR /usr/src
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
RUN git clone --depth 1 -c http.sslverify=false https://git.jabber.space/devs/stabber
WORKDIR /usr/src/stabber
RUN ./bootstrap.sh

View File

@@ -9,7 +9,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
autoconf-archive \
automake \
ca-certificates \
ccache \
gcc \
git \
@@ -41,8 +40,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
WORKDIR /usr/src
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
RUN git clone --depth 1 https://github.com/strophe/libstrophe
RUN git clone --depth 1 -c http.sslverify=false https://git.jabber.space/devs/stabber
RUN git clone --depth 1 -c http.sslverify=false https://github.com/strophe/libstrophe
WORKDIR /usr/src/stabber
RUN ./bootstrap.sh

View File

@@ -11,7 +11,6 @@ RUN dnf install -y \
autoconf-archive \
automake \
awk \
ca-certificates \
ccache \
gcc \
git \
@@ -51,7 +50,7 @@ ENV TERM=xterm
RUN mkdir -p /usr/src
WORKDIR /usr/src
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
RUN git clone --depth 1 -c http.sslverify=false https://git.jabber.space/devs/stabber
WORKDIR /usr/src/stabber
RUN ./bootstrap.sh
RUN ./configure --prefix=/usr --disable-dependency-tracking
@@ -60,7 +59,7 @@ RUN make install
WORKDIR /usr/src
RUN mkdir -p /usr/src/libstrophe
RUN git clone --depth 1 https://github.com/strophe/libstrophe
RUN git clone --depth 1 -c http.sslverify=false https://github.com/strophe/libstrophe
WORKDIR /usr/src/libstrophe
RUN ./bootstrap.sh
RUN ./configure --prefix=/usr

View File

@@ -10,7 +10,6 @@ RUN zypper --non-interactive in --no-recommends \
autoconf \
autoconf-archive \
automake \
ca-certificates \
ccache \
gcc \
git \
@@ -50,7 +49,7 @@ ENV TERM=xterm
RUN mkdir -p /usr/src
WORKDIR /usr/src
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
RUN git clone --depth 1 -c http.sslverify=false https://git.jabber.space/devs/stabber
WORKDIR /usr/src/stabber
RUN ./bootstrap.sh
RUN ./configure --prefix=/usr --disable-dependency-tracking

View File

@@ -8,7 +8,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
autoconf-archive \
automake \
ca-certificates \
ccache \
gcc \
git \
@@ -40,8 +39,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
WORKDIR /usr/src
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
RUN git clone --depth 1 https://github.com/strophe/libstrophe
RUN git clone --depth 1 -c http.sslverify=false https://git.jabber.space/devs/stabber
RUN git clone --depth 1 -c http.sslverify=false https://github.com/strophe/libstrophe
WORKDIR /usr/src/stabber
RUN ./bootstrap.sh

View File

@@ -308,22 +308,18 @@ tests_functionaltests_functionaltests_LDADD = -lcmocka -lstabber @FORKPTY_LIB@
# Parallel functional tests target (~3x faster than sequential)
# Usage: make check-functional-parallel
# To add more groups: increase FUNC_TEST_GROUPS and add group in functionaltests.c
FUNC_TEST_GROUPS = 1 2 3 4
check-functional-parallel: tests/functionaltests/functionaltests
@echo "Running functional tests in parallel ($(words $(FUNC_TEST_GROUPS)) groups)..."
@mkdir -p $(builddir)/test-logs $(builddir)/test-files
@pids=""; \
for g in $(FUNC_TEST_GROUPS); do \
./tests/functionaltests/functionaltests $$g > $(builddir)/test-logs/group$$g.log 2>&1 & \
pids="$$pids $$!"; \
done; \
failed=0; i=1; \
for pid in $$pids; do \
wait $$pid || { echo "Group $$i FAILED"; cat $(builddir)/test-logs/group$$i.log; failed=1; }; \
i=$$((i + 1)); \
done; \
@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; \
@@ -372,10 +368,7 @@ clean-local:
rm -f $(git_include) $(git_include).in
endif
clean-functional-tests:
rm -rf $(builddir)/test-files $(builddir)/test-logs
.PHONY: my-prof.supp clean-functional-tests
.PHONY: my-prof.supp
my-prof.supp:
@sed '/^# AUTO-GENERATED START/q' prof.supp > $@
@printf "\n\n# glib\n" >> $@
@@ -391,7 +384,7 @@ check-unit: tests/unittests/unittests
tests/unittests/unittests
@VALGRIND_CHECK_RULES@
VALGRIND_SUPPRESSIONS_FILES=$(srcdir)/prof.supp
VALGRIND_SUPPRESSIONS_FILES=prof.supp
# Code coverage targets (requires --enable-coverage)
coverage-clean:

View File

@@ -180,13 +180,30 @@ case "$ARCH" in
;;
esac
case "$ARCH" in
linux*)
echo
echo "--> Building with ./configure -C ${tests[0]} --enable-valgrind $*"
echo
# shellcheck disable=SC2086
./configure -C ${tests[0]} --enable-valgrind $*
$MAKE CC="${CC}"
if grep '^ID=' /etc/os-release | grep -q -e debian; then
$MAKE check-valgrind
else
$MAKE check-valgrind || log_content ./test-suite-memcheck.log
fi
$MAKE distclean
;;
esac
# Function to build and test a single configuration
build_and_test() {
local features="$1"
local extra_args="$2"
local idx="$3"
local run_valgrind="$4"
local run_coverage="$5"
local build_dir="build-$idx"
local log_file="build-$idx.log"
@@ -194,93 +211,14 @@ build_and_test() {
echo "=== Build $idx started at $(date) ==="
echo "--> Building in $build_dir with ./configure -C $features $extra_args"
local start_time=$SECONDS
mkdir -p "$build_dir"
cd "$build_dir"
# shellcheck disable=SC2086
if ! ../configure -C $features $extra_args; then
echo "ERROR: configure failed"
exit 1
fi
../configure -C $features $extra_args
if ! $MAKE CC="${CC}"; then
echo "ERROR: make failed"
exit 1
fi
# Run unit tests under Valgrind
local unit_passed=0 unit_failed=0
if [ "$run_valgrind" = "yes" ]; then
echo "--> Running unit tests under Valgrind..."
# Build unit tests first
$MAKE tests/unittests/unittests
# Run valgrind directly to capture cmocka output
valgrind --error-exitcode=1 --leak-check=full \
--suppressions=../prof.supp \
tests/unittests/unittests 2>&1 | tee unit-tests-output.log
valgrind_exit=${PIPESTATUS[0]}
if [ $valgrind_exit -ne 0 ]; then
echo "ERROR: Valgrind unit tests failed (exit code $valgrind_exit)"
exit 1
fi
# Extract unit test counts from cmocka output
unit_passed=$(grep -E "^\[ PASSED \] [0-9]+ test" unit-tests-output.log | grep -oE "[0-9]+" | head -1)
unit_failed=$(grep -E "^\[ FAILED \] [0-9]+ test" unit-tests-output.log | grep -oE "[0-9]+" | head -1)
[ -z "$unit_passed" ] && unit_passed=0
[ -z "$unit_failed" ] && unit_failed=0
echo "UNIT_TESTS: passed=$unit_passed failed=$unit_failed"
fi
# Set build index for port allocation: build 1 uses ports 5230-5233,
# build 2 uses 5234-5237, etc. This prevents port conflicts in parallel builds.
export PROF_BUILD_INDEX=$idx
local func_passed=0 func_failed=0
if ! $MAKE check-functional-parallel; then
echo "ERROR: functional tests failed"
exit 1
fi
# Extract functional test counts from group logs
echo "=== Functional test results ==="
for glog in ./test-logs/group*.log; do
if [ -f "$glog" ]; then
cnt=$(grep -E "^\[ PASSED \] [0-9]+ test" "$glog" | grep -oE "[0-9]+" | head -1)
[ -n "$cnt" ] && func_passed=$((func_passed + cnt))
cnt=$(grep -E "^\[ FAILED \] [0-9]+ test" "$glog" | grep -oE "[0-9]+" | head -1)
[ -n "$cnt" ] && func_failed=$((func_failed + cnt))
fi
done
echo "FUNC_TESTS: passed=$func_passed failed=$func_failed"
# Collect coverage data if enabled (lines, functions, branches)
# Must be done BEFORE make clean which removes .gcda files
local cov_lines="n/a" cov_funcs="n/a" cov_branches="n/a"
if [ "$run_coverage" = "yes" ]; then
echo "--> Collecting coverage data..."
if command -v lcov >/dev/null 2>&1; then
lcov --capture --directory . --output-file coverage.info \
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage.info \
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true
if [ -f coverage.info ] && [ -s coverage.info ]; then
local summary
summary=$(lcov --summary coverage.info \
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true)
cov_lines=$(echo "$summary" | grep -E "lines\.*:" | grep -oE "[0-9]+\.[0-9]+%" | head -1)
cov_funcs=$(echo "$summary" | grep -E "functions\.*:" | grep -oE "[0-9]+\.[0-9]+%" | head -1)
cov_branches=$(echo "$summary" | grep -E "branches\.*:" | grep -oE "[0-9]+\.[0-9]+%" | head -1)
[ -z "$cov_lines" ] && cov_lines="n/a"
[ -z "$cov_funcs" ] && cov_funcs="n/a"
[ -z "$cov_branches" ] && cov_branches="n/a"
else
echo "WARNING: coverage.info is empty or not created"
fi
else
echo "WARNING: lcov not found"
fi
echo "COVERAGE: lines=$cov_lines funcs=$cov_funcs branches=$cov_branches"
fi
$MAKE CC="${CC}"
$MAKE check-functional-parallel
./profanity -v
$MAKE clean
@@ -288,94 +226,46 @@ build_and_test() {
cd ..
rm -rf "$build_dir"
local elapsed=$((SECONDS - start_time))
local mins=$((elapsed / 60))
local secs=$((elapsed % 60))
echo "=== Build $idx completed at $(date) ==="
echo "STATS: unit_passed=$unit_passed unit_failed=$unit_failed func_passed=$func_passed func_failed=$func_failed cov_lines=$cov_lines cov_funcs=$cov_funcs cov_branches=$cov_branches time=${mins}m${secs}s"
} > "$log_file" 2>&1
}
# Run all 4 configurations in parallel
# Coverage enabled only for build 1 (Full) - it has most code paths
echo "Starting parallel builds..."
pids=()
for idx in 1 2 3 4; do
if [ $idx -le ${#tests[@]} ]; then
# All builds run Valgrind
if [ "$ARCH" = "linux" ]; then
run_valgrind="yes"
if [ $idx -eq 1 ]; then
extra_flags="--enable-valgrind --enable-coverage"
run_coverage="yes"
else
extra_flags="--enable-valgrind"
run_coverage="no"
fi
else
extra_flags=""
run_valgrind="no"
run_coverage="no"
fi
build_and_test "${tests[$((idx-1))]}" "$* $extra_flags" "$idx" "$run_valgrind" "$run_coverage" &
build_and_test "${tests[$((idx-1))]}" "$*" "$idx" &
pids+=("$!")
flags_desc=""
[ "$run_valgrind" = "yes" ] && flags_desc="Valgrind"
[ "$run_coverage" = "yes" ] && flags_desc="${flags_desc:+$flags_desc+}Coverage"
[ -n "$flags_desc" ] && flags_desc=" [+$flags_desc]"
echo "Started build $idx (PID: $!)$flags_desc"
echo "Started build $idx (PID: $!)"
fi
done
# Wait for all builds and check exit codes
failed_builds=()
failed=0
for i in "${!pids[@]}"; do
idx=$((i + 1))
if wait "${pids[$i]}"; then
stats=""
if [ -f "build-$idx.log" ]; then
stats_line=$(grep "^STATS:" "build-$idx.log" | tail -1)
unit_p=$(echo "$stats_line" | grep -oE "unit_passed=[0-9]+" | cut -d= -f2)
unit_f=$(echo "$stats_line" | grep -oE "unit_failed=[0-9]+" | cut -d= -f2)
func_p=$(echo "$stats_line" | grep -oE "func_passed=[0-9]+" | cut -d= -f2)
func_f=$(echo "$stats_line" | grep -oE "func_failed=[0-9]+" | cut -d= -f2)
cov_lines=$(echo "$stats_line" | grep -oE "cov_lines=[0-9.]+%|cov_lines=n/a" | cut -d= -f2)
cov_funcs=$(echo "$stats_line" | grep -oE "cov_funcs=[0-9.]+%|cov_funcs=n/a" | cut -d= -f2)
cov_branches=$(echo "$stats_line" | grep -oE "cov_branches=[0-9.]+%|cov_branches=n/a" | cut -d= -f2)
time=$(echo "$stats_line" | grep -oE "time=[0-9]+m[0-9]+s" | cut -d= -f2)
# Format: passed/failed for each test type
unit_str="${unit_p:-0}/${unit_f:-0}"
func_str="${func_p:-0}/${func_f:-0}"
if [ "$cov_lines" != "n/a" ] && [ -n "$cov_lines" ]; then
stats=" (unit: $unit_str, func: $func_str, cov: L:$cov_lines F:$cov_funcs B:$cov_branches, ${time:-?})"
else
stats=" (unit: $unit_str, func: $func_str, ${time:-?})"
fi
fi
echo "✓ Build $idx passed$stats"
echo "✓ Build $idx passed"
else
echo "✗ Build $idx failed"
failed_builds+=("$idx")
fi
done
echo
# Show failed builds full logs
for idx in "${failed_builds[@]}"; do
if [ -f "build-$idx.log" ]; then
echo "=== Build $idx (FAILED) - full log ==="
echo "--- Log for build $idx ---"
cat "build-$idx.log"
echo
echo "--- End log ---"
failed=1
fi
done
if [ ${#failed_builds[@]} -gt 0 ]; then
echo "FAILED: builds ${failed_builds[*]}"
exit 1
else
# 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

@@ -8,29 +8,6 @@
# * python suppressions file from https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp
#
# ============================================
# glibc AVX2 optimizations (false positives)
# See: https://sourceware.org/bugzilla/show_bug.cgi?id=19796
# ============================================
{
glibc_wcpncpy_avx2
Memcheck:Addr32
fun:__wcpncpy_avx2
fun:wcsxfrm_l
fun:g_utf8_collate_key
...
}
{
glibc_wcsxfrm_avx2
Memcheck:Addr32
...
fun:wcsxfrm_l
fun:g_utf8_collate_key
...
}
# ============================================
# Functional tests suppressions (stabber/pthread)
# ============================================

View File

@@ -15,14 +15,17 @@
* functional tests run less frequently than unit tests.
*
* Tests are organized into groups for better maintainability and parallel execution:
* Group 1: Connect, Ping, Rooms, Software
* Group 2: Message, Receipts, Roster, Chat Session
* Group 3: Presence, Disconnect
* Group 4: MUC, Carbons
* 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 N - run group N only (N = 1..num_groups)
* ./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
@@ -38,7 +41,6 @@
#include "config.h"
#include "common.h"
#include "proftest.h"
#include "test_connect.h"
#include "test_ping.h"
@@ -69,18 +71,11 @@ main(int argc, char* argv[])
}
}
char group_env[16];
snprintf(group_env, sizeof(group_env), "%d", group);
setenv("PROF_TEST_GROUP", group_env, 1);
fprintf(stderr, "[PROF_TEST] Starting functional tests, group=%d\n", group);
/* GROUP 1: Connect, Ping, Rooms, Software (17 tests)
* Basic XMPP session establishment and server queries */
/* ============================================================
* GROUP 1: Connect, Ping, Rooms, Software
* Basic XMPP session establishment and server queries
* ============================================================ */
/* Connection tests - verify login, roster, bookmarks */
const struct CMUnitTest group1_tests[] = {
/* Connection tests - verify login, roster, bookmarks */
PROF_FUNC_TEST(connect_jid_requests_roster),
PROF_FUNC_TEST(connect_jid_sends_presence_after_receiving_roster),
PROF_FUNC_TEST(connect_jid_requests_bookmarks),
@@ -106,11 +101,10 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(display_software_version_result_in_chat),
};
/* ============================================================
* GROUP 2: Message, Receipts, Roster, Chat Session
* 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),
PROF_FUNC_TEST(message_receive_console),
@@ -137,10 +131,8 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(new_session_when_message_received_from_different_fulljid),
};
/* ============================================================
* GROUP 3: Presence, Disconnect
* Online/away/xa/dnd/chat status management
* ============================================================ */
/* 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),
@@ -162,11 +154,10 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(disconnect_ends_session),
};
/* ============================================================
* GROUP 4: MUC, Carbons
* Multi-user chat and message synchronization
* ============================================================ */
/* 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),
@@ -197,31 +188,28 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(receive_private_carbon),
};
/* Test group registry for easy extension */
struct {
const char* name;
const struct CMUnitTest* tests;
size_t count;
} groups[] = {
{ "Group 1: Connect/Ping/Rooms/Software", group1_tests, ARRAY_SIZE(group1_tests) },
{ "Group 2: Message/Receipts/Roster/Session", group2_tests, ARRAY_SIZE(group2_tests) },
{ "Group 3: Presence/Disconnect", group3_tests, ARRAY_SIZE(group3_tests) },
{ "Group 4: MUC/Carbons", group4_tests, ARRAY_SIZE(group4_tests) },
};
const int num_groups = ARRAY_SIZE(groups);
int result = 0;
if (group > 0 && group <= num_groups) {
/* Run specific group */
result = _cmocka_run_group_tests(groups[group - 1].name, groups[group - 1].tests,
groups[group - 1].count, NULL, NULL);
} else {
/* Run all groups sequentially */
for (int i = 0; i < num_groups; i++) {
result |= _cmocka_run_group_tests(groups[i].name, groups[i].tests,
groups[i].count, NULL, NULL);
}
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

@@ -136,14 +136,8 @@ _create_logs_dir(void)
void
_cleanup_dirs(void)
{
const char *group_env = getenv("PROF_TEST_GROUP");
int group = group_env ? atoi(group_env) : 0;
int dir_id = (group >= 1 && group <= 4) ? group : stub_port;
fprintf(stderr, "[PROF_TEST] Cleaning up directories for group %d (dir_id %d)\n", group, dir_id);
char cmd[512];
snprintf(cmd, sizeof(cmd), "rm -rf ./test-files/%d", dir_id);
snprintf(cmd, sizeof(cmd), "rm -rf ./tests/functionaltests/files/%d", stub_port);
int res = system(cmd);
if (res == -1) {
assert_true(FALSE);
@@ -227,73 +221,30 @@ prof_start(void)
/* Set non-blocking mode for reading */
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
/* Brief wait for process to initialize */
usleep(50000); /* 50ms */
}
int
init_prof_test(void **state)
{
/* Get test group from environment for static resource allocation */
const char *group_env = getenv("PROF_TEST_GROUP");
int group = group_env ? atoi(group_env) : 0;
/* Get build index for port offset (for parallel CI builds) */
const char *build_env = getenv("PROF_BUILD_INDEX");
int build_idx = build_env ? atoi(build_env) : 0;
/* Calculate port base: each build uses a different range of 4 ports
* Build 0/1: 5230-5233, Build 2: 5234-5237, Build 3: 5238-5241, Build 4: 5242-5245 */
int port_base = 5230 + ((build_idx > 0 ? build_idx - 1 : 0) * 4);
/* Static resource allocation to avoid conflicts in parallel execution.
* Group 1-4: use static port assignment.
* Group 0 (all groups): use dynamic allocation as fallback. */
gboolean started = FALSE;
if (group >= 1 && group <= 4) {
/* Static allocation: each group gets a dedicated port */
stub_port = port_base + group - 1;
fprintf(stderr, "[PROF_TEST] Build %d, Group %d: trying port %d\n", build_idx, group, stub_port);
if (stbbr_start(STBBR_LOGDEBUG, stub_port, 0) == 0) {
for (int p = 5230; p < 5250; ++p) {
int ret = stbbr_start(STBBR_LOGDEBUG, p, 0);
if (ret == 0) {
stub_port = p;
started = TRUE;
fprintf(stderr, "[PROF_TEST] Started stabber on port %d\n", stub_port);
} else {
fprintf(stderr, "[PROF_TEST] Failed to start stabber on port %d\n", stub_port);
break;
}
}
/* Fallback to dynamic allocation if static failed or group=0 */
if (!started) {
fprintf(stderr, "[PROF_TEST] Using dynamic port allocation\n");
for (int p = port_base; p < port_base + 20; ++p) {
if (stbbr_start(STBBR_LOGDEBUG, p, 0) == 0) {
stub_port = p;
started = TRUE;
fprintf(stderr, "[PROF_TEST] Started stabber on port %d\n", stub_port);
break;
}
}
}
if (!started) {
fprintf(stderr, "[PROF_TEST] ERROR: could not start stabber on any port\n");
assert_true(FALSE);
assert_true(FALSE); // could not start stabber on any port in range
return -1;
}
// Generate unique XDG paths based on group for parallel execution
// Use ./test-files/ in current (build) directory for out-of-tree builds compatibility
int dir_id = (group >= 1 && group <= 4) ? group : stub_port;
// Generate unique XDG paths based on stub_port for parallel execution
snprintf(xdg_config_home, sizeof(xdg_config_home),
"./test-files/%d/xdg_config_home", dir_id);
"./tests/functionaltests/files/%d/xdg_config_home", stub_port);
snprintf(xdg_data_home, sizeof(xdg_data_home),
"./test-files/%d/xdg_data_home", dir_id);
fprintf(stderr, "[PROF_TEST] Group %d using directories: config=%s, data=%s\n",
group, xdg_config_home, xdg_data_home);
"./tests/functionaltests/files/%d/xdg_data_home", stub_port);
// Give stabber server thread time to start listening
usleep(100000); // 100ms
@@ -447,19 +398,9 @@ prof_output_regex(const char *pattern)
return 1;
}
usleep(50000);
usleep(50000); /* 50ms */
}
/* Timeout reached - log diagnostic info */
fprintf(stderr, "Timeout waiting for regex '%s' after %d seconds. Last output:\n", pattern, expect_timeout);
size_t len = strlen(output_buffer);
if (len > 500) {
fprintf(stderr, "...%s", output_buffer + len - 500);
} else {
fprintf(stderr, "%s", output_buffer);
}
fprintf(stderr, "\n");
regfree(&regex);
return 0;
}

View File

@@ -2,9 +2,9 @@
#define __H_PROFTEST
/*
* XDG paths are dynamic and generated per-test based on stub_port.
* Each test instance uses unique directories (./tests/functionaltests/files/{port}/xdg_*)
* to allow parallel test execution without file conflicts.
* 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];