Migrate qbox/goc to qiniu/goc (#8)

* build/install

* all branches

* e2e test

* goc binary

PATH

add checkout code

add +x

test github action

test for github action

test for github actions

test for github actions

test for github actions

fix ci

fix

fix temp path error

* go1.11 1.10 not support go env command

fix

* fix build dir error for go 1.11/1.12 mod project

* e2e test

test

test

fix yaml syntax error

fix

fix

fix

* fix env error

fix

fix

* add unit test

* add unit test coverage report

update

update

* add license
This commit is contained in:
Li Yiyang 2020-05-21 14:30:41 +08:00 committed by GitHub
parent 08fa9619b8
commit c2391c5b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 2103 additions and 5 deletions

63
.github/workflows/e2e_test_check.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: e2e test
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
jobs:
job_1:
name: Build goc binary
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.14.x
- name: Checkout code
uses: actions/checkout@v2
- name: Go build
run: |
cd cmd/goc
go build
- name: Go build test binary
run: |
cd tests/e2e
go get -u github.com/onsi/ginkgo/ginkgo
ginkgo build ./...
- name: Upload binary result for job 1
uses: actions/upload-artifact@v2
with:
name: goc
path: cmd/goc/goc
- name: Upload binary result for job 1
uses: actions/upload-artifact@v2
with:
name: e2e.test
path: tests/e2e/e2e.test
job_2:
name: E2E test
needs: job_1
strategy:
matrix:
go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Download built binary
uses: actions/download-artifact@v2
with:
path: /home/runner/tools
- name: Do test
run: |
cd tests
./run-ci-actions.sh

View File

@ -3,8 +3,6 @@ on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
paths-ignore:
- '**.md'
pull_request:

View File

@ -3,8 +3,6 @@ on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
paths-ignore:
- '**.md'
pull_request:
@ -27,4 +25,5 @@ jobs:
uses: actions/checkout@v2
- name: Go test
run: |
go test ./...
export DEFAULT_EXCEPT_PKGS=e2e
go test -p 1 -cover $(go list ./... | grep -v -E $DEFAULT_EXCEPT_PKGS)

View File

@ -1,5 +1,6 @@
![](https://github.com/qiniu/goc/workflows/ut-check/badge.svg)
![](https://github.com/qiniu/goc/workflows/style-check/badge.svg)
![](https://github.com/qiniu/goc/workflows/e2e%20test/badge.svg)
# goc
A Comprehensive Coverage Testing System for The Go Programming Language

105
cmd/goc/app/build.go Normal file
View File

@ -0,0 +1,105 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package app
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/qiniu/goc/pkg/build"
"github.com/spf13/cobra"
)
var buildCmd = &cobra.Command{
Use: "build",
Short: "Do cover for all go files and execute go build command",
Long: `This build command is a little different from the official one, for instance:
* 'goc build' is equal to 'goc cover && go build'
* 'goc build --center=http://127.0.0.1:7777 -- -static app/kodo' is equal to 'goc cover --center=http://127.0.0.1:7777 && go build -static app/kodo'
* 'goc build -- -o output' is equal to 'goc cover && go build -output, both relative/absolute output paths are supported'`,
Run: func(cmd *cobra.Command, args []string) {
newgopath, newwd, tmpdir, pkgs := build.MvProjectsToTmp(target, args)
doCover(cmd, args, newgopath, tmpdir)
newArgs, modified := modifyOutputArg(args)
doBuild(newArgs, newgopath, newwd)
// if not modified
// find the binary in temp build dir
// and copy them into original dir
if false == modified {
build.MvBinaryToOri(pkgs, tmpdir)
}
},
}
func init() {
buildCmd.Flags().StringVarP(&center, "center", "", "http://127.0.0.1:7777", "cover profile host center")
rootCmd.AddCommand(buildCmd)
}
func doBuild(args []string, newgopath string, newworkingdir string) {
log.Println("Go building in temp...")
newArgs := []string{"build"}
newArgs = append(newArgs, args...)
cmd := exec.Command("go", newArgs...)
cmd.Dir = newworkingdir
if newgopath != "" {
// Change to temp GOPATH for go install command
cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", newgopath))
}
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("Fail to execute: go build %v. The error is: %v, the stdout/stderr is: %v", strings.Join(args, " "), err, string(out))
}
log.Println("Go build exit successful.")
}
// As we build in the temp build dir, we have to modify the "-o output",
// if output is a relative path, transform it to abspath
func modifyOutputArg(args []string) (newArgs []string, modified bool) {
var output string
fs := flag.NewFlagSet("goc-build", flag.PanicOnError)
fs.StringVar(&output, "o", "", "output dir")
// parse the go args after "--"
fs.Parse(args)
// skip if output is not present
if output == "" {
modified = false
newArgs = args
return
}
abs, err := filepath.Abs(output)
if err != nil {
log.Fatalf("Fail to transform the path: %v to absolute path, the error is: %v", output, err)
}
// the second -o arg will overwrite the first one
newArgs = append(args, "-o", abs)
modified = true
return
}

221
cmd/goc/app/cover.go Normal file
View File

@ -0,0 +1,221 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package app
import (
"fmt"
"log"
"os"
"strings"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra"
)
var coverCmd = &cobra.Command{
Use: "cover",
Short: "do cover for the target source ",
Run: func(cmd *cobra.Command, args []string) {
doCover(cmd, args, "", "")
},
}
var (
target string
center string
)
func init() {
coverCmd.Flags().StringVarP(&center, "center", "", "http://127.0.0.1:7777", "cover profile host center")
coverCmd.Flags().StringVarP(&target, "target", "", ".", "target folder to cover")
rootCmd.AddCommand(coverCmd)
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func doCover(cmd *cobra.Command, args []string, newgopath string, newtarget string) {
if newtarget != "" {
target = newtarget
}
if !isDirExist(target) {
log.Fatalf("target directory %s not exist", target)
}
//
//
//
listArgs := []string{"list", "-json"}
if len(args) != 0 {
listArgs = append(listArgs, args...)
}
listArgs = append(listArgs, "./...")
pkgs := cover.ListPackages(target, listArgs, newgopath)
//
//
var seen = make(map[string]*cover.PackageCover)
var seenCache = make(map[string]*cover.PackageCover)
for _, pkg := range pkgs {
//
if pkg.Name == "main" {
log.Printf("handle package: %v", pkg.ImportPath)
// inject the main package
mainCover, err := cover.AddCounters(pkg, newgopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", pkg.ImportPath, err)
}
// new a testcover for this service
tc := cover.TestCover{
Mode: "atomic",
Center: center,
MainPkgCover: mainCover,
}
// handle its dependency
var internalPkgCache = make(map[string][]*cover.PackageCover)
tc.CacheCover = make(map[string]*cover.PackageCover)
for _, dep := range pkg.Deps {
if packageCover, ok := seen[dep]; ok {
tc.DepsCover = append(tc.DepsCover, packageCover)
continue
}
//only focus package neither standard Go library nor dependency library
if depPkg, ok := pkgs[dep]; ok {
if findInternal(dep) {
//scan exist cache cover to tc.CacheCover
if cache, ok := seenCache[dep]; ok {
log.Printf("cache cover exist: %s", cache.Package.ImportPath)
tc.CacheCover[cache.Package.Dir] = cache
continue
}
// add counter for internal package
inPkgCover, err := cover.AddCounters(depPkg, newgopath)
if err != nil {
log.Fatalf("failed to add counters for internal pkg %s, err: %v", depPkg.ImportPath, err)
}
parentDir := getInternalParent(depPkg.Dir)
parentImportPath := getInternalParent(depPkg.ImportPath)
//if internal parent dir or import is root path, ignore the dep. the dep is Go library nor dependency library
if parentDir == "" {
continue
}
if parentImportPath == "" {
continue
}
pkg := &cover.Package{
ImportPath: parentImportPath,
Dir: parentDir,
}
// Some internal package have same parent dir or import path
// Cache all vars by internal parent dir for all child internal counter vars
cacheCover := cover.AddCacheCover(pkg, inPkgCover)
if v, ok := tc.CacheCover[cacheCover.Package.Dir]; ok {
for cVar, val := range v.Vars {
cacheCover.Vars[cVar] = val
}
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
} else {
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
}
// Cache all internal vars to internal parent package
inCover := cover.CacheInternalCover(inPkgCover)
if v, ok := internalPkgCache[cacheCover.Package.Dir]; ok {
v = append(v, inCover)
internalPkgCache[cacheCover.Package.Dir] = v
} else {
var covers []*cover.PackageCover
covers = append(covers, inCover)
internalPkgCache[cacheCover.Package.Dir] = covers
}
seenCache[dep] = cacheCover
continue
}
packageCover, err := cover.AddCounters(depPkg, newgopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
}
tc.DepsCover = append(tc.DepsCover, packageCover)
seen[dep] = packageCover
}
}
if errs := cover.InjectCacheCounters(internalPkgCache, tc.CacheCover); len(errs) > 0 {
log.Fatalf("failed to inject cache counters for package: %s, err: %v", pkg.ImportPath, errs)
}
// inject Http Cover APIs
var httpCoverApis = fmt.Sprintf("%s/http_cover_apis_auto_generated.go", pkg.Dir)
if err := cover.InjectCountersHandlers(tc, httpCoverApis); err != nil {
log.Fatalf("failed to inject counters for package: %s, err: %v", pkg.ImportPath, err)
}
}
}
}
func isDirExist(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
// Refer: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L1334:6
// findInternal looks for the final "internal" path element in the given import path.
// If there isn't one, findInternal returns ok=false.
// Otherwise, findInternal returns ok=true and the index of the "internal".
func findInternal(path string) bool {
// Three cases, depending on internal at start/end of string or not.
// The order matters: we must return the index of the final element,
// because the final one produces the most restrictive requirement
// on the importer.
switch {
case strings.HasSuffix(path, "/internal"):
return true
case strings.Contains(path, "/internal/"):
return true
case path == "internal", strings.HasPrefix(path, "internal/"):
return true
}
return false
}
func getInternalParent(path string) string {
switch {
case strings.HasSuffix(path, "/internal"):
return strings.Split(path, "/internal")[0]
case strings.Contains(path, "/internal/"):
return strings.Split(path, "/internal/")[0]
case path == "internal":
return ""
case strings.HasPrefix(path, "internal/"):
return strings.Split(path, "internal/")[0]
}
return ""
}

69
cmd/goc/app/install.go Normal file
View File

@ -0,0 +1,69 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package app
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
"github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra"
)
var installCmd = &cobra.Command{
Use: "install",
Short: "Do cover for all go files and execute go install command",
Long: `This install command is a little different from the official one, for instance:
* 'goc install -- ./...' is equal to 'goc cover && go install ./...'
* 'goc install --center=http://127.0.0.1:7777 -- -static ./...' is equal to 'goc cover --center=http://127.0.0.1:7777 && go install -static ./...'`,
Run: func(cmd *cobra.Command, args []string) {
newgopath, newwd, tmpdir, pkgs := build.MvProjectsToTmp(target, args)
doCover(cmd, args, newgopath, tmpdir)
doInstall(args, newgopath, newwd, pkgs)
},
}
func init() {
installCmd.Flags().StringVarP(&center, "center", "", "http://127.0.0.1:7777", "cover profile host center")
rootCmd.AddCommand(installCmd)
}
func doInstall(args []string, newgopath string, newworkingdir string, pkgs map[string]*cover.Package) {
log.Println("Go building in temp...")
newArgs := []string{"install"}
newArgs = append(newArgs, args...)
cmd := exec.Command("go", newArgs...)
cmd.Dir = newworkingdir
// Change the temp GOBIN, to force binary install to original place
cmd.Env = append(os.Environ(), fmt.Sprintf("GOBIN=%v", build.FindWhereToInstall(pkgs)))
if newgopath != "" {
// Change to temp GOPATH for go install command
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%v", newgopath))
}
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("Fail to execute: go install %v. The error is: %v, the stdout/stderr is: %v", strings.Join(args, " "), err, string(out))
}
log.Printf("Go install successful. Binary installed in: %v", build.FindWhereToInstall(pkgs))
}

4
go.mod
View File

@ -4,7 +4,11 @@ go 1.13
require (
github.com/gin-gonic/gin v1.6.3
github.com/onsi/ginkgo v1.11.0
github.com/onsi/gomega v1.8.1
github.com/otiai10/copy v1.0.2
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.5.1
golang.org/x/tools v0.0.0-20200303214625-2b0b585e22fe
k8s.io/test-infra v0.0.0-20200511080351-8ac9dbfab055
)

9
go.sum
View File

@ -401,6 +401,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@ -522,11 +523,13 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@ -539,6 +542,7 @@ github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.m
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.0/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
@ -795,6 +799,7 @@ golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -849,6 +854,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -897,6 +903,7 @@ golang.org/x/tools v0.0.0-20200303214625-2b0b585e22fe h1:Kh3iY7o/2bMfQXZdwLdL9jD
golang.org/x/tools v0.0.0-20200303214625-2b0b585e22fe/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
@ -957,6 +964,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
@ -977,6 +985,7 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=

77
pkg/build/binarymove.go Normal file
View File

@ -0,0 +1,77 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package build
import (
"log"
"os"
"path/filepath"
"github.com/otiai10/copy"
"github.com/qiniu/goc/pkg/cover"
)
func MvBinaryToOri(pkgs map[string]*cover.Package, newgopath string) {
for _, pkg := range pkgs {
if pkg.Name == "main" {
_, binaryTarget := filepath.Split(pkg.Target)
binaryTmpPath := filepath.Join(getTmpwd(newgopath, pkgs, !checkIfLegacyProject(pkgs)), binaryTarget)
if false == checkIfFileExist(binaryTmpPath) {
continue
}
curwd, err := os.Getwd()
if err != nil {
log.Fatalf("Cannot get current working directoy, the error is: %v", err)
}
binaryOriPath := filepath.Join(curwd, binaryTarget)
if checkIfFileExist(binaryOriPath) {
// if we have file in the original place with same name,
// but this file is not a binary,
// then we skip it
if false == checkIfExecutable(binaryOriPath) {
log.Printf("Skipping binary: %v, as we find a file in the original place with same name but not executable.", binaryOriPath)
continue
}
}
log.Printf("Generating binary: %v", binaryOriPath)
if err = copy.Copy(binaryTmpPath, binaryOriPath); err != nil {
log.Println(err)
}
}
}
}
func checkIfExecutable(path string) bool {
fileInfo, err := os.Lstat(path)
if err != nil {
return false
}
return fileInfo.Mode()&0100 != 0
}
func checkIfFileExist(path string) bool {
fileInfo, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return !fileInfo.IsDir()
}

40
pkg/build/gomodules.go Normal file
View File

@ -0,0 +1,40 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package build
import (
"log"
"github.com/otiai10/copy"
"github.com/qiniu/goc/pkg/cover"
)
func cpGoModulesProject(tmpBuildDir string, pkgs map[string]*cover.Package) {
for _, v := range pkgs {
if v.Name == "main" {
dst := tmpBuildDir
src := v.Module.Dir
if err := copy.Copy(src, dst); err != nil {
log.Printf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
}
break
} else {
continue
}
}
}

83
pkg/build/legacy.go Normal file
View File

@ -0,0 +1,83 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package build
import (
"log"
"os"
"path/filepath"
"github.com/otiai10/copy"
"github.com/qiniu/goc/pkg/cover"
)
func cpLegacyProject(tmpBuildDir string, pkgs map[string]*cover.Package) {
visited := make(map[string]bool)
for k, v := range pkgs {
dst := filepath.Join(tmpBuildDir, "src", k)
src := v.Dir
if _, ok := visited[src]; ok {
// Skip if already copied
continue
}
if err := copy.Copy(src, dst); err != nil {
log.Printf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
}
visited[src] = true
cpDepPackages(tmpBuildDir, v, visited)
}
}
// only cp dependency in root(current gopath),
// skip deps in other GOPATHs
func cpDepPackages(tmpBuildDir string, pkg *cover.Package, visited map[string]bool) {
/*
oriGOPATH := os.Getenv("GOPATH")
if oriGOPATH == "" {
oriGOPATH = filepath.Join(os.Getenv("HOME"), "go")
}
gopaths := strings.Split(oriGOPATH, ":")
*/
gopath := pkg.Root
for _, dep := range pkg.Deps {
src := filepath.Join(gopath, "src", dep)
// Check if copied
if _, ok := visited[src]; ok {
// Skip if already copied
continue
}
// Check if we can found in the root gopath
_, err := os.Stat(src)
if err != nil {
continue
}
dst := filepath.Join(tmpBuildDir, "src", dep)
if err := copy.Copy(src, dst); err != nil {
log.Printf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
}
visited[src] = true
}
}

149
pkg/build/tmpfolder.go Normal file
View File

@ -0,0 +1,149 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package build
import (
"crypto/sha256"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/qiniu/goc/pkg/cover"
)
func MvProjectsToTmp(target string, args []string) (newgopath string, newWorkingDir string, tmpBuildDir string, pkgs map[string]*cover.Package) {
listArgs := []string{"list", "-json"}
if len(args) != 0 {
listArgs = append(listArgs, args...)
}
listArgs = append(listArgs, "./...")
pkgs = cover.ListPackages(target, listArgs, "")
tmpBuildDir, newWorkingDir, isMod := mvProjectsToTmp(pkgs)
origopath := os.Getenv("GOPATH")
if isMod == true {
newgopath = ""
} else if origopath == "" {
newgopath = tmpBuildDir
} else {
newgopath = fmt.Sprintf("%v:%v", tmpBuildDir, origopath)
}
log.Printf("New GOPATH: %v", newgopath)
return
}
func mvProjectsToTmp(pkgs map[string]*cover.Package) (string, string, bool) {
path, err := os.Getwd()
if err != nil {
log.Fatalf("Cannot get current working directoy, the error is: %v", err)
}
tmpBuildDir := filepath.Join(os.TempDir(), TmpFolderName(path))
// Delete previous tmp folder and its content
os.RemoveAll(tmpBuildDir)
// Create a new tmp folder
err = os.MkdirAll(filepath.Join(tmpBuildDir, "src"), os.ModePerm)
if err != nil {
log.Fatalf("Fail to create the temporary build directory. The err is: %v", err)
}
log.Printf("Temp project generated in: %v", tmpBuildDir)
isMod := false
var tmpWorkingDir string
if checkIfLegacyProject(pkgs) {
cpLegacyProject(tmpBuildDir, pkgs)
tmpWorkingDir = getTmpwd(tmpBuildDir, pkgs, false)
} else {
cpGoModulesProject(tmpBuildDir, pkgs)
tmpWorkingDir = getTmpwd(tmpBuildDir, pkgs, true)
isMod = true
}
log.Printf("New working/building directory in: %v", tmpWorkingDir)
return tmpBuildDir, tmpWorkingDir, isMod
}
func TmpFolderName(path string) string {
sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6])
return "goc-" + h
}
// Check if it is go module project
// true legacy
// flase go mod
func checkIfLegacyProject(pkgs map[string]*cover.Package) bool {
for _, v := range pkgs {
if v.Module == nil {
return true
}
return false
}
log.Fatalln("Should never be reached....")
return false
}
func getTmpwd(tmpBuildDir string, pkgs map[string]*cover.Package, isMod bool) string {
for _, pkg := range pkgs {
path, err := os.Getwd()
if err != nil {
log.Fatalf("Cannot get current working directoy, the error is: %v", err)
}
index := -1
var parentPath string
if isMod == false {
index = strings.Index(path, pkg.Root)
parentPath = pkg.Root
} else {
index = strings.Index(path, pkg.Module.Dir)
parentPath = pkg.Module.Dir
}
if index == -1 {
log.Fatalf("goc install not executed in project directory.")
}
tmpwd := filepath.Join(tmpBuildDir, path[len(parentPath):])
// log.Printf("New building directory in: %v", tmpwd)
return tmpwd
}
log.Fatalln("Should never be reached....")
return ""
}
func FindWhereToInstall(pkgs map[string]*cover.Package) string {
if GOBIN := os.Getenv("GOBIN"); GOBIN != "" {
return GOBIN
}
// old GOPATH dir
GOPATH := os.Getenv("GOPATH")
if true == checkIfLegacyProject(pkgs) {
for _, v := range pkgs {
return filepath.Join(v.Root, "bin")
}
}
if GOPATH != "" {
return filepath.Join(strings.Split(GOPATH, ":")[0], "bin")
}
return filepath.Join(os.Getenv("HOME"), "go", "bin")
}

133
pkg/build/tmpfolder_test.go Normal file
View File

@ -0,0 +1,133 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package build
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/qiniu/goc/pkg/cover"
)
const TEST_GO_LIST_LEGACY = `{
"Dir": "/Users/lyyyuna/gitup/linking/src/qiniu.com/linking/api/linking.v1",
"ImportPath": "qiniu.com/linking/api/linking.v1",
"Name": "linking",
"Target": "/Users/lyyyuna/gitup/linking/pkg/darwin_amd64/qiniu.com/linking/api/linking.v1.a",
"Root": "/Users/lyyyuna/gitup/linking",
"Match": [
"./..."
],
"Stale": true,
"StaleReason": "stale dependency: vendor/github.com/modern-go/concurrent",
"GoFiles": [
"client.go"
],
"Imports": [
"vendor/github.com/json-iterator/go",
"github.com/qiniu/rpc.v2",
"vendor/github.com/qiniu/xlog.v1",
"vendor/qiniu.com/auth/qiniumac.v1"
],
"ImportMap": {
"github.com/json-iterator/go": "vendor/github.com/json-iterator/go",
"github.com/qiniu/xlog.v1": "vendor/github.com/qiniu/xlog.v1",
"qiniu.com/auth/qiniumac.v1": "vendor/qiniu.com/auth/qiniumac.v1"
},
"Deps": [
"bufio"
]
}`
const TEST_GO_LIST_MOD = `{
"Dir": "/Users/lyyyuna/gitup/tonghu-chat",
"ImportPath": "github.com/lyyyuna/tonghu-chat",
"Name": "main",
"Target": "/Users/lyyyuna/go/bin/tonghu-chat",
"Root": "/Users/lyyyuna/gitup/tonghu-chat",
"Module": {
"Path": "github.com/lyyyuna/tonghu-chat",
"Main": true,
"Dir": "/Users/lyyyuna/gitup/tonghu-chat",
"GoMod": "/Users/lyyyuna/gitup/tonghu-chat/go.mod",
"GoVersion": "1.14"
},
"Match": [
"./..."
],
"Stale": true,
"StaleReason": "not installed but available in build cache",
"GoFiles": [
"main.go"
],
"Imports": [
"github.com/gin-gonic/gin",
"github.com/gorilla/websocket"
],
"Deps": [
"bufio"
]
}`
func constructPkg(raw string) *cover.Package {
var pkg cover.Package
if err := json.Unmarshal([]byte(raw), &pkg); err != nil {
panic(err)
}
return &pkg
}
func TestLegacyProjectJudgement(t *testing.T) {
pkgs := make(map[string]*cover.Package)
pkg := constructPkg(TEST_GO_LIST_LEGACY)
pkgs[pkg.ImportPath] = pkg
if expect, got := true, checkIfLegacyProject(pkgs); expect != got {
t.Fatalf("Expected %v, but got %v.", expect, got)
}
}
func TestModProjectJudgement(t *testing.T) {
pkgs := make(map[string]*cover.Package)
pkg := constructPkg(TEST_GO_LIST_MOD)
pkgs[pkg.ImportPath] = pkg
if expect, got := false, checkIfLegacyProject(pkgs); expect != got {
t.Fatalf("Expected %v, but got %v.", expect, got)
}
}
func TestNewDirParseInLegacyProject(t *testing.T) {
workingDir := "../../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project"
gopath, _ := filepath.Abs("../../tests/samples/simple_gopath_project")
os.Chdir(workingDir)
fmt.Println(gopath)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "off")
newgopath, newwd, tmpdir, _ := MvProjectsToTmp(".", nil)
if -1 == strings.Index(newwd, tmpdir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", newwd, tmpdir)
}
if -1 == strings.Index(newgopath, ":") || -1 == strings.Index(newgopath, tmpdir) {
t.Fatalf("The New GOPATH is wrong. newgopath: %v, tmpdir: %v", newgopath, tmpdir)
}
}

384
pkg/cover/cover.go Normal file
View File

@ -0,0 +1,384 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cover
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
"path"
"strconv"
"strings"
"time"
)
// TestCover is a collection of all counters
type TestCover struct {
Mode string
Center string // cover profile host center
MainPkgCover *PackageCover
DepsCover []*PackageCover
CacheCover map[string]*PackageCover
}
// PackageCover holds all the generate coverage variables of a package
type PackageCover struct {
Package *Package
Vars map[string]*FileVar
}
// FileVar holds the name of the generated coverage variables targeting the named file.
type FileVar struct {
File string
Var string
}
// Package map a package output by go list
// this is subset of pakcage struct in: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L58
type Package struct {
Dir string `json:"Dir"` // directory containing package sources
ImportPath string `json:"ImportPath"` // import path of package in dir
Name string `json:"Name"` // package name
Target string `json:",omitempty"` // installed target for this package (may be executable)
Root string `json:",omitempty"` // Go root, Go path dir, or module root dir containing this package
Module *ModulePublic `json:",omitempty"` // info about package's module, if any
Goroot bool `json:"Goroot,omitempty"` // is this package in the Go root?
Standard bool `json:"Standard,omitempty"` // is this package part of the standard Go library?
DepOnly bool `json:"DepOnly,omitempty"` // package is only a dependency, not explicitly listed
// Source files
GoFiles []string `json:"GoFiles,omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string `json:"CgoFiles,omitempty"` // .go source files that import "C"
// Dependency information
Deps []string `json:"Deps,omitempty"` // all (recursively) imported dependencies
Imports []string `json:",omitempty"` // import paths used by this package
ImportMap map[string]string `json:",omitempty"` // map from source import to ImportPath (identity entries omitted)
// Error information
Incomplete bool `json:"Incomplete,omitempty"` // this package or a dependency has an error
Error *PackageError `json:"Error,omitempty"` // error loading package
DepsErrors []*PackageError `json:"DepsErrors,omitempty"` // errors loading dependencies
}
type ModulePublic struct {
Path string `json:",omitempty"` // module path
Version string `json:",omitempty"` // module version
Versions []string `json:",omitempty"` // available module versions
Replace *ModulePublic `json:",omitempty"` // replaced by this module
Time *time.Time `json:",omitempty"` // time version was created
Update *ModulePublic `json:",omitempty"` // available update (with -u)
Main bool `json:",omitempty"` // is this the main module?
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
Dir string `json:",omitempty"` // directory holding local copy of files, if any
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
GoVersion string `json:",omitempty"` // go version used in module
Error *ModuleError `json:",omitempty"` // error loading module
}
type ModuleError struct {
Err string // error text
}
// PackageError is the error info for a package when list failed
type PackageError struct {
ImportStack []string // shortest path from package named on command line to this one
Pos string // position of error (if present, file:line:col)
Err string // the error itself
}
// ListPackages list all packages under specific via go list command
func ListPackages(dir string, args []string, newgopath string) map[string]*Package {
cmd := exec.Command("go", args...)
log.Printf("go list cmd is: %v", cmd.Args)
cmd.Dir = dir
if newgopath != "" {
cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", newgopath))
}
out, _ := cmd.Output()
// if err != nil {
// log.Fatalf("excute `go list -json ./...` command failed, err: %v, out: %v", err, string(out))
// }
dec := json.NewDecoder(bytes.NewReader(out))
pkgs := make(map[string]*Package, 0)
for {
var pkg Package
if err := dec.Decode(&pkg); err != nil {
if err == io.EOF {
break
}
log.Fatalf("reading go list output: %v", err)
}
if pkg.Error != nil {
log.Fatalf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error)
}
// for _, err := range pkg.DepsErrors {
// log.Fatalf("dependency package list failed, err: %v", err)
// }
pkgs[pkg.ImportPath] = &pkg
}
return pkgs
}
// AddCounters add counters for all go files under the package
func AddCounters(pkg *Package, newgopath string) (*PackageCover, error) {
coverVarMap := declareCoverVars(pkg)
// to construct: go tool cover -mode=atomic -o dest src (note: dest==src)
var args = []string{"tool", "cover", "-mode=atomic"}
for file, coverVar := range coverVarMap {
var newArgs = args
newArgs = append(newArgs, "-var", coverVar.Var)
longPath := path.Join(pkg.Dir, file)
newArgs = append(newArgs, "-o", longPath, longPath)
cmd := exec.Command("go", newArgs...)
if newgopath != "" {
cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", newgopath))
}
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("execuate go tool cover -mode=atomic -var %s -o %s %s failed, err: %v, out: %s", coverVar.Var, longPath, longPath, err, string(out))
}
}
return &PackageCover{
Package: pkg,
Vars: coverVarMap,
}, nil
}
// declareCoverVars attaches the required cover variables names
// to the files, to be used when annotating the files.
func declareCoverVars(p *Package) map[string]*FileVar {
coverVars := make(map[string]*FileVar)
coverIndex := 0
// We create the cover counters as new top-level variables in the package.
// We need to avoid collisions with user variables (GoCover_0 is unlikely but still)
// and more importantly with dot imports of other covered packages,
// so we append 12 hex digits from the SHA-256 of the import path.
// The point is only to avoid accidents, not to defeat users determined to
// break things.
sum := sha256.Sum256([]byte(p.ImportPath))
h := fmt.Sprintf("%x", sum[:6])
for _, file := range p.GoFiles {
// These names appear in the cmd/cover HTML interface.
var longFile = path.Join(p.ImportPath, file)
coverVars[file] = &FileVar{
File: longFile,
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
}
coverIndex++
}
for _, file := range p.CgoFiles {
// These names appear in the cmd/cover HTML interface.
var longFile = path.Join(p.ImportPath, file)
coverVars[file] = &FileVar{
File: longFile,
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
}
coverIndex++
}
return coverVars
}
func declareCacheVars(in *PackageCover) map[string]*FileVar {
sum := sha256.Sum256([]byte(in.Package.ImportPath))
h := fmt.Sprintf("%x", sum[:5])
vars := make(map[string]*FileVar)
coverIndex := 0
for _, v := range in.Vars {
cacheVar := fmt.Sprintf("GoCacheCover_%d_%x", coverIndex, h)
vars[cacheVar] = v
coverIndex++
}
return vars
}
func CacheInternalCover(in *PackageCover) *PackageCover {
c := &PackageCover{}
vars := declareCacheVars(in)
c.Package = in.Package
c.Vars = vars
return c
}
func AddCacheCover(pkg *Package, in *PackageCover) *PackageCover {
c := &PackageCover{}
sum := sha256.Sum256([]byte(pkg.ImportPath))
h := fmt.Sprintf("%x", sum[:6])
goFile := fmt.Sprintf("cache_vars_auto_generated_%x.go", h)
p := &Package{
Dir: fmt.Sprintf("%s/cache_%x", pkg.Dir, h),
ImportPath: fmt.Sprintf("%s/cache_%x", pkg.ImportPath, h),
Name: fmt.Sprintf("cache_%x", h),
}
p.GoFiles = append(p.GoFiles, goFile)
c.Package = p
c.Vars = declareCacheVars(in)
return c
}
// CoverageList is a collection and summary over multiple file Coverage objects
type CoverageList struct {
*Coverage
Groups []Coverage
ConcernedFiles map[string]bool
CovThresholdInt int
}
// Coverage stores test coverage summary data for one file
type Coverage struct {
FileName string
NCoveredStmts int
NAllStmts int
LineCovLink string
}
type codeBlock struct {
fileName string // the file the code block is in
numStatements int // number of statements in the code block
coverageCount int // number of times the block is covered
}
func CovList(f io.Reader) (g *CoverageList, err error) {
scanner := bufio.NewScanner(f)
scanner.Scan() // discard first line
g = NewCoverageList("", map[string]bool{}, 0)
for scanner.Scan() {
row := scanner.Text()
blk, err := toBlock(row)
if err != nil {
return nil, err
}
blk.addToGroupCov(g)
}
return
}
// NewCoverageList constructs new (file) group Coverage
func NewCoverageList(name string, concernedFiles map[string]bool, covThresholdInt int) *CoverageList {
return &CoverageList{
Coverage: newCoverage(name),
Groups: []Coverage{},
ConcernedFiles: concernedFiles,
CovThresholdInt: covThresholdInt,
}
}
func newCoverage(name string) *Coverage {
return &Coverage{name, 0, 0, ""}
}
// convert a line in profile file to a codeBlock struct
func toBlock(line string) (res *codeBlock, err error) {
slice := strings.Split(line, " ")
if len(slice) != 3 {
return nil, fmt.Errorf("the profile line %s is not expected", line)
}
blockName := slice[0]
nStmts, _ := strconv.Atoi(slice[1])
coverageCount, _ := strconv.Atoi(slice[2])
return &codeBlock{
fileName: blockName[:strings.Index(blockName, ":")],
numStatements: nStmts,
coverageCount: coverageCount,
}, nil
}
// add blk Coverage to file group Coverage
func (blk *codeBlock) addToGroupCov(g *CoverageList) {
if g.size() == 0 || g.lastElement().Name() != blk.fileName {
// when a new file name is processed
coverage := newCoverage(blk.fileName)
g.append(coverage)
}
cov := g.lastElement()
cov.NAllStmts += blk.numStatements
if blk.coverageCount > 0 {
cov.NCoveredStmts += blk.numStatements
}
}
func (g *CoverageList) size() int {
return len(g.Groups)
}
func (g *CoverageList) lastElement() *Coverage {
return &g.Groups[g.size()-1]
}
func (g *CoverageList) append(c *Coverage) {
g.Groups = append(g.Groups, *c)
}
// Group returns the collection of file Coverage objects
func (g *CoverageList) Group() *[]Coverage {
return &g.Groups
}
// Map returns maps the file name to its coverage for faster retrieval
// & membership check
func (g *CoverageList) Map() map[string]Coverage {
m := make(map[string]Coverage)
for _, c := range g.Groups {
m[c.Name()] = c
}
return m
}
// Name returns the file name
func (c *Coverage) Name() string {
return c.FileName
}
// Percentage returns the percentage of statements covered
func (c *Coverage) Percentage() string {
ratio, err := c.Ratio()
if err == nil {
return PercentStr(ratio)
}
return "N/A"
}
func (c *Coverage) Ratio() (ratio float32, err error) {
if c.NAllStmts == 0 {
err = fmt.Errorf("[%s] has 0 statement", c.Name())
} else {
ratio = float32(c.NCoveredStmts) / float32(c.NAllStmts)
}
return
}
// PercentStr converts a fraction number to percentage string representation
func PercentStr(f float32) string {
return fmt.Sprintf("%.1f%%", f*100)
}

97
pkg/cover/cover_test.go Normal file
View File

@ -0,0 +1,97 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cover
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func testCoverage() (c *Coverage) {
return &Coverage{FileName: "fake-coverage", NCoveredStmts: 200, NAllStmts: 300}
}
func TestCoverageRatio(t *testing.T) {
c := testCoverage()
actualRatio, _ := c.Ratio()
assert.Equal(t, float32(c.NCoveredStmts)/float32(c.NAllStmts), actualRatio)
}
func TestRatioErr(t *testing.T) {
c := &Coverage{FileName: "fake-coverage", NCoveredStmts: 200, NAllStmts: 0}
_, err := c.Ratio()
assert.NotNil(t, err)
}
func TestPercentageNA(t *testing.T) {
c := &Coverage{FileName: "fake-coverage", NCoveredStmts: 200, NAllStmts: 0}
assert.Equal(t, "N/A", c.Percentage())
}
func TestGenLocalCoverDiffReport(t *testing.T) {
//coverage increase
newList := &CoverageList{Groups: []Coverage{{FileName: "fake-coverage", NCoveredStmts: 15, NAllStmts: 20}}}
baseList := &CoverageList{Groups: []Coverage{{FileName: "fake-coverage", NCoveredStmts: 10, NAllStmts: 20}}}
rows := GenLocalCoverDiffReport(newList, baseList)
assert.Equal(t, 1, len(rows))
assert.Equal(t, []string{"fake-coverage", "50.0%", "75.0%", "25.0%"}, rows[0])
//coverage decrease
baseList = &CoverageList{Groups: []Coverage{{FileName: "fake-coverage", NCoveredStmts: 20, NAllStmts: 20}}}
rows = GenLocalCoverDiffReport(newList, baseList)
assert.Equal(t, []string{"fake-coverage", "100.0%", "75.0%", "-25.0%"}, rows[0])
//diff file
baseList = &CoverageList{Groups: []Coverage{{FileName: "fake-coverage-v1", NCoveredStmts: 10, NAllStmts: 20}}}
rows = GenLocalCoverDiffReport(newList, baseList)
assert.Equal(t, []string{"fake-coverage", "None", "75.0%", "75.0%"}, rows[0])
}
func TestCovList(t *testing.T) {
fileName := "qiniu.com/kodo/apiserver/server/main.go"
// percentage is 100%
p := strings.NewReader("mode: atomic\n" +
fileName + ":32.49,33.13 1 30\n")
covL, err := CovList(p)
covF := covL.Map()[fileName]
assert.Nil(t, err)
assert.Equal(t, "100.0%", covF.Percentage())
// percentage is 50%
p = strings.NewReader("mode: atomic\n" +
fileName + ":32.49,33.13 1 30\n" +
fileName + ":42.49,43.13 1 0\n")
covL, err = CovList(p)
covF = covL.Map()[fileName]
assert.Nil(t, err)
assert.Equal(t, "50.0%", covF.Percentage())
// two files
fileName1 := "qiniu.com/kodo/apiserver/server/svr.go"
p = strings.NewReader("mode: atomic\n" +
fileName + ":32.49,33.13 1 30\n" +
fileName1 + ":42.49,43.13 1 0\n")
covL, err = CovList(p)
covF = covL.Map()[fileName]
covF1 := covL.Map()[fileName1]
assert.Nil(t, err)
assert.Equal(t, "100.0%", covF.Percentage())
assert.Equal(t, "0.0%", covF1.Percentage())
}

56
pkg/cover/delta.go Normal file
View File

@ -0,0 +1,56 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cover
type GroupChanges struct {
Added []Coverage
Deleted []Coverage
Unchanged []Coverage
Changed []Incremental
BaseGroup *CoverageList
NewGroup *CoverageList
}
type Incremental struct {
base Coverage
new Coverage
}
func GenLocalCoverDiffReport(newList *CoverageList, baseList *CoverageList) [][]string {
var rows [][]string
basePMap := baseList.Map()
for _, l := range newList.Groups {
baseCov, ok := basePMap[l.Name()]
if !ok {
rows = append(rows, []string{l.FileName, "None", l.Percentage(), PercentStr(Delta(l, baseCov))})
continue
}
if l.Percentage() == baseCov.Percentage() {
continue
}
rows = append(rows, []string{l.FileName, baseCov.Percentage(), l.Percentage(), PercentStr(Delta(l, baseCov))})
}
return rows
}
func Delta(new Coverage, base Coverage) float32 {
baseRatio, _ := base.Ratio()
newRatio, _ := new.Ratio()
return newRatio - baseRatio
}

368
pkg/cover/instrument.go Normal file
View File

@ -0,0 +1,368 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cover
import (
"fmt"
"os"
"path"
"text/template"
)
// InjectCountersHandlers generate a file _cover_http_apis.go besides the main.go file
func InjectCountersHandlers(tc TestCover, dest string) error {
f, err := os.Create(dest)
if err != nil {
return err
}
if err := coverMainTmpl.Execute(f, tc); err != nil {
return err
}
return nil
}
var coverMainTmpl = template.Must(template.New("coverMain").Parse(coverMain))
const coverMain = `
// Code generated by goc system. DO NOT EDIT.
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"strings"
"sync/atomic"
"testing"
{{range $i, $pkgCover := .DepsCover}}
_cover{{$i}} {{$pkgCover.Package.ImportPath | printf "%q"}}
{{end}}
{{range $k, $pkgCover := .CacheCover}}
{{$pkgCover.Package.ImportPath | printf "%q"}}
{{end}}
)
func init() {
go registerHandlers()
}
func loadValues() (map[string][]uint32, map[string][]testing.CoverBlock) {
var (
coverCounters = make(map[string][]uint32)
coverBlocks = make(map[string][]testing.CoverBlock)
)
{{range $i, $pkgCover := .DepsCover}}
{{range $file, $cover := $pkgCover.Vars}}
loadFileCover(coverCounters, coverBlocks, {{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:])
{{end}}
{{end}}
{{range $file, $cover := .MainPkgCover.Vars}}
loadFileCover(coverCounters, coverBlocks, {{printf "%q" $cover.File}}, {{$cover.Var}}.Count[:], {{$cover.Var}}.Pos[:], {{$cover.Var}}.NumStmt[:])
{{end}}
{{range $k, $pkgCover := .CacheCover}}
{{range $v, $cover := $pkgCover.Vars}}
loadFileCover(coverCounters, coverBlocks, {{printf "%q" $cover.File}}, {{$pkgCover.Package.Name}}.{{$v}}.Count[:], {{$pkgCover.Package.Name}}.{{$v}}.Pos[:], {{$pkgCover.Package.Name}}.{{$v}}.NumStmt[:])
{{end}}
{{end}}
return coverCounters, coverBlocks
}
func loadFileCover(coverCounters map[string][]uint32, coverBlocks map[string][]testing.CoverBlock, fileName string, counter []uint32, pos []uint32, numStmts []uint16) {
if 3*len(counter) != len(pos) || len(counter) != len(numStmts) {
panic("coverage: mismatched sizes")
}
if coverCounters[fileName] != nil {
// Already registered.
return
}
coverCounters[fileName] = counter
block := make([]testing.CoverBlock, len(counter))
for i := range counter {
block[i] = testing.CoverBlock{
Line0: pos[3*i+0],
Col0: uint16(pos[3*i+2]),
Line1: pos[3*i+1],
Col1: uint16(pos[3*i+2] >> 16),
Stmts: numStmts[i],
}
}
coverBlocks[fileName] = block
}
func registerHandlers() {
ln, host, err := listen()
if err != nil {
log.Fatalf("profile listen failed, err:%v", err)
}
log.Println("profile listen on", host)
profileAddr := "http://" + host
if resp, err := registerSelf(profileAddr); err != nil {
log.Fatalf("register address %v failed, err: %v, response: %v", profileAddr, err, string(resp))
}
go genProfileAddr(host)
mux := http.NewServeMux()
// Coverage reports the current code coverage as a fraction in the range [0, 1].
// If coverage is not enabled, Coverage returns 0.
mux.HandleFunc("/v1/cover/coverage", func(w http.ResponseWriter, r *http.Request) {
counters, _ := loadValues()
var n, d int64
for _, counter := range counters {
for i := range counter {
if atomic.LoadUint32(&counter[i]) > 0 {
n++
}
d++
}
}
if d == 0 {
fmt.Fprint(w, 0)
return
}
fmt.Fprintf(w, "%f", float64(n)/float64(d))
})
// coverprofile reports a coverage profile with the coverage percentage
mux.HandleFunc("/v1/cover/profile", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "mode: atomic\n")
counters, blocks := loadValues()
var active, total int64
var count uint32
for name, counts := range counters {
block := blocks[name]
for i := range counts {
stmts := int64(block[i].Stmts)
total += stmts
count = atomic.LoadUint32(&counts[i]) // For -mode=atomic.
if count > 0 {
active += stmts
}
_, err := fmt.Fprintf(w, "%s:%d.%d,%d.%d %d %d\n", name,
block[i].Line0, block[i].Col0,
block[i].Line1, block[i].Col1,
stmts,
count)
if err != nil {
fmt.Fprintf(w, "invalid block format, err: %v", err)
return
}
}
}
})
mux.HandleFunc("/v1/cover/clear", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "TO BE IMPLEMENTED!\n")
})
log.Fatal(http.Serve(ln, mux))
}
func registerSelf(address string) ([]byte, error) {
req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/cover/register?name=%s&address=%s", {{.Center | printf "%q"}}, os.Args[0], address), nil)
if err != nil {
log.Fatalf("http.NewRequest failed: %v", err)
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil && isNetworkError(err) {
log.Printf("[WARN]error occured:%v, try again", err)
resp, err = http.DefaultClient.Do(req)
}
defer resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("registed faile, err:%v", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body, err:%v", err)
}
if resp.StatusCode != 200 {
err = fmt.Errorf("registed failed, response code %d", resp.StatusCode)
}
return body, err
}
func isNetworkError(err error) bool {
if err == io.EOF {
return true
}
_, ok := err.(net.Error)
return ok
}
func listen() (ln net.Listener, host string, err error) {
// 获取上次使用的监听地址
if previousAddr := getPreviousAddr(); previousAddr != "" {
ss := strings.Split(previousAddr, ":")
// listen on all network interface
ln, err = net.Listen("tcp4", ":"+ss[len(ss)-1])
if err == nil {
host = previousAddr
return
}
}
ln, err = net.Listen("tcp4", ":0")
if err != nil {
return
}
adds, err := net.InterfaceAddrs()
if err != nil {
return
}
var localIPV4 string
var nonLocalIPV4 string
for _, addr := range adds {
if ipNet, ok := addr.(*net.IPNet); ok && ipNet.IP.To4() != nil {
if ipNet.IP.IsLoopback() {
localIPV4 = ipNet.IP.String()
} else {
nonLocalIPV4 = ipNet.IP.String()
}
}
}
if nonLocalIPV4 != "" {
host = fmt.Sprintf("%s:%d", nonLocalIPV4, ln.Addr().(*net.TCPAddr).Port)
} else {
host = fmt.Sprintf("%s:%d", localIPV4, ln.Addr().(*net.TCPAddr).Port)
}
return
}
func getPreviousAddr() string {
file, err := os.Open(os.Args[0] + "_profile_listen_addr")
if err != nil {
return ""
}
defer file.Close()
reader := bufio.NewReader(file)
addr, _, _ := reader.ReadLine()
return string(addr)
}
func genProfileAddr(profileAddr string) {
fn := os.Args[0] + "_profile_listen_addr"
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println(err)
return
}
defer f.Close()
fmt.Fprintf(f, strings.TrimPrefix(profileAddr, "http://"))
}
`
var coverParentFileTmpl = template.Must(template.New("coverParentFileTmpl").Parse(coverParentFile))
const coverParentFile = `
// Code generated by goc system. DO NOT EDIT.
package {{.}}
`
var coverParentVarsTmpl = template.Must(template.New("coverParentVarsTmpl").Parse(coverParentVars))
const coverParentVars = `
import (
{{range $i, $pkgCover := .}}
_cover{{$i}} {{$pkgCover.Package.ImportPath | printf "%q"}}
{{end}}
)
{{range $i, $pkgCover := .}}
{{range $v, $cover := $pkgCover.Vars}}
var {{$v}} = &_cover{{$i}}.{{$cover.Var}}
{{end}}
{{end}}
`
func InjectCacheCounters(covers map[string][]*PackageCover, cache map[string]*PackageCover) []error {
var errs []error
for k, v := range covers {
if pkg, ok := cache[k]; ok {
err := checkCacheDir(pkg.Package.Dir)
if err != nil {
errs = append(errs, err)
continue
}
_, pkgName := path.Split(k)
err = injectCache(v, pkgName, fmt.Sprintf("%s/%s", pkg.Package.Dir, pkg.Package.GoFiles[0]))
if err != nil {
errs = append(errs, err)
continue
}
}
}
return errs
}
// InjectCacheCounters generate a file _cover_http_apis.go besides the main.go file
func injectCache(covers []*PackageCover, pkg, dest string) error {
f, err := os.Create(dest)
if err != nil {
return err
}
if err := coverParentFileTmpl.Execute(f, pkg); err != nil {
return err
}
if err := coverParentVarsTmpl.Execute(f, covers); err != nil {
return err
}
return nil
}
func checkCacheDir(p string) error {
_, err := os.Stat(p)
if os.IsNotExist(err) {
err := os.Mkdir(p, 0755)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,29 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestE2e(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "E2E goc Suite")
}

View File

@ -0,0 +1,167 @@
/*
Copyright 2020 Qiniu Cloud (七牛云)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e_test
import (
"fmt"
"os"
"os/exec"
"strings"
"time"
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/qiniu/goc/pkg/build"
)
var TESTS_ROOT string
var _ = BeforeSuite(func() {
TESTS_ROOT, _ = os.Getwd()
By("Current working directory: " + TESTS_ROOT)
TESTS_ROOT = filepath.Join(TESTS_ROOT, "..")
})
var _ = Describe("E2E", func() {
var GOPATH string
BeforeEach(func() {
GOPATH = os.Getenv("GOPATH")
// in GitHub Action, this value is empty
if GOPATH == "" {
GOPATH = filepath.Join(os.Getenv("HOME"), "go")
}
})
Context("Go module", func() {
It("Simple project", func() {
startTime := time.Now()
By("goc build")
testProjDir := filepath.Join(TESTS_ROOT, "samples/simple_project")
cmd := exec.Command("goc", "build")
cmd.Dir = testProjDir
out, err := cmd.CombinedOutput()
Expect(err).To(BeNil(), "goc build on this project should be successful", string(out))
By("goc install")
testProjDir = filepath.Join(TESTS_ROOT, "samples/simple_project")
cmd = exec.Command("goc", "install", "./...")
cmd.Dir = testProjDir
out, err = cmd.CombinedOutput()
Expect(err).To(BeNil(), "goc install on this project should be successful", string(out))
By("check files in generated temporary directory")
tempDir := filepath.Join(os.TempDir(), build.TmpFolderName(testProjDir))
_, err = os.Lstat(tempDir)
Expect(err).To(BeNil(), "projects should be copied to temporary directory")
By("check if cover variables are injected")
_, err = os.Lstat(filepath.Join(tempDir, "http_cover_apis_auto_generated.go"))
Expect(err).To(BeNil(), "a http server file should be generated")
By("check generated binary")
objects := []string{GOPATH + "/bin", testProjDir}
for _, dir := range objects {
obj := filepath.Join(dir, "simple-project")
fInfo, err := os.Lstat(obj)
Expect(err).To(BeNil())
Expect(startTime.Before(fInfo.ModTime())).To(Equal(true), "new binary should be generated, not the old one")
cmd := exec.Command("go", "tool", "objdump", "simple-project")
cmd.Dir = dir
out, err = cmd.CombinedOutput()
Expect(err).To(BeNil(), "the binary cannot be disassembled")
cnt := strings.Count(string(out), "GoCover")
Expect(cnt).To(BeNumerically(">", 0), "GoCover varibale should be in the binary")
cnt = strings.Count(string(out), "main.registerSelf")
Expect(cnt).To(BeNumerically(">", 0), "main.registerSelf function should be in the binary")
}
})
})
Context("GOPATH", func() {
var GOPATH string
BeforeEach(func() {
GOPATH = os.Getenv("GOPATH")
})
It("Simple GOPATH project", func() {
startTime := time.Now()
testProjDir := filepath.Join(TESTS_ROOT, "samples/simple_gopath_project")
oriWorkingDir := filepath.Join(testProjDir, "src/qiniu.com/simple_gopath_project")
GOPATH = testProjDir
By("goc build")
cmd := exec.Command("goc", "build")
cmd.Dir = oriWorkingDir
// use GOPATH mode to compile project
cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", GOPATH), "GO111MODULE=off")
out, err := cmd.CombinedOutput()
Expect(err).To(BeNil(), "goc build on this project should be successful", string(out), cmd.Dir)
By("goc install")
testProjDir = filepath.Join(TESTS_ROOT, "samples/simple_gopath_project")
cmd = exec.Command("goc", "install", "./...")
cmd.Dir = filepath.Join(testProjDir, "src/qiniu.com/simple_gopath_project")
// use GOPATH mode to compile project
cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", testProjDir), "GO111MODULE=off")
out, err = cmd.CombinedOutput()
Expect(err).To(BeNil(), "goc install on this project should be successful", string(out))
By("check files in generated temporary directory")
tempDir := filepath.Join(os.TempDir(), build.TmpFolderName(oriWorkingDir))
_, err = os.Lstat(tempDir)
Expect(err).To(BeNil(), "projects should be copied to temporary directory")
By("check if cover variables are injected")
newWorkingDir := filepath.Join(tempDir, "src/qiniu.com/simple_gopath_project")
_, err = os.Lstat(filepath.Join(newWorkingDir, "http_cover_apis_auto_generated.go"))
Expect(err).To(BeNil(), "a http server file should be generated")
By("check generated binary")
objects := []string{GOPATH + "/bin", oriWorkingDir}
for _, dir := range objects {
obj := filepath.Join(dir, "simple_gopath_project")
fInfo, err := os.Lstat(obj)
Expect(err).To(BeNil())
Expect(startTime.Before(fInfo.ModTime())).To(Equal(true), "new binary should be generated, not the old one")
cmd := exec.Command("go", "tool", "objdump", "simple_gopath_project")
cmd.Dir = dir
out, err = cmd.CombinedOutput()
Expect(err).To(BeNil(), "the binary cannot be disassembled")
cnt := strings.Count(string(out), "GoCover")
Expect(cnt).To(BeNumerically(">", 0), "GoCover varibale should be in the binary")
cnt = strings.Count(string(out), "main.registerSelf")
Expect(cnt).To(BeNumerically(">", 0), "main.registerSelf function should be in the binary")
}
})
})
})

25
tests/run-ci-actions.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# Copyright 2020 Qiniu Cloud (七牛云)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -ex
chmod +x /home/runner/tools/goc/goc
export PATH=/home/runner/tools/goc:$PATH
chmod +x /home/runner/tools/e2e.test/e2e.test
export PATH=/home/runner/tools/e2e.test:$PATH
cd e2e
e2e.test ./...

View File

@ -0,0 +1,9 @@
package main
import (
"fmt"
)
func main() {
fmt.Println("hello, world.")
}

View File

@ -0,0 +1,3 @@
module example.com/simple-project
go 1.11

View File

@ -0,0 +1,9 @@
package main
import (
"fmt"
)
func main() {
fmt.Println("hello, world.")
}