---
name: Tests & Coverage
"on":
pull_request:
workflow_call:
secrets:
BITSIGHT_API_KEY:
CODECOV_TOKEN:
workflow_dispatch:
permissions:
contents: read
jobs:
tests:
name: Tests & Coverage
runs-on: ubuntu-latest
permissions:
id-token: write # for step-security/harden-runner to fetch OIDC token
outputs:
status: ${{ steps.pytest.outputs.status }}
snippet: ${{ steps.pytest.outputs.snippet }}
mode: ${{ steps.pytest.outputs.mode }}
coverage: ${{ steps.coverage.outputs.line_rate }}
low_files: ${{ steps.coverage.outputs.low_files }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Prepare Python environment
uses: ./.github/actions/uv-env
with:
uv-sync-flags: "--all-extras --frozen"
cache-key-prefix: quality
- name: Audit dependencies
run: |
uv tool run pip-audit --strict
- name: Run test suite
id: pytest
shell: bash
env:
BITSIGHT_API_KEY: ${{ secrets.BITSIGHT_API_KEY }}
run: |
set -o pipefail
status=0
if [ -n "${BITSIGHT_API_KEY}" ]; then
MODE="online+offline"
CMD="uv run pytest \
--cov=src/birre \
--cov-report=term \
--cov-report=xml \
--cov-fail-under=70"
else
MODE="offline"
CMD="uv run pytest \
--offline \
--cov=src/birre \
--cov-report=term \
--cov-report=xml \
--cov-fail-under=70"
fi
{
echo "Running tests in $MODE mode"
} > pytest-output.txt
if ! eval "$CMD" >> pytest-output.txt 2>&1; then
status=$?
fi
tail -n 200 pytest-output.txt > pytest-snippet.txt || true
printf "snippet<<EOF\n%s\nEOF\n" "$(cat pytest-snippet.txt)" >> "$GITHUB_OUTPUT"
{
echo "status=$status"
echo "mode=$MODE"
} >> "$GITHUB_OUTPUT"
exit $status
- name: Test config file discovery
run: |
uv run python -c "
from birre.config import load_settings
from pathlib import Path
config_path = Path.cwd() / 'config.toml'
print(f'Config path: {config_path}')
config = load_settings()
print('Config loaded successfully')
"
- name: Test CLI entrypoint
run: |
uv run birre --help
- name: Extract coverage summary
id: coverage
if: always()
shell: bash
run: |
if [ ! -f coverage.xml ]; then
{
echo "line_rate="
echo "low_files="
} >> "$GITHUB_OUTPUT"
exit 0
fi
python - <<'PY'
import os
import xml.etree.ElementTree as ET
tree = ET.parse('coverage.xml')
root = tree.getroot()
line_rate = float(root.get('line-rate', 0.0)) * 100
files = []
for cls in root.findall('.//class'):
filename = cls.get('filename')
if not filename:
continue
rate = float(cls.get('line-rate', 0.0)) * 100
files.append((rate, filename))
files.sort()
low_lines = '\n'.join(f"- {fname}: {rate:.1f}%" for rate, fname in files[:3])
out = os.environ['GITHUB_OUTPUT']
with open(out, 'a', encoding='utf-8') as fh:
fh.write(f"line_rate={line_rate:.2f}\n")
fh.write("low_files<<EOF\n")
if low_lines:
fh.write(low_lines + "\n")
fh.write("EOF\n")
PY
- name: Upload coverage report
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
if: success()
with:
files: ./coverage.xml
flags: pr-validation
token: ${{ secrets.CODECOV_TOKEN }}
continue-on-error: true
summary:
name: Test Summary
if: ${{ always() && github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
needs: tests
permissions:
pull-requests: write # Allow posting summaries as PR comments
id-token: write # for step-security/harden-runner to fetch OIDC token
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3
with:
egress-policy: audit
- name: Find existing summary comment
id: find-comment
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: "## Tests & Coverage Results"
- name: Prepare body
id: body
shell: bash
env:
TEST_RESULT: ${{ needs.tests.result }}
TEST_SNIPPET: ${{ needs.tests.outputs.snippet }}
COVERAGE: ${{ needs.tests.outputs.coverage }}
LOW_FILES: ${{ needs.tests.outputs.low_files }}
run: |
comment_file=$(mktemp)
if [ "$TEST_RESULT" != 'success' ]; then
{
echo '## Tests & Coverage Results'
echo
echo "Test status: $TEST_RESULT"
if [ -n "$TEST_SNIPPET" ]; then
echo
echo '```'
printf '%s\n' "$TEST_SNIPPET"
echo '```'
else
echo 'See workflow logs for details.'
fi
if [ -n "$COVERAGE" ]; then
echo
echo "Coverage: ${COVERAGE}%"
fi
if [ -n "$LOW_FILES" ]; then
echo
echo 'Lowest coverage files:'
printf '%s\n' "$LOW_FILES"
fi
} > "$comment_file"
fi
if [ -s "$comment_file" ]; then
printf 'body<<EOF\n%s\nEOF\n' "$(cat "$comment_file")" >> "$GITHUB_OUTPUT"
fi
rm -f "$comment_file"
- name: Create or update summary comment
if: steps.body.outputs.body != ''
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
body: ${{ steps.body.outputs.body }}
edit-mode: replace
- name: Delete summary comment when clean
if: ${{ steps.find-comment.outputs.comment-id != '' && needs.tests.result == 'success' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: ${{ steps.find-comment.outputs.comment-id }}
})
- name: Emit job summary
shell: bash
env:
TEST_RESULT: ${{ needs.tests.result }}
COVERAGE: ${{ needs.tests.outputs.coverage }}
run: |
{
echo "## Tests & Coverage Results"
echo "Test status: $TEST_RESULT"
if [ -n "$COVERAGE" ]; then
echo "Coverage: ${COVERAGE}%"
fi
} >> "$GITHUB_STEP_SUMMARY"