ajhahn.de
← eeco
Go 182 lines
//go:build bench

package workflow

import (
	"fmt"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"testing"
	"time"

	"github.com/ajhahnde/eeco/internal/config"
)

// TestMain pins the user-global config dir to an empty temp dir so the
// global config layer is a hermetic no-op under the bench fixture.
func TestMain(m *testing.M) {
	gdir, err := os.MkdirTemp("", "eeco-global-")
	if err != nil {
		panic(err)
	}
	os.Setenv(config.GlobalConfigEnv, gdir)
	code := m.Run()
	os.RemoveAll(gdir)
	os.Exit(code)
}

const (
	benchFileCount = 50_000
	// benchSeed is fixed so the fixture is byte-identical every run; only
	// the wall-clock budget moves between machines.
	benchSeed    = 0xE6C0
	benchMaxWall = 5 * time.Second
)

// benchFixturePath is <eeco-repo-root>/dist/bench-fixture, derived from
// the test source location so the bench is invariant to CWD. The
// directory is gitignored via the repo's existing `/dist/` rule and is
// removed by the `make bench` target after the run.
func benchFixturePath(tb testing.TB) string {
	tb.Helper()
	_, here, _, ok := runtime.Caller(0)
	if !ok {
		tb.Fatal("runtime.Caller failed")
	}
	root := filepath.Clean(filepath.Join(filepath.Dir(here), "..", ".."))
	return filepath.Join(root, "dist", "bench-fixture")
}

// ensureFixture writes benchFileCount Go-like files into
// <bench-fixture>/repo, distributed across 250 packages of 200 files
// each. The marker file under .eeco-fixture-built prevents a rebuild
// on a second invocation in the same `make bench` run.
func ensureFixture(b *testing.B, dir string) {
	b.Helper()
	repo := filepath.Join(dir, "repo")
	marker := filepath.Join(repo, ".eeco-fixture-built")
	if _, err := os.Stat(marker); err == nil {
		return
	}
	if err := os.MkdirAll(repo, 0o755); err != nil {
		b.Fatal(err)
	}
	rng := rand.New(rand.NewSource(benchSeed))
	for i := range benchFileCount {
		sub := fmt.Sprintf("pkg%03d", i/200)
		path := filepath.Join(repo, sub, fmt.Sprintf("file%05d.go", i))
		if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
			b.Fatal(err)
		}
		body := fmt.Sprintf("package %s\n\n// file %d rnd=%d\nfunc F%d() {}\n",
			sub, i, rng.Int63(), i)
		if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
			b.Fatal(err)
		}
	}
	if err := os.WriteFile(marker, []byte("ok"), 0o644); err != nil {
		b.Fatal(err)
	}
}

// ensureGitAdded initialises <repo> as a git repo and `git add -A`s the
// full tree. Required by leak-guard's `gitx.TrackedFiles` probe.
// Cached by a marker so successive Run calls inside one benchmark do
// not redo the index work.
func ensureGitAdded(b *testing.B, repo string) {
	b.Helper()
	marker := filepath.Join(repo, ".eeco-fixture-git")
	if _, err := os.Stat(marker); err == nil {
		return
	}
	for _, argv := range [][]string{
		{"git", "init", "-q"},
		{"git", "config", "user.email", "bench@local"},
		{"git", "config", "user.name", "bench"},
		{"git", "add", "-A"},
	} {
		cmd := exec.Command(argv[0], argv[1:]...)
		cmd.Dir = repo
		if out, err := cmd.CombinedOutput(); err != nil {
			b.Fatalf("%v in %s: %v\n%s", argv, repo, err, out)
		}
	}
	if err := os.WriteFile(marker, []byte("ok"), 0o644); err != nil {
		b.Fatal(err)
	}
}

func benchConfig(b *testing.B, repo string) *config.Config {
	b.Helper()
	cfg, err := config.Load(repo, config.DefaultWorkspace)
	if err != nil {
		b.Fatalf("config.Load(%s): %v", repo, err)
	}
	return cfg
}

func BenchmarkCommentHygiene_50k(b *testing.B) {
	dir := benchFixturePath(b)
	ensureFixture(b, dir)
	repo := filepath.Join(dir, "repo")
	if _, err := os.Stat(filepath.Join(repo, ".git")); os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Join(repo, ".git"), 0o755); err != nil {
			b.Fatal(err)
		}
	}
	cfg := benchConfig(b, repo)
	env := Env{Config: cfg}

	b.ResetTimer()
	start := time.Now()
	for range b.N {
		res, err := (commentHygiene{}).Run(env)
		if err != nil {
			b.Fatalf("comment-hygiene: %v", err)
		}
		if res.Code != CodeClean {
			b.Fatalf("comment-hygiene unexpectedly flagged the fixture: %+v", res)
		}
	}
	wall := time.Since(start)
	if b.N > 0 {
		wall /= time.Duration(b.N)
	}
	b.ReportMetric(float64(wall.Milliseconds()), "ms/scan")
	if wall > benchMaxWall {
		b.Fatalf("comment-hygiene wall %s exceeds %s budget on %d files", wall, benchMaxWall, benchFileCount)
	}
}

func BenchmarkLeakGuard_50k(b *testing.B) {
	dir := benchFixturePath(b)
	ensureFixture(b, dir)
	repo := filepath.Join(dir, "repo")
	ensureGitAdded(b, repo)
	cfg := benchConfig(b, repo)
	env := Env{Config: cfg}

	b.ResetTimer()
	start := time.Now()
	for range b.N {
		res, err := (leakGuard{}).Run(env)
		if err != nil {
			b.Fatalf("leak-guard: %v", err)
		}
		if res.Code != CodeClean {
			b.Fatalf("leak-guard unexpectedly flagged the fixture: %+v", res)
		}
	}
	wall := time.Since(start)
	if b.N > 0 {
		wall /= time.Duration(b.N)
	}
	b.ReportMetric(float64(wall.Milliseconds()), "ms/scan")
	if wall > benchMaxWall {
		b.Fatalf("leak-guard wall %s exceeds %s budget on %d files", wall, benchMaxWall, benchFileCount)
	}
}