Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion internal/services/covgate/covgate.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type runner struct {
goModule func() (string, error)
goListPackages func(string) ([]string, error)
measure func(pkg string, testPaths []string) (float64, []byte, error)
prewarm func(testPaths []string) error
parallelism int
emitProgress bool
}
Expand All @@ -52,6 +53,7 @@ func Run(opts Opts) error {
goModule: gocover.GoModule,
goListPackages: gocover.GoListPackages,
measure: gocover.Measure,
prewarm: gocover.PrewarmBuild,
parallelism: effectiveParallelism(opts),
emitProgress: opts.Parallelism == 0,
}
Expand Down Expand Up @@ -101,12 +103,40 @@ func (r *runner) run(opts Opts) error {
tightnessTolerance: opts.TightnessTolerance,
}

if parallelism > 1 && len(pkgs) > 1 {
if err := r.prewarm(collectWarmPaths(pkgs, ctx)); err != nil {
return err
}
}

start := time.Now()
results := r.runPackages(pkgs, ctx, parallelism, w)
wallTime := time.Since(start)
return r.printResults(w, results, excluded, module, wallTime)
}

// collectWarmPaths returns the de-duplicated union of the test
// paths covgate will build for every package, using the same
// RelPkg + BuildTestPaths logic as checkPackage so the warm
// pass compiles exactly the set the real runs need.
func collectWarmPaths(pkgs []string, ctx checkPackageCtx) []string {
seen := make(map[string]struct{})
var paths []string
for _, pkg := range pkgs {
relPkg := gocover.RelPkg(pkg, ctx.module)
for _, p := range gocover.BuildTestPaths(
pkg, relPkg, ctx.srcPrefix, ctx.testDir,
) {
if _, ok := seen[p]; ok {
continue
}
seen[p] = struct{}{}
paths = append(paths, p)
}
}
return paths
}

// applyExclude removes packages matched by the comma-separated
// exclude patterns from pkgs. It preserves the original order of
// pkgs in both returned slices and prints a one-line notice to w
Expand All @@ -121,7 +151,7 @@ func (r *runner) applyExclude(
}

excludedSet := make(map[string]struct{})
for _, raw := range strings.Split(exclude, ",") {
for raw := range strings.SplitSeq(exclude, ",") {
entry := strings.TrimSpace(raw)
if entry == "" {
continue
Expand Down
203 changes: 201 additions & 2 deletions internal/services/covgate/covgate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -281,7 +282,7 @@ func TestRun_Exclude(t *testing.T) {
t.Errorf("expected exactly 1 SKIPPED, got %d:\n%s", got, out)
}
var skippedLine string
for _, line := range strings.Split(out, "\n") {
for line := range strings.SplitSeq(out, "\n") {
if strings.Contains(line, "pkg/b") {
skippedLine = line
break
Expand Down Expand Up @@ -318,6 +319,7 @@ func TestRun_Exclude(t *testing.T) {
return out, nil
},
measure: fakeMeasure(90.0),
prewarm: func([]string) error { return nil },
}

//nolint:exhaustruct // test uses partial initialization
Expand Down Expand Up @@ -408,6 +410,7 @@ func TestRun_Parallelism(t *testing.T) {
}, nil
},
measure: fakeMeasure(90.0),
prewarm: func([]string) error { return nil },
}

//nolint:exhaustruct // test uses partial initialization
Expand Down Expand Up @@ -489,6 +492,7 @@ func TestRun_EmitsProgress_WhenAutoParallelism(t *testing.T) {
},
measure: fakeMeasure(90.0),
emitProgress: true,
prewarm: func([]string) error { return nil },
}

//nolint:exhaustruct // test uses partial initialization
Expand Down Expand Up @@ -549,7 +553,7 @@ func TestRun_EmitsProgress_WhenAutoParallelism(t *testing.T) {
// (used to verify progress lines carry full row data, not just the
// counter).
func hasProgressLineWith(out, marker, contains string) bool {
for _, line := range strings.Split(out, "\n") {
for line := range strings.SplitSeq(out, "\n") {
if strings.Contains(line, marker) && strings.Contains(line, contains) {
return true
}
Expand All @@ -576,6 +580,7 @@ func TestRun_SuppressesProgress_WhenExplicitParallelism(t *testing.T) {
},
measure: fakeMeasure(90.0),
emitProgress: false,
prewarm: func([]string) error { return nil },
}

//nolint:exhaustruct // test uses partial initialization
Expand All @@ -601,6 +606,200 @@ func TestRun_SuppressesProgress_WhenExplicitParallelism(t *testing.T) {
}
}

func TestRun_Prewarm_InvokedOnce_WhenParallelAndMultiPkg(t *testing.T) {
tmp := t.TempDir()
t.Chdir(tmp)
for _, rel := range []string{"pkg/a", "pkg/b", "pkg/c"} {
//nolint:gosec // G301: test directory
if err := os.MkdirAll(filepath.Join(tmp, rel), 0o755); err != nil {
t.Fatal(err)
}
}

pkgs := []string{modName + "/pkg/a", modName + "/pkg/b", modName + "/pkg/c"}

var (
warmCalls int
warmPaths []string
)
prewarm := func(paths []string) error {
warmCalls++
warmPaths = paths
return nil
}

var buf bytes.Buffer
//nolint:exhaustruct // test uses partial initialization
r := runner{
goModule: func() (string, error) { return modName, nil },
goListPackages: func(string) ([]string, error) { return pkgs, nil },
measure: fakeMeasure(90.0),
prewarm: prewarm,
}

//nolint:exhaustruct // test uses partial initialization
err := r.run(Opts{Out: &buf, DefaultThreshold: 80.0, Parallelism: 3})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if warmCalls != 1 {
t.Fatalf("expected prewarm invoked exactly once, got %d", warmCalls)
}
// With empty SrcPrefix/TestDir, BuildTestPaths returns just
// the package path, so the union is the three package paths.
if len(warmPaths) != 3 {
t.Fatalf("expected 3 warm paths, got %d: %v", len(warmPaths), warmPaths)
}
for _, pkg := range pkgs {
if !slices.Contains(warmPaths, pkg) {
t.Errorf("warm paths missing %q: %v", pkg, warmPaths)
}
}
out := buf.String()
for _, want := range []string{"pkg/a", "pkg/b", "pkg/c", "Total time:"} {
if !strings.Contains(out, want) {
t.Errorf("output missing %q:\n%s", want, out)
}
}
}

func TestCollectWarmPaths_DeduplicatesSharedPaths(t *testing.T) {
// With empty SrcPrefix/TestDir, BuildTestPaths returns just the
// package path, so a duplicated package in the input must collapse
// to a single warm path (exercising the seen-set skip branch).
pkgA := modName + "/pkg/a"
pkgB := modName + "/pkg/b"
pkgs := []string{pkgA, pkgB, pkgA}

//nolint:exhaustruct // only module is needed for path collection
got := collectWarmPaths(pkgs, checkPackageCtx{module: modName})

want := []string{pkgA, pkgB}
if len(got) != len(want) {
t.Fatalf("expected %d deduplicated paths, got %d: %v", len(want), len(got), got)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("path %d: expected %q, got %q (full: %v)", i, want[i], got[i], got)
}
}
}

func TestRun_Prewarm_Skipped_WhenSinglePackageOrSerial(t *testing.T) {
cases := []struct {
name string
pkgs []string
parallelism int
}{
{
name: "SingleParallelism",
pkgs: []string{
modName + "/pkg/a",
modName + "/pkg/b",
modName + "/pkg/c",
},
parallelism: 1,
},
{
name: "SinglePackage",
pkgs: []string{modName + "/pkg/a"},
parallelism: 4,
},
{
name: "SinglePackageSerial",
pkgs: []string{modName + "/pkg/a"},
parallelism: 1,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
tmp := t.TempDir()
t.Chdir(tmp)
for _, pkg := range tc.pkgs {
rel := gocover.RelPkg(pkg, modName)
//nolint:gosec // G301: test directory
if err := os.MkdirAll(filepath.Join(tmp, rel), 0o755); err != nil {
t.Fatal(err)
}
}

warmCalls := 0
var buf bytes.Buffer
//nolint:exhaustruct // test uses partial initialization
r := runner{
goModule: func() (string, error) { return modName, nil },
goListPackages: func(string) ([]string, error) { return tc.pkgs, nil },
measure: fakeMeasure(90.0),
prewarm: func([]string) error { warmCalls++; return nil },
}

//nolint:exhaustruct // test uses partial initialization
err := r.run(Opts{
Out: &buf,
DefaultThreshold: 80.0,
Parallelism: tc.parallelism,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if warmCalls != 0 {
t.Errorf("expected prewarm skipped, got %d calls", warmCalls)
}
out := buf.String()
rel := gocover.RelPkg(tc.pkgs[0], modName)
if !strings.Contains(out, rel) {
t.Errorf("output missing per-package row %q:\n%s", rel, out)
}
})
}
}

func TestRun_Prewarm_ErrorPropagates(t *testing.T) {
tmp := t.TempDir()
t.Chdir(tmp)
for _, rel := range []string{"pkg/a", "pkg/b", "pkg/c"} {
//nolint:gosec // G301: test directory
if err := os.MkdirAll(filepath.Join(tmp, rel), 0o755); err != nil {
t.Fatal(err)
}
}

measureCalls := 0
var buf bytes.Buffer
//nolint:exhaustruct // test uses partial initialization
r := runner{
goModule: func() (string, error) { return modName, nil },
goListPackages: func(string) ([]string, error) {
return []string{
modName + "/pkg/a",
modName + "/pkg/b",
modName + "/pkg/c",
}, nil
},
measure: func(string, []string) (float64, []byte, error) {
measureCalls++
return 90.0, nil, nil
},
prewarm: func([]string) error { return fmt.Errorf("prewarm build: boom") },
}

//nolint:exhaustruct // test uses partial initialization
err := r.run(Opts{Out: &buf, DefaultThreshold: 80.0, Parallelism: 3})
if err == nil {
t.Fatal("expected error from prewarm build")
}
if !strings.Contains(err.Error(), "prewarm build") {
t.Errorf("error missing 'prewarm build': %v", err)
}
if measureCalls != 0 {
t.Errorf("expected measure never invoked, got %d calls", measureCalls)
}
if strings.Contains(buf.String(), "Total time:") {
t.Errorf("expected run to return before printing results:\n%s", buf.String())
}
}

func TestMeasure_Pass(t *testing.T) {
tmp := t.TempDir()
t.Chdir(tmp)
Expand Down
2 changes: 1 addition & 1 deletion internal/services/gocover/.covgate
Original file line number Diff line number Diff line change
@@ -1 +1 @@
92.1
93.3
22 changes: 22 additions & 0 deletions internal/services/gocover/gocover.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,28 @@ func MeasureWithEnv(
return coverage, output, nil
}

// PrewarmBuild compiles the test binaries for the given test
// paths without running any tests, populating the shared Go
// build cache so that subsequent parallel `go test` runs do
// not stampede on the same shared dependency compiles. It runs
// a single `go test -run='^$' <paths...>` (no coverage
// instrumentation) so Go's build planner deduplicates the
// shared compiles in one coherent pass. A genuine build error
// is returned (with combined output) so callers can surface it.
func PrewarmBuild(testPaths []string) error {
if len(testPaths) == 0 {
return nil
}
args := make([]string, 0, 2+len(testPaths))
args = append(args, "test", "-run=^$")
args = append(args, testPaths...)
cmd := cmdutil.GoCommand(args...)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("prewarm build: %w\n%s", err, out)
}
return nil
}

// ExecOutput runs a command with GOWORK=off and returns
// its stdout.
func ExecOutput(errW io.Writer, name string, args ...string) (string, error) {
Expand Down
Loading