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 158 additions and 493 deletions

View File

@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run tests - name: Run tests
run: | 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 docker run profanity ./ci-build.sh
code-style: code-style:
@@ -106,5 +106,25 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build and run coverage - name: Build and run coverage
run: | run: |
docker build -f Dockerfile.arch -t profanity-cov . docker build -f Dockerfile.debian -t profanity-cov .
docker run profanity-cov ./ci-build.sh --coverage-only docker run -v ${{ github.workspace }}/coverage:/coverage profanity-cov bash -c '
./bootstrap.sh
./configure --enable-coverage --enable-otr --enable-pgp --enable-omemo --enable-plugins
make -j$(nproc)
make check || true
make check-functional-parallel || true
lcov --capture --directory . --output-file /coverage/coverage.info \
--rc branch_coverage=1 \
--ignore-errors inconsistent
lcov --remove /coverage/coverage.info \
"/usr/include/*" "*/tests/*" \
--output-file /coverage/coverage.info \
--rc branch_coverage=1 \
--ignore-errors inconsistent,empty,unused
'
- 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.log
tests/unittests/unittests.trs tests/unittests/unittests.trs
test-suite.log test-suite.log
test-files/
test-logs/
# valgrind output # valgrind output
profval* profval*
@@ -110,7 +108,3 @@ breaks
*.zip *.zip
*.log* *.log*
coverage/ coverage/
*.gcno
*.gcda
*.gcov
coverage.info

View File

@@ -90,24 +90,6 @@ set -e
``` ```
This will run the same tests that the CI runs and refuse the push if it fails. 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:
- **Full** — all features enabled (+ coverage in `--coverage-only` mode)
- **Minimal** — all optional features disabled
- **NoEncrypt** — no encryption (OTR, PGP, OMEMO disabled)
- **Default** — default ./configure options
Each build runs Valgrind and functional tests on Linux.
Use `./ci-build.sh --coverage-only` to run only the Full build with coverage collection.
Output shows test results per build:
```
✓ Full PASSED
Unit tests: 437 passed, 0 failed
Functional tests: 69 passed, 0 failed
Coverage: Lines: 27.5% | Functions: 36.2% | Branches: 18.1%
Duration: 5m39s
```
Note that it will run on the actual content of the repository directory and not Note that it will run on the actual content of the repository directory and not
what may have been staged/committed. what may have been staged/committed.
@@ -165,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`: Functional tests require stabber to be installed. Once installed, tests run as part of `make check`:
```bash ```bash
make check # Run all tests (unit + functional) make check
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)
``` ```
#### 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 #### Writing functional tests
Use content-based stubbing with stabber: Use content-based stubbing with stabber:
@@ -205,5 +168,4 @@ Guidelines:
2. Use `stbbr_send(xml)` for presence, message, and other push-style stanzas. 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. 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. 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 \ autoconf-archive \
automake \ automake \
base-devel \ base-devel \
ca-certificates \
ccache \ ccache \
check \ check \
cmake \ cmake \
@@ -65,7 +64,7 @@ USER root
RUN pacman -U --noconfirm libstrophe-git/libstrophe-git-*.pkg.tar.zst RUN pacman -U --noconfirm libstrophe-git/libstrophe-git-*.pkg.tar.zst
WORKDIR /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 WORKDIR /usr/src/stabber
RUN ./bootstrap.sh RUN ./bootstrap.sh

View File

@@ -9,7 +9,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \ autoconf \
autoconf-archive \ autoconf-archive \
automake \ automake \
ca-certificates \
ccache \ ccache \
gcc \ gcc \
git \ git \
@@ -41,8 +40,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity} RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
WORKDIR /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
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/stabber WORKDIR /usr/src/stabber
RUN ./bootstrap.sh RUN ./bootstrap.sh

View File

@@ -11,7 +11,6 @@ RUN dnf install -y \
autoconf-archive \ autoconf-archive \
automake \ automake \
awk \ awk \
ca-certificates \
ccache \ ccache \
gcc \ gcc \
git \ git \
@@ -51,7 +50,7 @@ ENV TERM=xterm
RUN mkdir -p /usr/src RUN mkdir -p /usr/src
WORKDIR /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 WORKDIR /usr/src/stabber
RUN ./bootstrap.sh RUN ./bootstrap.sh
RUN ./configure --prefix=/usr --disable-dependency-tracking RUN ./configure --prefix=/usr --disable-dependency-tracking
@@ -60,7 +59,7 @@ RUN make install
WORKDIR /usr/src WORKDIR /usr/src
RUN mkdir -p /usr/src/libstrophe 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 WORKDIR /usr/src/libstrophe
RUN ./bootstrap.sh RUN ./bootstrap.sh
RUN ./configure --prefix=/usr RUN ./configure --prefix=/usr

View File

@@ -10,7 +10,6 @@ RUN zypper --non-interactive in --no-recommends \
autoconf \ autoconf \
autoconf-archive \ autoconf-archive \
automake \ automake \
ca-certificates \
ccache \ ccache \
gcc \ gcc \
git \ git \
@@ -50,7 +49,7 @@ ENV TERM=xterm
RUN mkdir -p /usr/src RUN mkdir -p /usr/src
WORKDIR /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 WORKDIR /usr/src/stabber
RUN ./bootstrap.sh RUN ./bootstrap.sh
RUN ./configure --prefix=/usr --disable-dependency-tracking 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 \
autoconf-archive \ autoconf-archive \
automake \ automake \
ca-certificates \
ccache \ ccache \
gcc \ gcc \
git \ git \
@@ -40,8 +39,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity} RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
WORKDIR /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
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/stabber WORKDIR /usr/src/stabber
RUN ./bootstrap.sh 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) # Parallel functional tests target (~3x faster than sequential)
# Usage: make check-functional-parallel # 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 check-functional-parallel: tests/functionaltests/functionaltests
@echo "Running functional tests in parallel ($(words $(FUNC_TEST_GROUPS)) groups)..." @echo "Running functional tests in parallel (4 groups)..."
@mkdir -p $(builddir)/test-logs $(builddir)/test-files @mkdir -p $(builddir)/test-logs
@pids=""; \ @failed=0; \
for g in $(FUNC_TEST_GROUPS); do \ ./tests/functionaltests/functionaltests 1 > $(builddir)/test-logs/group1.log 2>&1 & pid1=$$!; \
./tests/functionaltests/functionaltests $$g > $(builddir)/test-logs/group$$g.log 2>&1 & \ ./tests/functionaltests/functionaltests 2 > $(builddir)/test-logs/group2.log 2>&1 & pid2=$$!; \
pids="$$pids $$!"; \ ./tests/functionaltests/functionaltests 3 > $(builddir)/test-logs/group3.log 2>&1 & pid3=$$!; \
done; \ ./tests/functionaltests/functionaltests 4 > $(builddir)/test-logs/group4.log 2>&1 & pid4=$$!; \
failed=0; i=1; \ wait $$pid1 || { echo "Group 1 FAILED (exit $$?)"; cat $(builddir)/test-logs/group1.log; failed=1; }; \
for pid in $$pids; do \ wait $$pid2 || { echo "Group 2 FAILED (exit $$?)"; cat $(builddir)/test-logs/group2.log; failed=1; }; \
wait $$pid || { echo "Group $$i FAILED"; cat $(builddir)/test-logs/group$$i.log; failed=1; }; \ wait $$pid3 || { echo "Group 3 FAILED (exit $$?)"; cat $(builddir)/test-logs/group3.log; failed=1; }; \
i=$$((i + 1)); \ wait $$pid4 || { echo "Group 4 FAILED (exit $$?)"; cat $(builddir)/test-logs/group4.log; failed=1; }; \
done; \
echo "=== Test Results Summary ==="; \ echo "=== Test Results Summary ==="; \
grep -E 'PASSED|FAILED|Running' $(builddir)/test-logs/group*.log || true; \ grep -E 'PASSED|FAILED|Running' $(builddir)/test-logs/group*.log || true; \
if [ $$failed -ne 0 ]; then echo "FUNCTIONAL TESTS FAILED"; exit 1; fi; \ 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 rm -f $(git_include) $(git_include).in
endif endif
clean-functional-tests: .PHONY: my-prof.supp
rm -rf $(builddir)/test-files $(builddir)/test-logs
.PHONY: my-prof.supp clean-functional-tests
my-prof.supp: my-prof.supp:
@sed '/^# AUTO-GENERATED START/q' prof.supp > $@ @sed '/^# AUTO-GENERATED START/q' prof.supp > $@
@printf "\n\n# glib\n" >> $@ @printf "\n\n# glib\n" >> $@
@@ -391,7 +384,7 @@ check-unit: tests/unittests/unittests
tests/unittests/unittests tests/unittests/unittests
@VALGRIND_CHECK_RULES@ @VALGRIND_CHECK_RULES@
VALGRIND_SUPPRESSIONS_FILES=$(srcdir)/prof.supp VALGRIND_SUPPRESSIONS_FILES=prof.supp
# Code coverage targets (requires --enable-coverage) # Code coverage targets (requires --enable-coverage)
coverage-clean: coverage-clean:

View File

@@ -15,72 +15,14 @@ error_handler()
log_content ./test-suite.log log_content ./test-suite.log
log_content ./test-suite-memcheck.log log_content ./test-suite-memcheck.log
echo >&2 echo
echo "Error ${ERR_CODE} with command '${BASH_COMMAND}' on line ${BASH_LINENO[0]}. Exiting." >&2 echo "Error ${ERR_CODE} with command '${BASH_COMMAND}' on line ${BASH_LINENO[0]}. Exiting."
echo >&2 echo
exit ${ERR_CODE} exit ${ERR_CODE}
} }
trap error_handler ERR trap error_handler ERR
# =============================================================================
# Constants
# =============================================================================
# Number of parallel build configurations
readonly TEST_BUILDS=4
# Human-readable names for each build configuration
readonly BUILD_NAMES=(
"Full" # 1. All features enabled
"Minimal" # 2. All optional features disabled
"NoEncrypt" # 3. No encryption (otr, pgp, omemo disabled)
"Default" # 4. Default ./configure options
)
# Regex patterns for parsing test output
readonly CMOCKA_PASSED_PATTERN='^\[ PASSED \] [0-9]+ test'
readonly CMOCKA_FAILED_PATTERN='^\[ FAILED \] [0-9]+ test'
# Coverage extraction patterns (matches both Docker and CI paths)
readonly COVERAGE_PATTERNS='*/profanity/src/* */src/src/*'
# =============================================================================
# Helper Functions
# =============================================================================
# Parse STATS line from build log and set global variables
# Usage: parse_build_stats <log_file>
parse_build_stats() {
local log_file="$1"
local stats_line
stats_line=$(grep "^STATS:" "$log_file" 2>/dev/null | tail -1)
STAT_UNIT_P=$(echo "$stats_line" | grep -oE "unit_passed=[0-9]+" | cut -d= -f2)
STAT_UNIT_F=$(echo "$stats_line" | grep -oE "unit_failed=[0-9]+" | cut -d= -f2)
STAT_FUNC_P=$(echo "$stats_line" | grep -oE "func_passed=[0-9]+" | cut -d= -f2)
STAT_FUNC_F=$(echo "$stats_line" | grep -oE "func_failed=[0-9]+" | cut -d= -f2)
STAT_COV_LINES=$(echo "$stats_line" | grep -oE "cov_lines=[0-9.]+%|cov_lines=n/a" | cut -d= -f2)
STAT_COV_FUNCS=$(echo "$stats_line" | grep -oE "cov_funcs=[0-9.]+%|cov_funcs=n/a" | cut -d= -f2)
STAT_COV_BRANCHES=$(echo "$stats_line" | grep -oE "cov_branches=[0-9.]+%|cov_branches=n/a" | cut -d= -f2)
STAT_TIME=$(echo "$stats_line" | grep -oE "time=[0-9]+m[0-9]+s" | cut -d= -f2)
: "${STAT_UNIT_P:=0}"
: "${STAT_UNIT_F:=0}"
: "${STAT_FUNC_P:=0}"
: "${STAT_FUNC_F:=0}"
}
# Extract test count from log file
# Usage: extract_test_count <log_file> <pattern>
extract_test_count() {
grep -E "$2" "$1" 2>/dev/null | grep -oE "[0-9]+" | head -1
}
# =============================================================================
# Test Verification
# =============================================================================
# Verify that test failures are properly detected # Verify that test failures are properly detected
# This is a meta-test: it runs a deliberately failing test # This is a meta-test: it runs a deliberately failing test
# and checks that the test framework reports the failure correctly # and checks that the test framework reports the failure correctly
@@ -119,11 +61,11 @@ EOF
# Test 1: Single failing test detection # Test 1: Single failing test detection
echo " Testing single test failure detection..." echo " Testing single test failure detection..."
if /tmp/test_must_fail > /tmp/test_must_fail.log 2>&1; then if /tmp/test_must_fail > /tmp/test_must_fail.log 2>&1; then
echo "ERROR: Test that should fail returned success (exit code 0)" >&2 echo "ERROR: Test that should fail returned success (exit code 0)"
echo "This means the test framework is NOT detecting failures correctly!" >&2 echo "This means the test framework is NOT detecting failures correctly!"
echo "--- Test output ---" >&2 echo "--- Test output ---"
cat /tmp/test_must_fail.log >&2 cat /tmp/test_must_fail.log
echo "--- End output ---" >&2 echo "--- End output ---"
rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log
exit 1 exit 1
fi fi
@@ -143,8 +85,8 @@ EOF
wait $pid4 || failed=$((failed + 1)) wait $pid4 || failed=$((failed + 1))
if [ $failed -ne 2 ]; then if [ $failed -ne 2 ]; then
echo "ERROR: Expected 2 failures in parallel tests, got $failed" >&2 echo "ERROR: Expected 2 failures in parallel tests, got $failed"
echo "Parallel failure detection is broken!" >&2 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 rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log /tmp/p?.log
exit 1 exit 1
fi fi
@@ -168,17 +110,6 @@ num_cores()
# Run test failure detection verification first # Run test failure detection verification first
verify_test_failure_detection verify_test_failure_detection
# Parse arguments
COVERAGE_ONLY=no
for arg in "$@"; do
case "$arg" in
--coverage-only)
COVERAGE_ONLY=yes
shift
;;
esac
done
./bootstrap.sh ./bootstrap.sh
tests=() tests=()
@@ -249,13 +180,30 @@ case "$ARCH" in
;; ;;
esac 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 # Function to build and test a single configuration
build_and_test() { build_and_test() {
local features="$1" local features="$1"
local extra_args="$2" local extra_args="$2"
local idx="$3" local idx="$3"
local run_valgrind="$4"
local run_coverage="$5"
local build_dir="build-$idx" local build_dir="build-$idx"
local log_file="build-$idx.log" local log_file="build-$idx.log"
@@ -263,210 +211,61 @@ build_and_test() {
echo "=== Build $idx started at $(date) ===" echo "=== Build $idx started at $(date) ==="
echo "--> Building in $build_dir with ./configure -C $features $extra_args" echo "--> Building in $build_dir with ./configure -C $features $extra_args"
local start_time=$SECONDS
mkdir -p "$build_dir" mkdir -p "$build_dir"
cd "$build_dir" cd "$build_dir"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! ../configure -C $features $extra_args; then ../configure -C $features $extra_args
echo "ERROR: configure failed"
exit 1
fi
if ! $MAKE CC="${CC}"; then $MAKE CC="${CC}"
echo "ERROR: make failed" $MAKE check-functional-parallel
exit 1
fi
# Run unit tests
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
else
echo "--> Running unit tests..."
$MAKE tests/unittests/unittests
tests/unittests/unittests 2>&1 | tee unit-tests-output.log
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "ERROR: Unit tests failed"
exit 1
fi
fi
# Extract unit test counts from cmocka output
unit_passed=$(extract_test_count unit-tests-output.log "$CMOCKA_PASSED_PATTERN")
unit_failed=$(extract_test_count unit-tests-output.log "$CMOCKA_FAILED_PATTERN")
: "${unit_passed:=0}" "${unit_failed:=0}"
echo "UNIT_TESTS: passed=$unit_passed failed=$unit_failed"
# 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=$(extract_test_count "$glog" "$CMOCKA_PASSED_PATTERN")
[ -n "$cnt" ] && func_passed=$((func_passed + cnt))
cnt=$(extract_test_count "$glog" "$CMOCKA_FAILED_PATTERN")
[ -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-full.info \
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true
# Extract only production code from src/ directory, exclude tests
# shellcheck disable=SC2086
lcov --extract coverage-full.info $COVERAGE_PATTERNS \
--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
./profanity -v ./profanity -v
# Save coverage.info before cleanup (for CI artifact)
# Only copy in CI environment to avoid leaving artifacts during local runs
if [ "$run_coverage" = "yes" ] && [ -f coverage.info ] && [ -n "$CI" ]; then
cp coverage.info ../coverage.info
echo "Coverage report saved to coverage.info"
fi
$MAKE clean $MAKE clean
cd .. cd ..
rm -rf "$build_dir" rm -rf "$build_dir"
local elapsed=$((SECONDS - start_time))
local mins=$((elapsed / 60))
local secs=$((elapsed % 60))
echo "=== Build $idx completed at $(date) ===" 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 } > "$log_file" 2>&1
} }
# Run configurations # Run all 4 configurations in parallel
# Coverage enabled only for build 1 (Full) - it has most code paths echo "Starting parallel builds..."
echo pids=()
echo "=== Start build ===" for idx in 1 2 3 4; do
echo if [ $idx -le ${#tests[@]} ]; then
build_and_test "${tests[$((idx-1))]}" "$*" "$idx" &
if [ "$COVERAGE_ONLY" = "yes" ]; then pids+=("$!")
echo "Running coverage-only mode (${BUILD_NAMES[0]} build)..." echo "Started build $idx (PID: $!)"
echo fi
run_valgrind="no" done
run_coverage="yes"
extra_flags="--enable-coverage"
build_and_test "${tests[0]}" "$* $extra_flags" "1" "$run_valgrind" "$run_coverage" &
pids=("$!")
echo "${BUILD_NAMES[0]}: ${tests[0]} [+Coverage]"
else
echo "Starting $TEST_BUILDS parallel build configurations..."
echo
pids=()
for idx in $(seq 1 $TEST_BUILDS); do
if [ $idx -le ${#tests[@]} ]; then
# All builds run Valgrind on Linux
if [ "$ARCH" = "linux" ]; then
run_valgrind="yes"
extra_flags="--enable-valgrind"
else
run_valgrind="no"
extra_flags=""
fi
run_coverage="no"
build_and_test "${tests[$((idx-1))]}" "$* $extra_flags" "$idx" "$run_valgrind" "$run_coverage" &
pids+=("$!")
flags_desc=""
[ "$run_valgrind" = "yes" ] && flags_desc=" [+Valgrind]"
echo "${BUILD_NAMES[$((idx-1))]}: ${tests[$((idx-1))]}$flags_desc"
fi
done
fi
echo
# Wait for all builds and check exit codes # Wait for all builds and check exit codes
echo "Waiting for builds to complete..." failed=0
echo
failed_builds=()
for i in "${!pids[@]}"; do for i in "${!pids[@]}"; do
idx=$((i + 1)) idx=$((i + 1))
if wait "${pids[$i]}"; then if wait "${pids[$i]}"; then
if [ -f "build-$idx.log" ]; then echo "✓ Build $idx passed"
parse_build_stats "build-$idx.log"
echo "${BUILD_NAMES[$i]} PASSED"
echo " Unit tests: $STAT_UNIT_P passed, $STAT_UNIT_F failed"
echo " Functional tests: $STAT_FUNC_P passed, $STAT_FUNC_F failed"
if [ "$STAT_COV_LINES" != "n/a" ] && [ -n "$STAT_COV_LINES" ]; then
echo " Coverage: Lines: $STAT_COV_LINES | Functions: $STAT_COV_FUNCS | Branches: $STAT_COV_BRANCHES"
fi
echo " Duration: ${STAT_TIME:-?}"
else
echo "${BUILD_NAMES[$i]} passed (no stats available)"
fi
else else
echo "${BUILD_NAMES[$i]} FAILED" >&2 echo "Build $idx failed"
failed_builds+=("$idx") echo "--- Log for build $idx ---"
cat "build-$idx.log"
echo "--- End log ---"
failed=1
fi fi
done
# Show all logs on success too
if [ $failed -eq 0 ]; then
echo echo
done echo "All builds passed!"
for idx in 1 2 3 4; do
# Show failed builds full logs if [ -f "build-$idx.log" ]; then
for idx in "${failed_builds[@]}"; do echo "--- Log for build $idx ---"
if [ -f "build-$idx.log" ]; then cat "build-$idx.log"
echo "=== ${BUILD_NAMES[$((idx-1))]} FAILURE LOG ===" >&2 fi
cat "build-$idx.log" >&2 done
echo >&2
fi
done
if [ ${#failed_builds[@]} -gt 0 ]; then
echo "RESULT: FAILED (builds ${failed_builds[*]})" >&2
exit 1
else else
if [ "$COVERAGE_ONLY" = "yes" ]; then exit 1
echo "RESULT: COVERAGE BUILD PASSED ✓"
else
echo "RESULT: ALL $TEST_BUILDS BUILDS PASSED ✓"
fi
fi fi

View File

@@ -8,29 +8,6 @@
# * python suppressions file from https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp # * 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) # Functional tests suppressions (stabber/pthread)
# ============================================ # ============================================

View File

@@ -15,14 +15,17 @@
* functional tests run less frequently than unit tests. * functional tests run less frequently than unit tests.
* *
* Tests are organized into groups for better maintainability and parallel execution: * Tests are organized into groups for better maintainability and parallel execution:
* Group 1: Connect, Ping, Rooms, Software * Group 1: Connect, Ping, Rooms, Software (17 tests)
* Group 2: Message, Receipts, Roster, Chat Session * Group 2: Message, Receipts, Roster, Chat Session (17 tests)
* Group 3: Presence, Disconnect * Group 3: Presence (16 tests)
* Group 4: MUC, Carbons * Group 4: MUC, Carbons (19 tests)
* *
* Parallel execution: * Parallel execution:
* ./functionaltests - run all tests sequentially * ./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: * For parallel execution, run multiple groups simultaneously:
* ./functionaltests 1 & ./functionaltests 2 & ./functionaltests 3 & ./functionaltests 4 & wait * ./functionaltests 1 & ./functionaltests 2 & ./functionaltests 3 & ./functionaltests 4 & wait
@@ -38,7 +41,6 @@
#include "config.h" #include "config.h"
#include "common.h"
#include "proftest.h" #include "proftest.h"
#include "test_connect.h" #include "test_connect.h"
#include "test_ping.h" #include "test_ping.h"
@@ -69,18 +71,11 @@ main(int argc, char* argv[])
} }
} }
char group_env[16]; /* GROUP 1: Connect, Ping, Rooms, Software (17 tests)
snprintf(group_env, sizeof(group_env), "%d", group); * Basic XMPP session establishment and server queries */
setenv("PROF_TEST_GROUP", group_env, 1);
fprintf(stderr, "[PROF_TEST] Starting functional tests, group=%d\n", group);
/* ============================================================ /* Connection tests - verify login, roster, bookmarks */
* GROUP 1: Connect, Ping, Rooms, Software
* Basic XMPP session establishment and server queries
* ============================================================ */
const struct CMUnitTest group1_tests[] = { const struct CMUnitTest group1_tests[] = {
/* Connection tests - verify login, roster, bookmarks */
PROF_FUNC_TEST(connect_jid_requests_roster), PROF_FUNC_TEST(connect_jid_requests_roster),
PROF_FUNC_TEST(connect_jid_sends_presence_after_receiving_roster), PROF_FUNC_TEST(connect_jid_sends_presence_after_receiving_roster),
PROF_FUNC_TEST(connect_jid_requests_bookmarks), 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), PROF_FUNC_TEST(display_software_version_result_in_chat),
}; };
/* ============================================================ /* GROUP 2: Message, Receipts, Roster, Chat Session (17 tests)
* GROUP 2: Message, Receipts, Roster, Chat Session * Core messaging and contact management */
* Core messaging and contact management
* ============================================================ */
const struct CMUnitTest group2_tests[] = { const struct CMUnitTest group2_tests[] = {
/* Basic message send/receive */ /* Basic message send/receive */
PROF_FUNC_TEST(message_send), PROF_FUNC_TEST(message_send),
PROF_FUNC_TEST(message_receive_console), 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), PROF_FUNC_TEST(new_session_when_message_received_from_different_fulljid),
}; };
/* ============================================================ /* GROUP 3: Presence (16 tests)
* GROUP 3: Presence, Disconnect * Online/away/xa/dnd/chat status management */
* Online/away/xa/dnd/chat status management
* ============================================================ */
const struct CMUnitTest group3_tests[] = { const struct CMUnitTest group3_tests[] = {
PROF_FUNC_TEST(presence_online), PROF_FUNC_TEST(presence_online),
PROF_FUNC_TEST(presence_online_with_message), PROF_FUNC_TEST(presence_online_with_message),
@@ -162,11 +154,10 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(disconnect_ends_session), PROF_FUNC_TEST(disconnect_ends_session),
}; };
/* ============================================================ /* GROUP 4: MUC, Carbons (19 tests)
* GROUP 4: MUC, Carbons * Multi-user chat and message synchronization */
* Multi-user chat and message synchronization
* ============================================================ */
const struct CMUnitTest group4_tests[] = { const struct CMUnitTest group4_tests[] = {
/* MUC room join with various options - XEP-0045 */ /* MUC room join with various options - XEP-0045 */
PROF_FUNC_TEST(sends_room_join), PROF_FUNC_TEST(sends_room_join),
PROF_FUNC_TEST(sends_room_join_with_nick), PROF_FUNC_TEST(sends_room_join_with_nick),
@@ -197,31 +188,28 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(receive_private_carbon), 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; int result = 0;
if (group > 0 && group <= num_groups) { switch (group) {
/* Run specific group */ case 1:
result = _cmocka_run_group_tests(groups[group - 1].name, groups[group - 1].tests, result = cmocka_run_group_tests_name("Group 1: Connect/Ping/Rooms/Software", group1_tests, NULL, NULL);
groups[group - 1].count, NULL, NULL); break;
} else { case 2:
/* Run all groups sequentially */ result = cmocka_run_group_tests_name("Group 2: Message/Receipts/Roster/Session", group2_tests, NULL, NULL);
for (int i = 0; i < num_groups; i++) { break;
result |= _cmocka_run_group_tests(groups[i].name, groups[i].tests, case 3:
groups[i].count, NULL, NULL); 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; return result;

View File

@@ -17,9 +17,6 @@
#include "proftest.h" #include "proftest.h"
/* Number of parallel test groups for CI builds */
#define TEST_GROUPS 4
char *config_orig; char *config_orig;
char *data_orig; char *data_orig;
@@ -139,14 +136,8 @@ _create_logs_dir(void)
void void
_cleanup_dirs(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 <= TEST_GROUPS) ? group : stub_port;
printf("[PROF_TEST] Cleaning up directories for group %d (dir_id %d)\n", group, dir_id);
char cmd[512]; 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); int res = system(cmd);
if (res == -1) { if (res == -1) {
assert_true(FALSE); assert_true(FALSE);
@@ -230,74 +221,30 @@ prof_start(void)
/* Set non-blocking mode for reading */ /* Set non-blocking mode for reading */
int flags = fcntl(fd, F_GETFL, 0); int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK); fcntl(fd, F_SETFL, flags | O_NONBLOCK);
/* Brief wait for process to initialize */
usleep(50000); /* 50ms */
} }
int int
init_prof_test(void **state) 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 TEST_GROUPS ports.
* Build 0 (local/default): 5230-5233, Full: 5230-5233, Minimal: 5234-5237, etc.
* Build 0 and Full share the same range because build 0 is for local runs or sequential run (no parallel builds),
* while Full/Minimal/NoEncrypt/Default are used in CI where they run in parallel. */
int port_base = 5230 + ((build_idx > 0 ? build_idx - 1 : 0) * TEST_GROUPS);
/* Static resource allocation to avoid conflicts in parallel execution.
* Group 1-4: use static port assignment.
* Group 0 (all groups): use dynamic allocation as fallback. */
gboolean started = FALSE; gboolean started = FALSE;
for (int p = 5230; p < 5250; ++p) {
if (group >= 1 && group <= TEST_GROUPS) { int ret = stbbr_start(STBBR_LOGDEBUG, p, 0);
/* Static allocation: each group gets a dedicated port */ if (ret == 0) {
stub_port = port_base + group - 1; stub_port = p;
printf("[PROF_TEST] Build %d, Group %d: trying port %d\n", build_idx, group, stub_port);
if (stbbr_start(STBBR_LOGDEBUG, stub_port, 0) == 0) {
started = TRUE; started = TRUE;
printf("[PROF_TEST] Started stabber on port %d\n", stub_port); break;
} else {
printf("[PROF_TEST] Failed to start stabber on port %d\n", stub_port);
} }
} }
/* Fallback to dynamic allocation if static failed or group=0 */
if (!started) { if (!started) {
printf("[PROF_TEST] Using dynamic port allocation\n"); assert_true(FALSE); // could not start stabber on any port in range
for (int p = port_base; p < port_base + 20; ++p) {
if (stbbr_start(STBBR_LOGDEBUG, p, 0) == 0) {
stub_port = p;
started = TRUE;
printf("[PROF_TEST] Started stabber on port %d\n", stub_port);
break;
}
}
}
if (!started) {
fprintf(stderr, "[PROF_TEST] ERROR: could not start stabber on any port\n");
return -1; return -1;
} }
/* Generate unique XDG paths based on group for parallel execution. // Generate unique XDG paths based on stub_port for parallel execution
* Use ./test-files/ in current (build) directory for out-of-tree builds compatibility. */
int dir_id = (group >= 1 && group <= TEST_GROUPS) ? group : stub_port;
snprintf(xdg_config_home, sizeof(xdg_config_home), 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), snprintf(xdg_data_home, sizeof(xdg_data_home),
"./test-files/%d/xdg_data_home", dir_id); "./tests/functionaltests/files/%d/xdg_data_home", stub_port);
printf("[PROF_TEST] Group %d using directories: config=%s, data=%s\n",
group, xdg_config_home, xdg_data_home);
// Give stabber server thread time to start listening // Give stabber server thread time to start listening
usleep(100000); // 100ms usleep(100000); // 100ms
@@ -454,16 +401,6 @@ prof_output_regex(const char *pattern)
usleep(50000); /* 50ms */ 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); regfree(&regex);
return 0; return 0;
} }

View File

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