-
Notifications
You must be signed in to change notification settings - Fork 100
feat: add gotest-junit tester #730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "github.com/aws/aws-k8s-tester/internal/testers/gotestjunit" | ||
| ) | ||
|
|
||
| func main() { | ||
| gotestjunit.Main() | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,168 @@ | ||||||||||||||||||||
| package gotestjunit | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import ( | ||||||||||||||||||||
| "bytes" | ||||||||||||||||||||
| "fmt" | ||||||||||||||||||||
| "io" | ||||||||||||||||||||
| "os" | ||||||||||||||||||||
| "os/exec" | ||||||||||||||||||||
| "os/signal" | ||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||
| "strings" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| "github.com/aws/aws-k8s-tester/internal" | ||||||||||||||||||||
| "github.com/jstemmer/go-junit-report/v2/junit" | ||||||||||||||||||||
| "github.com/jstemmer/go-junit-report/v2/parser/gotest" | ||||||||||||||||||||
| "github.com/octago/sflags/gen/gpflag" | ||||||||||||||||||||
| "k8s.io/klog/v2" | ||||||||||||||||||||
| "sigs.k8s.io/kubetest2/pkg/artifacts" | ||||||||||||||||||||
| "sigs.k8s.io/kubetest2/pkg/testers" | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| type Tester struct { | ||||||||||||||||||||
| argv []string | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const usage = `kubetest2 --test=gotest-junit -- [TestCommand] [TestArgs] | ||||||||||||||||||||
| TestCommand: the Go test binary to invoke | ||||||||||||||||||||
| TestArgs: arguments passed to test command | ||||||||||||||||||||
|
|
||||||||||||||||||||
| This tester executes a Go test binary and generates JUnit XML output. | ||||||||||||||||||||
| ` | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func (t *Tester) Execute() error { | ||||||||||||||||||||
| fs, err := gpflag.Parse(t) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return fmt.Errorf("failed to initialize tester: %v", err) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fs.Usage = func() { | ||||||||||||||||||||
| fmt.Print(usage) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if len(os.Args) < 2 { | ||||||||||||||||||||
| fs.Usage() | ||||||||||||||||||||
| return nil | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| help := fs.BoolP("help", "h", false, "") | ||||||||||||||||||||
| _ = fs.Parse(os.Args[1:2]) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if *help { | ||||||||||||||||||||
| fs.Usage() | ||||||||||||||||||||
| return nil | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| t.argv = os.Args[1:] | ||||||||||||||||||||
| if err := testers.WriteVersionToMetadata(internal.Version, ""); err != nil { | ||||||||||||||||||||
| return err | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return t.Test() | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func expandEnv(args []string) []string { | ||||||||||||||||||||
| expanded := make([]string, len(args)) | ||||||||||||||||||||
| for i, arg := range args { | ||||||||||||||||||||
| if strings.Contains(arg, `\$`) { | ||||||||||||||||||||
| expanded[i] = strings.ReplaceAll(arg, `\$`, `$`) | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| expanded[i] = os.ExpandEnv(arg) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return expanded | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func (t *Tester) Test() error { | ||||||||||||||||||||
| args := expandEnv(t.argv) | ||||||||||||||||||||
| return run(args[0], args[1:]) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func run(binary string, args []string) error { | ||||||||||||||||||||
| var buf bytes.Buffer | ||||||||||||||||||||
|
|
||||||||||||||||||||
| cmd := exec.Command(binary, args...) | ||||||||||||||||||||
| cmd.Stdin = os.Stdin | ||||||||||||||||||||
| cmd.Stdout = io.MultiWriter(os.Stdout, &buf) | ||||||||||||||||||||
| cmd.Stderr = io.MultiWriter(os.Stderr, &buf) | ||||||||||||||||||||
|
Comment on lines
+85
to
+86
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I wonder if we should add this to be more consistent with upstream kubetest2's |
||||||||||||||||||||
|
|
||||||||||||||||||||
| signals := make(chan os.Signal, 5) | ||||||||||||||||||||
| signal.Notify(signals) | ||||||||||||||||||||
| defer signal.Stop(signals) | ||||||||||||||||||||
|
Comment on lines
+88
to
+90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason we are doing this with channels and having to manage signal handling vs just waiting on the binary in the main thread?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could probably just simplify it to |
||||||||||||||||||||
|
|
||||||||||||||||||||
| if err := cmd.Start(); err != nil { | ||||||||||||||||||||
| return err | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| wait := make(chan error, 1) | ||||||||||||||||||||
| go func() { | ||||||||||||||||||||
| wait <- cmd.Wait() | ||||||||||||||||||||
| close(wait) | ||||||||||||||||||||
| }() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| var testErr error | ||||||||||||||||||||
| for { | ||||||||||||||||||||
| select { | ||||||||||||||||||||
| case sig := <-signals: | ||||||||||||||||||||
| _ = cmd.Process.Signal(sig) | ||||||||||||||||||||
| case testErr = <-wait: | ||||||||||||||||||||
| goto done | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| done: | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if err := writeJUnit(binary, &buf); err != nil { | ||||||||||||||||||||
| klog.Errorf("failed to write junit: %v", err) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return testErr | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func writeJUnit(binary string, output *bytes.Buffer) error { | ||||||||||||||||||||
| parser := gotest.NewParser() | ||||||||||||||||||||
| report, err := parser.Parse(output) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return err | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| name := filepath.Base(binary) | ||||||||||||||||||||
| name = strings.TrimSuffix(name, ".test") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if err := os.MkdirAll(artifacts.BaseDir(), 0755); err != nil { | ||||||||||||||||||||
| return err | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| hostname, _ := os.Hostname() | ||||||||||||||||||||
| testsuites := junit.CreateFromReport(report, hostname) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| for i, suite := range testsuites.Suites { | ||||||||||||||||||||
| if suite.Name == "" { | ||||||||||||||||||||
| testsuites.Suites[i].Name = name | ||||||||||||||||||||
| } | ||||||||||||||||||||
| filename := fmt.Sprintf("junit_%s.xml", name) | ||||||||||||||||||||
| if len(testsuites.Suites) > 1 { | ||||||||||||||||||||
| filename = fmt.Sprintf("junit_%s_%02d.xml", name, i) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| f, err := os.Create(filepath.Join(artifacts.BaseDir(), filename)) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return err | ||||||||||||||||||||
| } | ||||||||||||||||||||
| single := junit.Testsuites{Suites: []junit.Testsuite{testsuites.Suites[i]}} | ||||||||||||||||||||
| err = single.WriteXML(f) | ||||||||||||||||||||
| f.Close() | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return err | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return nil | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func NewDefaultTester() *Tester { | ||||||||||||||||||||
| return &Tester{} | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func Main() { | ||||||||||||||||||||
| t := NewDefaultTester() | ||||||||||||||||||||
| if err := t.Execute(); err != nil { | ||||||||||||||||||||
| klog.Fatalf("failed to run gotest-junit tester: %v", err) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intention to export these? Do we expect things outside this command to use them/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, but it's consistent with the
ginkgov1code.