Skip to content

Commit d18ad82

Browse files
0xpppppclaude
andcommitted
Merge upstream/dev: 16个重要更新
包含以下关键更新: - NoFxAiOS#769: Funding Rate缓存机制(减少90% API调用) - NoFxAiOS#819: 修复AI决策盈亏计算未考虑杠杆 - NoFxAiOS#651: 修复历史最高收益率未传递给AI - NoFxAiOS#817: 修复Docker重启数据丢失问题 - NoFxAiOS#823: 添加单元测试和CI覆盖率 - NoFxAiOS#784: Hook模块解耦 - 多个安全和bug修复 冲突解决: - LoginPage.tsx: 合并了两边的import语句 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
2 parents 60828c4 + ee0e31a commit d18ad82

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+6118
-331
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Go Test Coverage
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
branches:
7+
- dev
8+
- main
9+
push:
10+
branches:
11+
- dev
12+
- main
13+
14+
permissions:
15+
contents: read
16+
pull-requests: write
17+
18+
jobs:
19+
test-coverage:
20+
name: Go Unit Tests & Coverage
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
pull-requests: write
25+
steps:
26+
- name: Checkout code
27+
uses: actions/checkout@v4
28+
29+
- name: Set up Go
30+
uses: actions/setup-go@v5
31+
with:
32+
go-version: '1.25'
33+
34+
- name: Set up Python
35+
uses: actions/setup-python@v5
36+
with:
37+
python-version: '3.11'
38+
39+
- name: Install Python dependencies
40+
run: |
41+
python -m pip install --upgrade pip
42+
pip install -r .github/workflows/scripts/requirements.txt
43+
44+
- name: Cache Go modules
45+
uses: actions/cache@v4
46+
with:
47+
path: ~/go/pkg/mod
48+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
49+
restore-keys: |
50+
${{ runner.os }}-go-
51+
52+
- name: Download dependencies
53+
run: go mod download
54+
55+
- name: Verify Go coverage tool
56+
run: |
57+
go tool cover -h || echo "Warning: go tool cover not available"
58+
59+
- name: Run tests with coverage
60+
env:
61+
DATA_ENCRYPTION_KEY: "test-encryption-key-for-ci-only-not-production"
62+
run: |
63+
go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
64+
65+
- name: Calculate coverage and generate report
66+
id: coverage
67+
run: |
68+
chmod +x .github/workflows/scripts/calculate_coverage.py
69+
python .github/workflows/scripts/calculate_coverage.py coverage.out coverage_report.md
70+
71+
- name: Comment PR with coverage
72+
if: github.event_name == 'pull_request'
73+
continue-on-error: true
74+
env:
75+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76+
run: |
77+
chmod +x .github/workflows/scripts/comment_pr.py
78+
python .github/workflows/scripts/comment_pr.py \
79+
${{ github.event.pull_request.number }} \
80+
"${{ steps.coverage.outputs.coverage }}" \
81+
"${{ steps.coverage.outputs.emoji }}" \
82+
"${{ steps.coverage.outputs.status }}" \
83+
"${{ steps.coverage.outputs.badge_color }}" \
84+
coverage_report.md
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Calculate Go test coverage and generate reports.
4+
5+
This script parses the coverage.out file generated by `go test -coverprofile`,
6+
extracts coverage statistics, and generates formatted reports.
7+
"""
8+
9+
import sys
10+
import re
11+
import os
12+
from typing import Dict, List, Tuple
13+
14+
15+
def parse_coverage_file(coverage_file: str) -> Tuple[float, Dict[str, float]]:
16+
"""
17+
Parse coverage output file and extract coverage data.
18+
19+
Args:
20+
coverage_file: Path to coverage.out file
21+
22+
Returns:
23+
Tuple of (total_coverage, package_coverage_dict)
24+
"""
25+
if not os.path.exists(coverage_file):
26+
print(f"Error: Coverage file {coverage_file} not found", file=sys.stderr)
27+
sys.exit(1)
28+
29+
# Run go tool cover to get coverage data
30+
import subprocess
31+
32+
try:
33+
result = subprocess.run(
34+
['go', 'tool', 'cover', '-func', coverage_file],
35+
capture_output=True,
36+
text=True,
37+
check=True
38+
)
39+
except subprocess.CalledProcessError as e:
40+
print(f"Error running go tool cover: {e}", file=sys.stderr)
41+
sys.exit(1)
42+
43+
lines = result.stdout.strip().split('\n')
44+
package_coverage = {}
45+
total_coverage = 0.0
46+
47+
for line in lines:
48+
# Skip empty lines
49+
if not line.strip():
50+
continue
51+
52+
# Check for total coverage line
53+
if line.startswith('total:'):
54+
# Extract percentage from "total: (statements) XX.X%"
55+
match = re.search(r'(\d+\.\d+)%', line)
56+
if match:
57+
total_coverage = float(match.group(1))
58+
continue
59+
60+
# Parse package/file coverage
61+
# Format: "package/file.go:function statements coverage%"
62+
parts = line.split()
63+
if len(parts) >= 3:
64+
file_path = parts[0]
65+
coverage_str = parts[-1]
66+
67+
# Extract package name from file path
68+
package = file_path.split(':')[0]
69+
package_name = '/'.join(package.split('/')[:-1]) if '/' in package else package
70+
71+
# Extract coverage percentage
72+
match = re.search(r'(\d+\.\d+)%', coverage_str)
73+
if match:
74+
coverage_pct = float(match.group(1))
75+
76+
# Aggregate by package
77+
if package_name not in package_coverage:
78+
package_coverage[package_name] = []
79+
package_coverage[package_name].append(coverage_pct)
80+
81+
# Calculate average coverage per package
82+
package_avg = {
83+
pkg: sum(coverages) / len(coverages)
84+
for pkg, coverages in package_coverage.items()
85+
}
86+
87+
return total_coverage, package_avg
88+
89+
90+
def get_coverage_status(coverage: float) -> Tuple[str, str, str]:
91+
"""
92+
Get coverage status based on percentage.
93+
94+
Args:
95+
coverage: Coverage percentage
96+
97+
Returns:
98+
Tuple of (emoji, status_text, badge_color)
99+
"""
100+
if coverage >= 80:
101+
return '🟢', 'excellent', 'brightgreen'
102+
elif coverage >= 60:
103+
return '🟡', 'good', 'yellow'
104+
elif coverage >= 40:
105+
return '🟠', 'fair', 'orange'
106+
else:
107+
return '🔴', 'needs improvement', 'red'
108+
109+
110+
def generate_coverage_report(coverage_file: str, output_file: str) -> None:
111+
"""
112+
Generate a detailed coverage report in markdown format.
113+
114+
Args:
115+
coverage_file: Path to coverage.out file
116+
output_file: Path to output markdown file
117+
"""
118+
import subprocess
119+
120+
try:
121+
result = subprocess.run(
122+
['go', 'tool', 'cover', '-func', coverage_file],
123+
capture_output=True,
124+
text=True,
125+
check=True
126+
)
127+
except subprocess.CalledProcessError as e:
128+
print(f"Error generating coverage report: {e}", file=sys.stderr)
129+
sys.exit(1)
130+
131+
with open(output_file, 'w') as f:
132+
f.write("## Coverage by Package\n\n")
133+
f.write("```\n")
134+
f.write(result.stdout)
135+
f.write("```\n")
136+
137+
138+
def set_github_output(name: str, value: str) -> None:
139+
"""
140+
Set GitHub Actions output variable.
141+
142+
Args:
143+
name: Output variable name
144+
value: Output variable value
145+
"""
146+
github_output = os.environ.get('GITHUB_OUTPUT')
147+
if github_output:
148+
with open(github_output, 'a') as f:
149+
f.write(f"{name}={value}\n")
150+
else:
151+
print(f"::set-output name={name}::{value}")
152+
153+
154+
def main():
155+
"""Main entry point."""
156+
if len(sys.argv) < 2:
157+
print("Usage: calculate_coverage.py <coverage_file> [output_file]", file=sys.stderr)
158+
sys.exit(1)
159+
160+
coverage_file = sys.argv[1]
161+
output_file = sys.argv[2] if len(sys.argv) > 2 else 'coverage_report.md'
162+
163+
# Parse coverage data
164+
total_coverage, package_coverage = parse_coverage_file(coverage_file)
165+
166+
# Get coverage status
167+
emoji, status, badge_color = get_coverage_status(total_coverage)
168+
169+
# Generate detailed report
170+
generate_coverage_report(coverage_file, output_file)
171+
172+
# Output results
173+
print(f"Total Coverage: {total_coverage}%")
174+
print(f"Status: {status}")
175+
print(f"Badge Color: {badge_color}")
176+
177+
# Set GitHub Actions outputs
178+
set_github_output('coverage', f'{total_coverage}%')
179+
set_github_output('coverage_num', str(total_coverage))
180+
set_github_output('status', status)
181+
set_github_output('emoji', emoji)
182+
set_github_output('badge_color', badge_color)
183+
184+
# Print package breakdown
185+
if package_coverage:
186+
print("\nCoverage by Package:")
187+
for package, coverage in sorted(package_coverage.items()):
188+
print(f" {package}: {coverage:.1f}%")
189+
190+
191+
if __name__ == '__main__':
192+
main()

0 commit comments

Comments
 (0)