a73x

internal/covsnap/covsnap.go

Ref:   Size: 1.8 KiB   History

// Package covsnap lets a long-running binary flush integration-coverage
// counters on demand. It is a no-op unless built with `go build -cover` AND
// GOCOVERDIR is set, so it costs nothing in a normal build.
package covsnap

import (
	"context"
	"log/slog"
	"os"
	"os/signal"
	"runtime/coverage"
	"sync"
	"syscall"
)

// write is a seam for tests; production writes real coverage data.
var write = func(dir string) error {
	if err := coverage.WriteMetaDir(dir); err != nil {
		return err
	}
	return coverage.WriteCountersDir(dir)
}

// Install starts a goroutine that, on each SIGUSR1, snapshots coverage into
// $GOCOVERDIR. Unset GOCOVERDIR -> returns without registering anything.
//
// The returned stop tears the handler down synchronously: it unregisters the
// signal and blocks until the goroutine has exited, so no handler outlives the
// caller. Production wires this to the process lifetime and lets ctx end it, so
// it ignores the return; a test that installs and uninstalls in the same
// process must call stop, or its handler leaks into the next test and a stray
// SIGUSR1 races the package-level write seam. stop is idempotent.
func Install(ctx context.Context) (stop func()) {
	dir := os.Getenv("GOCOVERDIR")
	if dir == "" {
		return func() {}
	}
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGUSR1)
	quit := make(chan struct{})
	done := make(chan struct{})
	go func() {
		defer close(done)
		defer signal.Stop(ch)
		for {
			select {
			case <-ctx.Done():
				return
			case <-quit:
				return
			case <-ch:
				if err := write(dir); err != nil {
					slog.Warn("covsnap: write failed", "dir", dir, "err", err)
					continue
				}
				slog.Info("covsnap: coverage written", "dir", dir)
			}
		}
	}()
	var once sync.Once
	return func() {
		once.Do(func() {
			signal.Stop(ch)
			close(quit)
		})
		<-done
	}
}