commit 5e202c488bb7acd4043444e670b6de14748d4c17 Author: William Dillon Date: Tue Nov 25 20:47:31 2025 -0500 first commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fd97746 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module code.wmdillon.com/wmdillon/gotestprofile + +go 1.25.3 diff --git a/main.go b/main.go new file mode 100644 index 0000000..7a4296b --- /dev/null +++ b/main.go @@ -0,0 +1,107 @@ +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)) +}