From ecae1552f303012c3afb58e8df8ea00a637a5768 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Tue, 6 Feb 2024 11:26:24 +1100 Subject: [PATCH 1/2] Work around ARG_MAX OS limits This is not an ideal solution: we can probably figure out the limit in advance and then chunk the test cases once, rather than making slices repeatedly while recursing and hitting the same E2BIG errno repeatedly. --- internal/runner/rspec.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/internal/runner/rspec.go b/internal/runner/rspec.go index afb12a1a..68b577cb 100644 --- a/internal/runner/rspec.go +++ b/internal/runner/rspec.go @@ -1,22 +1,22 @@ package runner import ( + "errors" "fmt" - "log" "math" "os" "os/exec" "path/filepath" "strings" + "syscall" "time" "github.com/buildkite/test-splitter/internal/api" ) -type Rspec struct { -} +type Rspec struct{} -func (Rspec) GetFiles() []string { +func (Rspec) FindFiles() ([]string, error) { var files []string // Use filepath.Walk to traverse the directory recursively @@ -34,24 +34,39 @@ func (Rspec) GetFiles() []string { // Handle potential error from filepath.Walk if err != nil { - log.Fatal("Error when getting files: ", err) + return nil, fmt.Errorf("walking to find files: %w", err) } - return files + return files, nil } -func (Rspec) Run(testCases []string) error { +func (r Rspec) Run(testCases []string) error { args := []string{"--options", ".rspec.ci"} args = append(args, testCases...) + // TODO: Figure out in advance whether we'll hit ARG_MAX and make args + // an appropriate size + fmt.Println("+++ :test-analytics: Executing tests 🏃") fmt.Println("bin/rspec", strings.Join(args, " ")) cmd := exec.Command("bin/rspec", args...) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout - return cmd.Run() + + err := cmd.Run() + if errors.Is(err, syscall.E2BIG) { // Will this work on e.g. Windows? + n := len(testCases) / 2 + if err := r.Run(testCases[:n]); err != nil { + return err + } + if err := r.Run(testCases[n:]); err != nil { + return err + } + return nil + } + return err } type RspecExample struct { From c9f27eb995fe6090517c64bfcc828d37617499f3 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Tue, 6 Feb 2024 11:28:59 +1100 Subject: [PATCH 2/2] Sketch of how to support other frameworks This uses an anonymous interface type to define the set of methods needed on the testRunner by main, and introduces a flag as a way to specify the framework type. Aside from the `GoTests` being an empty example, note that there's no environment var that corresponds to the new flag, and there are no flags corresponding to the config currently specified by env var... unifying these might be nice? --- internal/runner/go.go | 17 +++++++++++++++++ main.go | 27 ++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 internal/runner/go.go diff --git a/internal/runner/go.go b/internal/runner/go.go new file mode 100644 index 00000000..fbacb1af --- /dev/null +++ b/internal/runner/go.go @@ -0,0 +1,17 @@ +package runner + +import "github.com/buildkite/test-splitter/internal/api" + +type GoTests struct{} + +func (GoTests) FindFiles() ([]string, error) { + return nil, nil +} + +func (GoTests) Run(testCases []string) error { + return nil +} + +func (GoTests) Report([]api.TestCase) { + +} diff --git a/main.go b/main.go index 0800bd1c..b5061721 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ package main import ( "encoding/json" + "flag" "fmt" "log" @@ -22,12 +23,32 @@ type RspecData struct { } func main() { - // TODO: detect test runner and use appropriate runner - testRunner := runner.Rspec{} + testType := flag.String("type", "rspec", "Test `framework` to be run [rspec, go]") + flag.Parse() + + var testRunner interface { + FindFiles() ([]string, error) + Run([]string) error + Report([]api.TestCase) + } + + switch *testType { + case "rspec": + testRunner = runner.Rspec{} + + case "go": + testRunner = runner.GoTests{} + + default: + log.Fatalf("Unsupported test type %q", *testType) + } // get files fmt.Println("--- :test-analytics: Gathering test plan context and creating test plan request 🐿️") - files := testRunner.GetFiles() + files, err := testRunner.FindFiles() + if err != nil { + log.Fatalf("Couldn't find test files: %v", err) + } fmt.Printf("Found %d files\n", len(files)) // fetch env vars