Commits

Miki Tebeka committed ee8d116 Merge

Merge pull request #2 from tekii/master

added suites that maps to go packages to avoid test name collition

Comments (0)

Files changed (5)

+2013-07-29 version 0.1.3
+    * suites added (using package name as suite name)
+
 2013-06-18 version 0.1.2
     * testcases node added for bamboo compatibility, some dummy change. 
     * xml generation now use template engine
 ==============
-go2xunit 0.1.1
+go2xunit 0.1.3
 ==============
 
 Converts `go test -v` output to xunit compatible XML output (used in
 	passPrefix  = "--- PASS: "
 	failPrefix  = "--- FAIL: "
 
-	version = "0.1.2"
+	version = "0.1.3"
 )
 
 // "end of test" regexp for name and time, examples:
 // --- FAIL: TestSubFail (0.00 seconds)
 var endRegexp *regexp.Regexp = regexp.MustCompile(`([^ ]+) \((\d+\.\d+)`)
 
-type Test struct {
-	Name, Time, Message string
-	Failed              bool
-}
-
-type TestResults struct {
-	Tests  []*Test
-	Count  int
-	Failed int
-	Bamboo bool
-}
-
 // parseEnd parses "end of test" line and returns (name, time, error)
 func parseEnd(prefix, line string) (string, string, error) {
 	matches := endRegexp.FindStringSubmatch(line[len(prefix):])
 	return matches[1], matches[2], nil
 }
 
+// "end of tested file" regexp for parsing package & file name
+// ok  	teky/cointreau/gs1/deliver	0.015s
+// FAIL	teky/cointreau/gs1/deliver	0.010s
+var endTestRegexp *regexp.Regexp = regexp.MustCompile(`^(ok  |FAIL)\t([^ ]+)\t(\d+\.\d+)s$`)
+
+// parseEndTest parses "end of test file" line and returns (status, name, time, error)
+func parseEndTest(line string) (string, string, string, error) {
+	matches := endTestRegexp.FindStringSubmatch(line)
+
+	if len(matches) == 0 {
+		return "", "", "", fmt.Errorf("can't parse %s", line)
+	}
+
+	return matches[1], matches[2], matches[3], nil
+}
+
+type Test struct {
+	Name, Time, Message string
+	Failed              bool
+}
+
+type Suite struct {
+	Name   string
+	Count  int
+	Failed int
+	Time   string
+	Status string
+	Tests  []*Test
+}
+
+type TestResults struct {
+	Suites []*Suite
+	Bamboo bool
+}
+
 // parseOutput parses output of "go test -v", returns a list of tests
-func parseOutput(rd io.Reader) ([]*Test, error) {
-	tests := []*Test{}
+func parseOutput(rd io.Reader) ([]*Suite, error) {
+	suites := []*Suite{}
 	var test *Test = nil
+	var suite *Suite = nil
 
 	nextTest := func() {
 		// We are switching to the next test, store the current one.
+		if suite == nil {
+			suite = &Suite{}
+			suite.Tests = make([]*Test, 0, 1)
+		}
 		if test == nil {
 			return
 		}
+		suite.Tests = append(suite.Tests, test)
+		test = nil
+	}
+	nextSuite := func() {
+		// We are switching to the next test, store the current one.
+		if suite == nil {
+			return
+		}
 
-		tests = append(tests, test)
-		test = nil
+		suites = append(suites, suite)
+		suite = nil
 	}
 
 	reader := bufio.NewReader(rd)
 
 		switch err {
 		case io.EOF:
-			nextTest()
-			return tests, nil
+			if suite != nil || test != nil {
+				// if suite or test in progress EOF is an unexpected EOF
+				return nil, fmt.Errorf("Unexpected EOF")
+			}
+			return suites, nil
 		case nil:
 			// nil is OK
 
 		}
 
 		line := string(buf)
-
 		switch {
 		case strings.HasPrefix(line, startPrefix):
 		case strings.HasPrefix(line, failPrefix):
 			nextTest()
 
 			// Extract the test name and the duration:
-			name, time, err := parseEnd(passPrefix, line)
+			name, time, err := parseEnd(failPrefix, line)
 			if err != nil {
 				return nil, err
 			}
 			if err != nil {
 				return nil, err
 			}
-
 			// Create the test structure and store it.
-			tests = append(tests, &Test{
+			suite.Tests = append(suite.Tests, &Test{
 				Name:   name,
 				Time:   time,
 				Failed: false,
 			test = nil
 		case line == "FAIL":
 			nextTest()
+
+		case strings.HasPrefix(line, "ok  \t") || strings.HasPrefix(line, "FAIL\t"):
+			// End of suite, read data
+			status, name, time, err := parseEndTest(line)
+			if err != nil {
+				return nil, err
+			}
+			suite.Name = name
+			suite.Count = len(suite.Tests)
+			suite.Failed = numFailures(suite.Tests)
+			suite.Time = time
+			suite.Status = status
+			nextSuite()
 		default:
 			if test != nil { // test != nil marks we're in the middle of a test
 				test.Message += line + "\n"
 	return count
 }
 
+func hasFailures(suites []*Suite) bool {
+	for _, suite := range suites {
+		if numFailures(suite.Tests) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
 var xmlTemplate string = `<?xml version="1.0" encoding="utf-8"?>
 {{if .Bamboo}}<testsuites>{{end}}
-  <testsuite name="go2xunit" tests="{{.Count}}" errors="0" failures="{{.Failed}}" skip="0">
-{{range $test := .Tests}}    <testcase classname="go2xunit" name="{{$test.Name}}" time="{{$test.Time}}">
+{{range $suite := .Suites}}  <testsuite name="{{.Name}}" tests="{{.Count}}" errors="0" failures="{{.Failed}}" skip="0">
+{{range  $test := $suite.Tests}}    <testcase classname="{{$suite.Name}}" name="{{$test.Name}}" time="{{$test.Time}}">
 {{if $test.Failed }}      <failure type="go.error" message="error">
-        <![CDATA[{{$test.Message}}]]></failure>
-{{end}}    </testcase>
+        <![CDATA[{{$test.Message}}]]>
+      </failure>{{end}}    </testcase>
 {{end}}  </testsuite>
-{{if .Bamboo}}</testsuites>{{end}}
-	`
+{{end}}{{if .Bamboo}}</testsuites>{{end}}
+`
 
 // writeXML exits xunit XML of tests to out
-func writeXML(tests []*Test, out io.Writer, bamboo bool) {
-	count := len(tests)
-	failed := numFailures(tests)
-
-	testsResult := TestResults{Tests: tests, Count: count, Failed: failed, Bamboo: bamboo}
+func writeXML(suites []*Suite, out io.Writer, bamboo bool) {
+	testsResult := TestResults{Suites: suites, Bamboo: bamboo}
 	t := template.New("test template")
 	t, err := t.Parse(xmlTemplate)
 	if err != nil {
 		log.Fatalf("error: %s", err)
 	}
 
-	tests, err := parseOutput(input)
+	suites, err := parseOutput(input)
 	if err != nil {
 		log.Fatalf("error: %s", err)
 	}
-	if len(tests) == 0 {
+	if len(suites) == 0 {
 		log.Fatalf("error: no tests found")
 		os.Exit(1)
 	}
 
-	writeXML(tests, output, *bamboo)
-	if *fail && numFailures(tests) > 0 {
+	writeXML(suites, output, *bamboo)
+	if *fail && hasFailures(suites) {
 		os.Exit(1)
 	}
 }
 	}
 }
 
-func loadTests(filename string, t *testing.T) []*Test {
+func loadTests(filename string, t *testing.T) []*Suite {
 	file, err := os.Open(filename)
 	if err != nil {
 		t.Fatalf("can't open %s - %s", filename, err)
 	}
 
-	tests, err := parseOutput(file)
+	suites, err := parseOutput(file)
 	if err != nil {
 		t.Fatalf("error parsing - %s", err)
 	}
 
-	return tests
+	return suites
 }
 
 func Test_parseOutput(t *testing.T) {
-	tests := loadTests("gotest.out", t)
+	suites := loadTests("gotest.out", t)
+	if len(suites) != 2 {
+		t.Fatalf("got %d suites instead of 2", len(suites))
+	}
+	if suites[0].Name != "_/home/miki/Projects/goroot/src/xunit" {
+		t.Fatalf("bad Suite name %s, expected _/home/miki/Projects/goroot/src/xunit", suites[0].Name)
+	}
+	if suites[1].Name != "_/home/miki/Projects/goroot/src/anotherTest" {
+		t.Fatalf("bad Suite name %s, expected _/home/miki/Projects/goroot/src/anotherTest", suites[1].Name)
+	}
+
+	tests := suites[0].Tests
 	if len(tests) != 4 {
 		t.Fatalf("got %d tests instead of 4", len(tests))
 	}
 	if len(test.Message) != 0 {
 		t.Fatalf("bad message (should be empty): %s", test.Message)
 	}
+	tests = suites[1].Tests
+	if len(tests) != 1 {
+		t.Fatalf("got %d tests instead of 1", len(tests))
+	}
+
+	if numFailures(tests) != 0 {
+		t.Fatalf("wrong number of failed %d, should be 0", numFailures(tests))
+	}
+
+	test = tests[0]
+	if test.Name != "TestAdd" {
+		t.Fatalf("bad test name %s, expected TestAdd", test.Name)
+	}
+
+	if test.Time != "0.00" {
+		t.Fatalf("bad test time %s, expected 0.00", test.Time)
+	}
+
+	if len(test.Message) != 0 {
+		t.Fatalf("bad message (should be empty): %s", test.Message)
+	}
 }
 
 func Test_parseOutputBad(t *testing.T) {
-	tests := loadTests("go2xunit.go", t)
-	if len(tests) != 0 {
-		t.Fatalf("managed to find tests in junk")
+	suites := loadTests("go2xunit.go", t)
+	if len(suites) != 0 {
+		t.Fatalf("managed to find suites in junk")
 	}
 }
 FAIL
 exit status 1
 FAIL	_/home/miki/Projects/goroot/src/xunit	0.004s
+=== RUN TestAdd
+--- PASS: TestAdd (0.00 seconds)
+ok  	_/home/miki/Projects/goroot/src/anotherTest	0.000s
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.