Merge branch 'master' into juliens/osx-dev

This commit is contained in:
Julien Stroheker 2020-01-28 15:33:17 -05:00
Родитель 577d8a2c0a 8dcb1cc618
Коммит 1410903c7f
59 изменённых файлов: 3412 добавлений и 146 удалений

4
.gitignore поставляемый
Просмотреть файл

@ -16,3 +16,7 @@ __pycache__
/python/az/aro/dist
/secrets
/mdm_statsd.socket
/uts.txt
/cover.out
/coverage.*
/report.xml

69
.pipelines/ci.yml Normal file
Просмотреть файл

@ -0,0 +1,69 @@
# Azure DevOps Pipeline running CI
trigger:
- master
variables:
- template: vars.yml
jobs:
- job: "Python_Unit_Tests"
pool:
vmImage: "ubuntu-latest"
strategy:
matrix:
Python27:
python.version: "2.7"
Python35:
python.version: "3.5"
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "$(python.version)"
- template: ./templates/template-setup-golang-env.yml
parameters:
gobin: ${{ variables.GOBIN }}
gopath: ${{ variables.GOPATH }}
goroot: ${{ variables.GOROOT }}
modulePath: ${{ variables.modulePath }}
- script: |
set -x
pip install virtualenv
make test-python
displayName: "🧪Run Python Unit Tests : $(python.version)"
workingDirectory: "${{ variables.modulePath }}"
- job: "Golang_Unit_Tests"
pool:
vmImage: "ubuntu-latest"
steps:
- template: ./templates/template-setup-golang-env.yml
parameters:
gobin: ${{ variables.GOBIN }}
gopath: ${{ variables.GOPATH }}
goroot: ${{ variables.GOROOT }}
modulePath: ${{ variables.modulePath }}
- script: |
set -x
make test-go
[[ -z "$(git status -s)" ]]
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
displayName: "📊 Publish tests results"
inputs:
testResultsFiles: $(System.DefaultWorkingDirectory)/**/report.xml
condition: succeededOrFailed()
- task: PublishCodeCoverageResults@1
displayName: "📈 Publish code coverage"
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: "$(System.DefaultWorkingDirectory)/**/coverage.xml"
reportDirectory: "$(System.DefaultWorkingDirectory)/**/coverage"
failIfCoverageEmpty: false
condition: succeededOrFailed()

Просмотреть файл

@ -0,0 +1,18 @@
parameters:
gobin: ""
gopath: ""
goroot: ""
modulePath: ""
steps:
- script: |
mkdir -p '${{ parameters.gobin }}'
mkdir -p '${{ parameters.gopath }}/pkg'
mkdir -p '${{ parameters.modulePath }}'
ls -a | grep -v ${{ parameters.gopath }} | xargs mv -t ${{ parameters.modulePath }}
echo "##vso[task.prependpath]${{ parameters.gobin }}"
echo "##vso[task.prependpath]${{ parameters.goroot }}/bin"
sudo add-apt-repository ppa:kubuntu-ppa/backports
sudo apt-get update
sudo apt-get install libgpgme-dev gcc -y
go version
displayName: "⚙️ Set up the workspace"

5
.pipelines/vars.yml Normal file
Просмотреть файл

@ -0,0 +1,5 @@
variables:
GOBIN: "$(GOPATH)/bin" # Go binaries path
GOROOT: "/usr/local/go1.13" # Go installation path
GOPATH: "$(system.defaultWorkingDirectory)/gopath" # Go workspace path
modulePath: "$(GOPATH)/src/github.com/Azure/ARO-RP" # Path to the module's code

44
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",

Просмотреть файл

@ -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]]

Просмотреть файл

@ -40,7 +40,7 @@ client: generate
--input-file=/swagger/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/preview/2019-12-31-preview/redhatopenshift.json \
--output-folder=/python/client
sudo chown -R $(id -un):$(id -gn) pkg/client python/client
sudo chown -R $$(id -un):$$(id -gn) pkg/client python/client
sed -i -e 's|azure/aro-rp|Azure/ARO-RP|g' pkg/client/services/preview/redhatopenshift/mgmt/2019-12-31-preview/redhatopenshift/models.go pkg/client/services/preview/redhatopenshift/mgmt/2019-12-31-preview/redhatopenshift/redhatopenshiftapi/interfaces.go
rm -rf python/client/azure/mgmt/redhatopenshift/v2019_12_31_preview/aio
>python/client/__init__.py
@ -81,7 +81,7 @@ secrets-update:
oc create secret generic aro-v4-dev --from-file=secrets --dry-run -o yaml | oc apply -f -
e2e:
go test ./test/e2e -timeout "60m" -v -ginkgo.v -tags e2e
go test ./test/e2e -timeout 60m -v -ginkgo.v -tags e2e
test-go: generate
go build ./...
@ -93,9 +93,10 @@ test-go: generate
@[ -z "$$(ls pkg/util/*.go 2>/dev/null)" ] || (echo error: go files are not allowed in pkg/util, use a subpackage; exit 1)
@[ -z "$$(find -name "*:*")" ] || (echo error: filenames with colons are not allowed on Windows, please rename; exit 1)
@sha256sum --quiet -c .sha256sum || (echo error: client library is stale, please run make client; exit 1)
go test -tags e2e -run ^$$ ./test/e2e/...
go vet ./...
go test ./...
go test ./... -coverprofile cover.out | tee uts.txt
test-python: generate pyenv${PYTHON_VERSION}
. pyenv${PYTHON_VERSION}/bin/activate && \

Просмотреть файл

@ -9,6 +9,15 @@
}
}
},
{
"component": {
"type": "git",
"git": {
"commitHash": "3a14fb1c4737b3995174c5f4d6d08a348b9b4180",
"repositoryUrl": "https://github.com/AlekSi/gocov-xml/"
}
}
},
{
"component": {
"type": "git",
@ -108,6 +117,15 @@
}
}
},
{
"component": {
"type": "git",
"git": {
"commitHash": "b6eca663ebb7e7ef9798914d19f53ba2c6f74c96",
"repositoryUrl": "https://github.com/axw/gocov/"
}
}
},
{
"component": {
"type": "git",
@ -450,6 +468,15 @@
}
}
},
{
"component": {
"type": "git",
"git": {
"commitHash": "cc1f095d5cc5eca2844f5c5ea7bb37f6b9bf6cac",
"repositoryUrl": "https://github.com/jstemmer/go-junit-report/"
}
}
},
{
"component": {
"type": "git",

Просмотреть файл

@ -24,17 +24,17 @@ func monitor(ctx context.Context, log *logrus.Entry) error {
return err
}
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), env, uuid)
if err != nil {
return err
}
m, err := statsd.New(ctx, log.WithField("component", "metrics"), env)
if err != nil {
return err
}
defer m.Close()
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), env, m, uuid)
if err != nil {
return err
}
mon := pkgmonitor.NewMonitor(log.WithField("component", "monitor"), env, db, m)
return mon.Run(ctx)

Просмотреть файл

@ -30,7 +30,13 @@ func rp(ctx context.Context, log *logrus.Entry) error {
return err
}
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), env, uuid)
m, err := statsd.New(ctx, log.WithField("component", "metrics"), env)
if err != nil {
return err
}
defer m.Close()
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), env, m, uuid)
if err != nil {
return err
}
@ -40,12 +46,6 @@ func rp(ctx context.Context, log *logrus.Entry) error {
done := make(chan struct{})
signal.Notify(sigterm, syscall.SIGTERM)
m, err := statsd.New(ctx, log.WithField("component", "metrics"), env)
if err != nil {
return err
}
defer m.Close()
f, err := frontend.NewFrontend(ctx, log.WithField("component", "frontend"), env, db, api.APIs, m)
if err != nil {
return err

Просмотреть файл

@ -1,16 +1,19 @@
# Testing
* unit tests
* Unit tests
```
make test
```
* e2e tests
* E2e tests
1. start the RP
1. run the e2e
```
make e2e
```
Note the test will tell you will what Environment variables to define
1. Start the RP
1. Run the e2e:
```
make e2e
```
The test will tell you what environment variables need to be defined.

Просмотреть файл

@ -15,6 +15,7 @@ import (
"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/database"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/metrics/noop"
utillog "github.com/Azure/ARO-RP/pkg/util/log"
)
@ -28,7 +29,7 @@ func run(ctx context.Context, log *logrus.Entry) error {
return err
}
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), env, "")
db, err := database.NewDatabase(ctx, log.WithField("component", "database"), env, &noop.Noop{}, "")
if err != nil {
return err
}

Просмотреть файл

@ -5,20 +5,19 @@ package main
import (
"context"
"fmt"
"encoding/json"
"os"
"strings"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/ugorji/go/codec"
"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/redhatopenshift"
utillog "github.com/Azure/ARO-RP/pkg/util/log"
)
func writeKubeconfig(ctx context.Context, resourceid string) error {
res, err := azure.ParseResourceID(resourceid)
func writeKubeconfig(ctx context.Context, resourceID string) error {
res, err := azure.ParseResourceID(resourceID)
if err != nil {
return err
}
@ -27,47 +26,46 @@ func writeKubeconfig(ctx context.Context, resourceid string) error {
if err != nil {
return err
}
openshiftclusters := redhatopenshift.NewOpenShiftClustersClient(res.SubscriptionID, authorizer)
oc, err := openshiftclusters.Get(ctx, res.ResourceGroup, res.ResourceName)
if err != nil {
return err
}
creds, err := openshiftclusters.ListCredentials(ctx, res.ResourceGroup, res.ResourceName)
if err != nil {
return err
}
tokenURL, err := getTokenURLFromConsoleURL(*oc.Properties.ConsoleProfile.URL)
if err != nil {
return err
}
token, err := getAuthorizedToken(tokenURL, *creds.KubeadminUsername, *creds.KubeadminPassword)
if err != nil {
return err
}
adminKubeconfig, err := makeKubeconfig(strings.Replace(*oc.Properties.ApiserverProfile.URL, "https://", "", 1), *creds.KubeadminUsername, token, "kube-system")
if err != nil {
return err
}
h := &codec.JsonHandle{
Indent: 4,
}
err = api.AddExtensions(&h.BasicHandle)
if err != nil {
return err
}
adminKubeconfig := makeKubeconfig(strings.Replace(*oc.Properties.ApiserverProfile.URL, "https://", "", 1), *creds.KubeadminUsername, token, "kube-system")
return codec.NewEncoder(os.Stdout, h).Encode(adminKubeconfig)
e := json.NewEncoder(os.Stdout)
e.SetIndent("", " ")
return e.Encode(adminKubeconfig)
}
func main() {
if len(os.Args) != 2 {
fmt.Printf("usage: %s resourceid\n", os.Args[0])
os.Exit(2)
}
ctx := context.Background()
log := utillog.GetLogger()
if len(os.Args) != 2 {
log.Fatalf("usage: %s resourceid\n", os.Args[0])
}
err := writeKubeconfig(ctx, os.Args[1])
if err != nil {
fmt.Printf("%v\n", err)
log.Fatal(err)
}
}

Просмотреть файл

@ -9,7 +9,7 @@ import (
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
)
func makeKubeconfig(endpoint, username, token, namespace string) (*v1.Config, error) {
func makeKubeconfig(endpoint, username, token, namespace string) *v1.Config {
clustername := strings.Replace(endpoint, ".", "-", -1)
authinfoname := username + "/" + clustername
contextname := namespace + "/" + clustername + "/" + username
@ -45,5 +45,5 @@ func makeKubeconfig(endpoint, username, token, namespace string) (*v1.Config, er
},
},
CurrentContext: contextname,
}, nil
}
}

Просмотреть файл

@ -5,7 +5,6 @@ package main
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"strings"
@ -17,25 +16,28 @@ func parseTokenResponse(location string) (string, error) {
return "", err
}
for _, param := range strings.Split(locURL.Fragment, "&") {
nameValue := strings.Split(param, "=")
if nameValue[0] == "access_token" {
return nameValue[1], nil
}
v, err := url.ParseQuery(locURL.Fragment)
if err != nil {
return "", err
}
return "", fmt.Errorf("token not found in response")
return v.Get("access_token"), nil
}
func getTokenURLFromConsoleURL(consoleURL string) (*url.URL, error) {
tokenURL, err := url.Parse(strings.Replace(consoleURL, "console-openshift-console.apps.", "oauth-openshift.apps.", 1))
tokenURL, err := url.Parse(consoleURL)
if err != nil {
return nil, err
}
tokenURL.Host = strings.Replace(tokenURL.Host, "console-openshift-console", "oauth-openshift", 1)
tokenURL.Path = "/oauth/authorize"
q := tokenURL.Query()
q.Set("response_type", "token")
q.Set("client_id", "openshift-challenging-client")
tokenURL.RawQuery = q.Encode()
return tokenURL, nil
}
@ -44,24 +46,26 @@ func getAuthorizedToken(tokenURL *url.URL, username, password string) (string, e
req.SetBasicAuth(username, password)
req.Header.Add("X-CSRF-Token", "1")
resp, err := (&http.Client{
cli := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }}).Do(req)
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := cli.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 302 {
if resp.StatusCode != http.StatusFound {
return "", err
}
loc := resp.Header["Location"]
if loc == nil {
return "", fmt.Errorf("no Location header found")
}
return parseTokenResponse(loc[0])
return parseTokenResponse(resp.Header.Get("Location"))
}

Просмотреть файл

@ -10,23 +10,22 @@ import (
func TestGetTokenURLFromConsoleURL(t *testing.T) {
got, err := getTokenURLFromConsoleURL("https://console-openshift-console.apps.zhz4qvm8.as-rp-v4.osadev.cloud")
if err != nil {
t.Errorf("GetTokenURLFromConsoleURL() error = %v", err)
return
t.Fatal(err)
}
if got.String() != "https://oauth-openshift.apps.zhz4qvm8.as-rp-v4.osadev.cloud/oauth/authorize?client_id=openshift-challenging-client&response_type=token" {
t.Errorf("GetTokenURLFromConsoleURL() = %v", got)
t.Error(got)
}
}
func TestParseTokenResponse(t *testing.T) {
location := "https://oauth-openshift.apps.zhz4qvm8.as-rp-v4.osadev.cloud/oauth/token/implicit#access_token=fIof3McZ5DVt1Uy6atsnUhis-y43dMctA5irrxH8ixk&expires_in=86400&scope=user%3Afull&token_type=Bearer"
want := "fIof3McZ5DVt1Uy6atsnUhis-y43dMctA5irrxH8ixk"
got, err := parseTokenResponse(location)
if err != nil {
t.Errorf("parseTokenResponse() error = %v", err)
return
t.Fatal(err)
}
if got != want {
t.Errorf("parseTokenResponse() = %v, want %v", got, want)
t.Error(got)
}
}

Просмотреть файл

@ -15,10 +15,15 @@ import (
"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/metrics"
dbmetrics "github.com/Azure/ARO-RP/pkg/metrics/statsd/cosmosdb"
)
// Database represents a database
type Database struct {
log *logrus.Entry
m metrics.Interface
AsyncOperations AsyncOperations
Monitors Monitors
OpenShiftClusters OpenShiftClusters
@ -26,7 +31,7 @@ type Database struct {
}
// NewDatabase returns a new Database
func NewDatabase(ctx context.Context, log *logrus.Entry, env env.Interface, uuid string) (db *Database, err error) {
func NewDatabase(ctx context.Context, log *logrus.Entry, env env.Interface, m metrics.Interface, uuid string) (db *Database, err error) {
databaseAccount, masterKey := env.CosmosDB()
h := &codec.JsonHandle{
@ -43,10 +48,11 @@ func NewDatabase(ctx context.Context, log *logrus.Entry, env env.Interface, uuid
}
c := &http.Client{
Transport: &http.Transport{
Transport: dbmetrics.New(log, &http.Transport{
// disable HTTP/2 for now: https://github.com/golang/go/issues/36026
TLSNextProto: map[string]func(string, *tls.Conn) http.RoundTripper{},
},
TLSNextProto: map[string]func(string, *tls.Conn) http.RoundTripper{},
MaxIdleConnsPerHost: 20,
}, m),
Timeout: 30 * time.Second,
}
@ -55,7 +61,10 @@ func NewDatabase(ctx context.Context, log *logrus.Entry, env env.Interface, uuid
return nil, err
}
db = &Database{}
db = &Database{
log: log,
m: m,
}
db.AsyncOperations, err = NewAsyncOperations(uuid, dbc, env.DatabaseName(), "AsyncOperations")
if err != nil {
@ -77,5 +86,7 @@ func NewDatabase(ctx context.Context, log *logrus.Entry, env env.Interface, uuid
return nil, err
}
go db.emitMetrics(ctx)
return db, nil
}

26
pkg/database/metrics.go Normal file
Просмотреть файл

@ -0,0 +1,26 @@
package database
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"time"
"github.com/Azure/ARO-RP/pkg/util/recover"
)
func (db *Database) emitMetrics(ctx context.Context) {
defer recover.Panic(db.log)
t := time.NewTicker(time.Minute)
defer t.Stop()
for range t.C {
i, err := db.OpenShiftClusters.QueueLength(ctx, "OpenShiftClusters")
if err != nil {
db.log.Error(err)
} else {
db.m.EmitGauge("database.openshiftclusters.queue.length", int64(i), nil)
}
}
}

Просмотреть файл

@ -16,8 +16,9 @@ import (
)
type openShiftClusters struct {
c cosmosdb.OpenShiftClusterDocumentClient
uuid string
c cosmosdb.OpenShiftClusterDocumentClient
collc cosmosdb.CollectionClient
uuid string
}
// OpenShiftClusters is the database interface for OpenShiftClusterDocuments
@ -25,6 +26,7 @@ type OpenShiftClusters interface {
Create(context.Context, *api.OpenShiftClusterDocument) (*api.OpenShiftClusterDocument, error)
ListAll(context.Context) (*api.OpenShiftClusterDocuments, error)
Get(context.Context, string) (*api.OpenShiftClusterDocument, error)
QueueLength(context.Context, string) (int, error)
Patch(context.Context, string, func(*api.OpenShiftClusterDocument) error) (*api.OpenShiftClusterDocument, error)
PatchWithLease(context.Context, string, func(*api.OpenShiftClusterDocument) error) (*api.OpenShiftClusterDocument, error)
Update(context.Context, *api.OpenShiftClusterDocument) (*api.OpenShiftClusterDocument, error)
@ -64,8 +66,9 @@ func NewOpenShiftClusters(ctx context.Context, uuid string, dbc cosmosdb.Databas
}
return &openShiftClusters{
c: cosmosdb.NewOpenShiftClusterDocumentClient(collc, collid),
uuid: uuid,
c: cosmosdb.NewOpenShiftClusterDocumentClient(collc, collid),
collc: collc,
uuid: uuid,
}, nil
}
@ -126,6 +129,37 @@ func (c *openShiftClusters) Get(ctx context.Context, key string) (*api.OpenShift
}
}
// QueueLength returns OpenShiftClusters un-queued document count.
// If error occurs, 0 is returned with error message
func (c *openShiftClusters) QueueLength(ctx context.Context, collid string) (int, error) {
partitions, err := c.collc.PartitionKeyRanges(ctx, collid)
if err != nil {
return 0, err
}
var countTotal int
for _, r := range partitions.PartitionKeyRanges {
result := c.c.Query("", &cosmosdb.Query{
Query: `SELECT VALUE COUNT(1) FROM OpenShiftClusters doc WHERE doc.openShiftCluster.properties.provisioningState IN ("Creating", "Deleting", "Updating") AND (doc.leaseExpires ?? 0) < GetCurrentTimestamp() / 1000`,
}, &cosmosdb.Options{
PartitionKeyRangeID: r.ID,
})
// because we aggregate count we don't expect pagination in this query result,
// so we gonna call Next() only once per partition.
var data struct {
api.MissingFields
Document []int `json:"Documents,omitempty"`
}
err := result.NextRaw(ctx, &data)
if err != nil {
return 0, err
}
countTotal = countTotal + data.Document[0]
}
return countTotal, nil
}
func (c *openShiftClusters) Patch(ctx context.Context, key string, f func(*api.OpenShiftClusterDocument) error) (*api.OpenShiftClusterDocument, error) {
return c.patch(ctx, key, f, nil)
}
@ -207,7 +241,7 @@ func (c *openShiftClusters) ListByPrefix(subscriptionID string, prefix string) (
func (c *openShiftClusters) Dequeue(ctx context.Context) (*api.OpenShiftClusterDocument, error) {
i := c.c.Query("", &cosmosdb.Query{
Query: `SELECT * FROM OpenShiftClusters doc WHERE NOT (doc.openShiftCluster.properties.provisioningState IN ("Succeeded", "Failed")) AND (doc.leaseExpires ?? 0) < GetCurrentTimestamp() / 1000`,
Query: `SELECT * FROM OpenShiftClusters doc WHERE doc.openShiftCluster.properties.provisioningState IN ("Creating", "Deleting", "Updating") AND (doc.leaseExpires ?? 0) < GetCurrentTimestamp() / 1000`,
}, nil)
for {

Просмотреть файл

@ -22,6 +22,11 @@ func (f *frontend) postOpenShiftClusterCredentials(w http.ResponseWriter, r *htt
log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
vars := mux.Vars(r)
if f.apis[vars["api-version"]].OpenShiftClusterCredentialsConverter == nil {
api.WriteError(w, http.StatusNotFound, api.CloudErrorCodeInvalidResourceType, "", "The resource type '%s' could not be found in the namespace '%s' for api version '%s'.", vars["resourceType"], vars["resourceProviderNamespace"], vars["api-version"])
return
}
body := r.Context().Value(middleware.ContextKeyBody).([]byte)
if len(body) > 0 && !json.Valid(body) {
api.WriteError(w, http.StatusBadRequest, api.CloudErrorCodeInvalidRequestContent, "", "The request content was invalid and could not be deserialized.")

Просмотреть файл

@ -60,11 +60,20 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
},
}
apis := map[string]*api.Version{
"2019-12-31-preview": api.APIs["2019-12-31-preview"],
"no-credentials": {
OpenShiftClusterConverter: api.APIs["2019-12-31-preview"].OpenShiftClusterConverter,
OpenShiftClusterValidator: api.APIs["2019-12-31-preview"].OpenShiftClusterValidator,
},
}
mockSubID := "00000000-0000-0000-0000-000000000000"
type test struct {
name string
resourceID string
apiVersion string
mocks func(*test, *mock_database.MockOpenShiftClusters)
wantStatusCode int
wantResponse func(*test) *v20191231preview.OpenShiftClusterCredentials
@ -101,6 +110,13 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
}
},
},
{
name: "credentials request is not allowed in the API version",
resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
apiVersion: "no-credentials",
wantStatusCode: http.StatusNotFound,
wantError: `404: InvalidResourceType: : The resource type 'openshiftclusters' could not be found in the namespace 'microsoft.redhatopenshift' for api version 'no-credentials'.`,
},
{
name: "cluster exists in db in creating state",
resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
@ -239,30 +255,37 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
openshiftClusters := mock_database.NewMockOpenShiftClusters(controller)
subscriptions := mock_database.NewMockSubscriptions(controller)
subscriptions.EXPECT().
Get(gomock.Any(), mockSubID).
Return(&api.SubscriptionDocument{
Subscription: &api.Subscription{
State: api.SubscriptionStateRegistered,
Properties: &api.SubscriptionProperties{
TenantID: "11111111-1111-1111-1111-111111111111",
if tt.mocks != nil {
subscriptions.EXPECT().
Get(gomock.Any(), mockSubID).
Return(&api.SubscriptionDocument{
Subscription: &api.Subscription{
State: api.SubscriptionStateRegistered,
Properties: &api.SubscriptionProperties{
TenantID: "11111111-1111-1111-1111-111111111111",
},
},
},
}, nil)
}, nil)
tt.mocks(tt, openshiftClusters)
tt.mocks(tt, openshiftClusters)
}
f, err := NewFrontend(ctx, logrus.NewEntry(logrus.StandardLogger()), env, &database.Database{
OpenShiftClusters: openshiftClusters,
Subscriptions: subscriptions,
}, api.APIs, &noop.Noop{})
}, apis, &noop.Noop{})
if err != nil {
t.Fatal(err)
}
go f.Run(ctx, nil, nil)
req, err := http.NewRequest(http.MethodPost, "https://server"+tt.resourceID+"/listcredentials?api-version=2019-12-31-preview", nil)
reqAPIVersion := "2019-12-31-preview"
if tt.apiVersion != "" {
reqAPIVersion = tt.apiVersion
}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("https://server%s/listcredentials?api-version=%s", tt.resourceID, reqAPIVersion), nil)
if err != nil {
t.Fatal(err)
}

Просмотреть файл

@ -0,0 +1,82 @@
package cosmodb
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"net/http"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/Azure/ARO-RP/pkg/metrics"
)
var _ http.RoundTripper = (*tracerRoundTripper)(nil)
type tracerRoundTripper struct {
log *logrus.Entry
m metrics.Interface
tr http.RoundTripper
}
func New(log *logrus.Entry, tr *http.Transport, m metrics.Interface) *tracerRoundTripper {
return &tracerRoundTripper{
log: log,
m: m,
tr: tr,
}
}
func (t *tracerRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
start := time.Now()
defer func() {
if resp == nil {
return
}
var ru float64
// Sometimes we get request-charge="" because pkranges API is free
// We log this on debug mode only and ignore
requestCharge := strings.Trim(resp.Header.Get("x-ms-request-charge"), `"`)
if requestCharge != "" {
ru, err = strconv.ParseFloat(requestCharge, 64)
if err != nil {
// we don't want to kill all DB calls if this fails
t.log.Error(err)
}
}
parts := strings.Split(req.URL.Path, "/")
if len(parts) >= 2 && parts[len(parts)-2] == "docs" {
parts[len(parts)-1] = "{id}"
}
path := strings.Join(parts, "/")
// emit RU only if we managed to parse RU value
if err == nil {
t.m.EmitFloat("client.cosmosdb.requestunits", ru, map[string]string{
"code": strconv.Itoa(resp.StatusCode),
"verb": req.Method,
"path": path,
})
}
t.m.EmitGauge("client.cosmosdb.count", 1, map[string]string{
"code": strconv.Itoa(resp.StatusCode),
"verb": req.Method,
"path": path,
})
t.m.EmitFloat("client.cosmosdb.duration", time.Now().Sub(start).Seconds(), map[string]string{
"code": strconv.Itoa(resp.StatusCode),
"verb": req.Method,
"path": path,
})
}()
return t.tr.RoundTrip(req)
}

Просмотреть файл

@ -98,7 +98,12 @@ func validate(path string, v, w reflect.Value, ignoreCase bool) error {
for i.Next() {
k := i.Key()
err := validate(fmt.Sprintf("%s[%q]", path, k.Interface()), v.MapIndex(k), w.MapIndex(k), ignoreCase)
mapW := w.MapIndex(k)
if !mapW.IsValid() {
return validationError(path)
}
err := validate(fmt.Sprintf("%s[%q]", path, k.Interface()), v.MapIndex(k), mapW, ignoreCase)
if err != nil {
return err
}

Просмотреть файл

@ -8,11 +8,12 @@ import (
)
type ts struct {
Mutable string `json:"mutable,omitempty" mutable:"true"` // should be able to change
Case string `json:"case,omitempty" mutable:"case"` // should be case insensitive
Empty string `json:"empty,omitempty" mutable:""` // default to immutable
EmptyNoJSON string `mutable:"false"` // handle no json tag
None string // default to immutable
Mutable string `json:"mutable,omitempty" mutable:"true"` // should be able to change
Case string `json:"case,omitempty" mutable:"case"` // should be case insensitive
Empty string `json:"empty,omitempty" mutable:""` // default to immutable
Map map[string]string `json:"map,omitempty"`
EmptyNoJSON string `mutable:"false"` // handle no json tag
None string // default to immutable
}
func TestValidate(t *testing.T) {
@ -22,6 +23,9 @@ func TestValidate(t *testing.T) {
Empty: "before",
EmptyNoJSON: "before",
None: "before",
Map: map[string]string{
"key": "value",
},
}
tests := []struct {
name string
@ -57,6 +61,20 @@ func TestValidate(t *testing.T) {
},
wantErr: "400: PropertyChangeNotAllowed: empty: Changing property 'empty' is not allowed.",
},
{
name: "can NOT replace a map",
modify: func(s *ts) {
s.Map = map[string]string{"new": "value"}
},
wantErr: "400: PropertyChangeNotAllowed: map: Changing property 'map' is not allowed.",
},
{
name: "can NOT change a value in a map",
modify: func(s *ts) {
s.Map = map[string]string{"key": "new-value"}
},
wantErr: "400: PropertyChangeNotAllowed: map[\"key\"]: Changing property 'map[\"key\"]' is not allowed.",
},
{
name: "can NOT change EmptyNoJSON",
modify: func(s *ts) {

Просмотреть файл

@ -268,6 +268,21 @@ func (mr *MockOpenShiftClustersMockRecorder) PatchWithLease(arg0, arg1, arg2 int
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchWithLease", reflect.TypeOf((*MockOpenShiftClusters)(nil).PatchWithLease), arg0, arg1, arg2)
}
// QueueLength mocks base method
func (m *MockOpenShiftClusters) QueueLength(arg0 context.Context, arg1 string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueueLength", arg0, arg1)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueueLength indicates an expected call of QueueLength
func (mr *MockOpenShiftClustersMockRecorder) QueueLength(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLength", reflect.TypeOf((*MockOpenShiftClusters)(nil).QueueLength), arg0, arg1)
}
// Update mocks base method
func (m *MockOpenShiftClusters) Update(arg0 context.Context, arg1 *api.OpenShiftClusterDocument) (*api.OpenShiftClusterDocument, error) {
m.ctrl.T.Helper()

Просмотреть файл

@ -15,19 +15,21 @@ import (
"github.com/Azure/ARO-RP/test/util/ready"
)
var _ = Describe("Check the node count is correct [CheckNodeCount][EveryPR]", func() {
It("should be possible to list nodes and confirm they are as expected", func() {
By("Verifying that the expected number of nodes exist and are ready")
var _ = Describe("Check nodes", func() {
Specify("node count should match the cluster resource and nodes should be ready", func() {
ctx := context.Background()
cs, err := Clients.openshiftclusters.Get(ctx, os.Getenv("RESOURCEGROUP"), os.Getenv("CLUSTER"))
oc, err := Clients.OpenshiftClusters.Get(ctx, os.Getenv("RESOURCEGROUP"), os.Getenv("CLUSTER"))
Expect(err).NotTo(HaveOccurred())
var expectedNodeCount int32 = 3 // for masters
for _, wp := range *cs.WorkerProfiles {
for _, wp := range *oc.WorkerProfiles {
expectedNodeCount += *wp.Count
}
nodes, err := Clients.kubernetes.CoreV1().Nodes().List(metav1.ListOptions{})
nodes, err := Clients.Kubernetes.CoreV1().Nodes().List(metav1.ListOptions{})
Expect(err).NotTo(HaveOccurred())
var nodeCount int64
for _, node := range nodes.Items {
if ready.NodeIsReady(&node) {
@ -38,6 +40,7 @@ var _ = Describe("Check the node count is correct [CheckNodeCount][EveryPR]", fu
}
}
}
Expect(nodeCount).To(Equal(expectedNodeCount))
})
})

Просмотреть файл

@ -4,9 +4,7 @@ package e2e
// Licensed under the Apache License 2.0.
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
. "github.com/onsi/ginkgo"
@ -14,18 +12,14 @@ import (
"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/sirupsen/logrus"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
kapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/clientcmd/api/latest"
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/redhatopenshift"
)
type ClientSet struct {
openshiftclusters redhatopenshift.OpenShiftClustersClient
kubernetes kubernetes.Interface
OpenshiftClusters redhatopenshift.OpenShiftClustersClient
Kubernetes kubernetes.Interface
}
var (
@ -33,58 +27,45 @@ var (
Clients *ClientSet
)
func restConfigFromV1Config(kc *v1.Config) (*rest.Config, error) {
var c kapi.Config
err := latest.Scheme.Convert(kc, &c, nil)
if err != nil {
return nil, err
}
kubeconfig := clientcmd.NewDefaultClientConfig(c, &clientcmd.ConfigOverrides{})
return kubeconfig.ClientConfig()
}
func newClientSet() (*ClientSet, error) {
authorizer, err := auth.NewAuthorizerFromEnvironment()
if err != nil {
return nil, err
}
d, err := ioutil.ReadFile("../../admin.kubeconfig")
if err != nil {
return nil, err
}
var config *v1.Config
json.Unmarshal(d, &config)
if err != nil {
return nil, err
}
restconfig, err := restConfigFromV1Config(config)
kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(),
&clientcmd.ConfigOverrides{},
)
restconfig, err := kubeconfig.ClientConfig()
if err != nil {
return nil, err
}
cli, err := kubernetes.NewForConfig(restconfig)
if err != nil {
return nil, err
}
cs := &ClientSet{
openshiftclusters: redhatopenshift.NewOpenShiftClustersClient(os.Getenv("AZURE_SUBSCRIPTION_ID"), authorizer),
kubernetes: cli,
}
return cs, nil
return &ClientSet{
OpenshiftClusters: redhatopenshift.NewOpenShiftClustersClient(os.Getenv("AZURE_SUBSCRIPTION_ID"), authorizer),
Kubernetes: cli,
}, nil
}
var _ = BeforeSuite(func() {
Log.Info("BeforeSuite")
for _, key := range []string{
"AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET",
"CLUSTER", "RESOURCEGROUP",
"AZURE_SUBSCRIPTION_ID",
"CLUSTER",
"RESOURCEGROUP",
} {
if _, found := os.LookupEnv(key); !found {
panic(fmt.Sprintf("environment variable %q unset", key))
}
}
var err error
Clients, err = newClientSet()
if err != nil {

4
vendor/github.com/AlekSi/gocov-xml/.gitignore сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
*.json
coverage.xml
gocov-xml

19
vendor/github.com/AlekSi/gocov-xml/LICENSE сгенерированный поставляемый Normal file
Просмотреть файл

@ -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.

12
vendor/github.com/AlekSi/gocov-xml/Makefile сгенерированный поставляемый Normal file
Просмотреть файл

@ -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

62
vendor/github.com/AlekSi/gocov-xml/README.md сгенерированный поставляемый Normal file
Просмотреть файл

@ -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 <absolute path to 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)

61
vendor/github.com/AlekSi/gocov-xml/coverage-04.dtd сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,61 @@
<!-- Portions (C) International Organization for Standardization 1986:
Permission to copy in any form is granted for use with
conforming SGML systems and applications as defined in
ISO 8879, provided this notice is included in all copies.
-->
<!ELEMENT coverage (sources?,packages)>
<!ATTLIST coverage line-rate CDATA #REQUIRED>
<!ATTLIST coverage branch-rate CDATA #REQUIRED>
<!ATTLIST coverage lines-covered CDATA #REQUIRED>
<!ATTLIST coverage lines-valid CDATA #REQUIRED>
<!ATTLIST coverage branches-covered CDATA #REQUIRED>
<!ATTLIST coverage branches-valid CDATA #REQUIRED>
<!ATTLIST coverage complexity CDATA #REQUIRED>
<!ATTLIST coverage version CDATA #REQUIRED>
<!ATTLIST coverage timestamp CDATA #REQUIRED>
<!ELEMENT sources (source*)>
<!ELEMENT source (#PCDATA)>
<!ELEMENT packages (package*)>
<!ELEMENT package (classes)>
<!ATTLIST package name CDATA #REQUIRED>
<!ATTLIST package line-rate CDATA #REQUIRED>
<!ATTLIST package branch-rate CDATA #REQUIRED>
<!ATTLIST package complexity CDATA #REQUIRED>
<!ELEMENT classes (class*)>
<!ELEMENT class (methods,lines)>
<!ATTLIST class name CDATA #REQUIRED>
<!ATTLIST class filename CDATA #REQUIRED>
<!ATTLIST class line-rate CDATA #REQUIRED>
<!ATTLIST class branch-rate CDATA #REQUIRED>
<!ATTLIST class complexity CDATA #REQUIRED>
<!ELEMENT methods (method*)>
<!ELEMENT method (lines)>
<!ATTLIST method name CDATA #REQUIRED>
<!ATTLIST method signature CDATA #REQUIRED>
<!ATTLIST method line-rate CDATA #REQUIRED>
<!ATTLIST method branch-rate CDATA #REQUIRED>
<!ATTLIST method complexity CDATA #REQUIRED>
<!ELEMENT lines (line*)>
<!ELEMENT line (conditions*)>
<!ATTLIST line number CDATA #REQUIRED>
<!ATTLIST line hits CDATA #REQUIRED>
<!ATTLIST line branch CDATA "false">
<!ATTLIST line condition-coverage CDATA "100%">
<!ELEMENT conditions (condition*)>
<!ELEMENT condition EMPTY>
<!ATTLIST condition number CDATA #REQUIRED>
<!ATTLIST condition type CDATA #REQUIRED>
<!ATTLIST condition coverage CDATA #REQUIRED>

179
vendor/github.com/AlekSi/gocov-xml/coverage-with-data.xml сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,179 @@
<?xml version="1.0"?>
<!--DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-03.dtd"-->
<coverage line-rate="0.9" branch-rate="0.75" version="1.9" timestamp="1187350905008">
<sources>
<source>C:/local/mvn-coverage-example/src/main/java</source>
<source>--source</source>
</sources>
<packages>
<package name="" line-rate="1.0" branch-rate="1.0" complexity="1.0">
<classes>
<class name="Main" filename="Main.java" line-rate="1.0" branch-rate="1.0" complexity="1.0">
<methods>
<method name="&lt;init&gt;" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="10" hits="3" branch="false"/>
</lines>
</method>
<method name="doSearch" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="23" hits="3" branch="false"/>
<line number="25" hits="3" branch="false"/>
<line number="26" hits="3" branch="false"/>
<line number="28" hits="3" branch="false"/>
<line number="29" hits="3" branch="false"/>
<line number="30" hits="3" branch="false"/>
</lines>
</method>
<method name="main" signature="([Ljava/lang/String;)V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="16" hits="3" branch="false"/>
<line number="17" hits="3" branch="false"/>
<line number="18" hits="3" branch="false"/>
<line number="19" hits="3" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="10" hits="3" branch="false"/>
<line number="16" hits="3" branch="false"/>
<line number="17" hits="3" branch="false"/>
<line number="18" hits="3" branch="false"/>
<line number="19" hits="3" branch="false"/>
<line number="23" hits="3" branch="false"/>
<line number="25" hits="3" branch="false"/>
<line number="26" hits="3" branch="false"/>
<line number="28" hits="3" branch="false"/>
<line number="29" hits="3" branch="false"/>
<line number="30" hits="3" branch="false"/>
</lines>
</class>
</classes>
</package>
<package name="search" line-rate="0.8421052631578947" branch-rate="0.75" complexity="3.25">
<classes>
<class name="search.BinarySearch" filename="search/BinarySearch.java" line-rate="0.9166666666666666" branch-rate="0.8333333333333334" complexity="3.0">
<methods>
<method name="&lt;init&gt;" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="12" hits="3" branch="false"/>
</lines>
</method>
<method name="find" signature="([II)I" line-rate="0.9090909090909091" branch-rate="0.8333333333333334">
<lines>
<line number="16" hits="3" branch="false"/>
<line number="18" hits="12" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="20" hits="9" branch="false"/>
<line number="21" hits="9" branch="false"/>
<line number="23" hits="9" branch="true" condition-coverage="50% (1/2)">
<conditions>
<condition number="0" type="jump" coverage="50%"/>
</conditions>
</line>
<line number="24" hits="0" branch="false"/>
<line number="25" hits="9" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="26" hits="6" branch="false"/>
<line number="28" hits="3" branch="false"/>
<line number="29" hits="9" branch="false"/>
<line number="31" hits="3" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="12" hits="3" branch="false"/>
<line number="16" hits="3" branch="false"/>
<line number="18" hits="12" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="20" hits="9" branch="false"/>
<line number="21" hits="9" branch="false"/>
<line number="23" hits="9" branch="true" condition-coverage="50% (1/2)">
<conditions>
<condition number="0" type="jump" coverage="50%"/>
</conditions>
</line>
<line number="24" hits="0" branch="false"/>
<line number="25" hits="9" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="26" hits="6" branch="false"/>
<line number="28" hits="3" branch="false"/>
<line number="29" hits="9" branch="false"/>
<line number="31" hits="3" branch="false"/>
</lines>
</class>
<class name="search.ISortedArraySearch" filename="search/ISortedArraySearch.java" line-rate="1.0" branch-rate="1.0" complexity="1.0">
<methods>
</methods>
<lines>
</lines>
</class>
<class name="search.LinearSearch" filename="search/LinearSearch.java" line-rate="0.7142857142857143" branch-rate="0.6666666666666666" complexity="6.0">
<methods>
<method name="&lt;init&gt;" signature="()V" line-rate="1.0" branch-rate="1.0">
<lines>
<line number="9" hits="3" branch="false"/>
</lines>
</method>
<method name="find" signature="([II)I" line-rate="0.6666666666666666" branch-rate="0.6666666666666666">
<lines>
<line number="13" hits="9" branch="true" condition-coverage="50% (1/2)">
<conditions>
<condition number="0" type="jump" coverage="50%"/>
</conditions>
</line>
<line number="15" hits="9" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="16" hits="3" branch="false"/>
<line number="17" hits="6" branch="true" condition-coverage="50% (1/2)">
<conditions>
<condition number="0" type="jump" coverage="50%"/>
</conditions>
</line>
<line number="19" hits="0" branch="false"/>
<line number="24" hits="0" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="9" hits="3" branch="false"/>
<line number="13" hits="9" branch="true" condition-coverage="50% (1/2)">
<conditions>
<condition number="0" type="jump" coverage="50%"/>
</conditions>
</line>
<line number="15" hits="9" branch="true" condition-coverage="100% (2/2)">
<conditions>
<condition number="0" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="16" hits="3" branch="false"/>
<line number="17" hits="6" branch="true" condition-coverage="50% (1/2)">
<conditions>
<condition number="0" type="jump" coverage="50%"/>
</conditions>
</line>
<line number="19" hits="0" branch="false"/>
<line number="24" hits="0" branch="false"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>

209
vendor/github.com/AlekSi/gocov-xml/gocov-xml.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -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("<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n")
encoder := xml.NewEncoder(os.Stdout)
encoder.Indent("", "\t")
err = encoder.Encode(coverage)
if err != nil {
panic(err)
}
fmt.Println()
}

22
vendor/github.com/axw/gocov/.gitignore сгенерированный поставляемый Normal file
Просмотреть файл

@ -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

1
vendor/github.com/axw/gocov/.travis.yml сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
language: go

5
vendor/github.com/axw/gocov/AUTHORS сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
# List of gocov authors.
Andrew Wilkins <axwalk@gmail.com>
Dave Cheney <dave@cheney.net>
Greg Ward <greg@gerg.ca>

50
vendor/github.com/axw/gocov/LICENSE сгенерированный поставляемый Normal file
Просмотреть файл

@ -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.

57
vendor/github.com/axw/gocov/README.md сгенерированный поставляемый Normal file
Просмотреть файл

@ -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 <coverprofile>` 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 <coverage.json>` 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 <coverage.json> <package[.receiver].function>`
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.

5
vendor/github.com/axw/gocov/go.mod сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
module github.com/axw/gocov
go 1.12
require golang.org/x/tools v0.0.0-20190617190820-da514acc4774

7
vendor/github.com/axw/gocov/go.sum сгенерированный поставляемый Normal file
Просмотреть файл

@ -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=

115
vendor/github.com/axw/gocov/gocov.go сгенерированный поставляемый Normal file
Просмотреть файл

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

238
vendor/github.com/axw/gocov/gocov/annotate.go сгенерированный поставляемый Normal file
Просмотреть файл

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

313
vendor/github.com/axw/gocov/gocov/convert.go сгенерированный поставляемый Normal file
Просмотреть файл

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

145
vendor/github.com/axw/gocov/gocov/internal/testflag/testflag.go сгенерированный поставляемый Normal file
Просмотреть файл

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

90
vendor/github.com/axw/gocov/gocov/main.go сгенерированный поставляемый Normal file
Просмотреть файл

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

224
vendor/github.com/axw/gocov/gocov/report.go сгенерированный поставляемый Normal file
Просмотреть файл

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

104
vendor/github.com/axw/gocov/gocov/test.go сгенерированный поставляемый Normal file
Просмотреть файл

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

82
vendor/github.com/axw/gocov/gocovutil/packages.go сгенерированный поставляемый Normal file
Просмотреть файл

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

1
vendor/github.com/jstemmer/go-junit-report/.gitignore сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
go-junit-report

16
vendor/github.com/jstemmer/go-junit-report/.travis.yml сгенерированный поставляемый Normal file
Просмотреть файл

@ -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"

20
vendor/github.com/jstemmer/go-junit-report/LICENSE сгенерированный поставляемый Normal file
Просмотреть файл

@ -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.

49
vendor/github.com/jstemmer/go-junit-report/README.md сгенерированный поставляемый Normal file
Просмотреть файл

@ -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

182
vendor/github.com/jstemmer/go-junit-report/formatter/formatter.go сгенерированный поставляемый Normal file
Просмотреть файл

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

45
vendor/github.com/jstemmer/go-junit-report/go-junit-report.go сгенерированный поставляемый Normal file
Просмотреть файл

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

3
vendor/github.com/jstemmer/go-junit-report/go.mod сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
module github.com/jstemmer/go-junit-report
go 1.2

319
vendor/github.com/jstemmer/go-junit-report/parser/parser.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -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
// ("<package>.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
}

2
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

256
vendor/golang.org/x/tools/cover/profile.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -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 <span> 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
}