Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions cmd/tool/parser/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Package parser package is responsible for parsing the output of the `go test`
// command and returning additional info about failred test cases, such as file
// and line number of failed test.
package parser

import (
"bufio"
"fmt"
"regexp"
"strconv"
"strings"

"gotest.tools/gotestsum/internal/log"
)

// ParseFailure parses the output of the `go test` for a test failure and
// returns the file and line number of the failed test case.
func ParseFailure(output string) (file string, line int, err error) {
re, err := regexp.Compile(`^\s*([_\w]+\.go):(\d+):`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are many other valid filename characters, space, dash, dots, and maybe even unicode? I assume other characters like + could also be valid?

I'm not sure if regex is the best tool here. https://pkg.go.dev/strings#Cut or strings.Index maybe?

if err != nil {
return "", 0, fmt.Errorf("failed to compile regexp: %v", err)
}

scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
outputLine := scanner.Text()
// Usually the failure would contain a line like this:
// some_test.go:42 (surrounded by white-space)
// the full path to the file is not available
matches := re.FindStringSubmatch(outputLine)

if len(matches) == 3 {
parts := matches[1:]
file = parts[0]
line, err = strconv.Atoi(parts[1])
if err != nil {
log.Debugf("failed to convert line number to int: %v", err)
return "", 0, nil
}
break
}
}
return file, line, scanner.Err()
}
20 changes: 20 additions & 0 deletions cmd/tool/parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package parser

import (
"testing"

"gotest.tools/v3/assert"
)

func TestParseFailure_Ok(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some negative tests would also be great. Ex:

What if the file:line appears in the middle of other text?
What if there is no match?

// given
failure := ` some_1s_test.go:42: \n`

// when
file, line, err := ParseFailure(failure)

// then
assert.NilError(t, err)
assert.Equal(t, file, "some_1s_test.go")
assert.Equal(t, line, 42)
}
20 changes: 18 additions & 2 deletions internal/junitxml/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"time"

"gotest.tools/gotestsum/cmd/tool/parser"
"gotest.tools/gotestsum/internal/log"
"gotest.tools/gotestsum/testjson"
)
Expand Down Expand Up @@ -47,6 +48,8 @@ type JUnitTestCase struct {
Classname string `xml:"classname,attr"`
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
File string `xml:"file,attr,omitempty"`
Line int `xml:"line,attr,omitempty"`
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
Expand Down Expand Up @@ -192,18 +195,22 @@ func packageTestCases(pkg *testjson.Package, formatClassname FormatFunc) []JUnit
var buf bytes.Buffer
pkg.WriteOutputTo(&buf, 0) //nolint:errcheck
jtc := newJUnitTestCase(testjson.TestCase{Test: "TestMain"}, formatClassname)
failureOutput := buf.String()
appendFailFileLine(&jtc, failureOutput)
jtc.Failure = &JUnitFailure{
Message: "Failed",
Contents: buf.String(),
Contents: failureOutput,
}
cases = append(cases, jtc)
}

for _, tc := range pkg.Failed {
jtc := newJUnitTestCase(tc, formatClassname)
failureOutput := strings.Join(pkg.OutputLines(tc), "")
appendFailFileLine(&jtc, failureOutput)
jtc.Failure = &JUnitFailure{
Message: "Failed",
Contents: strings.Join(pkg.OutputLines(tc), ""),
Contents: failureOutput,
}
cases = append(cases, jtc)
}
Expand Down Expand Up @@ -243,3 +250,12 @@ func write(out io.Writer, suites JUnitTestSuites) error {
_, err = out.Write(doc)
return err
}

func appendFailFileLine(jtc *JUnitTestCase, failureOutput string) {
file, line, err := parser.ParseFailure(failureOutput)
if err != nil {
log.Warnf("Failed to parse file:line from test output: %v", err)
}
jtc.File = file
jtc.Line = line
}