123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- 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 =======
- `
|