Skip to content

Commit cf5526f

Browse files
GHA-220 Add 'Rules updated' column to update-rule-metadata PR summary (#123)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f0cf1e1 commit cf5526f

File tree

5 files changed

+285
-52
lines changed

5 files changed

+285
-52
lines changed

.github/workflows/test-update-rule-metadata.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,23 @@ jobs:
263263
echo "================================"
264264
echo "✓ Integration test completed"
265265
266+
generate-summary-tests:
267+
name: Test Generate Summary Logic
268+
runs-on: ubuntu-latest
269+
270+
steps:
271+
- name: Checkout code
272+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
273+
274+
- name: Run generate-summary unit tests
275+
run: |
276+
bash update-rule-metadata/test-generate-summary.sh \
277+
update-rule-metadata/generate-summary.sh
278+
266279
validation-summary:
267280
name: Test Summary
268281
runs-on: ubuntu-latest
269-
needs: [input-parameter-tests, branch-parameter-tests, output-validation, integration-tests, vault-and-env-tests]
282+
needs: [input-parameter-tests, branch-parameter-tests, output-validation, integration-tests, vault-and-env-tests, generate-summary-tests]
270283
if: always()
271284

272285
steps:
@@ -280,12 +293,14 @@ jobs:
280293
echo "Output Validation: ${{ needs.output-validation.result }}"
281294
echo "Integration Tests: ${{ needs.integration-tests.result }}"
282295
echo "Vault & Env Variable Tests: ${{ needs.vault-and-env-tests.result }}"
296+
echo "Generate Summary Tests: ${{ needs.generate-summary-tests.result }}"
283297
echo "================================"
284298
285299
if [[ "${{ needs.input-parameter-tests.result }}" == "success" && \
286300
"${{ needs.branch-parameter-tests.result }}" == "success" && \
287301
"${{ needs.output-validation.result }}" == "success" && \
288-
"${{ needs.vault-and-env-tests.result }}" == "success" ]]; then
302+
"${{ needs.vault-and-env-tests.result }}" == "success" && \
303+
"${{ needs.generate-summary-tests.result }}" == "success" ]]; then
289304
echo "✓ All validation tests passed!"
290305
echo "✓ Action is properly configured and ready to use"
291306
else

update-rule-metadata/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ This action depends on:
3434
| Output | Description |
3535
|--------------------|-----------------------------------------------------------------------------------------------------------|
3636
| `has-changes` | Boolean indicating whether any rule metadata changes were detected (from check-changes step) |
37-
| `summary` | Summary of the rule metadata updates including rule counts for each language (from generate-summary step) |
37+
| `summary` | Summary of the rule metadata updates including rules-to-update and rules-updated counts for each language (from generate-summary step) |
3838
| `pull-request-url` | URL of the created pull request (only available if changes were detected, from create-pr step) |
3939

4040
## Usage

update-rule-metadata/action.yml

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,7 @@ runs:
160160
echo "Processing directory: $dir"
161161
cd "$dir"
162162
163-
# Extract a meaningful name for logging (use last part of path)
164-
dir_name=$(basename "$dir")
165-
parent_dir=$(dirname "$dir")
166-
if [ "$parent_dir" != "." ]; then
167-
dir_name="${parent_dir##*/}/${dir_name}"
168-
fi
169-
170-
echo "=== $dir_name/sonarpedia.json ===" >> $log_file
163+
echo "=== PATH:$dir ===" >> $log_file
171164
172165
# Calculate relative path to rule-api.jar from current directory
173166
rel_path=$(realpath --relative-to="$PWD" "$original_dir/rule-api.jar")
@@ -203,48 +196,10 @@ runs:
203196
- name: Generate summary
204197
id: generate-summary
205198
shell: bash
199+
env:
200+
RULE_API_VERSION: ${{ steps.download.outputs.rule-api-version }}
206201
run: |
207-
summary_file="rule-api-summary.md"
208-
current_sonarpedia=""
209-
has_entries=false
210-
total_rules=0
211-
212-
# Build a markdown table
213-
echo "| Sonarpedia | Rules to update |" > "$summary_file"
214-
echo "|---|---:|" >> "$summary_file"
215-
216-
while IFS= read -r line; do
217-
if [[ $line == "=== "* ]]; then
218-
current_sonarpedia=$(echo "$line" | sed 's/=== \(.*\) ===/\1/')
219-
elif [[ $line == *"Found "* && $line == *" rule(s) to update"* ]]; then
220-
rule_count=$(echo "$line" | grep -o 'Found [0-9]\+' | grep -o '[0-9]\+')
221-
if [[ -n "$rule_count" && "$rule_count" != "0" && -n "$current_sonarpedia" ]]; then
222-
echo "| \`${current_sonarpedia}\` | ${rule_count} |" >> "$summary_file"
223-
total_rules=$((total_rules + rule_count))
224-
has_entries=true
225-
fi
226-
fi
227-
done < rule-api-logs.txt
228-
229-
if [[ "$has_entries" == "true" ]]; then
230-
echo "| **Total** | **${total_rules}** |" >> "$summary_file"
231-
fi
232-
233-
echo -e "\nRule API Version: ${{ steps.download.outputs.rule-api-version }}" >> "$summary_file"
234-
235-
# Write summary to output using delimiter to preserve newlines
236-
if [[ "$has_entries" == "false" ]]; then
237-
echo "summary=Update rule metadata" >> $GITHUB_OUTPUT
238-
else
239-
{
240-
echo "summary<<EOF"
241-
cat "$summary_file"
242-
echo "EOF"
243-
} >> $GITHUB_OUTPUT
244-
fi
245-
246-
rm rule-api-logs.txt
247-
rm -f "$summary_file"
202+
bash "$GITHUB_ACTION_PATH/generate-summary.sh" "$RULE_API_VERSION"
248203
249204
- name: Check Rule Metadata Changes
250205
id: check-changes
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env bash
2+
# Generate the rule-update summary markdown table from rule-api-logs.txt.
3+
# Usage: generate-summary.sh <rule-api-version> [log-file]
4+
# rule-api-version Version string appended at the end of the summary.
5+
# log-file Path to the log file (default: rule-api-logs.txt).
6+
#
7+
# Reads the log file, builds a markdown table with one row per sonarpedia
8+
# directory, and writes the result to GITHUB_OUTPUT (summary=...).
9+
# Exits 0 in all cases; sets summary to "Update rule metadata" when there
10+
# are no rules to report.
11+
12+
set -euo pipefail
13+
14+
rule_api_version="${1:-}"
15+
log_file="${2:-rule-api-logs.txt}"
16+
17+
summary_file="rule-api-summary.md"
18+
current_sonarpedia=""
19+
current_dir=""
20+
has_entries=false
21+
total_rules=0
22+
total_updated=0
23+
24+
echo "| Sonarpedia | Rules to update | Rules updated |" > "$summary_file"
25+
echo "|---|---:|---:|" >> "$summary_file"
26+
27+
while IFS= read -r line; do
28+
if [[ $line == "=== PATH:"* ]]; then
29+
current_dir=$(echo "$line" | sed 's/=== PATH:\(.*\) ===/\1/')
30+
current_sonarpedia="${current_dir}/sonarpedia.json"
31+
elif [[ $line == *"Found "* && $line == *" rule(s) to update"* ]]; then
32+
rule_count=$(echo "$line" | grep -oE 'Found [0-9]+' | grep -oE '[0-9]+')
33+
if [[ -n "$rule_count" && "$rule_count" != "0" && -n "$current_sonarpedia" ]]; then
34+
# Count distinct rule IDs changed under this sonarpedia directory.
35+
# Each rule has an .html and a .json file; count unique basenames.
36+
updated_count=$(git diff --name-only HEAD -- "${current_dir}" \
37+
| grep -v 'sonarpedia\.json$' \
38+
| grep -E '\.(html|json)$' \
39+
| sed 's/\.[^.]*$//' \
40+
| sort -u \
41+
| wc -l \
42+
| tr -d ' ' || true)
43+
echo "| \`${current_sonarpedia}\` | ${rule_count} | ${updated_count} |" >> "$summary_file"
44+
total_rules=$((total_rules + rule_count))
45+
total_updated=$((total_updated + updated_count))
46+
has_entries=true
47+
fi
48+
fi
49+
done < "$log_file"
50+
51+
if [[ "$has_entries" == "true" ]]; then
52+
echo "| **Total** | **${total_rules}** | **${total_updated}** |" >> "$summary_file"
53+
fi
54+
55+
if [[ -n "$rule_api_version" ]]; then
56+
echo -e "\nRule API Version: ${rule_api_version}" >> "$summary_file"
57+
fi
58+
59+
if [[ "$has_entries" == "false" ]]; then
60+
echo "summary=Update rule metadata" >> "${GITHUB_OUTPUT:-/dev/null}"
61+
else
62+
{
63+
echo "summary<<EOF"
64+
cat "$summary_file"
65+
echo "EOF"
66+
} >> "${GITHUB_OUTPUT:-/dev/null}"
67+
fi
68+
69+
rm -f "$log_file" "$summary_file"
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env bash
2+
# Unit tests for generate-summary.sh.
3+
# Usage: test-generate-summary.sh <path-to-generate-summary.sh>
4+
# Exits 0 if all tests pass, 1 on first failure.
5+
6+
set -euo pipefail
7+
8+
_script_arg="${1:-$(dirname "$0")/generate-summary.sh}"
9+
SCRIPT="$(cd "$(dirname "$_script_arg")" && pwd)/$(basename "$_script_arg")"
10+
11+
pass=0
12+
fail=0
13+
14+
INITIAL_COMMIT_MSG="initial"
15+
OLD_RULE_CONTENT='<rule>old</rule>'
16+
NEW_RULE_CONTENT='<rule>new</rule>'
17+
UPDATED_JSON='{"updated":true}'
18+
19+
assert_output_contains() {
20+
local test_name="$1"
21+
local pattern="$2"
22+
local output_file="$3"
23+
if grep -q "$pattern" "$output_file"; then
24+
echo "$test_name"
25+
pass=$((pass + 1))
26+
else
27+
echo "$test_name"
28+
echo " Expected pattern: $pattern"
29+
echo " Actual output:"
30+
sed 's/^/ /' "$output_file"
31+
fail=$((fail + 1))
32+
fi
33+
return 0
34+
}
35+
36+
# ---------------------------------------------------------------------------
37+
# Helper: create an isolated git repo, commit initial files, stage changes,
38+
# write a log file, run the script, and return the output file path.
39+
# ---------------------------------------------------------------------------
40+
run_test() {
41+
local work_dir
42+
work_dir=$(mktemp -d)
43+
44+
# Each test receives the work_dir; caller populates it before calling run_script.
45+
echo "$work_dir"
46+
return 0
47+
}
48+
49+
init_git() {
50+
git init -q
51+
git config user.email "test@test.com"
52+
git config user.name "Test"
53+
return 0
54+
}
55+
56+
run_script() {
57+
local work_dir="$1"
58+
local gh_output="$work_dir/gh_output"
59+
(cd "$work_dir" && GITHUB_OUTPUT="$gh_output" bash "$SCRIPT" "2.99.0") || true
60+
echo "$gh_output"
61+
return 0
62+
}
63+
64+
# ---------------------------------------------------------------------------
65+
# Test 1: flat structure (sonar-rpg style)
66+
# sonarpedia.json at the repo root — mirrors SonarSource/sonar-rpg.
67+
# ---------------------------------------------------------------------------
68+
echo "--- Test 1: flat structure (sonar-rpg style) ---"
69+
WORK_DIR=$(run_test)
70+
cd "$WORK_DIR"
71+
init_git
72+
mkdir -p rules
73+
echo '{}' > sonarpedia.json
74+
echo "$OLD_RULE_CONTENT" > rules/S1000.html
75+
echo '{}' > rules/S1000.json
76+
git add . && git commit -q -m "$INITIAL_COMMIT_MSG"
77+
78+
echo "$NEW_RULE_CONTENT" > rules/S1000.html
79+
echo "$UPDATED_JSON" > rules/S1001.json
80+
git add .
81+
82+
printf '=== PATH:. ===\nRunning rule-api update\nFound 3 rule(s) to update\n' > rule-api-logs.txt
83+
84+
OUTPUT=$(run_script "$WORK_DIR")
85+
assert_output_contains "flat: row shows 3 to update and 2 updated" \
86+
'./sonarpedia.json.*3.*2' "$OUTPUT"
87+
rm -rf "$WORK_DIR"
88+
89+
# ---------------------------------------------------------------------------
90+
# Test 2: 2-level structure (sonar-security frontend/<lang> style)
91+
# ---------------------------------------------------------------------------
92+
echo "--- Test 2: 2-level structure (sonar-security frontend/java style) ---"
93+
WORK_DIR=$(run_test)
94+
cd "$WORK_DIR"
95+
init_git
96+
mkdir -p frontend/java/rules frontend/python/rules
97+
echo '{}' > frontend/java/sonarpedia.json
98+
echo '{}' > frontend/python/sonarpedia.json
99+
echo "$OLD_RULE_CONTENT" > frontend/java/rules/S1000.html
100+
echo '{}' > frontend/java/rules/S1000.json
101+
echo "$OLD_RULE_CONTENT" > frontend/python/rules/S2000.html
102+
git add . && git commit -q -m "$INITIAL_COMMIT_MSG"
103+
104+
# Update java rules only; python unchanged.
105+
echo "$NEW_RULE_CONTENT" > frontend/java/rules/S1000.html
106+
echo "$UPDATED_JSON" > frontend/java/rules/S1001.json
107+
git add .
108+
109+
printf '=== PATH:frontend/java ===\nRunning rule-api update\nFound 5 rule(s) to update\n=== PATH:frontend/python ===\nRunning rule-api update\nFound 2 rule(s) to update\n' > rule-api-logs.txt
110+
111+
OUTPUT=$(run_script "$WORK_DIR")
112+
assert_output_contains "2-level: java row shows 5 to update and 2 updated" \
113+
'frontend/java/sonarpedia.json.*5.*2' "$OUTPUT"
114+
assert_output_contains "2-level: python row shows 2 to update and 0 updated" \
115+
'frontend/python/sonarpedia.json.*2.*0' "$OUTPUT"
116+
rm -rf "$WORK_DIR"
117+
118+
# ---------------------------------------------------------------------------
119+
# Test 3: deep 3-level structure (sonar-security dotnet style)
120+
# frontend/dotnet/<plugin>/sonarpedia.json — the path the old bug truncated.
121+
# ---------------------------------------------------------------------------
122+
echo "--- Test 3: deep 3-level structure (sonar-security dotnet style) ---"
123+
WORK_DIR=$(run_test)
124+
cd "$WORK_DIR"
125+
init_git
126+
mkdir -p frontend/dotnet/sonar-security-csharp-frontend-plugin/rules
127+
mkdir -p frontend/dotnet/sonar-security-vbnet-frontend-plugin/rules
128+
echo '{}' > frontend/dotnet/sonar-security-csharp-frontend-plugin/sonarpedia.json
129+
echo '{}' > frontend/dotnet/sonar-security-vbnet-frontend-plugin/sonarpedia.json
130+
echo "$OLD_RULE_CONTENT" > frontend/dotnet/sonar-security-csharp-frontend-plugin/rules/S3649.html
131+
echo '{}' > frontend/dotnet/sonar-security-csharp-frontend-plugin/rules/S3649.json
132+
echo "$OLD_RULE_CONTENT" > frontend/dotnet/sonar-security-vbnet-frontend-plugin/rules/S3649.html
133+
git add . && git commit -q -m "$INITIAL_COMMIT_MSG"
134+
135+
echo "$NEW_RULE_CONTENT" > frontend/dotnet/sonar-security-csharp-frontend-plugin/rules/S3649.html
136+
echo "$UPDATED_JSON" > frontend/dotnet/sonar-security-csharp-frontend-plugin/rules/S3650.json
137+
git add .
138+
139+
printf '=== PATH:frontend/dotnet/sonar-security-csharp-frontend-plugin ===\nRunning rule-api update\nFound 4 rule(s) to update\n=== PATH:frontend/dotnet/sonar-security-vbnet-frontend-plugin ===\nRunning rule-api update\nFound 4 rule(s) to update\n' > rule-api-logs.txt
140+
141+
OUTPUT=$(run_script "$WORK_DIR")
142+
assert_output_contains "3-level: csharp row shows 4 to update and 2 updated" \
143+
'sonar-security-csharp-frontend-plugin/sonarpedia.json.*4.*2' "$OUTPUT"
144+
assert_output_contains "3-level: vbnet row shows 4 to update and 0 updated" \
145+
'sonar-security-vbnet-frontend-plugin/sonarpedia.json.*4.*0' "$OUTPUT"
146+
rm -rf "$WORK_DIR"
147+
148+
# ---------------------------------------------------------------------------
149+
# Test 5: deduplication — both .html and .json changed for same rule = 1 rule
150+
# ---------------------------------------------------------------------------
151+
echo "--- Test 5: deduplication (.html + .json same rule counts as 1) ---"
152+
WORK_DIR=$(run_test)
153+
cd "$WORK_DIR"
154+
init_git
155+
mkdir -p rules
156+
echo '{}' > sonarpedia.json
157+
echo "$OLD_RULE_CONTENT" > rules/S1896.html
158+
echo '{}' > rules/S1896.json
159+
git add . && git commit -q -m "$INITIAL_COMMIT_MSG"
160+
161+
echo "$NEW_RULE_CONTENT" > rules/S1896.html
162+
echo "$UPDATED_JSON" > rules/S1896.json
163+
git add .
164+
165+
printf '=== PATH:. ===\nRunning rule-api update\nFound 2 rule(s) to update\n' > rule-api-logs.txt
166+
167+
OUTPUT=$(run_script "$WORK_DIR")
168+
assert_output_contains "dedup: S1896.html+S1896.json counts as 1 rule updated" \
169+
'./sonarpedia.json.*2.*1' "$OUTPUT"
170+
rm -rf "$WORK_DIR"
171+
172+
# ---------------------------------------------------------------------------
173+
# Test 4: no rules to update — fallback summary string
174+
# ---------------------------------------------------------------------------
175+
echo "--- Test 4: no rules to update (fallback summary) ---"
176+
WORK_DIR=$(run_test)
177+
cd "$WORK_DIR"
178+
init_git
179+
echo '{}' > sonarpedia.json
180+
git add . && git commit -q -m "$INITIAL_COMMIT_MSG"
181+
182+
printf '=== PATH:. ===\nRunning rule-api update\nFound 0 rule(s) to update\n' > rule-api-logs.txt
183+
184+
OUTPUT=$(run_script "$WORK_DIR")
185+
assert_output_contains "zero rules: fallback summary written" \
186+
'summary=Update rule metadata' "$OUTPUT"
187+
rm -rf "$WORK_DIR"
188+
189+
# ---------------------------------------------------------------------------
190+
# Results
191+
# ---------------------------------------------------------------------------
192+
echo ""
193+
echo "Results: $pass passed, $fail failed"
194+
[[ "$fail" -eq 0 ]]

0 commit comments

Comments
 (0)