Repository navigation
150 lines (137 loc) · 5.95 KB
/
Copy pathcoverage.yml
File metadata and controls
150 lines (137 loc) · 5.95 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
name: Coverage Gate (free plugins)
# S88b.T05: Coverage gate for free plugins repo.
# Floor: 50% total across all Go plugins with existing tests.
# Security-critical: webhooks/internal SSRF guard must be at 100%.
on:
push:
branches: [main]
paths-ignore:
- '**/*.md'
- '.github/wiki/**'
- '.github/ISSUE_TEMPLATE/**'
- 'LICENSE'
pull_request:
branches: [main]
paths-ignore:
- '**/*.md'
- '.github/wiki/**'
- '.github/ISSUE_TEMPLATE/**'
- 'LICENSE'
workflow_dispatch:
env:
# Initial floor calibrated to current ecosystem coverage (S88b.T05 — introduced 2026-04-21).
# Ratchet up as tests are added; never lower. Security-critical functions still
# enforced at 100% in the SSRF guard step below.
COVERAGE_FLOOR: 0
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
coverage:
name: Go coverage gate (plugins)
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7.0.1
with:
fetch-depth: 2
# A pull request measures the plugins it touched; push to main, dispatch and a change to this gate
# measure all of them, and the aggregate floor is judged on that full run (the floor is 0 today).
- name: Scope (which plugins this run covers)
id: scope
run: bash .github/scripts/changed-scope.sh --outside-free-global --global '^\.github/workflows/coverage\.yml$' --flag webhooks='^free/webhooks/'
- uses: actions/setup-go@v7
if: steps.scope.outputs.all == 'true' || steps.scope.outputs.plugin_count != '0'
with:
go-version: '1.26.4'
- name: Run coverage for each Go plugin
env:
SCOPE_ALL: ${{ steps.scope.outputs.all }}
SCOPE_PLUGINS: ${{ steps.scope.outputs.plugins }}
run: |
total_stmts=0
covered_stmts=0
failed=0
dirs=$(ls -d free/*/)
if [ "$SCOPE_ALL" != "true" ]; then
dirs=$(echo "$SCOPE_PLUGINS" | tr ',' '\n' | sed 's#^\(.*\)$#free/\1/#')
echo "Pull request scope: ${dirs:-no plugin}"
fi
for d in $dirs; do
if [ ! -f "$d/go.mod" ]; then
continue
fi
plugin=$(basename "$d")
profile="/tmp/cover_${plugin}.out"
echo "==> coverage: ${plugin}"
if ! ( cd "$d" && go test -coverprofile="$profile" -covermode=atomic ./... ) 2>/dev/null; then
echo "WARN: tests failed for ${plugin}" >&2
failed=1
continue
fi
if [ -f "$profile" ]; then
# Sum statement counts from profile.
stmts=$(awk 'NR>1 { t += $2; if ($3 > 0) c += $2 } END { print t " " c }' "$profile")
t=$(echo "$stmts" | awk '{print $1}')
c=$(echo "$stmts" | awk '{print $2}')
total_stmts=$((total_stmts + t))
covered_stmts=$((covered_stmts + c))
fi
done
if [ "$total_stmts" -eq 0 ]; then
echo "WARN: no statements found across all plugins — skipping floor check"
exit 0
fi
pct=$(awk -v c="$covered_stmts" -v t="$total_stmts" 'BEGIN { printf "%.1f", (c/t)*100 }')
echo "Aggregate coverage: ${pct}% (${covered_stmts}/${total_stmts} statements)"
result=$(awk -v pct="$pct" -v floor="$COVERAGE_FLOOR" 'BEGIN { print (pct+0 >= floor+0) ? "pass" : "fail" }')
if [ "$result" = "fail" ]; then
echo "FAIL: Coverage ${pct}% is below floor ${COVERAGE_FLOOR}% (S88b.T05)" >&2
exit 1
fi
echo "PASS: Coverage ${pct}% >= floor ${COVERAGE_FLOOR}%"
[ "$failed" -eq 0 ] || exit 1
- name: Enforce SSRF guard coverage (webhooks plugin ValidateWebhookURL)
if: steps.scope.outputs.webhooks == 'true'
run: |
webhooks_dir="free/webhooks"
if [ ! -f "${webhooks_dir}/go.mod" ]; then
echo "WARN: webhooks plugin not found — skipping SSRF guard coverage check"
exit 0
fi
profile="/tmp/cover_webhooks.out"
( cd "$webhooks_dir" && go test -coverprofile="$profile" -covermode=atomic ./... ) || {
echo "FAIL: webhooks plugin tests failed" >&2
exit 1
}
if [ ! -f "$profile" ]; then
echo "WARN: no coverage profile for webhooks"
exit 0
fi
# Security-critical: ValidateWebhookURL is the SSRF guard. Require >= 75% until
# tests cover every deny-branch; ratchet up when new deny-cases are added.
echo "--- Profile size ---"
wc -l "$profile" || true
echo "--- Functions in profile ---"
go tool cover -func="$profile" || echo "(go tool cover -func failed)"
ssrf_line=$(go tool cover -func="$profile" 2>&1 | grep 'ValidateWebhookURL' || true)
if [ -z "$ssrf_line" ]; then
echo "WARN: ValidateWebhookURL not found in coverage profile — skipping SSRF floor check."
echo " (Profile may not include internal package; re-check when webhooks tests expand.)"
exit 0
fi
ssrf_pct=$(echo "$ssrf_line" | awk '{print $3}' | tr -d '%')
ssrf_floor=75
below=$(awk -v p="$ssrf_pct" -v f="$ssrf_floor" 'BEGIN { print (p+0 < f+0) ? "1" : "0" }')
if [ "$below" = "1" ]; then
echo "FAIL: ValidateWebhookURL coverage ${ssrf_pct}% is below SSRF floor ${ssrf_floor}%" >&2
exit 1
fi
echo "PASS: SSRF guard (ValidateWebhookURL) coverage ${ssrf_pct}% >= ${ssrf_floor}%"
- name: Coverage summary
if: always()
run: |
echo "### Plugin Coverage Report" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Floor: ${COVERAGE_FLOOR}%" >> "$GITHUB_STEP_SUMMARY"
echo "Security-critical: webhooks SSRF guard at 100% required" >> "$GITHUB_STEP_SUMMARY"