ci: refactor ci-build.sh with constants and helper functions

- Add CMOCKA_PASSED_PATTERN, CMOCKA_FAILED_PATTERN constants
- Add COVERAGE_PATTERNS constant for lcov extraction
- Add parse_build_stats() function for parsing build logs
- Add extract_test_count() helper function
- Organize code into sections: Constants, Helper Functions, Test Verification
This commit is contained in:
2026-01-29 17:41:58 +03:00
parent 5100efca3b
commit cf86e60af6
2 changed files with 68 additions and 30 deletions

View File

@@ -91,12 +91,15 @@ 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, each with Valgrind and functional tests. 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). Use `./ci-build.sh --coverage-only` to run only build 1 with coverage collection.
Output shows test results per build: 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 1 PASSED
✓ Build 2 passed (unit: 398/0, func: 69/0, 5m00s) 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

View File

@@ -23,6 +23,55 @@ error_handler()
trap error_handler ERR trap error_handler ERR
# =============================================================================
# Constants
# =============================================================================
# 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>
# Sets: STAT_UNIT_P, STAT_UNIT_F, STAT_FUNC_P, STAT_FUNC_F,
# STAT_COV_LINES, STAT_COV_FUNCS, STAT_COV_BRANCHES, STAT_TIME
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
@@ -246,10 +295,9 @@ build_and_test() {
fi fi
fi fi
# Extract unit test counts from cmocka output # 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_passed=$(extract_test_count unit-tests-output.log "$CMOCKA_PASSED_PATTERN")
unit_failed=$(grep -E "^\[ FAILED \] [0-9]+ test" unit-tests-output.log | grep -oE "[0-9]+" | head -1) unit_failed=$(extract_test_count unit-tests-output.log "$CMOCKA_FAILED_PATTERN")
[ -z "$unit_passed" ] && unit_passed=0 : "${unit_passed:=0}" "${unit_failed:=0}"
[ -z "$unit_failed" ] && unit_failed=0
echo "UNIT_TESTS: passed=$unit_passed failed=$unit_failed" echo "UNIT_TESTS: passed=$unit_passed failed=$unit_failed"
# Set build index for port allocation: build 1 uses ports 5230-5233, # Set build index for port allocation: build 1 uses ports 5230-5233,
@@ -264,9 +312,9 @@ build_and_test() {
echo "=== Functional test results ===" echo "=== Functional test results ==="
for glog in ./test-logs/group*.log; do for glog in ./test-logs/group*.log; do
if [ -f "$glog" ]; then if [ -f "$glog" ]; then
cnt=$(grep -E "^\[ PASSED \] [0-9]+ test" "$glog" | grep -oE "[0-9]+" | head -1) cnt=$(extract_test_count "$glog" "$CMOCKA_PASSED_PATTERN")
[ -n "$cnt" ] && func_passed=$((func_passed + cnt)) [ -n "$cnt" ] && func_passed=$((func_passed + cnt))
cnt=$(grep -E "^\[ FAILED \] [0-9]+ test" "$glog" | grep -oE "[0-9]+" | head -1) cnt=$(extract_test_count "$glog" "$CMOCKA_FAILED_PATTERN")
[ -n "$cnt" ] && func_failed=$((func_failed + cnt)) [ -n "$cnt" ] && func_failed=$((func_failed + cnt))
fi fi
done done
@@ -281,8 +329,8 @@ build_and_test() {
lcov --capture --directory . --output-file coverage-full.info \ lcov --capture --directory . --output-file coverage-full.info \
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true --rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true
# Extract only production code from src/ directory, exclude tests # Extract only production code from src/ directory, exclude tests
# Pattern matches both Docker (/src/src/*) and CI (/usr/src/profanity/src/*) # shellcheck disable=SC2086
lcov --extract coverage-full.info '*/profanity/src/*' '*/src/src/*' \ lcov --extract coverage-full.info $COVERAGE_PATTERNS \
--output-file coverage.info \ --output-file coverage.info \
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true --rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true
if [ -f coverage.info ] && [ -s coverage.info ]; then if [ -f coverage.info ] && [ -s coverage.info ]; then
@@ -374,28 +422,15 @@ 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 if [ -f "build-$idx.log" ]; then
stats_line=$(grep "^STATS:" "build-$idx.log" | tail -1) parse_build_stats "build-$idx.log"
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)
unit_p=${unit_p:-0}
unit_f=${unit_f:-0}
func_p=${func_p:-0}
func_f=${func_f:-0}
echo "✓ BUILD $idx PASSED" echo "✓ BUILD $idx PASSED"
echo " Unit tests: $unit_p passed, $unit_f failed" echo " Unit tests: $STAT_UNIT_P passed, $STAT_UNIT_F failed"
echo " Functional tests: $func_p passed, $func_f failed" echo " Functional tests: $STAT_FUNC_P passed, $STAT_FUNC_F failed"
if [ "$cov_lines" != "n/a" ] && [ -n "$cov_lines" ]; then if [ "$STAT_COV_LINES" != "n/a" ] && [ -n "$STAT_COV_LINES" ]; then
echo " Coverage: Lines: $cov_lines | Functions: $cov_funcs | Branches: $cov_branches" echo " Coverage: Lines: $STAT_COV_LINES | Functions: $STAT_COV_FUNCS | Branches: $STAT_COV_BRANCHES"
fi fi
echo " Duration: ${time:-?}" echo " Duration: ${STAT_TIME:-?}"
else else
echo "✓ Build $idx passed (no stats available)" echo "✓ Build $idx passed (no stats available)"
fi fi