Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,32 @@ jobs:
uses: "actions/checkout@v6"
with:
path: "${{ inputs.plugin-key }}"
- name: "Detect coverage configuration"
id: "coverage-config"
# Use default `bash` shell with `github-actions-runner` user
shell: "bash"
working-directory: "${{ github.workspace }}/${{ inputs.plugin-key }}"
run: |
if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.base_ref }}" != "${{ github.event.repository.default_branch }}" ]]; then
echo "coverage-enabled=false" >> $GITHUB_OUTPUT
echo "ℹ️ Code coverage is disabled for pull requests targeting non-default branch (${{ github.base_ref }})."
exit 0
fi

CONFIG_FILE=".glpi-coverage.json"
if [[ -f "$CONFIG_FILE" ]]; then
ENABLED=$(jq -r '.enabled // true' "$CONFIG_FILE")
if [[ "$ENABLED" != "true" ]]; then
echo "coverage-enabled=false" >> $GITHUB_OUTPUT
echo "ℹ️ Code coverage is disabled via $CONFIG_FILE"
exit 0
fi
echo "coverage-enabled=true" >> $GITHUB_OUTPUT
else
echo "coverage-enabled=false" >> $GITHUB_OUTPUT
echo "ℹ️ No $CONFIG_FILE found, code coverage is disabled."
exit 0
fi
- name: "Execute init script"
if: ${{ inputs.init-script != '' }}
# Use default `bash` shell with `github-actions-runner` user
Expand Down Expand Up @@ -262,18 +288,55 @@ jobs:
shell: "bash"
run: |
sudo service apache2 start
- name: "Setup coverage driver"
if: ${{ !cancelled() && steps.coverage-config.outputs.coverage-enabled == 'true' }}
shell: "bash"
run: |
if ! php -m | grep -q -E 'xdebug|pcov'; then
echo -e "\033[0;33mInstalling PCOV driver...\033[0m"
sudo pecl install pcov || true
fi
Comment on lines +295 to +298
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add pcov in the githubactions-php-apache image (inherited by githubactions-glpi-apache).


- name: "PHPUnit"
if: ${{ !cancelled() && hashFiles(format('{0}/phpunit.xml', inputs.plugin-key)) != '' }}
env:
PCOV_ENABLED: "${{ steps.coverage-config.outputs.coverage-enabled == 'true' && '1' || '0' }}"
XDEBUG_MODE: "${{ steps.coverage-config.outputs.coverage-enabled == 'true' && 'coverage' || 'off' }}"
run: |
echo -e "\033[0;33mExecuting PHPUnit...\033[0m"
PHPUNIT_FLAGS="--colors=always"
PHP_CMD="php"

if [[ "${{ steps.coverage-config.outputs.coverage-enabled }}" == "true" ]]; then
PHPUNIT_FLAGS="$PHPUNIT_FLAGS --coverage-text --coverage-cobertura=cobertura.xml --coverage-clover=clover.xml"
# Explicitly load PCOV if needed
PHP_CMD="php -d extension=pcov.so"
Comment on lines +312 to +313
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the extention should be always loaded, but maybe with a pcov.enabled = 0 ini value by default (in the githubactions-php-apache image).

The only requirement here would the be to enable pcov through the PCOV_ENABLED env variable, see krakjoe/pcov@f7e9f7b

fi

if [[ -f "vendor/bin/phpunit" ]]; then
vendor/bin/phpunit --colors=always
$PHP_CMD vendor/bin/phpunit $PHPUNIT_FLAGS
elif [[ -f "../../vendor/bin/phpunit" ]]; then
../../vendor/bin/phpunit --colors=always
$PHP_CMD ../../vendor/bin/phpunit $PHPUNIT_FLAGS
else
echo -e "\033[0;31mPHPUnit binary not found!\033[0m"
exit 1
fi
- name: "Fix coverage paths for IDE import"
if: ${{ !cancelled() && steps.coverage-config.outputs.coverage-enabled == 'true' }}
run: |
echo "Sanitizing paths in clover.xml..."
sed -i 's|/var/www/glpi/plugins/${{ inputs.plugin-key }}/|plugins/${{ inputs.plugin-key }}/|g' clover.xml
- name: "Upload coverage report"
uses: "actions/upload-artifact@v6"
if: ${{ !cancelled() && steps.coverage-config.outputs.coverage-enabled == 'true' }}
with:
name: "coverage-report"
path: |
/var/www/glpi/plugins/${{ inputs.plugin-key }}/cobertura.xml
/var/www/glpi/plugins/${{ inputs.plugin-key }}/clover.xml
/var/www/glpi/plugins/${{ inputs.plugin-key }}/.glpi-coverage.json
include-hidden-files: true
overwrite: true
- name: "Jest"
if: ${{ !cancelled() && hashFiles(format('{0}/jest.config.js', inputs.plugin-key)) != '' }}
run: |
Expand Down
94 changes: 94 additions & 0 deletions .github/workflows/coverage-refresh.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: "Coverage refresh"

on:
workflow_call:
inputs:
plugin-key:
required: true
type: string
workflow-name:
description: "Name of the CI workflow to trigger for coverage refresh. Must match the 'name' field in the plugin's CI workflow file."
required: false
type: string
default: "Continuous integration"

jobs:
check-and-refresh:
name: "Check and refresh coverage artifact"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
sparse-checkout: ".glpi-coverage.json"

- name: "Check coverage configuration"
id: "coverage-config"
run: |
CONFIG_FILE=".glpi-coverage.json"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "ℹ️ No $CONFIG_FILE found, skipping coverage refresh."
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi

ENABLED=$(jq -r '.enabled // true' "$CONFIG_FILE")
if [[ "$ENABLED" != "true" ]]; then
echo "ℹ️ Code coverage is disabled via $CONFIG_FILE, skipping refresh."
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi

echo "skip=false" >> $GITHUB_OUTPUT

- name: "Check artifact expiry"
if: steps.coverage-config.outputs.skip != 'true'
id: "check-expiry"
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "Checking for existing coverage artifacts..."

# The clearlyip action uses the naming pattern: coverage-{branch_name}
DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
ARTIFACT_NAME="coverage-${DEFAULT_BRANCH}"

# List artifacts matching the coverage pattern
ARTIFACTS=$(gh api \
"/repos/${{ github.repository }}/actions/artifacts?name=${ARTIFACT_NAME}&per_page=1" \
--jq '.artifacts[0]' 2>/dev/null || echo "null")

if [[ "$ARTIFACTS" == "null" || -z "$ARTIFACTS" ]]; then
echo "⚠️ No coverage artifact found. Refresh needed."
echo "needs-refresh=true" >> $GITHUB_OUTPUT
exit 0
fi

EXPIRES_AT=$(echo "$ARTIFACTS" | jq -r '.expires_at // empty')
if [[ -z "$EXPIRES_AT" ]]; then
echo "⚠️ Could not determine artifact expiry. Refresh needed."
echo "needs-refresh=true" >> $GITHUB_OUTPUT
exit 0
fi

EXPIRES_TS=$(date -d "$EXPIRES_AT" +%s)
TOMORROW_TS=$(date -d "+1 day" +%s)

if [[ "$EXPIRES_TS" -le "$TOMORROW_TS" ]]; then
echo "⏰ Coverage artifact expires at $EXPIRES_AT (within 1 day). Refresh needed."
echo "needs-refresh=true" >> $GITHUB_OUTPUT
else
echo "✅ Coverage artifact is valid until $EXPIRES_AT. No refresh needed."
echo "needs-refresh=false" >> $GITHUB_OUTPUT
fi

- name: "Trigger CI workflow"
if: steps.check-expiry.outputs.needs-refresh == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "🔄 Triggering CI workflow on default branch to refresh coverage artifact..."
gh workflow run "${{ inputs.workflow-name }}" \
--repo "${{ github.repository }}" \
--ref "${{ github.event.repository.default_branch }}"
echo "✅ Workflow dispatch triggered."
119 changes: 119 additions & 0 deletions .github/workflows/coverage-report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: "Coverage report"

on:
workflow_call:
inputs:
plugin-key:
required: true
type: string

permissions:
pull-requests: write
actions: read

jobs:
coverage-report:
runs-on: "ubuntu-latest"
name: "Coverage report"
steps:
- name: "Check target branch"
id: "check-branch"
run: |
if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.base_ref }}" != "${{ github.event.repository.default_branch }}" ]]; then
echo "ℹ️ Code coverage is disabled for pull requests targeting non-default branch (${{ github.base_ref }})."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi

- name: "Download coverage report"
if: steps.check-branch.outputs.skip != 'true'
uses: "actions/download-artifact@v7"
with:
name: "coverage-report"

- name: "Read coverage configuration"
id: "coverage-config"
run: |
if [[ "${{ steps.check-branch.outputs.skip }}" == "true" ]]; then
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi

CONFIG_FILE=".glpi-coverage.json"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "⚠️ No $CONFIG_FILE found, skipping coverage report."
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi

ENABLED=$(jq -r '.enabled // true' "$CONFIG_FILE")
if [[ "$ENABLED" != "true" ]]; then
echo "ℹ️ Code coverage is disabled via $CONFIG_FILE"
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi

echo "skip=false" >> $GITHUB_OUTPUT
echo "only-list-changed-files=$(jq -r '.only_list_changed_files // true' "$CONFIG_FILE")" >> $GITHUB_OUTPUT
echo "badge=$(jq -r '.badge // true' "$CONFIG_FILE")" >> $GITHUB_OUTPUT
echo "overall-coverage-fail-threshold=$(jq -r '.overall_coverage_fail_threshold // 0' "$CONFIG_FILE")" >> $GITHUB_OUTPUT
echo "file-coverage-error-min=$(jq -r '.file_coverage_error_min // 50' "$CONFIG_FILE")" >> $GITHUB_OUTPUT
echo "file-coverage-warning-max=$(jq -r '.file_coverage_warning_max // 75' "$CONFIG_FILE")" >> $GITHUB_OUTPUT
echo "fail-on-negative-difference=$(jq -r '.fail_on_negative_difference // false' "$CONFIG_FILE")" >> $GITHUB_OUTPUT
echo "retention-days=$(jq -r '.retention_days // 90' "$CONFIG_FILE")" >> $GITHUB_OUTPUT

- name: "Generate coverage report"
if: steps.coverage-config.outputs.skip != 'true'
uses: "clearlyip/code-coverage-report-action@v6"
id: "coverage-report"
with:
filename: "cobertura.xml"
only_list_changed_files: ${{ steps.coverage-config.outputs.only-list-changed-files }}
badge: ${{ steps.coverage-config.outputs.badge }}
overall_coverage_fail_threshold: ${{ steps.coverage-config.outputs.overall-coverage-fail-threshold }}
file_coverage_error_min: ${{ steps.coverage-config.outputs.file-coverage-error-min }}
file_coverage_warning_max: ${{ steps.coverage-config.outputs.file-coverage-warning-max }}
fail_on_negative_difference: ${{ steps.coverage-config.outputs.fail-on-negative-difference }}
retention_days: ${{ steps.coverage-config.outputs.retention-days }}
artifact_download_workflow_names: "Continuous integration"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it should not be hardcoded.


- name: "Generating Markdown report"
if: github.event_name == 'pull_request' && steps.coverage-config.outputs.skip != 'true' && steps.coverage-report.outputs.file != ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if: github.event_name == 'pull_request' could maybe added directly at the job level.

run: |
COVERAGE="${{ steps.coverage-report.outputs.coverage }}"
REPORT_FILE="code-coverage-results.md"
ARTIFACT_LINK="📥 [Download coverage-report artifact](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) _(contains \`clover.xml\` for IDE import + config file)_"

# Split: keep header/badge visible, collapse the table inside <details>
FIRST_TABLE_LINE=$(grep -n "^|" "$REPORT_FILE" | head -1 | cut -d: -f1)

if [[ -z "$FIRST_TABLE_LINE" ]]; then
{
cat "$REPORT_FILE"
echo ""
echo "$ARTIFACT_LINK"
} > "${REPORT_FILE}.tmp"
else
{
head -n "$((FIRST_TABLE_LINE - 1))" "$REPORT_FILE"
echo ""
echo "<details>"
echo "<summary>📋 Details</summary>"
echo ""
tail -n "+${FIRST_TABLE_LINE}" "$REPORT_FILE"
echo ""
echo "$ARTIFACT_LINK"
echo ""
echo "</details>"
} > "${REPORT_FILE}.tmp"
fi

mv "${REPORT_FILE}.tmp" "$REPORT_FILE"

- name: "Add coverage PR comment"
if: github.event_name == 'pull_request' && steps.coverage-config.outputs.skip != 'true' && steps.coverage-report.outputs.file != ''
uses: "marocchino/sticky-pull-request-comment@v2"
with:
header: coverage
path: code-coverage-results.md
Loading