diff --git a/.gitignore b/.gitignore index 0133bf51fb5..7fb1d0c0487 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ __pycache__ /python/az/aro/dist /secrets /mdm_statsd.socket -uts.txt -cover.out -coverage.* -report.xml \ No newline at end of file +/uts.txt +/cover.out +/coverage.* +/report.xml diff --git a/.pipelines/ci.yml b/.pipelines/ci.yml index 1f60db47220..bc2d636466c 100644 --- a/.pipelines/ci.yml +++ b/.pipelines/ci.yml @@ -12,15 +12,9 @@ jobs: strategy: matrix: Python27: - python.version: "2.7.17" + python.version: "2.7" Python35: - python.version: "3.5.9" - Python36: - python.version: "3.6.10" - Python37: - python.version: "3.7.6" - Python38: - python.version: "3.8.1" + python.version: "3.5" steps: - task: UsePythonVersion@0 @@ -54,9 +48,9 @@ jobs: set -x make test-go [[ -z "$(git status -s)" ]] - bash -c 'go-junit-report < uts.txt > report.xml' - bash -c 'gocov convert cover.out > coverage.json' - bash -c 'gocov-xml < coverage.json > coverage.xml' + go run ./vendor/github.com/jstemmer/go-junit-report/go-junit-report.go < uts.txt > report.xml + go run ./vendor/github.com/axw/gocov/gocov/*.go convert cover.out > coverage.json + go run ./vendor/github.com/AlekSi/gocov-xml/gocov-xml.go < coverage.json > coverage.xml workingDirectory: "${{ variables.modulePath }}" displayName: "🧪Run Golang Unit Tests" - task: PublishTestResults@2 diff --git a/.pipelines/templates/template-setup-golang-env.yml b/.pipelines/templates/template-setup-golang-env.yml index 3f6f0b20a87..855d6625947 100644 --- a/.pipelines/templates/template-setup-golang-env.yml +++ b/.pipelines/templates/template-setup-golang-env.yml @@ -8,16 +8,9 @@ steps: mkdir -p '${{ parameters.gobin }}' mkdir -p '${{ parameters.gopath }}/pkg' mkdir -p '${{ parameters.modulePath }}' - shopt -s extglob - shopt -s dotglob - mv !(gopath) '${{ parameters.modulePath }}' - export PATH=${{ parameters.gobin }}:$PATH - export PATH=${{ parameters.goroot }}/bin:$PATH + ls -a | grep -v ${{ parameters.gopath }} | xargs mv -t ${{ parameters.modulePath }} echo "##vso[task.prependpath]${{ parameters.gobin }}" echo "##vso[task.prependpath]${{ parameters.goroot }}/bin" - go get -v github.com/jstemmer/go-junit-report - go get -v github.com/axw/gocov/gocov - go get -v github.com/AlekSi/gocov-xml sudo add-apt-repository ppa:kubuntu-ppa/backports sudo apt-get update sudo apt-get install libgpgme-dev gcc -y diff --git a/Gopkg.lock b/Gopkg.lock index 0438d6170b2..420f6df36e7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -9,6 +9,14 @@ revision = "b4cdc8d6eb508c4e74df26094d1adb678c87f818" version = "v0.51.0" +[[projects]] + branch = "master" + digest = "1:112418214e9ccdfdf047b8f1099e5b57ef44365460050dad349ac00b32405d10" + name = "github.com/AlekSi/gocov-xml" + packages = ["."] + pruneopts = "UT" + revision = "3a14fb1c4737b3995174c5f4d6d08a348b9b4180" + [[projects]] digest = "1:e072b8b6e9ea2e1a1a119dc668f3e121b27b2a82b98f79d2b95006f78798462e" name = "github.com/Azure/azure-sdk-for-go" @@ -176,6 +184,19 @@ revision = "cd610ee76770524d9f1135bfdc241e1b60dfd00b" version = "v1.28.6" +[[projects]] + digest = "1:853f8d8dae66462f0479a2aff520f02af6d9003d98bd476085f117d5b8daa20a" + name = "github.com/axw/gocov" + packages = [ + ".", + "gocov", + "gocov/internal/testflag", + "gocovutil", + ] + pruneopts = "UT" + revision = "b6eca663ebb7e7ef9798914d19f53ba2c6f74c96" + version = "v1.0.0" + [[projects]] digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" name = "github.com/beorn7/perks" @@ -594,6 +615,18 @@ revision = "acfec88f7a0d5140ace3dcdbee10184e3684a9e1" version = "v1.1.9" +[[projects]] + digest = "1:076c531484852c227471112d49465873aaad47e5ad6e1aec3a5b092a436117ef" + name = "github.com/jstemmer/go-junit-report" + packages = [ + ".", + "formatter", + "parser", + ] + pruneopts = "UT" + revision = "cc1f095d5cc5eca2844f5c5ea7bb37f6b9bf6cac" + version = "v0.9.1" + [[projects]] branch = "master" digest = "1:400e113a367b511b9b09ca642ee11d9885485a93838526d697033af334a2fdde" @@ -1231,7 +1264,7 @@ [[projects]] branch = "master" - digest = "1:835727a7ae21040542e69c98e140240055d2d264013b8fd41108826192e1b603" + digest = "1:0b579a0502f74c473b7ac8beb5aacaaa093594eab7df242ea9cd683b48028360" name = "golang.org/x/net" packages = [ "context", @@ -1333,10 +1366,11 @@ [[projects]] branch = "master" - digest = "1:3e7fbc517dc9fec1648669f7f171a362f4e25acac328a42788419e713dee8419" + digest = "1:8bb1d2f050249349dbaeda1ce06e22d9f185acda87c0a5c5a4c43da45d46ed69" name = "golang.org/x/tools" packages = [ "cmd/goimports", + "cover", "go/ast/astutil", "go/gcexportdata", "go/internal/gcimporter", @@ -1747,6 +1781,7 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/AlekSi/gocov-xml", "github.com/Azure/azure-sdk-for-go/services/authorization/mgmt/2015-07-01/authorization", "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute", "github.com/Azure/azure-sdk-for-go/services/cosmos-db/mgmt/2015-04-08/documentdb", @@ -1773,6 +1808,7 @@ "github.com/BurntSushi/toml", "github.com/alvaroloes/enumer", "github.com/apparentlymart/go-cidr/cidr", + "github.com/axw/gocov/gocov", "github.com/containers/image/copy", "github.com/containers/image/docker", "github.com/containers/image/pkg/blobinfocache/memory", @@ -1783,6 +1819,7 @@ "github.com/golang/mock/mockgen", "github.com/gorilla/mux", "github.com/jim-minter/go-cosmosdb/cmd/gencosmosdb", + "github.com/jstemmer/go-junit-report", "github.com/onsi/ginkgo", "github.com/onsi/gomega", "github.com/onsi/gomega/format", @@ -1827,12 +1864,13 @@ "golang.org/x/tools/go/ast/astutil", "golang.org/x/tools/go/packages", "k8s.io/api/core/v1", - "k8s.io/apimachinery/pkg/api/errors", "k8s.io/apimachinery/pkg/apis/meta/v1", "k8s.io/apimachinery/pkg/util/wait", "k8s.io/client-go/kubernetes", "k8s.io/client-go/rest", "k8s.io/client-go/tools/clientcmd", + "k8s.io/client-go/tools/clientcmd/api", + "k8s.io/client-go/tools/clientcmd/api/latest", "k8s.io/client-go/tools/clientcmd/api/v1", "k8s.io/client-go/tools/metrics", "k8s.io/client-go/util/retry", diff --git a/Gopkg.toml b/Gopkg.toml index 5d72bc49700..1ee62eacf5c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -2,7 +2,10 @@ required = [ "github.com/alvaroloes/enumer", "github.com/jim-minter/go-cosmosdb/cmd/gencosmosdb", "github.com/golang/mock/mockgen", - "golang.org/x/tools/cmd/goimports" + "golang.org/x/tools/cmd/goimports", + "github.com/jstemmer/go-junit-report", + "github.com/axw/gocov/gocov", + "github.com/AlekSi/gocov-xml" ] [[constraint]] diff --git a/Makefile b/Makefile index 18b54d3d41f..082750e82ba 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ proxy: go build -ldflags "-X main.gitCommit=$(COMMIT)" ./hack/proxy pyenv${PYTHON_VERSION}: - virtualenv pyenv${PYTHON_VERSION} + virtualenv --python=/usr/bin/python${PYTHON_VERSION} pyenv${PYTHON_VERSION} . pyenv${PYTHON_VERSION}/bin/activate && \ pip install azdev && \ azdev setup -r . && \ diff --git a/vendor/github.com/AlekSi/gocov-xml/.gitignore b/vendor/github.com/AlekSi/gocov-xml/.gitignore new file mode 100644 index 00000000000..2345c606e8d --- /dev/null +++ b/vendor/github.com/AlekSi/gocov-xml/.gitignore @@ -0,0 +1,4 @@ +*.json +coverage.xml + +gocov-xml diff --git a/vendor/github.com/AlekSi/gocov-xml/LICENSE b/vendor/github.com/AlekSi/gocov-xml/LICENSE new file mode 100644 index 00000000000..39424b4984c --- /dev/null +++ b/vendor/github.com/AlekSi/gocov-xml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Alexey Palazhchenko + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/AlekSi/gocov-xml/Makefile b/vendor/github.com/AlekSi/gocov-xml/Makefile new file mode 100644 index 00000000000..862ace09f0b --- /dev/null +++ b/vendor/github.com/AlekSi/gocov-xml/Makefile @@ -0,0 +1,12 @@ +all: fvb + +prepare: + go get -u github.com/axw/gocov/... + go get -u github.com/gorilla/mux/... + gocov test -v github.com/gorilla/mux > mux.json + +fvb: + gofmt -e -s -w . + go vet . + go run ./gocov-xml.go < mux.json > coverage.xml + xmllint --valid --noout coverage.xml diff --git a/vendor/github.com/AlekSi/gocov-xml/README.md b/vendor/github.com/AlekSi/gocov-xml/README.md new file mode 100644 index 00000000000..4d8297b236a --- /dev/null +++ b/vendor/github.com/AlekSi/gocov-xml/README.md @@ -0,0 +1,62 @@ +# gocov XML + +A tool to generate Go coverage in XML report for using with tools/plugins like Jenkins/Cobertura. + +> Table of Contents + +- [gocov XML](#gocov-xml) + - [Installation](#installation) + - [Usage](#usage) + - [Examples](#examples) + - [Generate coverage by passing `gocov` output as input to `gocov-xml`](#generate-coverage-by-passing-gocov-output-as-input-to-gocov-xml) + - [Specifying optional source](#specifying-optional-source) + - [Authors](#authors) + +This is a simple helper tool for generating XML output in [Cobertura](http://cobertura.sourceforge.net/) format +for CIs like [Jenkins](https://wiki.jenkins-ci.org/display/JENKINS/Cobertura+Plugin), [vsts](https://www.visualstudio.com/team-services) and others +from [github.com/axw/gocov](https://github.com/axw/gocov) output. +The generated XML output is in the latest [coverage-04.dtd](http://cobertura.sourceforge.net/xml/coverage-04.dtd) schema + +## Installation + +Just type the following to install the program and its dependencies: + +```bash +go get github.com/axw/gocov/... +go get github.com/AlekSi/gocov-xml +``` + +## Usage + +> **NOTE**: `gocov-xml` reads data from the standard input. + +```bash +gocov [-source ] +``` + +Where, + +- **`source`**: Absolute path to source. Defaults to the current working directory. + +### Examples + +#### Generate coverage by passing `gocov` output as input to `gocov-xml` + +```bash +gocov test github.com/gorilla/mux | gocov-xml > coverage.xml +``` + +#### Specifying optional source + +```bash +gocov test github.com/gorilla/mux | gocov-xml -source /abs/path/to/source > coverage.xml +``` + +## Authors + +- [Alexey Palazhchenko (AlekSi)](https://github.com/AlekSi) +- [Yukinari Toyota (t-yuki)](https://github.com/t-yuki) +- [Marin Bek (marinbek)](https://github.com/marinbek) +- [Alex Castle (acastle)](https://github.com/acastle) +- [Billy Yao (yaoyaozong)](https://github.com/yaoyaozong) +- [Abhijith DA (abhijithda)](https://github.com/abhijithda) diff --git a/vendor/github.com/AlekSi/gocov-xml/coverage-04.dtd b/vendor/github.com/AlekSi/gocov-xml/coverage-04.dtd new file mode 100644 index 00000000000..412b9f8c242 --- /dev/null +++ b/vendor/github.com/AlekSi/gocov-xml/coverage-04.dtd @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/AlekSi/gocov-xml/coverage-with-data.xml b/vendor/github.com/AlekSi/gocov-xml/coverage-with-data.xml new file mode 100644 index 00000000000..c7c6f215fce --- /dev/null +++ b/vendor/github.com/AlekSi/gocov-xml/coverage-with-data.xml @@ -0,0 +1,179 @@ + + + + + + C:/local/mvn-coverage-example/src/main/java + --source + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/AlekSi/gocov-xml/gocov-xml.go b/vendor/github.com/AlekSi/gocov-xml/gocov-xml.go new file mode 100644 index 00000000000..e9106f78578 --- /dev/null +++ b/vendor/github.com/AlekSi/gocov-xml/gocov-xml.go @@ -0,0 +1,209 @@ +package main + +import ( + "encoding/json" + "encoding/xml" + "flag" + "fmt" + "go/token" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/axw/gocov" +) + +// Coverage information +type Coverage struct { + XMLName xml.Name `xml:"coverage"` + LineRate float32 `xml:"line-rate,attr"` + BranchRate float32 `xml:"branch-rate,attr"` + LinesCovered float32 `xml:"lines-covered,attr"` + LinesValid int64 `xml:"lines-valid,attr"` + BranchesCovered int64 `xml:"branches-covered,attr"` + BranchesValid int64 `xml:"branches-valid,attr"` + Complexity float32 `xml:"complexity,attr"` + Version string `xml:"version,attr"` + Timestamp int64 `xml:"timestamp,attr"` + Packages []Package `xml:"packages>package"` + Sources []string `xml:"sources>source"` +} + +// Package information +type Package struct { + Name string `xml:"name,attr"` + LineRate float32 `xml:"line-rate,attr"` + BranchRate float32 `xml:"branch-rate,attr"` + Complexity float32 `xml:"complexity,attr"` + Classes []Class `xml:"classes>class"` + LineCount int64 `xml:"line-count,attr"` + LineHits int64 `xml:"line-hits,attr"` +} + +// Class information +type Class struct { + Name string `xml:"name,attr"` + Filename string `xml:"filename,attr"` + LineRate float32 `xml:"line-rate,attr"` + BranchRate float32 `xml:"branch-rate,attr"` + Complexity float32 `xml:"complexity,attr"` + Methods []Method `xml:"methods>method"` + Lines []Line `xml:"lines>line"` + LineCount int64 `xml:"line-count,attr"` + LineHits int64 `xml:"line-hits,attr"` +} + +// Method information +type Method struct { + Name string `xml:"name,attr"` + Signature string `xml:"signature,attr"` + LineRate float32 `xml:"line-rate,attr"` + BranchRate float32 `xml:"branch-rate,attr"` + Complexity float32 `xml:"complexity,attr"` + Lines []Line `xml:"lines>line"` + LineCount int64 `xml:"line-count,attr"` + LineHits int64 `xml:"line-hits,attr"` +} + +// Line information +type Line struct { + Number int `xml:"number,attr"` + Hits int64 `xml:"hits,attr"` +} + +func main() { + sourcePathPtr := flag.String( + "source", + "", + "Absolute path to source. Defaults to current working directory.", + ) + + flag.Parse() + + // Parse the commandline arguments. + var sourcePath string + var err error + if *sourcePathPtr != "" { + sourcePath = *sourcePathPtr + if !filepath.IsAbs(sourcePath) { + panic(fmt.Sprintf("Source path is a relative path: %s", sourcePath)) + } + } else { + sourcePath, err = os.Getwd() + if err != nil { + panic(err) + } + } + + sources := make([]string, 1) + sources[0] = sourcePath + var r struct{ Packages []gocov.Package } + var totalLines, totalHits int64 + err = json.NewDecoder(os.Stdin).Decode(&r) + if err != nil { + panic(err) + } + + fset := token.NewFileSet() + tokenFiles := make(map[string]*token.File) + + // convert packages + packages := make([]Package, len(r.Packages)) + for i, gPackage := range r.Packages { + // group functions by filename and "class" (type) + files := make(map[string]map[string]*Class) + for _, gFunction := range gPackage.Functions { + // get the releative path by base path. + fpath, err := filepath.Rel(sourcePath, gFunction.File) + if err != nil { + panic(err) + } + classes := files[fpath] + if classes == nil { + // group functions by "class" (type) in a File + classes = make(map[string]*Class) + files[fpath] = classes + } + + s := strings.Split("-."+gFunction.Name, ".") // className is "-" for package-level functions + className, methodName := s[len(s)-2], s[len(s)-1] + class := classes[className] + if class == nil { + class = &Class{Name: className, Filename: fpath, Methods: []Method{}, Lines: []Line{}} + classes[className] = class + } + + // from github.com/axw/gocov /gocov/annotate.go#printFunctionSource + // Load the file for line information. Probably overkill, maybe + // just compute the lines from offsets in here. + setContent := false + tokenFile := tokenFiles[gFunction.File] + if tokenFile == nil { + info, err := os.Stat(gFunction.File) + if err != nil { + panic(err) + } + tokenFile = fset.AddFile(gFunction.File, fset.Base(), int(info.Size())) + setContent = true + } + + tokenData, err := ioutil.ReadFile(gFunction.File) + if err != nil { + panic(err) + } + if setContent { + // This processes the content and records line number info. + tokenFile.SetLinesForContent(tokenData) + } + + // convert statements to lines + lines := make([]Line, len(gFunction.Statements)) + var funcHits int + for i, s := range gFunction.Statements { + lineno := tokenFile.Line(tokenFile.Pos(s.Start)) + line := Line{Number: lineno, Hits: s.Reached} + if int(s.Reached) > 0 { + funcHits++ + } + lines[i] = line + class.Lines = append(class.Lines, line) + } + lineRate := float32(funcHits) / float32(len(gFunction.Statements)) + + class.Methods = append(class.Methods, Method{Name: methodName, Lines: lines, LineRate: lineRate}) + class.LineCount += int64(len(gFunction.Statements)) + class.LineHits += int64(funcHits) + } + + // fill package with "classes" + p := Package{Name: gPackage.Name, Classes: []Class{}} + for _, classes := range files { + for _, class := range classes { + p.LineCount += class.LineCount + p.LineHits += class.LineHits + class.LineRate = float32(class.LineHits) / float32(class.LineCount) + p.Classes = append(p.Classes, *class) + } + p.LineRate = float32(p.LineHits) / float32(p.LineCount) + } + packages[i] = p + totalLines += p.LineCount + totalHits += p.LineHits + } + + coverage := Coverage{Sources: sources, Packages: packages, Timestamp: time.Now().UnixNano() / int64(time.Millisecond), LinesCovered: float32(totalHits), LinesValid: int64(totalLines), LineRate: float32(totalHits) / float32(totalLines)} + + fmt.Printf(xml.Header) + fmt.Printf("\n") + + encoder := xml.NewEncoder(os.Stdout) + encoder.Indent("", "\t") + err = encoder.Encode(coverage) + if err != nil { + panic(err) + } + + fmt.Println() +} diff --git a/vendor/github.com/axw/gocov/.gitignore b/vendor/github.com/axw/gocov/.gitignore new file mode 100644 index 00000000000..00268614f04 --- /dev/null +++ b/vendor/github.com/axw/gocov/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/axw/gocov/.travis.yml b/vendor/github.com/axw/gocov/.travis.yml new file mode 100644 index 00000000000..4f2ee4d9733 --- /dev/null +++ b/vendor/github.com/axw/gocov/.travis.yml @@ -0,0 +1 @@ +language: go diff --git a/vendor/github.com/axw/gocov/AUTHORS b/vendor/github.com/axw/gocov/AUTHORS new file mode 100644 index 00000000000..998341c9a0d --- /dev/null +++ b/vendor/github.com/axw/gocov/AUTHORS @@ -0,0 +1,5 @@ +# List of gocov authors. + +Andrew Wilkins +Dave Cheney +Greg Ward diff --git a/vendor/github.com/axw/gocov/LICENSE b/vendor/github.com/axw/gocov/LICENSE new file mode 100644 index 00000000000..368fcd25dfc --- /dev/null +++ b/vendor/github.com/axw/gocov/LICENSE @@ -0,0 +1,50 @@ +Copyright (c) 2012 The Gocov Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------- + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/axw/gocov/README.md b/vendor/github.com/axw/gocov/README.md new file mode 100644 index 00000000000..1bc0ff967d4 --- /dev/null +++ b/vendor/github.com/axw/gocov/README.md @@ -0,0 +1,57 @@ +# gocov + +Coverage reporting tool for The Go Programming Language + +[![Build Status](https://travis-ci.org/axw/gocov.svg?branch=master)](https://travis-ci.org/axw/gocov) + +## Installation + +```go get github.com/axw/gocov/gocov``` + +## Usage + +There are currently four gocov commands: ```test```, ```convert```, ```report``` and ```annotate```. + +#### gocov test + +Running `gocov test [args...]` will run `go test [args...]` with +an implicit `-coverprofile` added, and then output the result of +`gocov convert` with the profile. + +#### gocov convert + +Running `gocov convert ` will convert a coverage +profile generated by `go tool cover` to gocov's JSON interchange +format. For example: + + go test -coverprofile=c.out + gocov convert c.out | gocov annotate - + +#### gocov report + +Running `gocov report ` will generate a textual +report from the coverage data output by `gocov convert`. It is +assumed that the source code has not changed in between. + +Output from ```gocov test``` is printed to stdout so users can +pipe the output to ```gocov report``` to view a summary of the test +coverage, for example: - + + gocov test | gocov report + +#### gocov annotate + +Running `gocov annotate ` +will generate a source listing of the specified function, annotating +it with coverage information, such as which lines have been missed. + +## Related tools and services + +[GoCovGUI](http://github.com/nsf/gocovgui/): +A simple GUI wrapper for the gocov coverage analysis tool. + +[gocov-html](https://github.com/matm/gocov-html): +A simple helper tool for generating HTML output from gocov. + +[gocov-xml](https://github.com/AlekSi/gocov-xml): +A simple helper tool for generating XML output in Cobertura format for CIs like Jenkins and others from gocov. diff --git a/vendor/github.com/axw/gocov/go.mod b/vendor/github.com/axw/gocov/go.mod new file mode 100644 index 00000000000..1b4daf37732 --- /dev/null +++ b/vendor/github.com/axw/gocov/go.mod @@ -0,0 +1,5 @@ +module github.com/axw/gocov + +go 1.12 + +require golang.org/x/tools v0.0.0-20190617190820-da514acc4774 diff --git a/vendor/github.com/axw/gocov/go.sum b/vendor/github.com/axw/gocov/go.sum new file mode 100644 index 00000000000..967f84c27c5 --- /dev/null +++ b/vendor/github.com/axw/gocov/go.sum @@ -0,0 +1,7 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774 h1:CQVOmarCBFzTx0kbOU0ru54Cvot8SdSrNYjZPhQl+gk= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= diff --git a/vendor/github.com/axw/gocov/gocov.go b/vendor/github.com/axw/gocov/gocov.go new file mode 100644 index 00000000000..6e94ca7d69d --- /dev/null +++ b/vendor/github.com/axw/gocov/gocov.go @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The Gocov Authors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Package gocov is a code coverage analysis tool for Go. +package gocov + +import ( + "fmt" +) + +type Package struct { + // Name is the canonical path of the package. + Name string + + // Functions is a list of functions registered with this package. + Functions []*Function +} + +type Function struct { + // Name is the name of the function. If the function has a receiver, the + // name will be of the form T.N, where T is the type and N is the name. + Name string + + // File is the full path to the file in which the function is defined. + File string + + // Start is the start offset of the function's signature. + Start int + + // End is the end offset of the function. + End int + + // statements registered with this function. + Statements []*Statement +} + +type Statement struct { + // Start is the start offset of the statement. + Start int + + // End is the end offset of the statement. + End int + + // Reached is the number of times the statement was reached. + Reached int64 +} + +// Accumulate will accumulate the coverage information from the provided +// Package into this Package. +func (p *Package) Accumulate(p2 *Package) error { + if p.Name != p2.Name { + return fmt.Errorf("Names do not match: %q != %q", p.Name, p2.Name) + } + if len(p.Functions) != len(p2.Functions) { + return fmt.Errorf("Function counts do not match: %d != %d", len(p.Functions), len(p2.Functions)) + } + for i, f := range p.Functions { + err := f.Accumulate(p2.Functions[i]) + if err != nil { + return err + } + } + return nil +} + +// Accumulate will accumulate the coverage information from the provided +// Function into this Function. +func (f *Function) Accumulate(f2 *Function) error { + if f.Name != f2.Name { + return fmt.Errorf("Names do not match: %q != %q", f.Name, f2.Name) + } + if f.File != f2.File { + return fmt.Errorf("Files do not match: %q != %q", f.File, f2.File) + } + if f.Start != f2.Start || f.End != f2.End { + return fmt.Errorf("Source ranges do not match: %d-%d != %d-%d", f.Start, f.End, f2.Start, f2.End) + } + if len(f.Statements) != len(f2.Statements) { + return fmt.Errorf("Number of statements do not match: %d != %d", len(f.Statements), len(f2.Statements)) + } + for i, s := range f.Statements { + err := s.Accumulate(f2.Statements[i]) + if err != nil { + return err + } + } + return nil +} + +// Accumulate will accumulate the coverage information from the provided +// Statement into this Statement. +func (s *Statement) Accumulate(s2 *Statement) error { + if s.Start != s2.Start || s.End != s2.End { + return fmt.Errorf("Source ranges do not match: %d-%d != %d-%d", s.Start, s.End, s2.Start, s2.End) + } + s.Reached += s2.Reached + return nil +} diff --git a/vendor/github.com/axw/gocov/gocov/annotate.go b/vendor/github.com/axw/gocov/gocov/annotate.go new file mode 100644 index 00000000000..a0c612e2931 --- /dev/null +++ b/vendor/github.com/axw/gocov/gocov/annotate.go @@ -0,0 +1,238 @@ +// Copyright (c) 2012 The Gocov Authors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package main + +import ( + "flag" + "fmt" + "go/token" + "io/ioutil" + "math" + "os" + "regexp" + "sort" + "strings" + + "github.com/axw/gocov" +) + +const ( + hitPrefix = " " + missPrefix = "MISS" + RED = "\x1b[31;1m" + GREEN = "\x1b[32;1m" + NONE = "\x1b[0m" +) + +var ( + annotateFlags = flag.NewFlagSet("annotate", flag.ExitOnError) + annotateCeilingFlag = annotateFlags.Float64( + "ceiling", 101, + "Annotate only functions whose coverage is less than the specified percentage") + annotateColorFlag = annotateFlags.Bool( + "color", false, + "Differentiate coverage with color") +) + +type packageList []*gocov.Package +type functionList []*gocov.Function + +func (l packageList) Len() int { + return len(l) +} + +func (l packageList) Less(i, j int) bool { + return l[i].Name < l[j].Name +} + +func (l packageList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func (l functionList) Len() int { + return len(l) +} + +func (l functionList) Less(i, j int) bool { + return l[i].Name < l[j].Name +} + +func (l functionList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +type annotator struct { + fset *token.FileSet + files map[string]*token.File +} + +func percentReached(fn *gocov.Function) float64 { + if len(fn.Statements) == 0 { + return 0 + } + var reached int + for _, stmt := range fn.Statements { + if stmt.Reached > 0 { + reached++ + } + } + return float64(reached) / float64(len(fn.Statements)) * 100 +} + +func annotateSource() (rc int) { + annotateFlags.Parse(os.Args[2:]) + if annotateFlags.NArg() == 0 { + fmt.Fprintf(os.Stderr, "missing coverage file\n") + return 1 + } + + var data []byte + var err error + if filename := annotateFlags.Arg(0); filename == "-" { + data, err = ioutil.ReadAll(os.Stdin) + } else { + data, err = ioutil.ReadFile(filename) + } + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read coverage file: %s\n", err) + return 1 + } + + packages, err := unmarshalJson(data) + if err != nil { + fmt.Fprintf( + os.Stderr, "failed to unmarshal coverage data: %s\n", err) + return 1 + } + + // Sort packages, functions by name. + sort.Sort(packageList(packages)) + for _, pkg := range packages { + sort.Sort(functionList(pkg.Functions)) + } + + a := &annotator{} + a.fset = token.NewFileSet() + a.files = make(map[string]*token.File) + + var regexps []*regexp.Regexp + for _, arg := range annotateFlags.Args()[1:] { + re, err := regexp.Compile(arg) + if err != nil { + fmt.Fprintf(os.Stderr, "warning: failed to compile %q as a regular expression, ignoring\n", arg) + } else { + regexps = append(regexps, re) + } + } + if len(regexps) == 0 { + regexps = append(regexps, regexp.MustCompile(".")) + } + for _, pkg := range packages { + for _, fn := range pkg.Functions { + if percentReached(fn) >= *annotateCeilingFlag { + continue + } + name := pkg.Name + "/" + fn.Name + for _, regexp := range regexps { + if regexp.FindStringIndex(name) != nil { + err := a.printFunctionSource(fn) + if err != nil { + fmt.Fprintf(os.Stderr, "warning: failed to annotate function %q\n", name) + } + break + } + } + } + } + return +} + +func (a *annotator) printFunctionSource(fn *gocov.Function) error { + // Load the file for line information. Probably overkill, maybe + // just compute the lines from offsets in here. + setContent := false + file := a.files[fn.File] + if file == nil { + info, err := os.Stat(fn.File) + if err != nil { + return err + } + file = a.fset.AddFile(fn.File, a.fset.Base(), int(info.Size())) + setContent = true + } + + data, err := ioutil.ReadFile(fn.File) + if err != nil { + return err + } + if setContent { + // This processes the content and records line number info. + file.SetLinesForContent(data) + } + + statements := fn.Statements[:] + lineno := file.Line(file.Pos(fn.Start)) + lines := strings.Split(string(data)[fn.Start:fn.End], "\n") + linenoWidth := int(math.Log10(float64(lineno+len(lines)))) + 1 + fmt.Println() + for i, line := range lines { + // Go through statements one at a time, seeing if we've hit + // them or not. + // + // The prefix approach isn't perfect, as it doesn't + // distinguish multiple statements per line. It'll have to + // do for now. We could do fancy ANSI colouring later. + lineno := lineno + i + statementFound := false + hit := false + for j := 0; j < len(statements); j++ { + start := file.Line(file.Pos(statements[j].Start)) + // FIXME instrumentation no longer records statements + // in line order, as function literals are processed + // after the body of a function. If/when that's changed, + // we can go back to checking just the first statement + // in each loop. + if start == lineno { + statementFound = true + if !hit && statements[j].Reached > 0 { + hit = true + } + statements = append(statements[:j], statements[j+1:]...) + } + } + if *annotateColorFlag { + color := NONE + if statementFound && !hit { + color = RED + } + fmt.Printf("%s%*d \t%s%s\n", color, linenoWidth, lineno, line, NONE) + } else { + hitmiss := hitPrefix + if statementFound && !hit { + hitmiss = missPrefix + } + fmt.Printf("%*d %s\t%s\n", linenoWidth, lineno, hitmiss, line) + } + } + fmt.Println() + + return nil +} diff --git a/vendor/github.com/axw/gocov/gocov/convert.go b/vendor/github.com/axw/gocov/gocov/convert.go new file mode 100644 index 00000000000..8d45fff9b38 --- /dev/null +++ b/vendor/github.com/axw/gocov/gocov/convert.go @@ -0,0 +1,313 @@ +// Copyright (c) 2013 The Gocov Authors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package main + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "path/filepath" + + "golang.org/x/tools/cover" + + "github.com/axw/gocov" + "github.com/axw/gocov/gocovutil" +) + +func convertProfiles(filenames ...string) error { + var ps gocovutil.Packages + for i := range filenames { + converter := converter{ + packages: make(map[string]*gocov.Package), + } + profiles, err := cover.ParseProfiles(filenames[i]) + if err != nil { + return err + } + for _, p := range profiles { + if err := converter.convertProfile(p); err != nil { + return err + } + } + + for _, pkg := range converter.packages { + ps.AddPackage(pkg) + } + } + bytes, err := marshalJson(ps) + if err != nil { + return err + } + fmt.Println(string(bytes)) + return nil +} + +type converter struct { + packages map[string]*gocov.Package +} + +// wrapper for gocov.Statement +type statement struct { + *gocov.Statement + *StmtExtent +} + +func (c *converter) convertProfile(p *cover.Profile) error { + file, pkgpath, err := findFile(p.FileName) + if err != nil { + return err + } + pkg := c.packages[pkgpath] + if pkg == nil { + pkg = &gocov.Package{Name: pkgpath} + c.packages[pkgpath] = pkg + } + // Find function and statement extents; create corresponding + // gocov.Functions and gocov.Statements, and keep a separate + // slice of gocov.Statements so we can match them with profile + // blocks. + extents, err := findFuncs(file) + if err != nil { + return err + } + var stmts []statement + for _, fe := range extents { + f := &gocov.Function{ + Name: fe.name, + File: file, + Start: fe.startOffset, + End: fe.endOffset, + } + for _, se := range fe.stmts { + s := statement{ + Statement: &gocov.Statement{Start: se.startOffset, End: se.endOffset}, + StmtExtent: se, + } + f.Statements = append(f.Statements, s.Statement) + stmts = append(stmts, s) + } + pkg.Functions = append(pkg.Functions, f) + } + // For each profile block in the file, find the statement(s) it + // covers and increment the Reached field(s). + blocks := p.Blocks + for _, s := range stmts { + for i, b := range blocks { + if b.StartLine > s.endLine || (b.StartLine == s.endLine && b.StartCol >= s.endCol) { + // Past the end of the statement + blocks = blocks[i:] + break + } + if b.EndLine < s.startLine || (b.EndLine == s.startLine && b.EndCol <= s.startCol) { + // Before the beginning of the statement + continue + } + s.Reached += int64(b.Count) + break + } + } + return nil +} + +// findFile finds the location of the named file in GOROOT, GOPATH etc. +func findFile(file string) (filename string, pkgpath string, err error) { + dir, file := filepath.Split(file) + if dir != "" { + dir = dir[:len(dir)-1] // drop trailing '/' + } + pkg, err := build.Import(dir, ".", build.FindOnly) + if err != nil { + return "", "", fmt.Errorf("can't find %q: %v", file, err) + } + return filepath.Join(pkg.Dir, file), pkg.ImportPath, nil +} + +// findFuncs parses the file and returns a slice of FuncExtent descriptors. +func findFuncs(name string) ([]*FuncExtent, error) { + fset := token.NewFileSet() + parsedFile, err := parser.ParseFile(fset, name, nil, 0) + if err != nil { + return nil, err + } + visitor := &FuncVisitor{fset: fset} + ast.Walk(visitor, parsedFile) + return visitor.funcs, nil +} + +type extent struct { + startOffset int + startLine int + startCol int + endOffset int + endLine int + endCol int +} + +// FuncExtent describes a function's extent in the source by file and position. +type FuncExtent struct { + extent + name string + stmts []*StmtExtent +} + +// StmtExtent describes a statements's extent in the source by file and position. +type StmtExtent extent + +// FuncVisitor implements the visitor that builds the function position list for a file. +type FuncVisitor struct { + fset *token.FileSet + funcs []*FuncExtent +} + +// Visit implements the ast.Visitor interface. +func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor { + var body *ast.BlockStmt + var name string + switch n := node.(type) { + case *ast.FuncLit: + body = n.Body + case *ast.FuncDecl: + body = n.Body + name = n.Name.Name + // Function name is prepended with "T." if there is a receiver, where + // T is the type of the receiver, dereferenced if it is a pointer. + if n.Recv != nil { + field := n.Recv.List[0] + switch recv := field.Type.(type) { + case *ast.StarExpr: + name = recv.X.(*ast.Ident).Name + "." + name + case *ast.Ident: + name = recv.Name + "." + name + } + } + } + if body != nil { + start := v.fset.Position(node.Pos()) + end := v.fset.Position(node.End()) + if name == "" { + name = fmt.Sprintf("@%d:%d", start.Line, start.Column) + } + fe := &FuncExtent{ + name: name, + extent: extent{ + startOffset: start.Offset, + startLine: start.Line, + startCol: start.Column, + endOffset: end.Offset, + endLine: end.Line, + endCol: end.Column, + }, + } + v.funcs = append(v.funcs, fe) + sv := StmtVisitor{fset: v.fset, function: fe} + sv.VisitStmt(body) + } + return v +} + +type StmtVisitor struct { + fset *token.FileSet + function *FuncExtent +} + +func (v *StmtVisitor) VisitStmt(s ast.Stmt) { + var statements *[]ast.Stmt + switch s := s.(type) { + case *ast.BlockStmt: + statements = &s.List + case *ast.CaseClause: + statements = &s.Body + case *ast.CommClause: + statements = &s.Body + case *ast.ForStmt: + if s.Init != nil { + v.VisitStmt(s.Init) + } + if s.Post != nil { + v.VisitStmt(s.Post) + } + v.VisitStmt(s.Body) + case *ast.IfStmt: + if s.Init != nil { + v.VisitStmt(s.Init) + } + v.VisitStmt(s.Body) + if s.Else != nil { + // Code copied from go.tools/cmd/cover, to deal with "if x {} else if y {}" + const backupToElse = token.Pos(len("else ")) // The AST doesn't remember the else location. We can make an accurate guess. + switch stmt := s.Else.(type) { + case *ast.IfStmt: + block := &ast.BlockStmt{ + Lbrace: stmt.If - backupToElse, // So the covered part looks like it starts at the "else". + List: []ast.Stmt{stmt}, + Rbrace: stmt.End(), + } + s.Else = block + case *ast.BlockStmt: + stmt.Lbrace -= backupToElse // So the block looks like it starts at the "else". + default: + panic("unexpected node type in if") + } + v.VisitStmt(s.Else) + } + case *ast.LabeledStmt: + v.VisitStmt(s.Stmt) + case *ast.RangeStmt: + v.VisitStmt(s.Body) + case *ast.SelectStmt: + v.VisitStmt(s.Body) + case *ast.SwitchStmt: + if s.Init != nil { + v.VisitStmt(s.Init) + } + v.VisitStmt(s.Body) + case *ast.TypeSwitchStmt: + if s.Init != nil { + v.VisitStmt(s.Init) + } + v.VisitStmt(s.Assign) + v.VisitStmt(s.Body) + } + if statements == nil { + return + } + for i := 0; i < len(*statements); i++ { + s := (*statements)[i] + switch s.(type) { + case *ast.CaseClause, *ast.CommClause, *ast.BlockStmt: + break + default: + start, end := v.fset.Position(s.Pos()), v.fset.Position(s.End()) + se := &StmtExtent{ + startOffset: start.Offset, + startLine: start.Line, + startCol: start.Column, + endOffset: end.Offset, + endLine: end.Line, + endCol: end.Column, + } + v.function.stmts = append(v.function.stmts, se) + } + v.VisitStmt(s) + } +} diff --git a/vendor/github.com/axw/gocov/gocov/internal/testflag/testflag.go b/vendor/github.com/axw/gocov/gocov/internal/testflag/testflag.go new file mode 100644 index 00000000000..0f52417bac3 --- /dev/null +++ b/vendor/github.com/axw/gocov/gocov/internal/testflag/testflag.go @@ -0,0 +1,145 @@ +// Copyright (c) 2015 The Gocov Authors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +// Parts of this taken from cmd/go/testflag.go and +// cmd/go/build.go; adapted for simplicity. +// +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testflag + +import "strings" + +type testFlagSpec struct { + name string + isBool bool +} + +var testFlagDefn = []*testFlagSpec{ + // test-specific + {name: "i", isBool: true}, + {name: "bench"}, + {name: "benchmem", isBool: true}, + {name: "benchtime"}, + {name: "covermode"}, + {name: "cpu"}, + {name: "cpuprofile"}, + {name: "memprofile"}, + {name: "memprofilerate"}, + {name: "blockprofile"}, + {name: "blockprofilerate"}, + {name: "parallel"}, + {name: "run"}, + {name: "short", isBool: true}, + {name: "timeout"}, + {name: "trace"}, + {name: "v", isBool: true}, + + // common build flags + {name: "a", isBool: true}, + {name: "race", isBool: true}, + {name: "x", isBool: true}, + {name: "asmflags"}, + {name: "buildmode"}, + {name: "compiler"}, + {name: "gccgoflags"}, + {name: "gcflags"}, + {name: "ldflags"}, + {name: "linkshared", isBool: true}, + {name: "pkgdir"}, + {name: "tags"}, + {name: "toolexec"}, +} + +// Split processes the arguments , separating flags and package +// names as done by "go test". +func Split(args []string) (packageNames, passToTest []string) { + inPkg := false + for i := 0; i < len(args); i++ { + if !strings.HasPrefix(args[i], "-") { + if !inPkg && packageNames == nil { + // First package name we've seen. + inPkg = true + } + if inPkg { + packageNames = append(packageNames, args[i]) + continue + } + } + + if inPkg { + // Found an argument beginning with "-"; end of package list. + inPkg = false + } + + n := parseTestFlag(args, i) + if n == 0 { + // This is a flag we do not know; we must assume + // that any args we see after this might be flag + // arguments, not package names. + inPkg = false + if packageNames == nil { + // make non-nil: we have seen the empty package list + packageNames = []string{} + } + passToTest = append(passToTest, args[i]) + continue + } + + passToTest = append(passToTest, args[i:i+n]...) + i += n - 1 + } + return packageNames, passToTest +} + +// parseTestFlag sees if argument i is a known flag and returns its +// definition, value, and whether it consumed an extra word. +func parseTestFlag(args []string, i int) (n int) { + arg := args[i] + if strings.HasPrefix(arg, "--") { // reduce two minuses to one + arg = arg[1:] + } + switch arg { + case "-?", "-h", "-help": + return 1 + } + if arg == "" || arg[0] != '-' { + return 0 + } + name := arg[1:] + // If there's already "test.", drop it for now. + name = strings.TrimPrefix(name, "test.") + equals := strings.Index(name, "=") + if equals >= 0 { + name = name[:equals] + } + for _, f := range testFlagDefn { + if name == f.name { + // Booleans are special because they have modes -x, -x=true, -x=false. + if !f.isBool && equals < 0 { + return 2 + } + return 1 + } + } + return 0 +} diff --git a/vendor/github.com/axw/gocov/gocov/main.go b/vendor/github.com/axw/gocov/gocov/main.go new file mode 100644 index 00000000000..d9be6a27c88 --- /dev/null +++ b/vendor/github.com/axw/gocov/gocov/main.go @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Gocov Authors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + + "github.com/axw/gocov" +) + +func usage() { + fmt.Fprintf(os.Stderr, "Usage:\n\n\tgocov command [arguments]\n\n") + fmt.Fprintf(os.Stderr, "The commands are:\n\n") + fmt.Fprintf(os.Stderr, "\tannotate\n") + fmt.Fprintf(os.Stderr, "\tconvert\n") + fmt.Fprintf(os.Stderr, "\treport\n") + fmt.Fprintf(os.Stderr, "\ttest\n") + fmt.Fprintf(os.Stderr, "\n") + flag.PrintDefaults() + os.Exit(2) +} + +func marshalJson(packages []*gocov.Package) ([]byte, error) { + return json.Marshal(struct{ Packages []*gocov.Package }{packages}) +} + +func unmarshalJson(data []byte) (packages []*gocov.Package, err error) { + result := &struct{ Packages []*gocov.Package }{} + err = json.Unmarshal(data, result) + if err == nil { + packages = result.Packages + } + return +} + +func main() { + flag.Usage = usage + flag.Parse() + + command := "" + if flag.NArg() > 0 { + command = flag.Arg(0) + switch command { + case "convert": + if flag.NArg() <= 1 { + fmt.Fprintln(os.Stderr, "missing cover profile") + os.Exit(1) + } + if err := convertProfiles(flag.Args()[1:]...); err != nil { + fmt.Fprintln(os.Stderr, "error:", err) + os.Exit(1) + } + case "annotate": + os.Exit(annotateSource()) + case "report": + os.Exit(reportCoverage()) + case "test": + if err := runTests(flag.Args()[1:]); err != nil { + fmt.Fprintln(os.Stderr, "error:", err) + os.Exit(1) + } + default: + fmt.Fprintf(os.Stderr, "Unknown command: %#q\n\n", command) + usage() + } + } else { + usage() + } +} diff --git a/vendor/github.com/axw/gocov/gocov/report.go b/vendor/github.com/axw/gocov/gocov/report.go new file mode 100644 index 00000000000..261feeb30df --- /dev/null +++ b/vendor/github.com/axw/gocov/gocov/report.go @@ -0,0 +1,224 @@ +// Copyright (c) 2012 The Gocov Authors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package main + +import ( + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "text/tabwriter" + + "github.com/axw/gocov" +) + +type report struct { + packages []*gocov.Package +} + +type reportFunction struct { + *gocov.Function + statementsReached int +} + +type reportFunctionList []reportFunction + +func (l reportFunctionList) Len() int { + return len(l) +} + +// TODO make sort method configurable? +func (l reportFunctionList) Less(i, j int) bool { + var left, right float64 + if len(l[i].Statements) > 0 { + left = float64(l[i].statementsReached) / float64(len(l[i].Statements)) + } + if len(l[j].Statements) > 0 { + right = float64(l[j].statementsReached) / float64(len(l[j].Statements)) + } + if left < right { + return true + } + return left == right && len(l[i].Statements) < len(l[j].Statements) +} + +func (l reportFunctionList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +type reverse struct { + sort.Interface +} + +func (r reverse) Less(i, j int) bool { + return r.Interface.Less(j, i) +} + +// NewReport creates a new report. +func newReport() (r *report) { + r = &report{} + return +} + +// AddPackage adds a package's coverage information to the report. +func (r *report) addPackage(p *gocov.Package) { + i := sort.Search(len(r.packages), func(i int) bool { + return r.packages[i].Name >= p.Name + }) + if i < len(r.packages) && r.packages[i].Name == p.Name { + r.packages[i].Accumulate(p) + } else { + head := r.packages[:i] + tail := append([]*gocov.Package{p}, r.packages[i:]...) + r.packages = append(head, tail...) + } +} + +// Clear clears the coverage information from the report. +func (r *report) clear() { + r.packages = nil +} + +// functionReports returns the packages functions as an array of +// reportFunction objects with the statements reached calculated +func functionReports(pkg *gocov.Package) reportFunctionList { + functions := make(reportFunctionList, len(pkg.Functions)) + for i, fn := range pkg.Functions { + reached := 0 + for _, stmt := range fn.Statements { + if stmt.Reached > 0 { + reached++ + } + } + functions[i] = reportFunction{fn, reached} + } + + return functions + +} + +// printTotalCoverage outputs the combined coverage for each +// package +func (r *report) printTotalCoverage(w io.Writer) { + var totalStatements, totalReached int + + for _, pkg := range r.packages { + functions := functionReports(pkg) + sort.Sort(reverse{functions}) + + for _, fn := range functions { + reached := fn.statementsReached + totalStatements += len(fn.Statements) + totalReached += reached + } + } + + coveragePercentage := float64(totalReached) / float64(totalStatements) * 100 + fmt.Fprintf(w, "Total Coverage: %.2f%% (%d/%d)", coveragePercentage, totalReached, totalStatements) + fmt.Fprintln(w) +} + +// PrintReport prints a coverage report to the given writer. +func printReport(w io.Writer, r *report) { + w = tabwriter.NewWriter(w, 0, 8, 0, '\t', 0) + //fmt.Fprintln(w, "Package\tFunction\tStatements\t") + //fmt.Fprintln(w, "-------\t--------\t---------\t") + for _, pkg := range r.packages { + printPackage(w, pkg) + fmt.Fprintln(w) + } + r.printTotalCoverage(w) +} + +func printPackage(w io.Writer, pkg *gocov.Package) { + functions := functionReports(pkg) + sort.Sort(reverse{functions}) + + var longestFunctionName int + var totalStatements, totalReached int + for _, fn := range functions { + reached := fn.statementsReached + totalStatements += len(fn.Statements) + totalReached += reached + var stmtPercent float64 = 0 + if len(fn.Statements) > 0 { + stmtPercent = float64(reached) / float64(len(fn.Statements)) * 100 + } + if len(fn.Name) > longestFunctionName { + longestFunctionName = len(fn.Name) + } + fmt.Fprintf(w, "%s/%s\t %s\t %.2f%% (%d/%d)\n", + pkg.Name, filepath.Base(fn.File), fn.Name, stmtPercent, + reached, len(fn.Statements)) + } + + var funcPercent float64 + if totalStatements > 0 { + funcPercent = float64(totalReached) / float64(totalStatements) * 100 + } + summaryLine := strings.Repeat("-", longestFunctionName) + fmt.Fprintf(w, "%s\t %s\t %.2f%% (%d/%d)\n", + pkg.Name, summaryLine, funcPercent, + totalReached, totalStatements) +} + +func reportCoverage() (rc int) { + files := make([]*os.File, 0, 1) + if flag.NArg() > 1 { + for _, name := range flag.Args()[1:] { + file, err := os.Open(name) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open file (%s): %s\n", name, err) + } else { + files = append(files, file) + } + } + } else { + files = append(files, os.Stdin) + } + report := newReport() + for _, file := range files { + data, err := ioutil.ReadAll(file) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read coverage file: %s\n", err) + return 1 + } + packages, err := unmarshalJson(data) + if err != nil { + fmt.Fprintf( + os.Stderr, "failed to unmarshal coverage data: %s\n", err) + return 1 + } + for _, pkg := range packages { + report.addPackage(pkg) + } + if file != os.Stdin { + file.Close() + } + } + fmt.Println() + printReport(os.Stdout, report) + return 0 +} diff --git a/vendor/github.com/axw/gocov/gocov/test.go b/vendor/github.com/axw/gocov/gocov/test.go new file mode 100644 index 00000000000..f5da51d1100 --- /dev/null +++ b/vendor/github.com/axw/gocov/gocov/test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2013 The Gocov Authors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/axw/gocov/gocov/internal/testflag" +) + +// resolvePackages returns a slice of resolved package names, given a slice of +// package names that could be relative or recursive. +func resolvePackages(pkgs []string) ([]string, error) { + var buf bytes.Buffer + cmd := exec.Command("go", append([]string{"list", "-e"}, pkgs...)...) + cmd.Stdin = os.Stdin + cmd.Stdout = &buf + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return nil, err + } + var resolvedPkgs []string + lines := strings.Split(buf.String(), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) > 0 { + resolvedPkgs = append(resolvedPkgs, line) + } + } + return resolvedPkgs, nil +} + +func runTests(args []string) error { + pkgs, testFlags := testflag.Split(args) + pkgs, err := resolvePackages(pkgs) + if err != nil { + return err + } + + tmpDir, err := ioutil.TempDir("", "gocov") + if err != nil { + return err + } + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + log.Printf("failed to clean up temp directory %q", tmpDir) + } + }() + + // Unique -coverprofile file names are used so that all the files can be + // later merged into a single file. + for i, pkg := range pkgs { + coverFile := filepath.Join(tmpDir, fmt.Sprintf("test%d.cov", i)) + cmdArgs := append([]string{"test", "-coverprofile", coverFile}, testFlags...) + cmdArgs = append(cmdArgs, pkg) + cmd := exec.Command("go", cmdArgs...) + cmd.Stdin = nil + // Write all test command output to stderr so as not to interfere with + // the JSON coverage output. + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return err + } + } + + // Packages without tests will not produce a coverprofile; only pick up the + // ones that were created. + files, err := filepath.Glob(filepath.Join(tmpDir, "test*.cov")) + if err != nil { + return err + } + + // Merge the profiles. + return convertProfiles(files...) +} diff --git a/vendor/github.com/axw/gocov/gocovutil/packages.go b/vendor/github.com/axw/gocov/gocovutil/packages.go new file mode 100644 index 00000000000..9b407c675cb --- /dev/null +++ b/vendor/github.com/axw/gocov/gocovutil/packages.go @@ -0,0 +1,82 @@ +package gocovutil + +import ( + "encoding/json" + "github.com/axw/gocov" + "io/ioutil" + "os" + "sort" +) + +// Packages represents a set of gocov.Package structures. +// The "AddPackage" method may be used to merge package +// coverage results into the set. +type Packages []*gocov.Package + +// AddPackage adds a package's coverage information to the +func (ps *Packages) AddPackage(p *gocov.Package) { + i := sort.Search(len(*ps), func(i int) bool { + return (*ps)[i].Name >= p.Name + }) + if i < len(*ps) && (*ps)[i].Name == p.Name { + (*ps)[i].Accumulate(p) + } else { + head := (*ps)[:i] + tail := append([]*gocov.Package{p}, (*ps)[i:]...) + *ps = append(head, tail...) + } +} + +// ReadPackages takes a list of filenames and parses their +// contents as a Packages object. +// +// The special filename "-" may be used to indicate standard input. +// Duplicate filenames are ignored. +func ReadPackages(filenames []string) (ps Packages, err error) { + copy_ := make([]string, len(filenames)) + copy(copy_, filenames) + filenames = copy_ + sort.Strings(filenames) + + // Eliminate duplicates. + unique := []string{filenames[0]} + if len(filenames) > 1 { + for _, f := range filenames[1:] { + if f != unique[len(unique)-1] { + unique = append(unique, f) + } + } + } + + // Open files. + var files []*os.File + for _, f := range filenames { + if f == "-" { + files = append(files, os.Stdin) + } else { + file, err := os.Open(f) + if err != nil { + return nil, err + } + defer file.Close() + files = append(files, os.Stdin) + } + } + + // Parse the files, accumulate Packages. + for _, file := range files { + data, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + result := &struct{ Packages []*gocov.Package }{} + err = json.Unmarshal(data, result) + if err != nil { + return nil, err + } + for _, p := range result.Packages { + ps.AddPackage(p) + } + } + return ps, nil +} diff --git a/vendor/github.com/jstemmer/go-junit-report/.gitignore b/vendor/github.com/jstemmer/go-junit-report/.gitignore new file mode 100644 index 00000000000..720bda6070d --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/.gitignore @@ -0,0 +1 @@ +go-junit-report diff --git a/vendor/github.com/jstemmer/go-junit-report/.travis.yml b/vendor/github.com/jstemmer/go-junit-report/.travis.yml new file mode 100644 index 00000000000..d0dff3ef8e5 --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/.travis.yml @@ -0,0 +1,16 @@ +language: go + +go: + - tip + - "1.13.x" + - "1.12.x" + - "1.11.x" + - "1.10.x" + - "1.9.x" + - "1.8.x" + - "1.7.x" + - "1.6.x" + - "1.5.x" + - "1.4.x" + - "1.3.x" + - "1.2.x" diff --git a/vendor/github.com/jstemmer/go-junit-report/LICENSE b/vendor/github.com/jstemmer/go-junit-report/LICENSE new file mode 100644 index 00000000000..f346564cefd --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012 Joel Stemmer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/jstemmer/go-junit-report/README.md b/vendor/github.com/jstemmer/go-junit-report/README.md new file mode 100644 index 00000000000..5b5f608be3d --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/README.md @@ -0,0 +1,49 @@ +# go-junit-report + +Converts `go test` output to an xml report, suitable for applications that +expect junit xml reports (e.g. [Jenkins](http://jenkins-ci.org)). + +[![Build Status][travis-badge]][travis-link] +[![Report Card][report-badge]][report-link] + +## Installation + +Go version 1.2 or higher is required. Install or update using the `go get` +command: + +```bash +go get -u github.com/jstemmer/go-junit-report +``` + +## Usage + +go-junit-report reads the `go test` verbose output from standard in and writes +junit compatible XML to standard out. + +```bash +go test -v 2>&1 | go-junit-report > report.xml +``` + +Note that it also can parse benchmark output with `-bench` flag: +```bash +go test -v -bench . -count 5 2>&1 | go-junit-report > report.xml +``` + +## Contribution + +Create an Issue and discuss the fix or feature, then fork the package. +Clone to github.com/jstemmer/go-junit-report. This is necessary because go import uses this path. +Fix or implement feature. Test and then commit change. +Specify #Issue and describe change in the commit message. +Create Pull Request. It can be merged by owner or administrator then. + +### Run Tests + +```bash +go test +``` + +[travis-badge]: https://travis-ci.org/jstemmer/go-junit-report.svg +[travis-link]: https://travis-ci.org/jstemmer/go-junit-report +[report-badge]: https://goreportcard.com/badge/github.com/jstemmer/go-junit-report +[report-link]: https://goreportcard.com/report/github.com/jstemmer/go-junit-report diff --git a/vendor/github.com/jstemmer/go-junit-report/formatter/formatter.go b/vendor/github.com/jstemmer/go-junit-report/formatter/formatter.go new file mode 100644 index 00000000000..6e1a0f31d68 --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/formatter/formatter.go @@ -0,0 +1,182 @@ +package formatter + +import ( + "bufio" + "encoding/xml" + "fmt" + "io" + "runtime" + "strings" + "time" + + "github.com/jstemmer/go-junit-report/parser" +) + +// JUnitTestSuites is a collection of JUnit test suites. +type JUnitTestSuites struct { + XMLName xml.Name `xml:"testsuites"` + Suites []JUnitTestSuite `xml:"testsuite"` +} + +// JUnitTestSuite is a single JUnit test suite which may contain many +// testcases. +type JUnitTestSuite struct { + XMLName xml.Name `xml:"testsuite"` + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Time string `xml:"time,attr"` + Name string `xml:"name,attr"` + Properties []JUnitProperty `xml:"properties>property,omitempty"` + TestCases []JUnitTestCase `xml:"testcase"` +} + +// JUnitTestCase is a single test case with its result. +type JUnitTestCase struct { + XMLName xml.Name `xml:"testcase"` + Classname string `xml:"classname,attr"` + Name string `xml:"name,attr"` + Time string `xml:"time,attr"` + SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` + Failure *JUnitFailure `xml:"failure,omitempty"` +} + +// JUnitSkipMessage contains the reason why a testcase was skipped. +type JUnitSkipMessage struct { + Message string `xml:"message,attr"` +} + +// JUnitProperty represents a key/value pair used to define properties. +type JUnitProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +// JUnitFailure contains data related to a failed test. +type JUnitFailure struct { + Message string `xml:"message,attr"` + Type string `xml:"type,attr"` + Contents string `xml:",chardata"` +} + +// JUnitReportXML writes a JUnit xml representation of the given report to w +// in the format described at http://windyroad.org/dl/Open%20Source/JUnit.xsd +func JUnitReportXML(report *parser.Report, noXMLHeader bool, goVersion string, w io.Writer) error { + suites := JUnitTestSuites{} + + // convert Report to JUnit test suites + for _, pkg := range report.Packages { + pkg.Benchmarks = mergeBenchmarks(pkg.Benchmarks) + ts := JUnitTestSuite{ + Tests: len(pkg.Tests) + len(pkg.Benchmarks), + Failures: 0, + Time: formatTime(pkg.Duration), + Name: pkg.Name, + Properties: []JUnitProperty{}, + TestCases: []JUnitTestCase{}, + } + + classname := pkg.Name + if idx := strings.LastIndex(classname, "/"); idx > -1 && idx < len(pkg.Name) { + classname = pkg.Name[idx+1:] + } + + // properties + if goVersion == "" { + // if goVersion was not specified as a flag, fall back to version reported by runtime + goVersion = runtime.Version() + } + ts.Properties = append(ts.Properties, JUnitProperty{"go.version", goVersion}) + if pkg.CoveragePct != "" { + ts.Properties = append(ts.Properties, JUnitProperty{"coverage.statements.pct", pkg.CoveragePct}) + } + + // individual test cases + for _, test := range pkg.Tests { + testCase := JUnitTestCase{ + Classname: classname, + Name: test.Name, + Time: formatTime(test.Duration), + Failure: nil, + } + + if test.Result == parser.FAIL { + ts.Failures++ + testCase.Failure = &JUnitFailure{ + Message: "Failed", + Type: "", + Contents: strings.Join(test.Output, "\n"), + } + } + + if test.Result == parser.SKIP { + testCase.SkipMessage = &JUnitSkipMessage{strings.Join(test.Output, "\n")} + } + + ts.TestCases = append(ts.TestCases, testCase) + } + + // individual benchmarks + for _, benchmark := range pkg.Benchmarks { + benchmarkCase := JUnitTestCase{ + Classname: classname, + Name: benchmark.Name, + Time: formatBenchmarkTime(benchmark.Duration), + } + + ts.TestCases = append(ts.TestCases, benchmarkCase) + } + + suites.Suites = append(suites.Suites, ts) + } + + // to xml + bytes, err := xml.MarshalIndent(suites, "", "\t") + if err != nil { + return err + } + + writer := bufio.NewWriter(w) + + if !noXMLHeader { + writer.WriteString(xml.Header) + } + + writer.Write(bytes) + writer.WriteByte('\n') + writer.Flush() + + return nil +} + +func mergeBenchmarks(benchmarks []*parser.Benchmark) []*parser.Benchmark { + var merged []*parser.Benchmark + benchmap := make(map[string][]*parser.Benchmark) + for _, bm := range benchmarks { + if _, ok := benchmap[bm.Name]; !ok { + merged = append(merged, &parser.Benchmark{Name: bm.Name}) + } + benchmap[bm.Name] = append(benchmap[bm.Name], bm) + } + + for _, bm := range merged { + for _, b := range benchmap[bm.Name] { + bm.Allocs += b.Allocs + bm.Bytes += b.Bytes + bm.Duration += b.Duration + } + n := len(benchmap[bm.Name]) + bm.Allocs /= n + bm.Bytes /= n + bm.Duration /= time.Duration(n) + } + + return merged +} + +func formatTime(d time.Duration) string { + return fmt.Sprintf("%.3f", d.Seconds()) +} + +func formatBenchmarkTime(d time.Duration) string { + return fmt.Sprintf("%.9f", d.Seconds()) +} diff --git a/vendor/github.com/jstemmer/go-junit-report/go-junit-report.go b/vendor/github.com/jstemmer/go-junit-report/go-junit-report.go new file mode 100644 index 00000000000..1332f3b65b1 --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/go-junit-report.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/jstemmer/go-junit-report/formatter" + "github.com/jstemmer/go-junit-report/parser" +) + +var ( + noXMLHeader = flag.Bool("no-xml-header", false, "do not print xml header") + packageName = flag.String("package-name", "", "specify a package name (compiled test have no package name in output)") + goVersionFlag = flag.String("go-version", "", "specify the value to use for the go.version property in the generated XML") + setExitCode = flag.Bool("set-exit-code", false, "set exit code to 1 if tests failed") +) + +func main() { + flag.Parse() + + if flag.NArg() != 0 { + fmt.Fprintf(os.Stderr, "%s does not accept positional arguments\n", os.Args[0]) + flag.Usage() + os.Exit(1) + } + + // Read input + report, err := parser.Parse(os.Stdin, *packageName) + if err != nil { + fmt.Printf("Error reading input: %s\n", err) + os.Exit(1) + } + + // Write xml + err = formatter.JUnitReportXML(report, *noXMLHeader, *goVersionFlag, os.Stdout) + if err != nil { + fmt.Printf("Error writing XML: %s\n", err) + os.Exit(1) + } + + if *setExitCode && report.Failures() > 0 { + os.Exit(1) + } +} diff --git a/vendor/github.com/jstemmer/go-junit-report/go.mod b/vendor/github.com/jstemmer/go-junit-report/go.mod new file mode 100644 index 00000000000..de52369acc9 --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/go.mod @@ -0,0 +1,3 @@ +module github.com/jstemmer/go-junit-report + +go 1.2 diff --git a/vendor/github.com/jstemmer/go-junit-report/parser/parser.go b/vendor/github.com/jstemmer/go-junit-report/parser/parser.go new file mode 100644 index 00000000000..e268128a2dc --- /dev/null +++ b/vendor/github.com/jstemmer/go-junit-report/parser/parser.go @@ -0,0 +1,319 @@ +package parser + +import ( + "bufio" + "io" + "regexp" + "strconv" + "strings" + "time" +) + +// Result represents a test result. +type Result int + +// Test result constants +const ( + PASS Result = iota + FAIL + SKIP +) + +// Report is a collection of package tests. +type Report struct { + Packages []Package +} + +// Package contains the test results of a single package. +type Package struct { + Name string + Duration time.Duration + Tests []*Test + Benchmarks []*Benchmark + CoveragePct string + + // Time is deprecated, use Duration instead. + Time int // in milliseconds +} + +// Test contains the results of a single test. +type Test struct { + Name string + Duration time.Duration + Result Result + Output []string + + SubtestIndent string + + // Time is deprecated, use Duration instead. + Time int // in milliseconds +} + +// Benchmark contains the results of a single benchmark. +type Benchmark struct { + Name string + Duration time.Duration + // number of B/op + Bytes int + // number of allocs/op + Allocs int +} + +var ( + regexStatus = regexp.MustCompile(`--- (PASS|FAIL|SKIP): (.+) \((\d+\.\d+)(?: seconds|s)\)`) + regexIndent = regexp.MustCompile(`^([ \t]+)---`) + regexCoverage = regexp.MustCompile(`^coverage:\s+(\d+\.\d+)%\s+of\s+statements(?:\sin\s.+)?$`) + regexResult = regexp.MustCompile(`^(ok|FAIL)\s+([^ ]+)\s+(?:(\d+\.\d+)s|\(cached\)|(\[\w+ failed]))(?:\s+coverage:\s+(\d+\.\d+)%\sof\sstatements(?:\sin\s.+)?)?$`) + // regexBenchmark captures 3-5 groups: benchmark name, number of times ran, ns/op (with or without decimal), B/op (optional), and allocs/op (optional). + regexBenchmark = regexp.MustCompile(`^(Benchmark[^ -]+)(?:-\d+\s+|\s+)(\d+)\s+(\d+|\d+\.\d+)\sns/op(?:\s+(\d+)\sB/op)?(?:\s+(\d+)\sallocs/op)?`) + regexOutput = regexp.MustCompile(`( )*\t(.*)`) + regexSummary = regexp.MustCompile(`^(PASS|FAIL|SKIP)$`) + regexPackageWithTest = regexp.MustCompile(`^# ([^\[\]]+) \[[^\]]+\]$`) +) + +// Parse parses go test output from reader r and returns a report with the +// results. An optional pkgName can be given, which is used in case a package +// result line is missing. +func Parse(r io.Reader, pkgName string) (*Report, error) { + reader := bufio.NewReader(r) + + report := &Report{make([]Package, 0)} + + // keep track of tests we find + var tests []*Test + + // keep track of benchmarks we find + var benchmarks []*Benchmark + + // sum of tests' time, use this if current test has no result line (when it is compiled test) + var testsTime time.Duration + + // current test + var cur string + + // coverage percentage report for current package + var coveragePct string + + // stores mapping between package name and output of build failures + var packageCaptures = map[string][]string{} + + // the name of the package which it's build failure output is being captured + var capturedPackage string + + // capture any non-test output + var buffers = map[string][]string{} + + // parse lines + for { + l, _, err := reader.ReadLine() + if err != nil && err == io.EOF { + break + } else if err != nil { + return nil, err + } + + line := string(l) + + if strings.HasPrefix(line, "=== RUN ") { + // new test + cur = strings.TrimSpace(line[8:]) + tests = append(tests, &Test{ + Name: cur, + Result: FAIL, + Output: make([]string, 0), + }) + + // clear the current build package, so output lines won't be added to that build + capturedPackage = "" + } else if matches := regexBenchmark.FindStringSubmatch(line); len(matches) == 6 { + bytes, _ := strconv.Atoi(matches[4]) + allocs, _ := strconv.Atoi(matches[5]) + + benchmarks = append(benchmarks, &Benchmark{ + Name: matches[1], + Duration: parseNanoseconds(matches[3]), + Bytes: bytes, + Allocs: allocs, + }) + } else if strings.HasPrefix(line, "=== PAUSE ") { + continue + } else if strings.HasPrefix(line, "=== CONT ") { + cur = strings.TrimSpace(line[8:]) + continue + } else if matches := regexResult.FindStringSubmatch(line); len(matches) == 6 { + if matches[5] != "" { + coveragePct = matches[5] + } + if strings.HasSuffix(matches[4], "failed]") { + // the build of the package failed, inject a dummy test into the package + // which indicate about the failure and contain the failure description. + tests = append(tests, &Test{ + Name: matches[4], + Result: FAIL, + Output: packageCaptures[matches[2]], + }) + } else if matches[1] == "FAIL" && !containsFailures(tests) && len(buffers[cur]) > 0 { + // This package didn't have any failing tests, but still it + // failed with some output. Create a dummy test with the + // output. + tests = append(tests, &Test{ + Name: "Failure", + Result: FAIL, + Output: buffers[cur], + }) + buffers[cur] = buffers[cur][0:0] + } + + // all tests in this package are finished + report.Packages = append(report.Packages, Package{ + Name: matches[2], + Duration: parseSeconds(matches[3]), + Tests: tests, + Benchmarks: benchmarks, + CoveragePct: coveragePct, + + Time: int(parseSeconds(matches[3]) / time.Millisecond), // deprecated + }) + + buffers[cur] = buffers[cur][0:0] + tests = make([]*Test, 0) + benchmarks = make([]*Benchmark, 0) + coveragePct = "" + cur = "" + testsTime = 0 + } else if matches := regexStatus.FindStringSubmatch(line); len(matches) == 4 { + cur = matches[2] + test := findTest(tests, cur) + if test == nil { + continue + } + + // test status + if matches[1] == "PASS" { + test.Result = PASS + } else if matches[1] == "SKIP" { + test.Result = SKIP + } else { + test.Result = FAIL + } + + if matches := regexIndent.FindStringSubmatch(line); len(matches) == 2 { + test.SubtestIndent = matches[1] + } + + test.Output = buffers[cur] + + test.Name = matches[2] + test.Duration = parseSeconds(matches[3]) + testsTime += test.Duration + + test.Time = int(test.Duration / time.Millisecond) // deprecated + } else if matches := regexCoverage.FindStringSubmatch(line); len(matches) == 2 { + coveragePct = matches[1] + } else if matches := regexOutput.FindStringSubmatch(line); capturedPackage == "" && len(matches) == 3 { + // Sub-tests start with one or more series of 4-space indents, followed by a hard tab, + // followed by the test output + // Top-level tests start with a hard tab. + test := findTest(tests, cur) + if test == nil { + continue + } + test.Output = append(test.Output, matches[2]) + } else if strings.HasPrefix(line, "# ") { + // indicates a capture of build output of a package. set the current build package. + packageWithTestBinary := regexPackageWithTest.FindStringSubmatch(line) + if packageWithTestBinary != nil { + // Sometimes, the text after "# " shows the name of the test binary + // (".test") in addition to the package + // e.g.: "# package/name [package/name.test]" + capturedPackage = packageWithTestBinary[1] + } else { + capturedPackage = line[2:] + } + } else if capturedPackage != "" { + // current line is build failure capture for the current built package + packageCaptures[capturedPackage] = append(packageCaptures[capturedPackage], line) + } else if regexSummary.MatchString(line) { + // unset current test name so any additional output after the + // summary is captured separately. + cur = "" + } else { + // buffer anything else that we didn't recognize + buffers[cur] = append(buffers[cur], line) + + // if we have a current test, also append to its output + test := findTest(tests, cur) + if test != nil { + if strings.HasPrefix(line, test.SubtestIndent+" ") { + test.Output = append(test.Output, strings.TrimPrefix(line, test.SubtestIndent+" ")) + } + } + } + } + + if len(tests) > 0 { + // no result line found + report.Packages = append(report.Packages, Package{ + Name: pkgName, + Duration: testsTime, + Time: int(testsTime / time.Millisecond), + Tests: tests, + Benchmarks: benchmarks, + CoveragePct: coveragePct, + }) + } + + return report, nil +} + +func parseSeconds(t string) time.Duration { + if t == "" { + return time.Duration(0) + } + // ignore error + d, _ := time.ParseDuration(t + "s") + return d +} + +func parseNanoseconds(t string) time.Duration { + // note: if input < 1 ns precision, result will be 0s. + if t == "" { + return time.Duration(0) + } + // ignore error + d, _ := time.ParseDuration(t + "ns") + return d +} + +func findTest(tests []*Test, name string) *Test { + for i := len(tests) - 1; i >= 0; i-- { + if tests[i].Name == name { + return tests[i] + } + } + return nil +} + +func containsFailures(tests []*Test) bool { + for _, test := range tests { + if test.Result == FAIL { + return true + } + } + return false +} + +// Failures counts the number of failed tests in this report +func (r *Report) Failures() int { + count := 0 + + for _, p := range r.Packages { + for _, t := range p.Tests { + if t.Result == FAIL { + count++ + } + } + } + + return count +} diff --git a/vendor/github.com/sirupsen/logrus/go.mod b/vendor/github.com/sirupsen/logrus/go.mod index ea2662260e3..12fdf989847 100644 --- a/vendor/github.com/sirupsen/logrus/go.mod +++ b/vendor/github.com/sirupsen/logrus/go.mod @@ -8,5 +8,3 @@ require ( github.com/stretchr/testify v1.2.2 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 ) - -go 1.13 diff --git a/vendor/golang.org/x/tools/cover/profile.go b/vendor/golang.org/x/tools/cover/profile.go new file mode 100644 index 00000000000..0bb35417748 --- /dev/null +++ b/vendor/golang.org/x/tools/cover/profile.go @@ -0,0 +1,256 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package cover provides support for parsing coverage profiles +// generated by "go test -coverprofile=cover.out". +package cover // import "golang.org/x/tools/cover" + +import ( + "bufio" + "errors" + "fmt" + "math" + "os" + "sort" + "strconv" + "strings" +) + +// Profile represents the profiling data for a specific file. +type Profile struct { + FileName string + Mode string + Blocks []ProfileBlock +} + +// ProfileBlock represents a single block of profiling data. +type ProfileBlock struct { + StartLine, StartCol int + EndLine, EndCol int + NumStmt, Count int +} + +type byFileName []*Profile + +func (p byFileName) Len() int { return len(p) } +func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName } +func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// ParseProfiles parses profile data in the specified file and returns a +// Profile for each source file described therein. +func ParseProfiles(fileName string) ([]*Profile, error) { + pf, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer pf.Close() + + files := make(map[string]*Profile) + buf := bufio.NewReader(pf) + // First line is "mode: foo", where foo is "set", "count", or "atomic". + // Rest of file is in the format + // encoding/base64/base64.go:34.44,37.40 3 1 + // where the fields are: name.go:line.column,line.column numberOfStatements count + s := bufio.NewScanner(buf) + mode := "" + for s.Scan() { + line := s.Text() + if mode == "" { + const p = "mode: " + if !strings.HasPrefix(line, p) || line == p { + return nil, fmt.Errorf("bad mode line: %v", line) + } + mode = line[len(p):] + continue + } + fn, b, err := parseLine(line) + if err != nil { + return nil, fmt.Errorf("line %q doesn't match expected format: %v", line, err) + } + p := files[fn] + if p == nil { + p = &Profile{ + FileName: fn, + Mode: mode, + } + files[fn] = p + } + p.Blocks = append(p.Blocks, b) + } + if err := s.Err(); err != nil { + return nil, err + } + for _, p := range files { + sort.Sort(blocksByStart(p.Blocks)) + // Merge samples from the same location. + j := 1 + for i := 1; i < len(p.Blocks); i++ { + b := p.Blocks[i] + last := p.Blocks[j-1] + if b.StartLine == last.StartLine && + b.StartCol == last.StartCol && + b.EndLine == last.EndLine && + b.EndCol == last.EndCol { + if b.NumStmt != last.NumStmt { + return nil, fmt.Errorf("inconsistent NumStmt: changed from %d to %d", last.NumStmt, b.NumStmt) + } + if mode == "set" { + p.Blocks[j-1].Count |= b.Count + } else { + p.Blocks[j-1].Count += b.Count + } + continue + } + p.Blocks[j] = b + j++ + } + p.Blocks = p.Blocks[:j] + } + // Generate a sorted slice. + profiles := make([]*Profile, 0, len(files)) + for _, profile := range files { + profiles = append(profiles, profile) + } + sort.Sort(byFileName(profiles)) + return profiles, nil +} + +// parseLine parses a line from a coverage file. +// It is equivalent to the regex +// ^(.+):([0-9]+)\.([0-9]+),([0-9]+)\.([0-9]+) ([0-9]+) ([0-9]+)$ +// +// However, it is much faster: https://golang.org/cl/179377 +func parseLine(l string) (fileName string, block ProfileBlock, err error) { + end := len(l) + + b := ProfileBlock{} + b.Count, end, err = seekBack(l, ' ', end, "Count") + if err != nil { + return "", b, err + } + b.NumStmt, end, err = seekBack(l, ' ', end, "NumStmt") + if err != nil { + return "", b, err + } + b.EndCol, end, err = seekBack(l, '.', end, "EndCol") + if err != nil { + return "", b, err + } + b.EndLine, end, err = seekBack(l, ',', end, "EndLine") + if err != nil { + return "", b, err + } + b.StartCol, end, err = seekBack(l, '.', end, "StartCol") + if err != nil { + return "", b, err + } + b.StartLine, end, err = seekBack(l, ':', end, "StartLine") + if err != nil { + return "", b, err + } + fn := l[0:end] + if fn == "" { + return "", b, errors.New("a FileName cannot be blank") + } + return fn, b, nil +} + +// seekBack searches backwards from end to find sep in l, then returns the +// value between sep and end as an integer. +// If seekBack fails, the returned error will reference what. +func seekBack(l string, sep byte, end int, what string) (value int, nextSep int, err error) { + // Since we're seeking backwards and we know only ASCII is legal for these values, + // we can ignore the possibility of non-ASCII characters. + for start := end - 1; start >= 0; start-- { + if l[start] == sep { + i, err := strconv.Atoi(l[start+1 : end]) + if err != nil { + return 0, 0, fmt.Errorf("couldn't parse %q: %v", what, err) + } + if i < 0 { + return 0, 0, fmt.Errorf("negative values are not allowed for %s, found %d", what, i) + } + return i, start, nil + } + } + return 0, 0, fmt.Errorf("couldn't find a %s before %s", string(sep), what) +} + +type blocksByStart []ProfileBlock + +func (b blocksByStart) Len() int { return len(b) } +func (b blocksByStart) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b blocksByStart) Less(i, j int) bool { + bi, bj := b[i], b[j] + return bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol +} + +// Boundary represents the position in a source file of the beginning or end of a +// block as reported by the coverage profile. In HTML mode, it will correspond to +// the opening or closing of a tag and will be used to colorize the source +type Boundary struct { + Offset int // Location as a byte offset in the source file. + Start bool // Is this the start of a block? + Count int // Event count from the cover profile. + Norm float64 // Count normalized to [0..1]. +} + +// Boundaries returns a Profile as a set of Boundary objects within the provided src. +func (p *Profile) Boundaries(src []byte) (boundaries []Boundary) { + // Find maximum count. + max := 0 + for _, b := range p.Blocks { + if b.Count > max { + max = b.Count + } + } + // Divisor for normalization. + divisor := math.Log(float64(max)) + + // boundary returns a Boundary, populating the Norm field with a normalized Count. + boundary := func(offset int, start bool, count int) Boundary { + b := Boundary{Offset: offset, Start: start, Count: count} + if !start || count == 0 { + return b + } + if max <= 1 { + b.Norm = 0.8 // Profile is in"set" mode; we want a heat map. Use cov8 in the CSS. + } else if count > 0 { + b.Norm = math.Log(float64(count)) / divisor + } + return b + } + + line, col := 1, 2 // TODO: Why is this 2? + for si, bi := 0, 0; si < len(src) && bi < len(p.Blocks); { + b := p.Blocks[bi] + if b.StartLine == line && b.StartCol == col { + boundaries = append(boundaries, boundary(si, true, b.Count)) + } + if b.EndLine == line && b.EndCol == col || line > b.EndLine { + boundaries = append(boundaries, boundary(si, false, 0)) + bi++ + continue // Don't advance through src; maybe the next block starts here. + } + if src[si] == '\n' { + line++ + col = 0 + } + col++ + si++ + } + sort.Sort(boundariesByPos(boundaries)) + return +} + +type boundariesByPos []Boundary + +func (b boundariesByPos) Len() int { return len(b) } +func (b boundariesByPos) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b boundariesByPos) Less(i, j int) bool { + if b[i].Offset == b[j].Offset { + return !b[i].Start && b[j].Start + } + return b[i].Offset < b[j].Offset +}