Files
nofx/.github/workflows/scripts/comment_pr.py
T
WquGuru 295124c1fa test(trader): add comprehensive unit tests and CI coverage reporting (#823)
* chore(config): add Python and uv support to project
- Add comprehensive Python .gitignore rules (pycache, venv, pytest, etc.)
- Add uv package manager specific ignores (.uv/, uv.lock)
- Initialize pyproject.toml for Python tooling
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* chore(deps): add testing dependencies
- Add github.com/stretchr/testify v1.11.1 for test assertions
- Add github.com/agiledragon/gomonkey/v2 v2.13.0 for mocking
- Promote github.com/rs/zerolog to direct dependency
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* ci(workflow): add PR test coverage reporting
Add GitHub Actions workflow to run unit tests and report coverage on PRs:
- Run Go tests with race detection and coverage profiling
- Calculate coverage statistics and generate detailed reports
- Post coverage results as PR comments with visual indicators
- Fix Go version to 1.23 (was incorrectly set to 1.25.0)
Coverage guidelines:
- Green (>=80%): excellent
- Yellow (>=60%): good
- Orange (>=40%): fair
- Red (<40%): needs improvement
This workflow is advisory only and does not block PR merging.
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* test(trader): add comprehensive unit tests for trader modules
Add unit test suites for multiple trader implementations:
- aster_trader_test.go: AsterTrader functionality tests
- auto_trader_test.go: AutoTrader lifecycle and operations tests
- binance_futures_test.go: Binance futures trader tests
- hyperliquid_trader_test.go: Hyperliquid trader tests
- trader_test_suite.go: Common test suite utilities and helpers
Also fix minor formatting issue in auto_trader.go (trailing whitespace)
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* test(trader): preserve existing calculatePnLPercentage unit tests
Merge existing calculatePnLPercentage tests with incoming comprehensive test suite:
- Preserve TestCalculatePnLPercentage with 9 test cases covering edge cases
- Preserve TestCalculatePnLPercentage_RealWorldScenarios with 3 trading scenarios
- Add math package import for floating-point precision comparison
- All tests validate PnL percentage calculation with different leverage scenarios
Co-authored-by: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-09 17:43:28 +08:00

247 lines
7.2 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Post or update coverage report comment on GitHub Pull Request.
This script generates a formatted coverage report comment and posts it to a PR,
or updates an existing coverage comment if one already exists.
"""
import os
import sys
import json
import requests
from typing import Optional
def read_file(file_path: str) -> str:
"""Read file content."""
try:
with open(file_path, 'r') as f:
return f.read()
except FileNotFoundError:
print(f"Warning: File {file_path} not found", file=sys.stderr)
return ""
def generate_comment_body(coverage: str, emoji: str, status: str,
badge_color: str, coverage_report_path: str) -> str:
"""
Generate the PR comment body.
Args:
coverage: Coverage percentage (e.g., "75.5%")
emoji: Status emoji
status: Status text
badge_color: Badge color
coverage_report_path: Path to detailed coverage report
Returns:
Formatted comment body in markdown
"""
coverage_report = read_file(coverage_report_path)
# URL encode the coverage percentage for the badge
coverage_encoded = coverage.replace('%', '%25')
comment = f"""## {emoji} Go Test Coverage Report
**Total Coverage:** `{coverage}` ({status})
![Coverage](https://img.shields.io/badge/coverage-{coverage_encoded}-{badge_color})
<details>
<summary>📊 Detailed Coverage Report (click to expand)</summary>
{coverage_report}
</details>
### Coverage Guidelines
- 🟢 >= 80%: Excellent
- 🟡 >= 60%: Good
- 🟠 >= 40%: Fair
- 🔴 < 40%: Needs improvement
---
*This is an automated coverage report. The coverage requirement is advisory and does not block PR merging.*
"""
return comment
def find_existing_comment(token: str, repo: str, pr_number: int) -> Optional[int]:
"""
Find existing coverage comment in the PR.
Args:
token: GitHub token
repo: Repository in format "owner/repo"
pr_number: Pull request number
Returns:
Comment ID if found, None otherwise
"""
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json'
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
comments = response.json()
# Look for existing coverage comment
for comment in comments:
if (comment.get('user', {}).get('type') == 'Bot' and
'Go Test Coverage Report' in comment.get('body', '')):
return comment['id']
except requests.exceptions.RequestException as e:
print(f"Error fetching comments: {e}", file=sys.stderr)
return None
def post_comment(token: str, repo: str, pr_number: int, body: str) -> bool:
"""
Post a new comment to the PR.
Args:
token: GitHub token
repo: Repository in format "owner/repo"
pr_number: Pull request number
body: Comment body
Returns:
True if successful, False otherwise
"""
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json'
}
data = {'body': body}
try:
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
print("✅ Coverage comment posted successfully")
return True
except requests.exceptions.RequestException as e:
print(f"Error posting comment: {e}", file=sys.stderr)
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}", file=sys.stderr)
return False
def update_comment(token: str, repo: str, comment_id: int, body: str) -> bool:
"""
Update an existing comment.
Args:
token: GitHub token
repo: Repository in format "owner/repo"
comment_id: Comment ID to update
body: New comment body
Returns:
True if successful, False otherwise
"""
url = f"https://api.github.com/repos/{repo}/issues/comments/{comment_id}"
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json'
}
data = {'body': body}
try:
response = requests.patch(url, headers=headers, json=data)
response.raise_for_status()
print("✅ Coverage comment updated successfully")
return True
except requests.exceptions.RequestException as e:
print(f"Error updating comment: {e}", file=sys.stderr)
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}", file=sys.stderr)
return False
def is_fork_pr(event_path: str) -> bool:
"""
Check if the PR is from a fork.
Args:
event_path: Path to GitHub event JSON file
Returns:
True if fork PR, False otherwise
"""
try:
with open(event_path, 'r') as f:
event = json.load(f)
pr = event.get('pull_request', {})
head_repo = pr.get('head', {}).get('repo', {}).get('full_name')
base_repo = pr.get('base', {}).get('repo', {}).get('full_name')
return head_repo != base_repo
except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
print(f"Warning: Could not determine if fork PR: {e}", file=sys.stderr)
return False
def main():
"""Main entry point."""
# Get environment variables
token = os.environ.get('GITHUB_TOKEN')
repo = os.environ.get('GITHUB_REPOSITORY')
event_path = os.environ.get('GITHUB_EVENT_PATH', '')
# Get arguments
if len(sys.argv) < 6:
print("Usage: comment_pr.py <pr_number> <coverage> <emoji> <status> <badge_color> [coverage_report_path]",
file=sys.stderr)
sys.exit(1)
pr_number = int(sys.argv[1])
coverage = sys.argv[2]
emoji = sys.argv[3]
status = sys.argv[4]
badge_color = sys.argv[5]
coverage_report_path = sys.argv[6] if len(sys.argv) > 6 else 'coverage_report.md'
# Validate environment
if not token:
print("Error: GITHUB_TOKEN environment variable not set", file=sys.stderr)
sys.exit(1)
if not repo:
print("Error: GITHUB_REPOSITORY environment variable not set", file=sys.stderr)
sys.exit(1)
# Check if fork PR
if event_path and is_fork_pr(event_path):
print("️ Fork PR detected - skipping comment (no write permissions)")
sys.exit(0)
# Generate comment body
comment_body = generate_comment_body(coverage, emoji, status, badge_color, coverage_report_path)
# Check for existing comment
existing_comment_id = find_existing_comment(token, repo, pr_number)
# Post or update comment
if existing_comment_id:
print(f"Found existing comment (ID: {existing_comment_id}), updating...")
success = update_comment(token, repo, existing_comment_id, comment_body)
else:
print("No existing comment found, creating new one...")
success = post_comment(token, repo, pr_number, comment_body)
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()