Skip to content

Commit 3605d24

Browse files
authored
Merge pull request #713 from ProgressPlanner/ari/fix-coverage-workflow
Fix some issues in the test-coverage workflow
2 parents 0919e59 + 76a2a3a commit 3605d24

1 file changed

Lines changed: 72 additions & 19 deletions

File tree

.github/workflows/code-coverage.yml

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ name: Code Coverage
22

33
on:
44
pull_request:
5-
branches:
6-
- develop
7-
- main
85
push:
96
branches:
107
- develop
@@ -46,8 +43,10 @@ jobs:
4643
coverage: xdebug # Use Xdebug for coverage (PCOV was causing PHP to crash)
4744
tools: composer
4845

49-
- name: Install SVN
50-
run: sudo apt-get install subversion
46+
- name: Install SVN and XML tools
47+
run: |
48+
sudo apt-get update
49+
sudo apt-get install -y subversion libxml2-utils
5150
5251
- name: Install Composer dependencies
5352
uses: ramsey/composer-install@v2
@@ -74,13 +73,23 @@ jobs:
7473
# Run PHPUnit with coverage - allow test failures but ensure coverage is generated
7574
# test-class-security.php is excluded via phpunit.xml.dist to avoid output contamination
7675
set +e
76+
# Run PHPUnit and capture both test output and coverage text separately
7777
php -d memory_limit=512M -d max_execution_time=300 \
7878
vendor/bin/phpunit --configuration phpunit.xml.dist \
7979
--coverage-clover=coverage.xml \
80-
--coverage-text 2>&1 | tee phpunit-output.log
80+
--coverage-text --colors=never > phpunit-with-coverage.log 2>&1
8181
PHPUNIT_EXIT=$?
8282
set -e
8383
84+
# Extract test output (everything before coverage section) for debugging
85+
# Coverage section typically starts with a line like "Code Coverage Report:" or summary table
86+
# Extract everything up to (but not including) the coverage section
87+
awk '/Code Coverage Report:|^Summary|^ Classes:|^ Methods:|^ Lines:/{exit} {print}' phpunit-with-coverage.log > phpunit-output.log || cat phpunit-with-coverage.log > phpunit-output.log
88+
89+
# Extract coverage text output (the coverage section)
90+
# Coverage section starts with summary or "Code Coverage Report"
91+
awk '/Code Coverage Report:|^Summary|^ Classes:|^ Methods:|^ Lines:/{flag=1} flag' phpunit-with-coverage.log > current-coverage-full.txt || tail -200 phpunit-with-coverage.log > current-coverage-full.txt
92+
8493
echo "End time: $(date)"
8594
echo "Memory after: $(free -h | grep Mem)"
8695
echo "=== Debug: PHPUnit exit code: $PHPUNIT_EXIT ==="
@@ -117,14 +126,36 @@ jobs:
117126
- name: Generate coverage report summary
118127
id: coverage
119128
run: |
120-
# Generate coverage text report and save it
121-
vendor/bin/phpunit --coverage-text --colors=never > current-coverage-full.txt 2>&1 || true
129+
# Extract overall coverage from coverage.xml (Clover format)
130+
# This avoids running PHPUnit twice - we already have coverage.xml from the first run
131+
if [ -f coverage.xml ]; then
132+
# Extract metrics from Clover XML using xmllint
133+
# Fallback to Python if xmllint fails
134+
STATEMENTS=$(xmllint --xpath 'string(//project/metrics/@statements)' coverage.xml 2>/dev/null || python3 -c "import xml.etree.ElementTree as ET; tree = ET.parse('coverage.xml'); metrics = tree.find('.//project/metrics'); print(metrics.get('statements', '0') if metrics is not None else '0')" 2>/dev/null || echo "0")
135+
COVERED_STATEMENTS=$(xmllint --xpath 'string(//project/metrics/@coveredstatements)' coverage.xml 2>/dev/null || python3 -c "import xml.etree.ElementTree as ET; tree = ET.parse('coverage.xml'); metrics = tree.find('.//project/metrics'); print(metrics.get('coveredstatements', '0') if metrics is not None else '0')" 2>/dev/null || echo "0")
136+
137+
# Calculate coverage percentage
138+
if [ "$STATEMENTS" != "0" ] && [ -n "$STATEMENTS" ] && [ -n "$COVERED_STATEMENTS" ]; then
139+
COVERAGE=$(echo "scale=2; ($COVERED_STATEMENTS * 100) / $STATEMENTS" | bc)
140+
else
141+
COVERAGE="0"
142+
fi
143+
144+
echo "current_coverage=$COVERAGE" >> $GITHUB_OUTPUT
145+
echo "Current code coverage: $COVERAGE% (from coverage.xml)"
146+
echo "Statements: $COVERED_STATEMENTS / $STATEMENTS"
147+
else
148+
echo "ERROR: coverage.xml not found!"
149+
echo "current_coverage=0" >> $GITHUB_OUTPUT
150+
exit 1
151+
fi
122152
123-
# Extract overall coverage from the text report (the summary line, not per-class lines)
124-
# Look for the line that starts with " Lines:" (two spaces at the start)
125-
COVERAGE=$(grep "^ Lines:" current-coverage-full.txt | tail -1 | awk '{print $2}' | sed 's/%//' || echo "0")
126-
echo "current_coverage=$COVERAGE" >> $GITHUB_OUTPUT
127-
echo "Current code coverage: $COVERAGE%"
153+
# Coverage text output was already extracted from phpunit-with-coverage.log in the previous step
154+
# If extraction failed, try to generate it again as fallback
155+
if [ ! -s current-coverage-full.txt ]; then
156+
echo "Warning: Could not extract coverage text from phpunit-with-coverage.log, generating separately..."
157+
vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-text --colors=never > current-coverage-full.txt 2>&1 || true
158+
fi
128159
129160
# Save detailed per-file coverage for later comparison
130161
# PHPUnit outputs class name on one line, stats on the next line
@@ -162,12 +193,34 @@ jobs:
162193
if: github.event_name == 'pull_request'
163194
id: base_coverage
164195
run: |
165-
# Generate coverage for base branch
166-
vendor/bin/phpunit --coverage-text --colors=never > base-coverage-full.txt 2>&1 || true
167-
# Extract overall coverage (summary line starting with " Lines:")
168-
BASE_COVERAGE=$(grep "^ Lines:" base-coverage-full.txt | tail -1 | awk '{print $2}' | sed 's/%//' || echo "0")
169-
echo "base_coverage=$BASE_COVERAGE" >> $GITHUB_OUTPUT
170-
echo "Base branch code coverage: $BASE_COVERAGE%"
196+
# Generate coverage for base branch (including coverage.xml)
197+
vendor/bin/phpunit --configuration phpunit.xml.dist \
198+
--coverage-clover=base-coverage.xml \
199+
--coverage-text --colors=never > base-coverage-full.txt 2>&1 || true
200+
201+
# Extract overall coverage from base-coverage.xml (Clover format)
202+
if [ -f base-coverage.xml ]; then
203+
# Extract metrics from Clover XML using xmllint
204+
# Fallback to Python if xmllint fails
205+
STATEMENTS=$(xmllint --xpath 'string(//project/metrics/@statements)' base-coverage.xml 2>/dev/null || python3 -c "import xml.etree.ElementTree as ET; tree = ET.parse('base-coverage.xml'); metrics = tree.find('.//project/metrics'); print(metrics.get('statements', '0') if metrics is not None else '0')" 2>/dev/null || echo "0")
206+
COVERED_STATEMENTS=$(xmllint --xpath 'string(//project/metrics/@coveredstatements)' base-coverage.xml 2>/dev/null || python3 -c "import xml.etree.ElementTree as ET; tree = ET.parse('base-coverage.xml'); metrics = tree.find('.//project/metrics'); print(metrics.get('coveredstatements', '0') if metrics is not None else '0')" 2>/dev/null || echo "0")
207+
208+
# Calculate coverage percentage
209+
if [ "$STATEMENTS" != "0" ] && [ -n "$STATEMENTS" ] && [ -n "$COVERED_STATEMENTS" ]; then
210+
BASE_COVERAGE=$(echo "scale=2; ($COVERED_STATEMENTS * 100) / $STATEMENTS" | bc)
211+
else
212+
BASE_COVERAGE="0"
213+
fi
214+
215+
echo "base_coverage=$BASE_COVERAGE" >> $GITHUB_OUTPUT
216+
echo "Base branch code coverage: $BASE_COVERAGE% (from base-coverage.xml)"
217+
echo "Statements: $COVERED_STATEMENTS / $STATEMENTS"
218+
else
219+
# Fallback to text extraction if XML not available
220+
BASE_COVERAGE=$(grep "^ Lines:" base-coverage-full.txt | tail -1 | awk '{print $2}' | sed 's/%//' || echo "0")
221+
echo "base_coverage=$BASE_COVERAGE" >> $GITHUB_OUTPUT
222+
echo "Base branch code coverage: $BASE_COVERAGE% (from text fallback)"
223+
fi
171224
172225
# Extract per-file coverage for comparison
173226
# PHPUnit outputs class name on one line, stats on the next line

0 commit comments

Comments
 (0)