You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
4.5 KiB
177 lines
4.5 KiB
package parser |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"strconv" |
|
"strings" |
|
|
|
"github.com/smartystreets/goconvey/convey/reporting" |
|
"github.com/smartystreets/goconvey/web/server/contract" |
|
) |
|
|
|
type testParser struct { |
|
test *contract.TestResult |
|
line string |
|
index int |
|
inJson bool |
|
jsonLines []string |
|
otherLines []string |
|
} |
|
|
|
func parseTestOutput(test *contract.TestResult) *contract.TestResult { |
|
parser := newTestParser(test) |
|
parser.parseTestFunctionOutput() |
|
return test |
|
} |
|
|
|
func newTestParser(test *contract.TestResult) *testParser { |
|
self := new(testParser) |
|
self.test = test |
|
return self |
|
} |
|
|
|
func (self *testParser) parseTestFunctionOutput() { |
|
if len(self.test.RawLines) > 0 { |
|
self.processLines() |
|
self.deserializeJson() |
|
self.composeCapturedOutput() |
|
} |
|
} |
|
|
|
func (self *testParser) processLines() { |
|
for self.index, self.line = range self.test.RawLines { |
|
if !self.processLine() { |
|
break |
|
} |
|
} |
|
} |
|
|
|
func (self *testParser) processLine() bool { |
|
if strings.HasSuffix(self.line, reporting.OpenJson) { |
|
self.inJson = true |
|
self.accountForOutputWithoutNewline() |
|
|
|
} else if self.line == reporting.CloseJson { |
|
self.inJson = false |
|
|
|
} else if self.inJson { |
|
self.jsonLines = append(self.jsonLines, self.line) |
|
|
|
} else if isPanic(self.line) { |
|
self.parsePanicOutput() |
|
return false |
|
|
|
} else if isGoTestLogOutput(self.line) { |
|
self.parseLogLocation() |
|
|
|
} else { |
|
self.otherLines = append(self.otherLines, self.line) |
|
} |
|
return true |
|
} |
|
|
|
// If fmt.Print(f) produces output with no \n and that output |
|
// is that last output before the framework spits out json |
|
// (which starts with ''>>>>>'') then without this code |
|
// all of the json is counted as output, not as json to be |
|
// parsed and displayed by the web UI. |
|
func (self *testParser) accountForOutputWithoutNewline() { |
|
prefix := strings.Split(self.line, reporting.OpenJson)[0] |
|
if prefix != "" { |
|
self.otherLines = append(self.otherLines, prefix) |
|
} |
|
} |
|
|
|
func (self *testParser) deserializeJson() { |
|
formatted := createArrayForJsonItems(self.jsonLines) |
|
var scopes []reporting.ScopeResult |
|
err := json.Unmarshal(formatted, &scopes) |
|
if err != nil { |
|
panic(fmt.Sprintf(bugReportRequest, err, formatted)) |
|
} |
|
self.test.Stories = scopes |
|
} |
|
func (self *testParser) parsePanicOutput() { |
|
for index, line := range self.test.RawLines[self.index:] { |
|
self.parsePanicLocation(index, line) |
|
self.preserveStackTraceIndentation(index, line) |
|
} |
|
self.test.Error = strings.Join(self.test.RawLines, "\n") |
|
} |
|
func (self *testParser) parsePanicLocation(index int, line string) { |
|
if !panicLineHasMetadata(line) { |
|
return |
|
} |
|
metaLine := self.test.RawLines[index+4] |
|
fields := strings.Split(metaLine, " ") |
|
fileAndLine := strings.Split(fields[0], ":") |
|
self.test.File = fileAndLine[0] |
|
if len(fileAndLine) >= 2 { |
|
self.test.Line, _ = strconv.Atoi(fileAndLine[1]) |
|
} |
|
} |
|
func (self *testParser) preserveStackTraceIndentation(index int, line string) { |
|
if panicLineShouldBeIndented(index, line) { |
|
self.test.RawLines[index] = "\t" + line |
|
} |
|
} |
|
func (self *testParser) parseLogLocation() { |
|
self.otherLines = append(self.otherLines, self.line) |
|
lineFields := strings.TrimSpace(self.line) |
|
if strings.HasPrefix(lineFields, "Error Trace:") { |
|
lineFields = strings.TrimPrefix(lineFields, "Error Trace:") |
|
} |
|
fields := strings.Split(lineFields, ":") |
|
self.test.File = strings.TrimSpace(fields[0]) |
|
self.test.Line, _ = strconv.Atoi(fields[1]) |
|
} |
|
|
|
func (self *testParser) composeCapturedOutput() { |
|
self.test.Message = strings.Join(self.otherLines, "\n") |
|
} |
|
|
|
func createArrayForJsonItems(lines []string) []byte { |
|
jsonArrayItems := strings.Join(lines, "") |
|
jsonArrayItems = removeTrailingComma(jsonArrayItems) |
|
return []byte(fmt.Sprintf("[%s]\n", jsonArrayItems)) |
|
} |
|
func removeTrailingComma(rawJson string) string { |
|
if trailingComma(rawJson) { |
|
return rawJson[:len(rawJson)-1] |
|
} |
|
return rawJson |
|
} |
|
func trailingComma(value string) bool { |
|
return strings.HasSuffix(value, ",") |
|
} |
|
|
|
func isGoTestLogOutput(line string) bool { |
|
return strings.Count(line, ":") == 2 |
|
} |
|
|
|
func isPanic(line string) bool { |
|
return strings.HasPrefix(line, "panic: ") |
|
} |
|
|
|
func panicLineHasMetadata(line string) bool { |
|
return strings.HasPrefix(line, "goroutine") && strings.Contains(line, "[running]") |
|
} |
|
func panicLineShouldBeIndented(index int, line string) bool { |
|
return strings.Contains(line, "+") || (index > 0 && strings.Contains(line, "panic: ")) |
|
} |
|
|
|
const bugReportRequest = ` |
|
Uh-oh! Looks like something went wrong. Please copy the following text and file a bug report at: |
|
|
|
https://github.com/smartystreets/goconvey/issues?state=open |
|
|
|
======= BEGIN BUG REPORT ======= |
|
|
|
ERROR: %v |
|
|
|
OUTPUT: %s |
|
|
|
======= END BUG REPORT ======= |
|
|
|
`
|
|
|