diff --git a/magefiles/go.mod b/magefiles/go.mod index e7b53c4063..887f3c00fa 100644 --- a/magefiles/go.mod +++ b/magefiles/go.mod @@ -7,6 +7,7 @@ require ( github.com/bufbuild/buf v1.35.1 github.com/ecordell/optgen v0.0.9 github.com/envoyproxy/protoc-gen-validate v1.0.4 + github.com/google/uuid v1.1.2 github.com/magefile/mage v1.15.0 github.com/planetscale/vtprotobuf v0.6.1-0.20240409071808-615f978279ca golang.org/x/tools v0.22.0 diff --git a/magefiles/go.sum b/magefiles/go.sum index a7152f81fe..81e96a7230 100644 --- a/magefiles/go.sum +++ b/magefiles/go.sum @@ -221,6 +221,7 @@ github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0Z github.com/google/pprof v0.0.0-20240622144329-c177fd99eaa9 h1:ouFdLLCOyCfnxGpQTMZKHLyHr/D1GFbQzEsJxumO16E= github.com/google/pprof v0.0.0-20240622144329-c177fd99eaa9/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= diff --git a/magefiles/test.go b/magefiles/test.go index 2a3d3377ae..242b7186ac 100644 --- a/magefiles/test.go +++ b/magefiles/test.go @@ -3,6 +3,7 @@ package main import ( + "context" "fmt" "os" "strings" @@ -19,15 +20,29 @@ var emptyEnv map[string]string func (t Test) All() error { ds := Testds{} c := Testcons{} - mg.Deps(t.Unit, t.Integration, t.Steelthread, t.Image, t.Analyzers, + ctx := context.Background() + cover := false + for _, arg := range os.Args { + if arg == "-cover=true" { + cover = true + break + } + } + ctx = context.WithValue(ctx, "cover", cover) + mg.CtxDeps(ctx, t.Unit, t.Integration, t.Steelthread, t.Image, t.Analyzers, ds.Crdb, ds.Postgres, ds.Spanner, ds.Mysql, c.Crdb, c.Spanner, c.Postgres, c.Mysql) + return nil } +func (t Test) Combine() error { + return combineCoverage() +} + // UnitCover Runs the unit tests and generates a coverage report -func (t Test) UnitCover() error { - if err := t.unit(true); err != nil { +func (t Test) UnitCover(ctx context.Context) error { + if err := t.unit(ctx, true); err != nil { return err } fmt.Println("Running coverage...") @@ -35,35 +50,32 @@ func (t Test) UnitCover() error { } // Unit Runs the unit tests -func (t Test) Unit() error { - return t.unit(false) +func (t Test) Unit(ctx context.Context) error { + return t.unit(ctx, false) } -func (Test) unit(coverage bool) error { +func (Test) unit(ctx context.Context, coverage bool) error { fmt.Println("running unit tests") args := []string{"-tags", "ci,skipintegrationtests", "-race", "-timeout", "10m", "-count=1"} - if coverage { - args = append(args, "-covermode=atomic", "-coverprofile=coverage.txt") - } - return goTest("./...", args...) + return goTest(ctx, "./...", args...) } // Image Run tests that run the built image -func (Test) Image() error { +func (Test) Image(ctx context.Context) error { mg.Deps(Build{}.Testimage) - return goDirTest("./cmd/spicedb", "./...", "-tags", "docker,image") + return goDirTest(ctx, "./cmd/spicedb", "./...", "-tags", "docker,image") } // Integration Run integration tests -func (Test) Integration() error { +func (Test) Integration(ctx context.Context) error { mg.Deps(checkDocker) - return goTest("./internal/services/integrationtesting/...", "-tags", "ci,docker", "-timeout", "15m") + return goTest(ctx, "./internal/services/integrationtesting/...", "-tags", "ci,docker", "-timeout", "15m") } // Steelthread Run steelthread tests -func (Test) Steelthread() error { +func (Test) Steelthread(ctx context.Context) error { fmt.Println("running steel thread tests") - return goTest("./internal/services/steelthreadtesting/...", "-tags", "steelthread,docker,image", "-timeout", "15m", "-v") + return goTest(ctx, "./internal/services/steelthreadtesting/...", "-tags", "steelthread,docker,image", "-timeout", "15m", "-v") } // RegenSteelthread Regenerate the steelthread tests @@ -75,8 +87,8 @@ func (Test) RegenSteelthread() error { } // Analyzers Run the analyzer unit tests -func (Test) Analyzers() error { - return goDirTest("./tools/analyzers", "./...") +func (Test) Analyzers(ctx context.Context) error { + return goDirTest(ctx, "./tools/analyzers", "./...") } // Wasm Run wasm browser tests @@ -95,99 +107,99 @@ func (Test) Wasm() error { type Testds mg.Namespace // Crdb Run datastore tests for crdb -func (tds Testds) Crdb() error { - return tds.crdb("") +func (tds Testds) Crdb(ctx context.Context) error { + return tds.crdb(ctx, "") } -func (tds Testds) CrdbVer(version string) error { - return tds.crdb(version) +func (tds Testds) CrdbVer(ctx context.Context, version string) error { + return tds.crdb(ctx, version) } -func (Testds) crdb(version string) error { - return datastoreTest("crdb", map[string]string{ +func (Testds) crdb(ctx context.Context, version string) error { + return datastoreTest(ctx, "crdb", map[string]string{ "CRDB_TEST_VERSION": version, }) } // Spanner Run datastore tests for spanner -func (Testds) Spanner() error { - return datastoreTest("spanner", emptyEnv) +func (Testds) Spanner(ctx context.Context) error { + return datastoreTest(ctx, "spanner", emptyEnv) } // Postgres Run datastore tests for postgres -func (tds Testds) Postgres() error { - return tds.postgres("") +func (tds Testds) Postgres(ctx context.Context) error { + return tds.postgres(ctx, "") } -func (tds Testds) PostgresVer(version string) error { - return tds.postgres(version) +func (tds Testds) PostgresVer(ctx context.Context, version string) error { + return tds.postgres(ctx, version) } -func (Testds) postgres(version string) error { - return datastoreTest("postgres", map[string]string{ +func (Testds) postgres(ctx context.Context, version string) error { + return datastoreTest(ctx, "postgres", map[string]string{ "POSTGRES_TEST_VERSION": version, }, "postgres") } // Pgbouncer Run datastore tests for postgres with Pgbouncer -func (tds Testds) Pgbouncer() error { - return tds.pgbouncer("") +func (tds Testds) Pgbouncer(ctx context.Context) error { + return tds.pgbouncer(ctx, "") } -func (tds Testds) PgbouncerVer(version string) error { - return tds.pgbouncer(version) +func (tds Testds) PgbouncerVer(ctx context.Context, version string) error { + return tds.pgbouncer(ctx, version) } -func (Testds) pgbouncer(version string) error { - return datastoreTest("postgres", map[string]string{ +func (Testds) pgbouncer(ctx context.Context, version string) error { + return datastoreTest(ctx, "postgres", map[string]string{ "POSTGRES_TEST_VERSION": version, }, "pgbouncer") } // Mysql Run datastore tests for mysql -func (Testds) Mysql() error { - return datastoreTest("mysql", emptyEnv) +func (Testds) Mysql(ctx context.Context) error { + return datastoreTest(ctx, "mysql", emptyEnv) } -func datastoreTest(datastore string, env map[string]string, tags ...string) error { +func datastoreTest(ctx context.Context, datastore string, env map[string]string, tags ...string) error { mergedTags := append([]string{"ci", "docker"}, tags...) tagString := strings.Join(mergedTags, ",") mg.Deps(checkDocker) - return goDirTestWithEnv(".", fmt.Sprintf("./internal/datastore/%s/...", datastore), env, "-tags", tagString, "-timeout", "10m") + return goDirTestWithEnv(ctx, ".", fmt.Sprintf("./internal/datastore/%s/...", datastore), env, "-tags", tagString, "-timeout", "10m") } type Testcons mg.Namespace // Crdb Run consistency tests for crdb -func (tc Testcons) Crdb() error { - return tc.crdb("") +func (tc Testcons) Crdb(ctx context.Context) error { + return tc.crdb(ctx, "") } -func (tc Testcons) CrdbVer(version string) error { - return tc.crdb(version) +func (tc Testcons) CrdbVer(ctx context.Context, version string) error { + return tc.crdb(ctx, version) } -func (Testcons) crdb(version string) error { - return consistencyTest("crdb", map[string]string{ +func (Testcons) crdb(ctx context.Context, version string) error { + return consistencyTest(ctx, "crdb", map[string]string{ "CRDB_TEST_VERSION": version, }) } // Spanner Run consistency tests for spanner -func (Testcons) Spanner() error { - return consistencyTest("spanner", emptyEnv) +func (Testcons) Spanner(ctx context.Context) error { + return consistencyTest(ctx, "spanner", emptyEnv) } -func (tc Testcons) Postgres() error { - return tc.postgres("") +func (tc Testcons) Postgres(ctx context.Context) error { + return tc.postgres(ctx, "") } -func (tc Testcons) PostgresVer(version string) error { - return tc.postgres(version) +func (tc Testcons) PostgresVer(ctx context.Context, version string) error { + return tc.postgres(ctx, version) } -func (Testcons) postgres(version string) error { - return consistencyTest("postgres", map[string]string{ +func (Testcons) postgres(ctx context.Context, version string) error { + return consistencyTest(ctx, "postgres", map[string]string{ "POSTGRES_TEST_VERSION": version, }) } @@ -205,13 +217,13 @@ func (Testcons) PgbouncerVer(version string) error { } // Mysql Run consistency tests for mysql -func (Testcons) Mysql() error { - return consistencyTest("mysql", emptyEnv) +func (Testcons) Mysql(ctx context.Context) error { + return consistencyTest(ctx, "mysql", emptyEnv) } -func consistencyTest(datastore string, env map[string]string) error { +func consistencyTest(ctx context.Context, datastore string, env map[string]string) error { mg.Deps(checkDocker) - return goDirTestWithEnv(".", "./internal/services/integrationtesting/...", + return goDirTestWithEnv(ctx, ".", "./internal/services/integrationtesting/...", env, "-tags", "ci,docker,datastoreconsistency", "-timeout", "10m", diff --git a/magefiles/util.go b/magefiles/util.go index 1834decb58..9dd5f63b1c 100644 --- a/magefiles/util.go +++ b/magefiles/util.go @@ -3,30 +3,59 @@ package main import ( + "context" "fmt" "io" "log" "os" "os/exec" + "path/filepath" "strings" + "github.com/google/uuid" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" ) // run go test in the root -func goTest(path string, args ...string) error { - return goDirTest(".", path, args...) +func goTest(ctx context.Context, path string, args ...string) error { + return goDirTest(ctx, ".", path, args...) } // run go test in a directory -func goDirTest(dir string, path string, args ...string) error { - testArgs := append([]string{"test", "-failfast", "-count=1"}, args...) +func goDirTest(ctx context.Context, dir string, path string, args ...string) error { + testArgs := append([]string{ + "test", + "-failfast", + "-count=1", + }, args...) + if cover, _ := ctx.Value("cover").(bool); cover { + if err := os.MkdirAll("coverage", 0o755); err != nil { + return fmt.Errorf("failed to create coverage directory: %w", err) + } + testArgs = append(testArgs, []string{ + "-covermode=atomic", + fmt.Sprintf("-coverprofile=coverage-%s.txt", uuid.New().String()), + }...) + } return RunSh(goCmdForTests(), WithV(), WithDir(dir), WithArgs(testArgs...))(path) } -func goDirTestWithEnv(dir string, path string, env map[string]string, args ...string) error { - testArgs := append([]string{"test", "-failfast", "-count=1"}, args...) +func goDirTestWithEnv(ctx context.Context, dir string, path string, env map[string]string, args ...string) error { + testArgs := append([]string{ + "test", + "-failfast", + "-count=1", + }, args...) + if cover, _ := ctx.Value("cover").(bool); cover { + if err := os.MkdirAll("coverage", 0o755); err != nil { + return fmt.Errorf("failed to create coverage directory: %w", err) + } + testArgs = append(testArgs, []string{ + "-covermode=atomic", + fmt.Sprintf("-coverprofile=coverage-%s.txt", uuid.New().String()), + }...) + } return RunSh(goCmdForTests(), WithV(), WithDir(dir), WithEnv(env), WithArgs(testArgs...))(path) } @@ -210,3 +239,29 @@ func run(dir string, env map[string]string, stdout, stderr io.Writer, cmd string err = c.Run() return sh.CmdRan(err), sh.ExitStatus(err), err } + +func combineCoverage() error { + files, err := filepath.Glob("coverage-*.txt") + if err != nil { + return err + } + if len(files) == 0 { + return fmt.Errorf("no coverage files found") + } + + f, err := os.Create("coverage.txt") + if err != nil { + return err + } + defer f.Close() + + args := []string{"run", "github.com/wadey/gocovmerge@latest"} + args = append(args, files...) + + err = RunSh(goCmdForTests(), WithV(), WithStdout(f))(args...) + if err != nil { + return err + } + + return nil +}