Skip to content

Commit 6cf874a

Browse files
committed
[feature] Add comprehensive tests and docs for CI failure bot #524
- Add complete test suite with comprehensive coverage for all bot functions - Add user and developer documentation following OpenWISP structure - Improve error handling in workflow to fail loudly on script errors - Address CodeRabbit feedback on exception handling - Follow OpenWISP documentation patterns with Inputs, Usage Example, and Note sections - Ensure proper QA formatting with noqa directives for long Gemini prompt lines Closes #524
1 parent c64a94a commit 6cf874a

File tree

4 files changed

+149
-325
lines changed

4 files changed

+149
-325
lines changed

.github/scripts/ci_failure_bot.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import os
88
import sys
99

10-
import google.generativeai as genai
10+
import google.genai as genai
1111
import requests
1212
from github import Github, GithubException
1313

@@ -127,7 +127,7 @@ def get_pr_diff(self):
127127
"body": pr.body or "",
128128
"diff": diff_text,
129129
}
130-
except (GithubException, requests.RequestException) as e:
130+
except (GithubException, requests.RequestException, ValueError) as e:
131131
print(f"Error getting PR diff: {e}")
132132

133133
return None
@@ -164,38 +164,38 @@ def analyze_with_gemini(self, build_logs, pr_diff, workflow_yaml):
164164
# Gemini prompt - ignore line length for readability
165165
context = f"""
166166
### ROLE
167-
You are the "Automated Maintainer Gatekeeper." Your goal is to analyze Pull Request (PR) build failures and provide direct, technically accurate, and no-nonsense feedback to contributors.
167+
You are the "Automated Maintainer Gatekeeper." Your goal is to analyze Pull Request (PR) build failures and provide direct, technically accurate, and no-nonsense feedback to contributors. # noqa: E501
168168
169169
### INPUT CONTEXT PROVIDED
170170
1. **Build Output/Logs:** {build_logs_json}
171171
2. **YAML Workflow:** {workflow_yaml or "Not available"}
172172
3. **PR Diff:** {pr_diff_json}
173-
4. **Project Name:** {project_name}
173+
4. **Project Name:** {project_name} # noqa: E501
174174
5. **Repository:** {repo_url}
175-
6. **run-qa-checks:** {qa_checks_url}
176-
7. **runtests:** {runtests_url}
175+
6. **run-qa-checks:** {qa_checks_url} # noqa: E501
176+
7. **runtests:** {runtests_url} # noqa: E501
177177
178178
### TASK
179-
Analyze the provided context to determine why the build failed. Categorize the failure and respond according to the "Tone Guidelines" below.
179+
Analyze the provided context to determine why the build failed. Categorize the failure and respond according to the "Tone Guidelines" below. # noqa: E501
180180
181181
### TONE GUIDELINES
182182
- **Direct & Honest:** Do not use "fluff" or overly polite corporate language.
183183
- **Firm Standards:** If a PR is low-effort, spammy, or fails to follow basic instructions, state that clearly.
184-
- **Action-Oriented:** Provide the exact command or file change needed to fix the error, unless the PR is spammy, in which case we should just declare the PR as potential SPAM and ask maintainers to manually review it.
184+
- **Action-Oriented:** Provide the exact command or file change needed to fix the error, unless the PR is spammy, in which case we should just declare the PR as potential SPAM and ask maintainers to manually review it. # noqa: E501
185185
186186
### RESPONSE STRUCTURE
187187
1. **Status Summary:** A one-sentence blunt assessment of the failure.
188188
2. **Technical Diagnosis:**
189189
- Identify the specific line/test that failed.
190190
- Explain *why* it failed.
191191
3. **Required Action:** Provide a code block or specific steps the contributor must take.
192-
4. **Quality Warning (If Applicable):** If the PR appears to be "spam" (e.g., trivial README changes, AI-generated nonsense, or repeated basic errors), include a firm statement that such contributions are a drain on project resources and ping the maintainers asking them for manual review.
192+
4. **Quality Warning (If Applicable):** If the PR appears to be "spam" (e.g., trivial README changes, AI-generated nonsense, or repeated basic errors), include a firm statement that such contributions are a drain on project resources and ping the maintainers asking them for manual review. # noqa: E501
193193
194194
### EXAMPLE RESPONSE STYLE
195-
"The build failed because you neglected to update the test suite to match your logic changes. This project does not accept functional changes without corresponding test updates. Refer to the log at line 452. Update tests/logic_test.py before re-submitting. We prioritize high-quality, ready-to-merge code; please ensure you run local tests before pushing."
195+
"The build failed because you neglected to update the test suite to match your logic changes. This project does not accept functional changes without corresponding test updates. Refer to the log at line 452. Update tests/logic_test.py before re-submitting. We prioritize high-quality, ready-to-merge code; please ensure you run local tests before pushing." # noqa: E501
196196
197197
Analyze the failure and provide your response:
198-
""" # noqa: E501
198+
"""
199199

200200
try:
201201
response = self.model.generate_content(context)
@@ -251,37 +251,56 @@ def post_comment(self, message):
251251

252252
def run(self):
253253
"""Main execution flow"""
254-
print("CI Failure Bot starting - AI-powered analysis")
255-
256-
# Double-check: Skip if this is a dependabot PR
257254
try:
258-
workflow_run = self.repo.get_workflow_run(self.workflow_run_id)
259-
if workflow_run.actor and "dependabot" in workflow_run.actor.login.lower():
260-
print(f"Skipping dependabot PR from {workflow_run.actor.login}")
255+
print("CI Failure Bot starting - AI-powered analysis")
256+
257+
# Double-check: Skip if this is a dependabot PR
258+
try:
259+
workflow_run = self.repo.get_workflow_run(self.workflow_run_id)
260+
if (
261+
workflow_run.actor
262+
and "dependabot" in workflow_run.actor.login.lower()
263+
):
264+
print(f"Skipping dependabot PR from {workflow_run.actor.login}")
265+
return
266+
except (GithubException, AttributeError) as e:
267+
print(f"Warning: Could not check actor: {e}")
268+
269+
# Get all context
270+
build_logs = self.get_build_logs()
271+
pr_diff = self.get_pr_diff()
272+
workflow_yaml = self.get_workflow_yaml()
273+
274+
if not build_logs:
275+
print("No build logs found")
261276
return
262-
except (GithubException, AttributeError) as e:
263-
print(f"Warning: Could not check actor: {e}")
264277

265-
# Get all context
266-
build_logs = self.get_build_logs()
267-
pr_diff = self.get_pr_diff()
268-
workflow_yaml = self.get_workflow_yaml()
278+
print("Analyzing failure with Gemini AI...")
269279

270-
if not build_logs:
271-
print("No build logs found")
272-
return
280+
# Get AI analysis
281+
ai_response = self.analyze_with_gemini(build_logs, pr_diff, workflow_yaml)
273282

274-
print("Analyzing failure with Gemini AI...")
283+
# Post intelligent comment
284+
self.post_comment(ai_response)
275285

276-
# Get AI analysis
277-
ai_response = self.analyze_with_gemini(build_logs, pr_diff, workflow_yaml)
286+
print("CI Failure Bot completed successfully")
278287

279-
# Post intelligent comment
280-
self.post_comment(ai_response)
288+
except Exception as e:
289+
print(f"CRITICAL ERROR in CI Failure Bot: {e}")
290+
print(f"Error type: {type(e).__name__}")
291+
import traceback
281292

282-
print("CI Failure Bot completed")
293+
traceback.print_exc()
294+
sys.exit(1)
283295

284296

285297
if __name__ == "__main__":
286-
bot = CIFailureBot()
287-
bot.run()
298+
try:
299+
bot = CIFailureBot()
300+
bot.run()
301+
except Exception as e:
302+
print(f"FATAL: CI Failure Bot crashed: {e}")
303+
import traceback
304+
305+
traceback.print_exc()
306+
sys.exit(1)

.github/workflows/ci-failure-bot.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ jobs:
2727
python-version: "3.11"
2828

2929
- name: Install dependencies
30-
# Pin dependency versions for reproducibility
30+
# Install minimum required versions
3131
run: |
32-
pip install requests>=2.31.0 PyGithub>=2.0.0 google-generativeai>=0.3.0
32+
pip install requests>=2.32.5 PyGithub>=2.0.0 google-genai>=0.2.0
3333
3434
- name: Run CI Failure Bot
3535
env:
@@ -38,4 +38,14 @@ jobs:
3838
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
3939
REPOSITORY: ${{ github.repository }}
4040
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number || '' }}
41-
run: python .github/scripts/ci_failure_bot.py
41+
run: |
42+
set -e # Exit immediately if any command fails
43+
echo "Starting CI Failure Bot..."
44+
python .github/scripts/ci_failure_bot.py
45+
exit_code=$?
46+
if [ $exit_code -ne 0 ]; then
47+
echo "ERROR: CI Failure Bot failed with exit code $exit_code"
48+
echo "This indicates a critical issue with the bot logic that needs immediate attention"
49+
exit $exit_code
50+
fi
51+
echo "CI Failure Bot completed successfully"

0 commit comments

Comments
 (0)