Skip to main content
Glama
coverage.go6.71 kB
// filepath: c:\Users\James\Documents\go-dev-mcp\internal\testing\metrics\coverage.go package metrics import ( "encoding/json" "fmt" "io" "os" "os/exec" "path/filepath" "strings" ) // CoverageResult represents a Go test coverage profile type CoverageResult struct { TotalLines int `json:"total_lines"` CoveredLines int `json:"covered_lines"` Percentage float64 `json:"percentage"` Packages map[string]PackageCoverage `json:"packages"` } // PackageCoverage represents coverage for a single package type PackageCoverage struct { TotalLines int `json:"total_lines"` CoveredLines int `json:"covered_lines"` Percentage float64 `json:"percentage"` } // GenerateCoverageProfile creates a coverage profile for the specified packages func GenerateCoverageProfile(outputPath string, packages ...string) error { // Set default if no packages specified if len(packages) == 0 { packages = []string{"./..."} } // Prepare command args := []string{"test"} args = append(args, packages...) args = append(args, "-coverprofile="+outputPath) // Run the go test command with coverage cmd := exec.Command("go", args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return fmt.Errorf("failed to generate coverage profile: %w", err) } return nil } // AnalyzeCoverageProfile parses a coverage profile and returns statistics func AnalyzeCoverageProfile(profilePath string) (*CoverageResult, error) { // Check if the profile exists if _, err := os.Stat(profilePath); os.IsNotExist(err) { return nil, fmt.Errorf("coverage profile not found: %s", profilePath) } // Run go tool cover to get coverage stats cmd := exec.Command("go", "tool", "cover", "-func="+profilePath) output, err := cmd.Output() if err != nil { return nil, fmt.Errorf("failed to analyze coverage profile: %w", err) } // Parse the coverage output result := &CoverageResult{ Packages: make(map[string]PackageCoverage), } totalCovered, totalLines := 0, 0 lines := strings.Split(string(output), "\n") for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } // Look for the package-level metrics if strings.Contains(line, "total:") { fields := strings.Fields(line) if len(fields) >= 3 { percentStr := strings.TrimSuffix(fields[len(fields)-1], "%") var percentage float64 fmt.Sscanf(percentStr, "%f", &percentage) result.Percentage = percentage } continue } // Parse per-file coverage parts := strings.Split(line, ":") if len(parts) < 2 { continue } filePath := strings.TrimSpace(parts[0]) pkgName := extractPackageName(filePath) // Extract coverage percentage from each function funcParts := strings.Split(parts[1], "\t") if len(funcParts) < 2 { continue } coverageStr := strings.TrimSpace(funcParts[len(funcParts)-1]) if coverageStr == "-" { continue // Skip functions with no statements } coverageStr = strings.TrimSuffix(coverageStr, "%") var coverage float64 fmt.Sscanf(coverageStr, "%f", &coverage) // For simplicity, we're estimating that each function has on average 5 statements // A more precise approach would parse the actual coverage profile in detail // or use go tool cover -html to get exact statement counts estStatements := 5 estCovered := int(float64(estStatements) * coverage / 100.0) // Update package coverage pkg, exists := result.Packages[pkgName] if !exists { pkg = PackageCoverage{} } pkg.TotalLines += estStatements pkg.CoveredLines += estCovered pkg.Percentage = float64(pkg.CoveredLines) / float64(pkg.TotalLines) * 100.0 result.Packages[pkgName] = pkg // Update total coverage totalLines += estStatements totalCovered += estCovered } // Update total coverage result.TotalLines = totalLines result.CoveredLines = totalCovered if totalLines > 0 { result.Percentage = float64(totalCovered) / float64(totalLines) * 100.0 } return result, nil } // GenerateCoverageHTML generates an HTML coverage report func GenerateCoverageHTML(profilePath, htmlOutputPath string) error { cmd := exec.Command("go", "tool", "cover", "-html="+profilePath, "-o="+htmlOutputPath) return cmd.Run() } // extractPackageName extracts the package name from a file path func extractPackageName(filePath string) string { // Simplistic package name extraction - in reality, you'd need to parse the file // or use go list to get the actual package name dir := filepath.Dir(filePath) return filepath.Base(dir) } // WriteCoverageReport outputs a coverage report in JSON format func WriteCoverageReport(result *CoverageResult, outputPath string) error { jsonData, err := json.MarshalIndent(result, "", " ") if err != nil { return fmt.Errorf("failed to marshal coverage result: %w", err) } return os.WriteFile(outputPath, jsonData, 0644) } // PrintCoverageReport outputs a coverage report to the specified writer func PrintCoverageReport(w io.Writer, result *CoverageResult) { fmt.Fprintf(w, "Coverage Report:\n") fmt.Fprintf(w, "----------------\n") fmt.Fprintf(w, "Total coverage: %.2f%% (%d/%d lines)\n\n", result.Percentage, result.CoveredLines, result.TotalLines) fmt.Fprintf(w, "Package Coverage:\n") for pkgName, pkg := range result.Packages { fmt.Fprintf(w, " %s: %.2f%% (%d/%d lines)\n", pkgName, pkg.Percentage, pkg.CoveredLines, pkg.TotalLines) } } // RunCoverageReport generates and analyzes coverage for the given packages func RunCoverageReport(outputDir string, packages ...string) (*CoverageResult, error) { // Ensure output directory exists if err := os.MkdirAll(outputDir, 0755); err != nil { return nil, fmt.Errorf("failed to create output directory: %w", err) } // Generate coverage profile profilePath := filepath.Join(outputDir, "coverage.out") if err := GenerateCoverageProfile(profilePath, packages...); err != nil { return nil, err } // Analyze coverage profile result, err := AnalyzeCoverageProfile(profilePath) if err != nil { return nil, err } // Generate HTML report htmlPath := filepath.Join(outputDir, "coverage.html") if err := GenerateCoverageHTML(profilePath, htmlPath); err != nil { return nil, fmt.Errorf("failed to generate HTML report: %w", err) } // Write JSON report jsonPath := filepath.Join(outputDir, "coverage.json") if err := WriteCoverageReport(result, jsonPath); err != nil { return nil, fmt.Errorf("failed to write JSON report: %w", err) } fmt.Printf("Coverage reports generated in %s\n", outputDir) fmt.Printf("- HTML report: %s\n", htmlPath) fmt.Printf("- JSON report: %s\n", jsonPath) return result, nil }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MrFixit96/go-dev-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server