refactor cover method

This commit is contained in:
jichangjun 2020-06-14 19:37:27 +08:00
parent 4be6d34cad
commit 0fb0487dbb
5 changed files with 174 additions and 171 deletions

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"github.com/qiniu/goc/pkg/build" "github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -52,7 +53,7 @@ goc build -- -ldflags "-extldflags -static" -tags="embed kodo"
}() }()
// doCover with original buildFlags, with new GOPATH( tmp:original ) // doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory // in the tmp directory
doCover(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir) cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory // do install in the temporary directory
gocBuild.Build() gocBuild.Build()
return return

View File

@ -13,6 +13,9 @@ var (
buildFlags string buildFlags string
packages string packages string
appArgs string appArgs string
goRunExecFlag string
goRunArguments string
) )
// addBasicFlags adds a // addBasicFlags adds a
@ -41,6 +44,8 @@ func addRunFlags(cmdset *pflag.FlagSet) {
addBuildFlags(cmdset) addBuildFlags(cmdset)
cmdset.Lookup("packages").Usage = "specify the package name, only ., ./... and *.go are supported" cmdset.Lookup("packages").Usage = "specify the package name, only ., ./... and *.go are supported"
cmdset.StringVar(&appArgs, "appargs", "", "specify the application's arguments") cmdset.StringVar(&appArgs, "appargs", "", "specify the application's arguments")
cmdset.StringVar(&goRunExecFlag, "exec", "", "same as -exec flag in 'go run' command")
cmdset.StringVar(&goRunArguments, "arguments", "", "same as 'arguments' in 'go run' command")
// bind to viper // bind to viper
viper.BindPFlags(cmdset) viper.BindPFlags(cmdset)
} }

View File

@ -17,13 +17,10 @@
package cmd package cmd
import ( import (
"fmt" "github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
"os"
"strings"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -52,7 +49,7 @@ goc cover --center=http://127.0.0.1:7777 --target=/path/to/target --mode=atomic
log.Fatalf("unknown -mode %v", mode) log.Fatalf("unknown -mode %v", mode)
} }
doCover(buildFlags, "", target) cover.Execute(buildFlags, "", target, mode, center)
}, },
} }
@ -61,166 +58,3 @@ func init() {
addCommonFlags(coverCmd.Flags()) addCommonFlags(coverCmd.Flags())
rootCmd.AddCommand(coverCmd) rootCmd.AddCommand(coverCmd)
} }
func doCover(args string, newgopath string, target string) {
if !isDirExist(target) {
log.Fatalf("target directory %s not exist", target)
}
listArgs := []string{"-json"}
if len(args) != 0 {
listArgs = append(listArgs, args)
}
listArgs = append(listArgs, "./...")
pkgs := cover.ListPackages(target, strings.Join(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, mode, 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: mode,
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, mode, 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, mode, 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 ""
}

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"github.com/qiniu/goc/pkg/build" "github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -49,7 +50,7 @@ goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
}() }()
// doCover with original buildFlags, with new GOPATH( tmp:original ) // doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory // in the tmp directory
doCover(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir) cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory // do install in the temporary directory
gocBuild.Install() gocBuild.Install()
}, },

View File

@ -22,7 +22,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"fmt" "fmt"
log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -33,6 +32,8 @@ import (
"strings" "strings"
"time" "time"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -112,6 +113,125 @@ type PackageError struct {
Err string // the error itself Err string // the error itself
} }
//Execute execute go tool cover for all the .go files in the target folder
func Execute(args, newGopath, target, mode, center string) {
if !isDirExist(target) {
log.Fatalf("target directory %s not exist", target)
}
listArgs := []string{"-json"}
if len(args) != 0 {
listArgs = append(listArgs, args)
}
listArgs = append(listArgs, "./...")
pkgs := ListPackages(target, strings.Join(listArgs, " "), newGopath)
var seen = make(map[string]*PackageCover)
var seenCache = make(map[string]*PackageCover)
for _, pkg := range pkgs {
if pkg.Name == "main" {
log.Printf("handle package: %v", pkg.ImportPath)
// inject the main package
mainCover, err := AddCounters(pkg, mode, 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 := TestCover{
Mode: mode,
Center: center,
MainPkgCover: mainCover,
}
// handle its dependency
var internalPkgCache = make(map[string][]*PackageCover)
tc.CacheCover = make(map[string]*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 := AddCounters(depPkg, mode, 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 := &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 := 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 := CacheInternalCover(inPkgCover)
if v, ok := internalPkgCache[cacheCover.Package.Dir]; ok {
v = append(v, inCover)
internalPkgCache[cacheCover.Package.Dir] = v
} else {
var covers []*PackageCover
covers = append(covers, inCover)
internalPkgCache[cacheCover.Package.Dir] = covers
}
seenCache[dep] = cacheCover
continue
}
packageCover, err := AddCounters(depPkg, mode, 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 := 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 := InjectCountersHandlers(tc, httpCoverApis); err != nil {
log.Fatalf("failed to inject counters for package: %s, err: %v", pkg.ImportPath, err)
}
}
}
}
// ListPackages list all packages under specific via go list command // ListPackages list all packages under specific via go list command
// The argument newgopath is if you need to go list in a different GOPATH // The argument newgopath is if you need to go list in a different GOPATH
func ListPackages(dir string, args string, newgopath string) map[string]*Package { func ListPackages(dir string, args string, newgopath string) map[string]*Package {
@ -167,6 +287,48 @@ func AddCounters(pkg *Package, mode, newgopath string) (*PackageCover, error) {
}, nil }, nil
} }
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 ""
}
func buildCoverCmd(file string, coverVar *FileVar, pkg *Package, mode, newgopath string) *exec.Cmd { func buildCoverCmd(file string, coverVar *FileVar, pkg *Package, mode, newgopath string) *exec.Cmd {
// to construct: go tool cover -mode=atomic -o dest src (note: dest==src) // to construct: go tool cover -mode=atomic -o dest src (note: dest==src)
var newArgs = []string{"tool", "cover"} var newArgs = []string{"tool", "cover"}