Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"packageParser.go",
"parser.go",
"rules.go",
"testParser.go",
"util.go",
],
importmap = "go-common/vendor/github.com/smartystreets/goconvey/web/server/parser",
importpath = "github.com/smartystreets/goconvey/web/server/parser",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/smartystreets/goconvey/convey/reporting:go_default_library",
"//vendor/github.com/smartystreets/goconvey/web/server/contract:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,178 @@
package parser
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/smartystreets/goconvey/web/server/contract"
)
var (
testNamePattern = regexp.MustCompile("^=== RUN:? +(.+)$")
)
func ParsePackageResults(result *contract.PackageResult, rawOutput string) {
newOutputParser(result, rawOutput).parse()
}
type outputParser struct {
raw string
lines []string
result *contract.PackageResult
tests []*contract.TestResult
// place holders for loops
line string
test *contract.TestResult
testMap map[string]*contract.TestResult
}
func newOutputParser(result *contract.PackageResult, rawOutput string) *outputParser {
self := new(outputParser)
self.raw = strings.TrimSpace(rawOutput)
self.lines = strings.Split(self.raw, "\n")
self.result = result
self.tests = []*contract.TestResult{}
self.testMap = make(map[string]*contract.TestResult)
return self
}
func (self *outputParser) parse() {
self.separateTestFunctionsAndMetadata()
self.parseEachTestFunction()
}
func (self *outputParser) separateTestFunctionsAndMetadata() {
for _, self.line = range self.lines {
if self.processNonTestOutput() {
break
}
self.processTestOutput()
}
}
func (self *outputParser) processNonTestOutput() bool {
if noGoFiles(self.line) {
self.recordFinalOutcome(contract.NoGoFiles)
} else if buildFailed(self.line) {
self.recordFinalOutcome(contract.BuildFailure)
} else if noTestFiles(self.line) {
self.recordFinalOutcome(contract.NoTestFiles)
} else if noTestFunctions(self.line) {
self.recordFinalOutcome(contract.NoTestFunctions)
} else {
return false
}
return true
}
func (self *outputParser) recordFinalOutcome(outcome string) {
self.result.Outcome = outcome
self.result.BuildOutput = strings.Join(self.lines, "\n")
}
func (self *outputParser) processTestOutput() {
self.line = strings.TrimSpace(self.line)
if isNewTest(self.line) {
self.registerTestFunction()
} else if isTestResult(self.line) {
self.recordTestMetadata()
} else if isPackageReport(self.line) {
self.recordPackageMetadata()
} else {
self.saveLineForParsingLater()
}
}
func (self *outputParser) registerTestFunction() {
testNameReg := testNamePattern.FindStringSubmatch(self.line)
if len(testNameReg) < 2 { // Test-related lines that aren't about a new test
return
}
self.test = contract.NewTestResult(testNameReg[1])
self.tests = append(self.tests, self.test)
self.testMap[self.test.TestName] = self.test
}
func (self *outputParser) recordTestMetadata() {
testName := strings.Split(self.line, " ")[2]
if test, ok := self.testMap[testName]; ok {
self.test = test
self.test.Passed = !strings.HasPrefix(self.line, "--- FAIL: ")
self.test.Skipped = strings.HasPrefix(self.line, "--- SKIP: ")
self.test.Elapsed = parseTestFunctionDuration(self.line)
}
}
func (self *outputParser) recordPackageMetadata() {
if packageFailed(self.line) {
self.recordTestingOutcome(contract.Failed)
} else if packagePassed(self.line) {
self.recordTestingOutcome(contract.Passed)
} else if isCoverageSummary(self.line) {
self.recordCoverageSummary(self.line)
}
}
func (self *outputParser) recordTestingOutcome(outcome string) {
self.result.Outcome = outcome
fields := strings.Split(self.line, "\t")
self.result.PackageName = strings.TrimSpace(fields[1])
self.result.Elapsed = parseDurationInSeconds(fields[2], 3)
}
func (self *outputParser) recordCoverageSummary(summary string) {
start := len("coverage: ")
end := strings.Index(summary, "%")
value := summary[start:end]
parsed, err := strconv.ParseFloat(value, 64)
if err != nil {
self.result.Coverage = -1
} else {
self.result.Coverage = parsed
}
}
func (self *outputParser) saveLineForParsingLater() {
self.line = strings.TrimLeft(self.line, "\t")
if self.test == nil {
fmt.Println("Potential error parsing output of", self.result.PackageName, "; couldn't handle this stray line:", self.line)
return
}
self.test.RawLines = append(self.test.RawLines, self.line)
}
// TestResults is a collection of TestResults that implements sort.Interface.
type TestResults []contract.TestResult
func (r TestResults) Len() int {
return len(r)
}
// Less compares TestResults on TestName
func (r TestResults) Less(i, j int) bool {
return r[i].TestName < r[j].TestName
}
func (r TestResults) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (self *outputParser) parseEachTestFunction() {
for _, self.test = range self.tests {
self.test = parseTestOutput(self.test)
if self.test.Error != "" {
self.result.Outcome = contract.Panicked
}
self.test.RawLines = []string{}
self.result.TestResults = append(self.result.TestResults, *self.test)
}
sort.Sort(TestResults(self.result.TestResults))
}

View File

@@ -0,0 +1,32 @@
package parser
import (
"log"
"github.com/smartystreets/goconvey/web/server/contract"
)
type Parser struct {
parser func(*contract.PackageResult, string)
}
func (self *Parser) Parse(packages []*contract.Package) {
for _, p := range packages {
if p.Active() && p.HasUsableResult() {
self.parser(p.Result, p.Output)
} else if p.Ignored {
p.Result.Outcome = contract.Ignored
} else if p.Disabled {
p.Result.Outcome = contract.Disabled
} else {
p.Result.Outcome = contract.TestRunAbortedUnexpectedly
}
log.Printf("[%s]: %s\n", p.Result.Outcome, p.Name)
}
}
func NewParser(helper func(*contract.PackageResult, string)) *Parser {
self := new(Parser)
self.parser = helper
return self
}

View File

@@ -0,0 +1,2 @@
#ignore
-timeout=1s

View File

@@ -0,0 +1,44 @@
package parser
import "strings"
func noGoFiles(line string) bool {
return strings.HasPrefix(line, "can't load package: ") &&
(strings.Contains(line, ": no buildable Go source files in ") || strings.Contains(line, ": no Go "))
}
func buildFailed(line string) bool {
return strings.HasPrefix(line, "# ") ||
strings.Contains(line, "cannot find package") ||
(strings.HasPrefix(line, "can't load package: ") && !strings.Contains(line, ": no Go ")) ||
(strings.Contains(line, ": found packages ") && strings.Contains(line, ".go) and ") && strings.Contains(line, ".go) in "))
}
func noTestFunctions(line string) bool {
return line == "testing: warning: no tests to run"
}
func noTestFiles(line string) bool {
return strings.HasPrefix(line, "?") && strings.Contains(line, "[no test files]")
}
func isNewTest(line string) bool {
return strings.HasPrefix(line, "=== ")
}
func isTestResult(line string) bool {
return strings.HasPrefix(line, "--- FAIL:") || strings.HasPrefix(line, "--- PASS:") || strings.HasPrefix(line, "--- SKIP:")
}
func isPackageReport(line string) bool {
return (strings.HasPrefix(line, "FAIL") ||
strings.HasPrefix(line, "exit status") ||
strings.HasPrefix(line, "PASS") ||
isCoverageSummary(line) ||
packagePassed(line))
}
func packageFailed(line string) bool {
return strings.HasPrefix(line, "FAIL\t")
}
func packagePassed(line string) bool {
return strings.HasPrefix(line, "ok \t")
}
func isCoverageSummary(line string) bool {
return strings.HasPrefix(line, "coverage: ") && strings.Contains(line, "% of statements")
}

View File

@@ -0,0 +1,177 @@
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 =======
`

View File

@@ -0,0 +1,49 @@
package parser
import (
"math"
"strings"
"time"
)
// parseTestFunctionDuration parses the duration in seconds as a float64
// from a line of go test output that looks something like this:
// --- PASS: TestOldSchool_PassesWithMessage (0.03 seconds)
func parseTestFunctionDuration(line string) float64 {
line = strings.Replace(line, "(", "", 1)
line = strings.Replace(line, ")", "", 1)
fields := strings.Split(line, " ")
return parseDurationInSeconds(fields[3], 2)
}
func parseDurationInSeconds(raw string, precision int) float64 {
elapsed, err := time.ParseDuration(raw)
if err != nil {
elapsed, _ = time.ParseDuration(raw + "s")
}
return round(elapsed.Seconds(), precision)
}
// round returns the rounded version of x with precision.
//
// Special cases are:
// round(±0) = ±0
// round(±Inf) = ±Inf
// round(NaN) = NaN
//
// Why, oh why doesn't the math package come with a round function?
// Inspiration: http://play.golang.org/p/ZmFfr07oHp
func round(x float64, precision int) float64 {
var rounder float64
pow := math.Pow(10, float64(precision))
intermediate := x * pow
if intermediate < 0.0 {
intermediate -= 0.5
} else {
intermediate += 0.5
}
rounder = float64(int64(intermediate))
return rounder / float64(pow)
}