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,30 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"contracts.go",
"result.go",
],
importmap = "go-common/vendor/github.com/smartystreets/goconvey/web/server/contract",
importpath = "github.com/smartystreets/goconvey/web/server/contract",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/smartystreets/goconvey/convey/reporting:go_default_library",
"//vendor/github.com/smartystreets/goconvey/web/server/messaging: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,27 @@
package contract
import "net/http"
type (
Server interface {
ReceiveUpdate(root string, update *CompleteOutput)
Watch(writer http.ResponseWriter, request *http.Request)
Ignore(writer http.ResponseWriter, request *http.Request)
Reinstate(writer http.ResponseWriter, request *http.Request)
Status(writer http.ResponseWriter, request *http.Request)
LongPollStatus(writer http.ResponseWriter, request *http.Request)
Results(writer http.ResponseWriter, request *http.Request)
Execute(writer http.ResponseWriter, request *http.Request)
TogglePause(writer http.ResponseWriter, request *http.Request)
}
Executor interface {
ExecuteTests([]*Package) *CompleteOutput
Status() string
ClearStatusFlag() bool
}
Shell interface {
GoTest(directory, packageName string, tags, arguments []string) (output string, err error)
}
)

View File

@@ -0,0 +1,120 @@
package contract
import (
"path/filepath"
"strings"
"go/build"
"github.com/smartystreets/goconvey/convey/reporting"
"github.com/smartystreets/goconvey/web/server/messaging"
)
type Package struct {
Path string
Name string
Ignored bool
Disabled bool
BuildTags []string
TestArguments []string
Error error
Output string
Result *PackageResult
HasImportCycle bool
}
func NewPackage(folder *messaging.Folder, hasImportCycle bool) *Package {
self := new(Package)
self.Path = folder.Path
self.Name = resolvePackageName(self.Path)
self.Result = NewPackageResult(self.Name)
self.Ignored = folder.Ignored
self.Disabled = folder.Disabled
self.BuildTags = folder.BuildTags
self.TestArguments = folder.TestArguments
self.HasImportCycle = hasImportCycle
return self
}
func (self *Package) Active() bool {
return !self.Disabled && !self.Ignored
}
func (self *Package) HasUsableResult() bool {
return self.Active() && (self.Error == nil || (self.Output != ""))
}
type CompleteOutput struct {
Packages []*PackageResult
Revision string
Paused bool
}
var ( // PackageResult.Outcome values:
Ignored = "ignored"
Disabled = "disabled"
Passed = "passed"
Failed = "failed"
Panicked = "panicked"
BuildFailure = "build failure"
NoTestFiles = "no test files"
NoTestFunctions = "no test functions"
NoGoFiles = "no go code"
TestRunAbortedUnexpectedly = "test run aborted unexpectedly"
)
type PackageResult struct {
PackageName string
Elapsed float64
Coverage float64
Outcome string
BuildOutput string
TestResults []TestResult
}
func NewPackageResult(packageName string) *PackageResult {
self := new(PackageResult)
self.PackageName = packageName
self.TestResults = []TestResult{}
self.Coverage = -1
return self
}
type TestResult struct {
TestName string
Elapsed float64
Passed bool
Skipped bool
File string
Line int
Message string
Error string
Stories []reporting.ScopeResult
RawLines []string `json:",omitempty"`
}
func NewTestResult(testName string) *TestResult {
self := new(TestResult)
self.Stories = []reporting.ScopeResult{}
self.RawLines = []string{}
self.TestName = testName
return self
}
func resolvePackageName(path string) string {
pkg, err := build.ImportDir(path, build.FindOnly)
if err == nil {
return pkg.ImportPath
}
nameArr := strings.Split(path, endGoPath)
return nameArr[len(nameArr)-1]
}
const (
separator = string(filepath.Separator)
endGoPath = separator + "src" + separator
)

View File

@@ -0,0 +1,23 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["messages.go"],
importmap = "go-common/vendor/github.com/smartystreets/goconvey/web/server/messaging",
importpath = "github.com/smartystreets/goconvey/web/server/messaging",
visibility = ["//visibility:public"],
)
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,56 @@
package messaging
///////////////////////////////////////////////////////////////////////////////
type WatcherCommand struct {
Instruction WatcherInstruction
Details string
}
type WatcherInstruction int
func (this WatcherInstruction) String() string {
switch this {
case WatcherPause:
return "Pause"
case WatcherResume:
return "Resume"
case WatcherIgnore:
return "Ignore"
case WatcherReinstate:
return "Reinstate"
case WatcherAdjustRoot:
return "AdjustRoot"
case WatcherExecute:
return "Execute"
case WatcherStop:
return "Stop"
default:
return "UNKNOWN INSTRUCTION"
}
}
const (
WatcherPause WatcherInstruction = iota
WatcherResume
WatcherIgnore
WatcherReinstate
WatcherAdjustRoot
WatcherExecute
WatcherStop
)
///////////////////////////////////////////////////////////////////////////////
type Folders map[string]*Folder
type Folder struct {
Path string // key
Root string
Ignored bool
Disabled bool
BuildTags []string
TestArguments []string
}
///////////////////////////////////////////////////////////////////////////////

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)
}