diff --git a/cmd/go2junit/go2junit.go b/cmd/go2junit/go2junit.go index 99fd805..f35e173 100644 --- a/cmd/go2junit/go2junit.go +++ b/cmd/go2junit/go2junit.go @@ -1,7 +1,119 @@ package main -import "fmt" +import ( + "bufio" + "encoding/json" + "encoding/xml" + "fmt" + "os" +) func main() { - fmt.Println("Hello") + + /* + testsuites := Testsuites{ + Time: 1.23, + TImestamp: time.Now(), + Suites: []*Testsuite{ + { + Name: "hello", + Tests: 0, + Failures: 0, + Errors: 0, + Disabled: 0, + Package: "", + Skipped: 0, + Time: "", + Timestamp: time.Now(), + Testsuites: []*Testsuite{ + { + Name: "abc", + Tests: 0, + Failures: 0, + Errors: 0, + Disabled: 0, + Package: "", + Skipped: 0, + Time: "", + Timestamp: time.Time{}, + Testsuites: nil, + Testcases: []*Testcase{ + { + Name: "test", + Classname: "", + Time: "", + Skipped: nil, + Error: &Result{ + Message: "error", + }, + Failure: nil, + SystemOut: "", + }, + }, + SystemOut: "ddd", + }, + }, + Testcases: nil, + SystemOut: "hello", + }, + }, + } + + */ + + testsuites := Testsuites{} + + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: go2junit \n") + os.Exit(1) + } + filename := os.Args[1] + + file, _ := os.Open(filename) + defer file.Close() + + scanner := bufio.NewScanner(file) + lineno := 0 + + testsuites = Testsuites{} + for scanner.Scan() { + lineno++ + var item TestEvent + line := scanner.Bytes() + if err := json.Unmarshal(line, &item); err != nil { + fmt.Fprintf(os.Stderr, "%d: %s: %v", lineno, line, err) + continue + } + + //.fmt.Printf("Parsed %d:\n%v\n\n", lineno, item) + + switch item.Action { + case "start": + testsuites.Suite(item.Time, item.Package) + case "run": + testsuites.Test(item.Time, item.Package, item.Test) + case "output": + testsuites.Output(item.Time, item.Package, item.Test, item.Output) + fmt.Fprintf(os.Stderr, "%s", item.Output) + case "pause": + testsuites.Output(item.Time, item.Package, item.Test, "PAUSED") + case "cont": + testsuites.Output(item.Time, item.Package, item.Test, "CONTINUED") + case "pass": + testsuites.Pass(item.Time, item.Package, item.Test, item.Elapsed) + case "bench": + testsuites.Bench(item.Time, item.Package, item.Test, item.Output, item.Elapsed) + case "fail": + testsuites.Fail(item.Time, item.Package, item.Test, item.Elapsed) + case "skip": + testsuites.Skip(item.Time, item.Package, item.Test) + } + } + testsuites.Complete() + + xml, err := xml.MarshalIndent(testsuites, "", " ") + if err != nil { + panic(err) + } + fmt.Printf("%s", xml) } diff --git a/cmd/go2junit/input.go b/cmd/go2junit/input.go new file mode 100644 index 0000000..62565f8 --- /dev/null +++ b/cmd/go2junit/input.go @@ -0,0 +1,12 @@ +package main + +import "time" + +type TestEvent struct { + Time time.Time + Action string + Package string + Test string + Elapsed float64 + Output string +} diff --git a/cmd/go2junit/output.go b/cmd/go2junit/output.go new file mode 100644 index 0000000..7b140c8 --- /dev/null +++ b/cmd/go2junit/output.go @@ -0,0 +1,209 @@ +package main + +import ( + "encoding/xml" + "log" + "strings" + "time" +) + +type Testsuites struct { + XMLName xml.Name `xml:"testsuites"` + + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Errors int `xml:"errors,attr"` + Disabled int `xml:"disabled,attr,omitempty"` + Skipped int `xml:"skipped,attr,omitempty"` + + Time float64 `xml:"time,attr"` + + Suites []*Testsuite `xml:"testsuite,omitempty"` +} + +type Testsuite struct { + // required attributes + Name string `xml:"name,attr"` + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Errors int `xml:"errors,attr"` + + // optional attributes + Disabled int `xml:"disabled,attr,omitempty"` + Package string `xml:"package,attr,omitempty"` + Skipped int `xml:"skipped,attr,omitempty"` + Time string `xml:"time,attr"` + Timestamp time.Time `xml:"timestamp,attr,omitempty"` + + Testsuites []*Testsuite `xml:"testsuite,omitempty"` + Testcases []*Testcase `xml:"testcase,omitempty"` + SystemOut string `xml:"system-out,omitempty"` +} + +type Result struct { + Message string `xml:"message,attr"` +} + +type Testcase struct { + // required attributes + Name string `xml:"name,attr"` + Classname string `xml:"classname,attr"` + + // optional attributes + Time float64 `xml:"time,attr,omitempty"` + + Skipped *Result `xml:"skipped,omitempty"` + Error *Result `xml:"error,omitempty"` + Failure *Result `xml:"failure,omitempty"` + SystemOut string `xml:"system-out,omitempty"` +} + +func (testsuites *Testsuites) getSuite(t time.Time, pkg string) *Testsuite { + for _, suite := range testsuites.Suites { + if suite.Name == pkg { + return suite + } + } + suite := Testsuite{ + Name: pkg, + Skipped: 0, + Timestamp: t, + } + log.Printf("Adding suite %s", pkg) + testsuites.Suites = append(testsuites.Suites, &suite) + return &suite +} + +func (suite *Testsuite) getSuite(t time.Time, name string) *Testsuite { + for _, s := range suite.Testsuites { + if s.Name == name { + return s + } + } + s := Testsuite{ + Name: name, + Timestamp: t, + } + suite.Testsuites = append(suite.Testsuites, &s) + return &s +} + +func (suite *Testsuite) getTest(t time.Time, testname string) *Testcase { + path := strings.Split(testname, "/") + for i := 0; i < len(path)-1; i++ { + suite = suite.getSuite(t, path[i]) + } + for _, test := range suite.Testcases { + if test.Name == path[len(path)-1] { + return test + } + } + if path[len(path)-1] == "" { + panic("Empty testcase") + } + test := Testcase{ + Name: path[len(path)-1], + } + suite.Testcases = append(suite.Testcases, &test) + return &test +} + +func (testsuites *Testsuites) getTest(t time.Time, pkg string, testname string) *Testcase { + suite := testsuites.getSuite(t, pkg) + test := suite.getTest(t, testname) + return test +} + +func (testsuites *Testsuites) Suite(t time.Time, pkg string) { + testsuites.getSuite(t, pkg) +} + +func (testsuites *Testsuites) Test(t time.Time, pkg string, test string) { + testsuites.getTest(t, pkg, test) +} + +func (testsuites *Testsuites) Output(t time.Time, pkg string, test string, output string) { + if test == "" { + ts := testsuites.getSuite(t, pkg) + ts.SystemOut = ts.SystemOut + output + return + } + tc := testsuites.getTest(t, pkg, test) + tc.SystemOut = tc.SystemOut + output +} + +func (testsuites *Testsuites) Pass(t time.Time, pkg string, test string, elapsed float64) { + if test == "" { + return + } + testsuites.getTest(t, pkg, test) +} + +func (testsuites *Testsuites) Bench(t time.Time, pkg string, test string, output string, elapsed float64) { + tc := testsuites.getTest(t, pkg, test) + tc.SystemOut = tc.SystemOut + output + "\n" +} + +func (testsuites *Testsuites) Fail(t time.Time, pkg string, test string, elapsed float64) { + if test == "" { + return + } + tc := testsuites.getTest(t, pkg, test) + tc.Time = elapsed + tc.Failure = &Result{ + Message: "failed", + } +} + +func (testsuites *Testsuites) Skip(t time.Time, pkg string, test string) { + if test == "" { + return + } + tc := testsuites.getTest(t, pkg, test) + tc.Failure = &Result{ + Message: "skipped", + } +} + +func (suite *Testsuite) Complete() { + suite.Tests = 0 + suite.Failures = 0 + suite.Errors = 0 + suite.Disabled = 0 + suite.Skipped = 0 + for _, ts := range suite.Testsuites { + ts.Complete() + suite.Tests += ts.Tests + suite.Failures += ts.Failures + suite.Errors += ts.Errors + suite.Disabled += ts.Disabled + suite.Skipped += ts.Skipped + } + for _, tc := range suite.Testcases { + suite.Tests += 1 + if tc.Failure != nil { + switch tc.Failure.Message { + case "skipped": + suite.Skipped++ + default: + suite.Failures++ + } + } + } +} + +func (testsuites *Testsuites) Complete() { + testsuites.Tests = 0 + testsuites.Failures = 0 + testsuites.Errors = 0 + testsuites.Disabled = 0 + testsuites.Skipped = 0 + for _, ts := range testsuites.Suites { + ts.Complete() + testsuites.Tests += ts.Tests + testsuites.Failures += ts.Failures + testsuites.Errors += ts.Errors + testsuites.Disabled += ts.Disabled + testsuites.Skipped += ts.Skipped + } +}