-
Notifications
You must be signed in to change notification settings - Fork 9.7k
175 lines (154 loc) · 6.36 KB
/
Copy pathtest-coverage-advisor.yml
File metadata and controls
175 lines (154 loc) · 6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# Advisory-only check: flags PRs that change source code (.py / .ts / .tsx)
# without touching any test file. It NEVER fails and is NOT a required check,
# so it cannot block other jobs or the merge — it only posts a sticky PR
# comment + job summary so the author knows tests appear to be missing.
name: Test Coverage Advisor
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
# Dedicated concurrency group so this never cancels or queues behind CI.
concurrency:
group: test-coverage-advisor-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
advise:
name: Test Coverage Advisor
# Skip drafts to avoid noise; they re-run on "ready_for_review".
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
# Full history so merge-base against the PR target works.
fetch-depth: 0
- name: Detect missing tests
id: detect
shell: bash
run: |
set -euo pipefail
BASE_REF="${{ github.event.pull_request.base.ref }}"
git fetch --no-tags --depth=50 origin "$BASE_REF" || true
MERGE_BASE=$(git merge-base HEAD "origin/$BASE_REF" || git rev-parse HEAD~1)
echo "BASE_REF=$BASE_REF"
echo "MERGE_BASE=$MERGE_BASE"
CHANGED=$(git diff --name-only --diff-filter=ACMR "$MERGE_BASE"...HEAD || true)
echo "=== Changed files ==="
echo "$CHANGED"
# --- Backend Python ---------------------------------------------
# Source: .py under src/backend or src/lfx, excluding tests,
# migrations and dunder/init plumbing.
PY_SRC=$(echo "$CHANGED" \
| grep -E '^(src/backend|src/lfx)/.*\.py$' \
| grep -Ev '(^|/)tests?/' \
| grep -Ev '(^|/)(test_[^/]+|conftest)\.py$' \
| grep -Ev '/alembic/versions/' \
| grep -Ev '/__init__\.py$' \
|| true)
PY_TEST=$(echo "$CHANGED" \
| grep -E '\.py$' \
| grep -E '((^|/)tests?/|(^|/)test_[^/]+\.py$|(^|/)conftest\.py$)' \
|| true)
# --- Frontend TS/TSX --------------------------------------------
# Source: .ts/.tsx under src/frontend/src, excluding *.test.* /
# *.spec.* / __tests__ and type declaration files.
FE_SRC=$(echo "$CHANGED" \
| grep -E '^src/frontend/src/.*\.(ts|tsx)$' \
| grep -Ev '\.(test|spec)\.(ts|tsx)$' \
| grep -Ev '(^|/)__tests__/' \
| grep -Ev '\.d\.ts$' \
|| true)
FE_TEST=$(echo "$CHANGED" \
| grep -E '\.(ts|tsx)$' \
| grep -E '(^src/frontend/tests/|\.(test|spec)\.(ts|tsx)$|(^|/)__tests__/)' \
|| true)
need_be=false
need_fe=false
[ -n "$PY_SRC" ] && [ -z "$PY_TEST" ] && need_be=true
[ -n "$FE_SRC" ] && [ -z "$FE_TEST" ] && need_fe=true
{
echo "need_be=$need_be"
echo "need_fe=$need_fe"
} >> "$GITHUB_OUTPUT"
# Multiline outputs for the comment body.
{
echo "py_src<<EOF"
echo "$PY_SRC"
echo "EOF"
echo "fe_src<<EOF"
echo "$FE_SRC"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Build advisory message
id: msg
shell: bash
run: |
set -euo pipefail
NEED_BE="${{ steps.detect.outputs.need_be }}"
NEED_FE="${{ steps.detect.outputs.need_fe }}"
BODY_FILE="$(mktemp)"
echo '<!-- test-coverage-advisor -->' > "$BODY_FILE"
if [ "$NEED_BE" != "true" ] && [ "$NEED_FE" != "true" ]; then
{
echo '### ✅ Test Coverage Advisor'
echo
echo 'No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉'
echo
echo '> _Advisory check only — never blocks merge._'
} >> "$BODY_FILE"
else
{
echo '### ⚠️ Test Coverage Advisor — tests appear to be missing'
echo
echo 'This PR changes source code but does **not** add or modify any test files.'
echo 'This is an **advisory** check: it will **not** block this PR or any other job —'
echo 'please add tests if the change is testable.'
echo
} >> "$BODY_FILE"
if [ "$NEED_BE" = "true" ]; then
{
echo '#### Backend (Python) — no `test_*.py` / `tests/` change found'
echo '```'
echo '${{ steps.detect.outputs.py_src }}'
echo '```'
} >> "$BODY_FILE"
fi
if [ "$NEED_FE" = "true" ]; then
{
echo '#### Frontend (TS/TSX) — no `*.test.*` / `*.spec.*` / `tests/` change found'
echo '```'
echo '${{ steps.detect.outputs.fe_src }}'
echo '```'
} >> "$BODY_FILE"
fi
{
echo
echo '> _Advisory check only — it always passes and is never a required status._'
} >> "$BODY_FILE"
fi
# Mirror into the run's Job Summary (works even on fork PRs).
cat "$BODY_FILE" >> "$GITHUB_STEP_SUMMARY"
echo "body_file=$BODY_FILE" >> "$GITHUB_OUTPUT"
- name: Find existing advisory comment
id: find
uses: peter-evans/find-comment@v3
continue-on-error: true
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: github-actions[bot]
body-includes: "<!-- test-coverage-advisor -->"
- name: Upsert advisory comment
uses: peter-evans/create-or-update-comment@v4
# Fork PRs get a read-only token — don't fail the run if so.
continue-on-error: true
with:
comment-id: ${{ steps.find.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: ${{ steps.msg.outputs.body_file }}
edit-mode: replace
- name: Always succeed
# Explicit: this job never blocks anything.
run: 'echo "Advisory check complete — this status is informational only."'