mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2025-12-06 13:54:41 +08:00
* 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>
193 lines
5.5 KiB
Python
Executable File
193 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Calculate Go test coverage and generate reports.
|
|
|
|
This script parses the coverage.out file generated by `go test -coverprofile`,
|
|
extracts coverage statistics, and generates formatted reports.
|
|
"""
|
|
|
|
import sys
|
|
import re
|
|
import os
|
|
from typing import Dict, List, Tuple
|
|
|
|
|
|
def parse_coverage_file(coverage_file: str) -> Tuple[float, Dict[str, float]]:
|
|
"""
|
|
Parse coverage output file and extract coverage data.
|
|
|
|
Args:
|
|
coverage_file: Path to coverage.out file
|
|
|
|
Returns:
|
|
Tuple of (total_coverage, package_coverage_dict)
|
|
"""
|
|
if not os.path.exists(coverage_file):
|
|
print(f"Error: Coverage file {coverage_file} not found", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Run go tool cover to get coverage data
|
|
import subprocess
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
['go', 'tool', 'cover', '-func', coverage_file],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True
|
|
)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error running go tool cover: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
lines = result.stdout.strip().split('\n')
|
|
package_coverage = {}
|
|
total_coverage = 0.0
|
|
|
|
for line in lines:
|
|
# Skip empty lines
|
|
if not line.strip():
|
|
continue
|
|
|
|
# Check for total coverage line
|
|
if line.startswith('total:'):
|
|
# Extract percentage from "total: (statements) XX.X%"
|
|
match = re.search(r'(\d+\.\d+)%', line)
|
|
if match:
|
|
total_coverage = float(match.group(1))
|
|
continue
|
|
|
|
# Parse package/file coverage
|
|
# Format: "package/file.go:function statements coverage%"
|
|
parts = line.split()
|
|
if len(parts) >= 3:
|
|
file_path = parts[0]
|
|
coverage_str = parts[-1]
|
|
|
|
# Extract package name from file path
|
|
package = file_path.split(':')[0]
|
|
package_name = '/'.join(package.split('/')[:-1]) if '/' in package else package
|
|
|
|
# Extract coverage percentage
|
|
match = re.search(r'(\d+\.\d+)%', coverage_str)
|
|
if match:
|
|
coverage_pct = float(match.group(1))
|
|
|
|
# Aggregate by package
|
|
if package_name not in package_coverage:
|
|
package_coverage[package_name] = []
|
|
package_coverage[package_name].append(coverage_pct)
|
|
|
|
# Calculate average coverage per package
|
|
package_avg = {
|
|
pkg: sum(coverages) / len(coverages)
|
|
for pkg, coverages in package_coverage.items()
|
|
}
|
|
|
|
return total_coverage, package_avg
|
|
|
|
|
|
def get_coverage_status(coverage: float) -> Tuple[str, str, str]:
|
|
"""
|
|
Get coverage status based on percentage.
|
|
|
|
Args:
|
|
coverage: Coverage percentage
|
|
|
|
Returns:
|
|
Tuple of (emoji, status_text, badge_color)
|
|
"""
|
|
if coverage >= 80:
|
|
return '🟢', 'excellent', 'brightgreen'
|
|
elif coverage >= 60:
|
|
return '🟡', 'good', 'yellow'
|
|
elif coverage >= 40:
|
|
return '🟠', 'fair', 'orange'
|
|
else:
|
|
return '🔴', 'needs improvement', 'red'
|
|
|
|
|
|
def generate_coverage_report(coverage_file: str, output_file: str) -> None:
|
|
"""
|
|
Generate a detailed coverage report in markdown format.
|
|
|
|
Args:
|
|
coverage_file: Path to coverage.out file
|
|
output_file: Path to output markdown file
|
|
"""
|
|
import subprocess
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
['go', 'tool', 'cover', '-func', coverage_file],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True
|
|
)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error generating coverage report: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
with open(output_file, 'w') as f:
|
|
f.write("## Coverage by Package\n\n")
|
|
f.write("```\n")
|
|
f.write(result.stdout)
|
|
f.write("```\n")
|
|
|
|
|
|
def set_github_output(name: str, value: str) -> None:
|
|
"""
|
|
Set GitHub Actions output variable.
|
|
|
|
Args:
|
|
name: Output variable name
|
|
value: Output variable value
|
|
"""
|
|
github_output = os.environ.get('GITHUB_OUTPUT')
|
|
if github_output:
|
|
with open(github_output, 'a') as f:
|
|
f.write(f"{name}={value}\n")
|
|
else:
|
|
print(f"::set-output name={name}::{value}")
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
if len(sys.argv) < 2:
|
|
print("Usage: calculate_coverage.py <coverage_file> [output_file]", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
coverage_file = sys.argv[1]
|
|
output_file = sys.argv[2] if len(sys.argv) > 2 else 'coverage_report.md'
|
|
|
|
# Parse coverage data
|
|
total_coverage, package_coverage = parse_coverage_file(coverage_file)
|
|
|
|
# Get coverage status
|
|
emoji, status, badge_color = get_coverage_status(total_coverage)
|
|
|
|
# Generate detailed report
|
|
generate_coverage_report(coverage_file, output_file)
|
|
|
|
# Output results
|
|
print(f"Total Coverage: {total_coverage}%")
|
|
print(f"Status: {status}")
|
|
print(f"Badge Color: {badge_color}")
|
|
|
|
# Set GitHub Actions outputs
|
|
set_github_output('coverage', f'{total_coverage}%')
|
|
set_github_output('coverage_num', str(total_coverage))
|
|
set_github_output('status', status)
|
|
set_github_output('emoji', emoji)
|
|
set_github_output('badge_color', badge_color)
|
|
|
|
# Print package breakdown
|
|
if package_coverage:
|
|
print("\nCoverage by Package:")
|
|
for package, coverage in sorted(package_coverage.items()):
|
|
print(f" {package}: {coverage:.1f}%")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|