Skip to content

Commit 5b7d7bf

Browse files
edburnsCopilot
andcommitted
Update java-sdk-tests.yml: address review concerns
- Add push path filter (java/**, test/**, workflow, actions) - Remove OS matrix, run on ubuntu-latest only - Set permissions: contents write, checks write, pull-requests write - Use SHA-pinned actions (matching copilot-sdk-java source) - Add persist-credentials: false on checkout - Use .github/actions/setup-copilot for COPILOT_CLI_PATH - Add COPILOT_GITHUB_TOKEN and COPILOT_CLI_PATH env vars to mvn verify - Add JaCoCo badge generation (generate-java-coverage-badge.sh) - Add JaCoCo badge PR creation (peter-evans/create-pull-request) - Add java-test-report composite action for step summary - Upload test results artifact on main branch for site generation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8901b2a commit 5b7d7bf

3 files changed

Lines changed: 350 additions & 21 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
name: "Java Test Report"
2+
description: "Generate and publish test reports with summary for Java SDK tests."
3+
inputs:
4+
report-path:
5+
description: "Path to the test report XML files (glob pattern)"
6+
required: false
7+
default: "java/target/surefire-reports*/TEST-*.xml"
8+
jacoco-path:
9+
description: "Path to the JaCoCo XML report"
10+
required: false
11+
default: "java/target/site/jacoco-coverage/jacoco.xml"
12+
jacoco-csv-path:
13+
description: "Path to the JaCoCo CSV report"
14+
required: false
15+
default: "java/target/site/jacoco-coverage/jacoco.csv"
16+
check-name:
17+
description: "Name for the check run"
18+
required: false
19+
default: "Java SDK Test Results"
20+
runs:
21+
using: "composite"
22+
steps:
23+
- name: Generate Test Summary
24+
shell: bash
25+
run: |
26+
echo "## 🧪 Copilot Java SDK :: Test Results" >> $GITHUB_STEP_SUMMARY
27+
echo "" >> $GITHUB_STEP_SUMMARY
28+
29+
if ls ${{ inputs.report-path }} 1>/dev/null 2>&1; then
30+
TESTS_RUN=$(grep -h "tests=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*tests="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}')
31+
FAILURES=$(grep -h "failures=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*failures="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}')
32+
ERRORS=$(grep -h "errors=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*errors="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}')
33+
SKIPPED=$(grep -h "skipped=" ${{ inputs.report-path }} 2>/dev/null | sed 's/.*skipped="\([0-9]*\)".*/\1/' | awk '{s+=$1} END {print s}')
34+
35+
TESTS_RUN=${TESTS_RUN:-0}
36+
FAILURES=${FAILURES:-0}
37+
ERRORS=${ERRORS:-0}
38+
SKIPPED=${SKIPPED:-0}
39+
PASSED=$((TESTS_RUN - FAILURES - ERRORS - SKIPPED))
40+
41+
if [ "$FAILURES" -eq 0 ] && [ "$ERRORS" -eq 0 ]; then
42+
echo "### ✅ All tests passed!" >> $GITHUB_STEP_SUMMARY
43+
else
44+
echo "### ❌ Some tests failed" >> $GITHUB_STEP_SUMMARY
45+
fi
46+
47+
echo "" >> $GITHUB_STEP_SUMMARY
48+
echo "| Metric | Count |" >> $GITHUB_STEP_SUMMARY
49+
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
50+
echo "| ✅ Passed | $PASSED |" >> $GITHUB_STEP_SUMMARY
51+
echo "| ❌ Failed | $FAILURES |" >> $GITHUB_STEP_SUMMARY
52+
echo "| 💥 Errors | $ERRORS |" >> $GITHUB_STEP_SUMMARY
53+
echo "| ⏭️ Skipped | $SKIPPED |" >> $GITHUB_STEP_SUMMARY
54+
echo "| 📊 Total | $TESTS_RUN |" >> $GITHUB_STEP_SUMMARY
55+
56+
echo "" >> $GITHUB_STEP_SUMMARY
57+
echo "### Test Classes" >> $GITHUB_STEP_SUMMARY
58+
echo "" >> $GITHUB_STEP_SUMMARY
59+
echo "| Class | Tests | Passed | Failed | Errors | Time |" >> $GITHUB_STEP_SUMMARY
60+
echo "|-------|-------|--------|--------|--------|------|" >> $GITHUB_STEP_SUMMARY
61+
62+
for file in ${{ inputs.report-path }}; do
63+
if [ -f "$file" ]; then
64+
CLASS=$(basename "$file" .xml | sed 's/TEST-//')
65+
T=$(grep -o 'tests="[0-9]*"' "$file" | head -1 | sed 's/[^0-9]//g')
66+
F=$(grep -o 'failures="[0-9]*"' "$file" | head -1 | sed 's/[^0-9]//g')
67+
E=$(grep -o 'errors="[0-9]*"' "$file" | head -1 | sed 's/[^0-9]//g')
68+
TIME=$(grep -o 'time="[0-9.]*"' "$file" | head -1 | sed 's/[^0-9.]//g')
69+
P=$((T - F - E))
70+
71+
STATUS="✅"
72+
if [ "${F:-0}" -gt 0 ] || [ "${E:-0}" -gt 0 ]; then
73+
STATUS="❌"
74+
fi
75+
76+
echo "| $STATUS $CLASS | ${T:-0} | ${P:-0} | ${F:-0} | ${E:-0} | ${TIME:-0}s |" >> $GITHUB_STEP_SUMMARY
77+
fi
78+
done
79+
else
80+
echo "⚠️ No test reports found at ${{ inputs.report-path }}" >> $GITHUB_STEP_SUMMARY
81+
fi
82+
83+
- name: Generate Coverage Summary
84+
shell: bash
85+
run: |
86+
JACOCO_XML="${{ inputs.jacoco-path }}"
87+
JACOCO_CSV="${{ inputs.jacoco-csv-path }}"
88+
89+
if [ -f "$JACOCO_XML" ]; then
90+
echo "" >> $GITHUB_STEP_SUMMARY
91+
echo "## 📊 Code Coverage" >> $GITHUB_STEP_SUMMARY
92+
echo "" >> $GITHUB_STEP_SUMMARY
93+
94+
# JaCoCo XML may be on a single line - split it for parsing
95+
# Extract report-level counters (last occurrence of each type before </report>)
96+
extract_counter() {
97+
local type=$1
98+
local field=$2
99+
# Split XML on > to get one tag per line, find counter, extract value
100+
sed 's/>/>\n/g' "$JACOCO_XML" | grep "<counter type=\"$type\"" | tail -1 | sed "s/.*$field=\"\([0-9]*\)\".*/\1/"
101+
}
102+
103+
INSTR_MISSED=$(extract_counter "INSTRUCTION" "missed")
104+
INSTR_COVERED=$(extract_counter "INSTRUCTION" "covered")
105+
106+
BRANCH_MISSED=$(extract_counter "BRANCH" "missed")
107+
BRANCH_COVERED=$(extract_counter "BRANCH" "covered")
108+
109+
LINE_MISSED=$(extract_counter "LINE" "missed")
110+
LINE_COVERED=$(extract_counter "LINE" "covered")
111+
112+
METHOD_MISSED=$(extract_counter "METHOD" "missed")
113+
METHOD_COVERED=$(extract_counter "METHOD" "covered")
114+
115+
CLASS_MISSED=$(extract_counter "CLASS" "missed")
116+
CLASS_COVERED=$(extract_counter "CLASS" "covered")
117+
118+
# Calculate percentages
119+
calc_pct() {
120+
local covered=$1
121+
local missed=$2
122+
if [ -n "$covered" ] && [ -n "$missed" ]; then
123+
local total=$((covered + missed))
124+
if [ "$total" -gt 0 ]; then
125+
echo "scale=1; $covered * 100 / $total" | bc
126+
else
127+
echo "0"
128+
fi
129+
else
130+
echo "N/A"
131+
fi
132+
}
133+
134+
INSTR_PCT=$(calc_pct "${INSTR_COVERED:-0}" "${INSTR_MISSED:-0}")
135+
BRANCH_PCT=$(calc_pct "${BRANCH_COVERED:-0}" "${BRANCH_MISSED:-0}")
136+
LINE_PCT=$(calc_pct "${LINE_COVERED:-0}" "${LINE_MISSED:-0}")
137+
METHOD_PCT=$(calc_pct "${METHOD_COVERED:-0}" "${METHOD_MISSED:-0}")
138+
CLASS_PCT=$(calc_pct "${CLASS_COVERED:-0}" "${CLASS_MISSED:-0}")
139+
140+
echo "| Metric | Covered | Missed | Coverage |" >> $GITHUB_STEP_SUMMARY
141+
echo "|--------|---------|--------|----------|" >> $GITHUB_STEP_SUMMARY
142+
echo "| 📝 Instructions | ${INSTR_COVERED:-0} | ${INSTR_MISSED:-0} | ${INSTR_PCT}% |" >> $GITHUB_STEP_SUMMARY
143+
echo "| 🌿 Branches | ${BRANCH_COVERED:-0} | ${BRANCH_MISSED:-0} | ${BRANCH_PCT}% |" >> $GITHUB_STEP_SUMMARY
144+
echo "| 📏 Lines | ${LINE_COVERED:-0} | ${LINE_MISSED:-0} | ${LINE_PCT}% |" >> $GITHUB_STEP_SUMMARY
145+
echo "| 🔧 Methods | ${METHOD_COVERED:-0} | ${METHOD_MISSED:-0} | ${METHOD_PCT}% |" >> $GITHUB_STEP_SUMMARY
146+
echo "| 📦 Classes | ${CLASS_COVERED:-0} | ${CLASS_MISSED:-0} | ${CLASS_PCT}% |" >> $GITHUB_STEP_SUMMARY
147+
148+
if [ -f "$JACOCO_CSV" ]; then
149+
extract_instruction_scope() {
150+
local scope=$1
151+
awk -F',' -v scope="$scope" -v generated_prefix="com.github.copilot.sdk.generated" '
152+
NR > 1 {
153+
is_generated = index($2, generated_prefix) == 1
154+
if ((scope == "generated" && is_generated) ||
155+
(scope == "handwritten" && !is_generated)) {
156+
missed += $4
157+
covered += $5
158+
}
159+
}
160+
END { print covered + 0 "," missed + 0 }
161+
' "$JACOCO_CSV"
162+
}
163+
164+
IFS=, read -r HANDWRITTEN_COVERED HANDWRITTEN_MISSED <<< "$(extract_instruction_scope handwritten)"
165+
IFS=, read -r GENERATED_COVERED GENERATED_MISSED <<< "$(extract_instruction_scope generated)"
166+
HANDWRITTEN_PCT=$(calc_pct "${HANDWRITTEN_COVERED:-0}" "${HANDWRITTEN_MISSED:-0}")
167+
GENERATED_PCT=$(calc_pct "${GENERATED_COVERED:-0}" "${GENERATED_MISSED:-0}")
168+
169+
echo "" >> $GITHUB_STEP_SUMMARY
170+
echo "### Coverage by Code Origin (Instructions)" >> $GITHUB_STEP_SUMMARY
171+
echo "" >> $GITHUB_STEP_SUMMARY
172+
echo "| Origin | Covered | Missed | Coverage |" >> $GITHUB_STEP_SUMMARY
173+
echo "|--------|---------|--------|----------|" >> $GITHUB_STEP_SUMMARY
174+
echo "| ✍️ Handwritten | ${HANDWRITTEN_COVERED:-0} | ${HANDWRITTEN_MISSED:-0} | ${HANDWRITTEN_PCT}% |" >> $GITHUB_STEP_SUMMARY
175+
echo "| 🤖 Generated | ${GENERATED_COVERED:-0} | ${GENERATED_MISSED:-0} | ${GENERATED_PCT}% |" >> $GITHUB_STEP_SUMMARY
176+
fi
177+
else
178+
echo "" >> $GITHUB_STEP_SUMMARY
179+
echo "## 📊 Code Coverage" >> $GITHUB_STEP_SUMMARY
180+
echo "" >> $GITHUB_STEP_SUMMARY
181+
echo "⚠️ No JaCoCo report found at $JACOCO_XML" >> $GITHUB_STEP_SUMMARY
182+
fi
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env bash
2+
# Generates SVG coverage badges from a JaCoCo CSV report.
3+
#
4+
# Usage: generate-coverage-badge.sh [jacoco.csv] [output-dir]
5+
# jacoco.csv - Path to JaCoCo CSV report (default: target/site/jacoco-coverage/jacoco.csv)
6+
# output-dir - Directory for the badge SVG (default: .github/badges)
7+
set -euo pipefail
8+
9+
CSV="${1:-target/site/jacoco-coverage/jacoco.csv}"
10+
BADGES_DIR="${2:-.github/badges}"
11+
GENERATED_PREFIX="com.github.copilot.sdk.generated"
12+
13+
if [ ! -f "$CSV" ]; then
14+
echo "⚠️ No JaCoCo CSV report found at $CSV"
15+
exit 0
16+
fi
17+
18+
calc_totals() {
19+
local scope=$1
20+
awk -F',' -v scope="$scope" -v generated_prefix="$GENERATED_PREFIX" '
21+
NR > 1 {
22+
is_generated = index($2, generated_prefix) == 1
23+
if (scope == "overall" ||
24+
(scope == "generated" && is_generated) ||
25+
(scope == "handwritten" && !is_generated)) {
26+
missed += $4
27+
covered += $5
28+
}
29+
}
30+
END { print missed + 0, covered + 0 }
31+
' "$CSV"
32+
}
33+
34+
format_pct() {
35+
local missed=$1
36+
local covered=$2
37+
local total=$((missed + covered))
38+
if [ "$total" -eq 0 ]; then
39+
echo "0"
40+
else
41+
awk "BEGIN { printf \"%.1f\", ($covered / $total) * 100 }" | sed 's/\.0$//'
42+
fi
43+
}
44+
45+
pick_color() {
46+
local pct=$1
47+
local color="#e05d44" # red <60
48+
if awk "BEGIN{exit!($pct>=100)}"; then color="#4c1" # bright green
49+
elif awk "BEGIN{exit!($pct>=90)}"; then color="#97ca00" # green
50+
elif awk "BEGIN{exit!($pct>=80)}"; then color="#a4a61d" # yellow-green
51+
elif awk "BEGIN{exit!($pct>=70)}"; then color="#dfb317" # yellow
52+
elif awk "BEGIN{exit!($pct>=60)}"; then color="#fe7d37" # orange
53+
fi
54+
echo "$color"
55+
}
56+
57+
generate_badge() {
58+
local label=$1
59+
local value=$2
60+
local output=$3
61+
local pct=${value%\%}
62+
local color
63+
color=$(pick_color "$pct")
64+
local lw=$(( ${#label} * 7 + 12 ))
65+
local vw=$(( ${#value} * 7 + 16 ))
66+
local tw=$((lw + vw))
67+
68+
cat > "$output" <<EOF
69+
<svg xmlns="http://www.w3.org/2000/svg" width="${tw}" height="20">
70+
<linearGradient id="b" x2="0" y2="100%">
71+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
72+
<stop offset="1" stop-opacity=".1"/>
73+
</linearGradient>
74+
<mask id="a"><rect width="${tw}" height="20" rx="3" fill="#fff"/></mask>
75+
<g mask="url(#a)">
76+
<rect width="${lw}" height="20" fill="#555"/>
77+
<rect x="${lw}" width="${vw}" height="20" fill="${color}"/>
78+
<rect width="${tw}" height="20" fill="url(#b)"/>
79+
</g>
80+
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
81+
<text x="$((lw/2))" y="15" fill="#010101" fill-opacity=".3">${label}</text>
82+
<text x="$((lw/2))" y="14">${label}</text>
83+
<text x="$((lw + vw/2))" y="15" fill="#010101" fill-opacity=".3">${value}</text>
84+
<text x="$((lw + vw/2))" y="14">${value}</text>
85+
</g>
86+
</svg>
87+
EOF
88+
}
89+
90+
mkdir -p "$BADGES_DIR"
91+
92+
read -r overall_missed overall_covered <<< "$(calc_totals overall)"
93+
read -r handwritten_missed handwritten_covered <<< "$(calc_totals handwritten)"
94+
read -r generated_missed generated_covered <<< "$(calc_totals generated)"
95+
96+
overall_pct=$(format_pct "$overall_missed" "$overall_covered")
97+
handwritten_pct=$(format_pct "$handwritten_missed" "$handwritten_covered")
98+
generated_pct=$(format_pct "$generated_missed" "$generated_covered")
99+
100+
echo "Overall coverage: ${overall_pct}%"
101+
echo "Handwritten coverage: ${handwritten_pct}%"
102+
echo "Generated coverage: ${generated_pct}%"
103+
104+
generate_badge "coverage" "${overall_pct}%" "${BADGES_DIR}/jacoco.svg"
105+
generate_badge "coverage handwritten" "${handwritten_pct}%" "${BADGES_DIR}/jacoco-handwritten.svg"
106+
generate_badge "coverage generated" "${generated_pct}%" "${BADGES_DIR}/jacoco-generated.svg"
107+
108+
echo "Badges generated in ${BADGES_DIR}"

0 commit comments

Comments
 (0)