diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 1969250..0000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,5 +0,0 @@ -plugins: - markdownlint: - enabled: true - shellcheck: - enabled: true diff --git a/.github/scripts/validate-cobertura-xml.sh b/.github/scripts/validate-cobertura-xml.sh new file mode 100644 index 0000000..5324075 --- /dev/null +++ b/.github/scripts/validate-cobertura-xml.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# Validate an untrusted Cobertura XML coverage report before passing it to QLTY. +# + +set -euo pipefail + +cov_path="${1:?usage: validate-cobertura-xml.sh }" +size_limit_bytes=$((10 * 1024 * 1024)) + +test -s "${cov_path}" + +if [ "$(wc -c < "${cov_path}")" -gt "${size_limit_bytes}" ]; then + echo "${cov_path} exceeds the 10 MiB size limit" >&2 + exit 1 +fi + +if grep -Eiq '&2 + exit 1 +fi + +root_name=$(xmllint --nonet --xpath 'local-name(/*)' "${cov_path}") +if [ "${root_name}" != "coverage" ]; then + echo "${cov_path} is not a Cobertura coverage report: expected root element coverage, got ${root_name}" >&2 + exit 1 +fi + +line_rate=$(xmllint --nonet --xpath 'string(/*/@line-rate)' "${cov_path}") +branch_rate=$(xmllint --nonet --xpath 'string(/*/@branch-rate)' "${cov_path}") +lines_covered=$(xmllint --nonet --xpath 'string(/*/@lines-covered)' "${cov_path}") +lines_valid=$(xmllint --nonet --xpath 'string(/*/@lines-valid)' "${cov_path}") + +for attr_name in line_rate branch_rate lines_covered lines_valid; do + if [ -z "${!attr_name}" ]; then + echo "${cov_path} is missing required coverage XML attribute ${attr_name//_/-}" >&2 + exit 1 + fi +done + +python3 - "${line_rate}" "${branch_rate}" "${lines_covered}" "${lines_valid}" <<'PY' +import sys + +EXPECTED_ARG_COUNT = 5 +if len(sys.argv) != EXPECTED_ARG_COUNT: + raise SystemExit(f"expected 4 coverage attributes, got {len(sys.argv) - 1}") + +for attr_name, value in zip(("line-rate", "branch-rate"), sys.argv[1:3]): + try: + rate = float(value) + except ValueError as exc: + raise SystemExit(f"coverage XML attribute {attr_name} must be numeric, got {value!r}") from exc + if not 0.0 <= rate <= 1.0: + raise SystemExit(f"coverage XML attribute {attr_name} must be between 0.0 and 1.0, got {rate}") + +for attr_name, value in zip(("lines-covered", "lines-valid"), sys.argv[3:5]): + try: + count = int(value) + except ValueError as exc: + raise SystemExit(f"coverage XML attribute {attr_name} must be an integer, got {value!r}") from exc + if count < 0: + raise SystemExit(f"coverage XML attribute {attr_name} must be zero or greater, got {count}") +PY diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0c7b2ef..aa8b25f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,9 @@ concurrency: permissions: contents: read +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: lint: runs-on: ubuntu-22.04 @@ -55,7 +58,7 @@ jobs: with: egress-policy: audit - - name: Checkout + - name: Checkout Repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 @@ -72,7 +75,49 @@ jobs: run: | ./test/run_cov.sh + - name: Install XML validation deps + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends \ + libxml2-utils + + - name: Validate coverage artifact + shell: bash + run: | + bash .github/scripts/validate-cobertura-xml.sh test/cov/all/kcov-merged/cov.xml + + - name: Upload coverage HTML report + id: upload_coverage_html + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-html + path: ${{github.workspace}}/test/cov/all/kcov-merged + if-no-files-found: error + retention-days: 7 + + - name: Enforce minimum coverage + env: + MIN_LINE_COVERAGE: "75" + COVERAGE_HTML_ARTIFACT_URL: ${{ steps.upload_coverage_html.outputs.artifact-url }} + run: | + echo "Checking if test coverage is above $MIN_LINE_COVERAGE%" + rate=$(sed -n 's/.*line-rate="\([0-9.]*\)".*/\1/p' test/cov/all/kcov-merged/cov.xml | head -n1) + percent=$(awk "BEGIN { printf(\"%.0f\", $rate * 100) }") + echo "Line-rate: $rate ($percent%)" + if [ "$percent" -lt "$MIN_LINE_COVERAGE" ]; then + echo "Coverage below $MIN_LINE_COVERAGE%! Failing." + echo "Check Coverage HTML artifact: $COVERAGE_HTML_ARTIFACT_URL" + exit 1 + fi + echo "Coverage OK." + - name: Publish coverage report to QLTY + if: >- + (success() || failure()) && + github.repository == 'Open-CMSIS-Pack/gen-pack' && + github.workflow != 'Release' && + github.event.pull_request.user.login != 'dependabot[bot]' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) uses: qltysh/qlty-action/coverage@a19242102d17e497f437d7466aa01b528537e899 # v2.2.0 with: oidc: true