package main import ( "bytes" "errors" "flag" "fmt" "log" "os" "os/exec" "strings" "time" ) var ( BaseOutputFilename string ) func ProfileOutputFilename() string { return fmt.Sprintf("%s.profile", BaseOutputFilename) } func HTMLOutputFilename() string { return fmt.Sprintf("%s.html", ProfileOutputFilename()) } func GenerateGoTestArgs() []string { return []string{ "test", // run the test command in go fmt.Sprintf("-coverprofile=%s", ProfileOutputFilename()), // generate a coverprofile with the given output name "./...", // on any and all go modules here, recursively } } func GenerateGoToolCoverArgs() []string { return []string{ "tool", "cover", // go tool cover fmt.Sprintf("-html=%s", ProfileOutputFilename()), "-o", HTMLOutputFilename(), } } func GenerateCoverageReport() (string, error) { cmd := exec.Command("go", GenerateGoTestArgs()...) output, err := cmd.CombinedOutput() output = bytes.TrimSpace(output) if err != nil { return "", fmt.Errorf("error generating the coverage profile: %w\n%s", err, output) } return string(output), nil } func GenerateHTMLOutput() (string, error) { cmd := exec.Command("go", GenerateGoToolCoverArgs()...) output, err := cmd.CombinedOutput() output = bytes.TrimSpace(output) if err != nil { return "", fmt.Errorf("error generating the html output: %w\n%s", err, output) } return string(output), nil } func Clean() error { errs := make([]error, 0, 2) profileOutputFilename := ProfileOutputFilename() htmlOutputFilename := HTMLOutputFilename() if err := os.Remove(profileOutputFilename); err != nil && !errors.Is(err, os.ErrNotExist) { errs = append(errs, fmt.Errorf("error deleting %s: %w", profileOutputFilename, err)) } if err := os.Remove(htmlOutputFilename); err != nil && !errors.Is(err, os.ErrNotExist) { errs = append(errs, fmt.Errorf("error deleting %s: %w", htmlOutputFilename, err)) } return errors.Join(errs...) } func main() { programStart := time.Now() log.SetFlags(0) flag.StringVar(&BaseOutputFilename, "o", "testcoverage", "base filename used for output files (testcoverage.profile, testcoverage.html, etc)") flag.Parse() if args := flag.Args(); len(args) == 1 { if strings.EqualFold(args[0], "clean") { if err := Clean(); err != nil { log.Fatalf("error cleaning: %v\n", err) } log.Printf("finished cleaning after %v\n", time.Since(programStart)) return } } if _, err := GenerateCoverageReport(); err != nil { log.Fatalf("%v; cannot proceed\n", err) } placeholderTime := time.Now() log.Printf("wrote %s after %v\n", ProfileOutputFilename(), placeholderTime.Sub(programStart)) if _, err := GenerateHTMLOutput(); err != nil { log.Fatalf("%v; cannot proceed\n", err) } finishedTime := time.Now() log.Printf("wrote %s after %v\n", HTMLOutputFilename(), finishedTime.Sub(placeholderTime)) log.Printf("finished generating report after %v\n", time.Since(programStart)) }