1.Add code coverage counter clear API
2.Refact core cover method and add cover unit test
This commit is contained in:
parent
7bf0db074d
commit
9795355196
49
cmd/clear.go
Normal file
49
cmd/clear.go
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2020 Qiniu Cloud (qiniu.com)
|
||||
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/goc/pkg/cover"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var clearCmd = &cobra.Command{
|
||||
Use: "clear",
|
||||
Short: "Clear code coverage counters of all the registered services",
|
||||
Long: `Clear code coverage counters for the services under test at runtime.
|
||||
|
||||
Examples:
|
||||
# clear coverage counter by special service url
|
||||
goc clear --center=http://127.0.0.1:7777`,
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
res, err := cover.NewWorker().Clear(center)
|
||||
if err != nil {
|
||||
log.Fatalf("call host %v failed, err: %v, response: %v", center, err, string(res))
|
||||
}
|
||||
fmt.Fprint(os.Stdout, string(res))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
clearCmd.Flags().StringVarP(¢er, "center", "", "http://127.0.0.1:7777", "cover profile host center")
|
||||
rootCmd.AddCommand(clearCmd)
|
||||
}
|
17
cmd/cover.go
17
cmd/cover.go
@ -30,6 +30,13 @@ var coverCmd = &cobra.Command{
|
||||
Use: "cover",
|
||||
Short: "do cover for the target source ",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if mode == "" {
|
||||
log.Fatalf("Error: flag needs an argument: -mode %v", mode)
|
||||
}
|
||||
if mode != "set" && mode != "count" && mode != "atomic" {
|
||||
log.Fatalf("unknown -mode %v", mode)
|
||||
}
|
||||
|
||||
doCover(cmd, args, "", "")
|
||||
},
|
||||
}
|
||||
@ -37,11 +44,13 @@ var coverCmd = &cobra.Command{
|
||||
var (
|
||||
target string
|
||||
center string
|
||||
mode string
|
||||
)
|
||||
|
||||
func init() {
|
||||
coverCmd.Flags().StringVarP(¢er, "center", "", "http://127.0.0.1:7777", "cover profile host center")
|
||||
coverCmd.Flags().StringVarP(&target, "target", "", ".", "target folder to cover")
|
||||
coverCmd.Flags().StringVarP(&mode, "mode", "", "count", "coverage mode: set, count, atomic")
|
||||
|
||||
rootCmd.AddCommand(coverCmd)
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
@ -68,14 +77,14 @@ func doCover(cmd *cobra.Command, args []string, newgopath string, newtarget stri
|
||||
if pkg.Name == "main" {
|
||||
log.Printf("handle package: %v", pkg.ImportPath)
|
||||
// inject the main package
|
||||
mainCover, err := cover.AddCounters(pkg, newgopath)
|
||||
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: "atomic",
|
||||
Mode: mode,
|
||||
Center: center,
|
||||
MainPkgCover: mainCover,
|
||||
}
|
||||
@ -103,7 +112,7 @@ func doCover(cmd *cobra.Command, args []string, newgopath string, newtarget stri
|
||||
}
|
||||
|
||||
// add counter for internal package
|
||||
inPkgCover, err := cover.AddCounters(depPkg, newgopath)
|
||||
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)
|
||||
}
|
||||
@ -149,7 +158,7 @@ func doCover(cmd *cobra.Command, args []string, newgopath string, newtarget stri
|
||||
continue
|
||||
}
|
||||
|
||||
packageCover, err := cover.AddCounters(depPkg, newgopath)
|
||||
packageCover, err := cover.AddCounters(depPkg, mode, newgopath)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
// Action provides methods to contact with the coverd service under test
|
||||
type Action interface {
|
||||
Profile(host string) ([]byte, error)
|
||||
Clear() error
|
||||
Clear(host string) ([]byte, error)
|
||||
InitSystem(host string) ([]byte, error)
|
||||
}
|
||||
|
||||
@ -61,7 +61,14 @@ func (w *client) Profile(host string) ([]byte, error) {
|
||||
return profile, err
|
||||
}
|
||||
|
||||
func (w *client) Clear() error { return nil }
|
||||
func (w *client) Clear(host string) ([]byte, error) {
|
||||
u := fmt.Sprintf("%s%s", host, CoverProfileClearAPI)
|
||||
resp, err := w.do("POST", u, nil)
|
||||
if err != nil && isNetworkError(err) {
|
||||
resp, err = w.do("POST", u, nil)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (w *client) InitSystem(host string) ([]byte, error) {
|
||||
u := fmt.Sprintf("%s%s", host, CoverInitSystemAPI)
|
||||
|
@ -145,23 +145,14 @@ func ListPackages(dir string, args []string, newgopath string) map[string]*Packa
|
||||
}
|
||||
|
||||
// AddCounters add counters for all go files under the package
|
||||
func AddCounters(pkg *Package, newgopath string) (*PackageCover, error) {
|
||||
func AddCounters(pkg *Package, mode, 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))
|
||||
}
|
||||
cmd := buildCoverCmd(file, coverVar, pkg, mode, 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 nil, fmt.Errorf("execuate go tool cover -mode=atomic -var %s -o %s/%s failed, err: %v, out: %s", coverVar.Var, pkg.Dir, file, err, string(out))
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,6 +162,20 @@ func AddCounters(pkg *Package, newgopath string) (*PackageCover, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
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)
|
||||
var newArgs = []string{"tool", "cover"}
|
||||
newArgs = append(newArgs, "-mode", mode)
|
||||
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))
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// declareCoverVars attaches the required cover variables names
|
||||
// to the files, to be used when annotating the files.
|
||||
func declareCoverVars(p *Package) map[string]*FileVar {
|
||||
|
@ -17,6 +17,12 @@
|
||||
package cover
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -95,3 +101,114 @@ func TestCovList(t *testing.T) {
|
||||
assert.Equal(t, "100.0%", covF.Percentage())
|
||||
assert.Equal(t, "0.0%", covF1.Percentage())
|
||||
}
|
||||
|
||||
func TestBuildCoverCmd(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
file string
|
||||
coverVar *FileVar
|
||||
pkg *Package
|
||||
mode string
|
||||
newgopath string
|
||||
expectCmd *exec.Cmd
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
file: "c.go",
|
||||
coverVar: &FileVar{
|
||||
File: "example/b/c/c.go",
|
||||
Var: "GoCover_0_643131623532653536333031",
|
||||
},
|
||||
pkg: &Package{
|
||||
Dir: "/go/src/goc/cmd/example-project/b/c",
|
||||
},
|
||||
mode: "count",
|
||||
newgopath: "",
|
||||
expectCmd: &exec.Cmd{
|
||||
Path: lookCmdPath("go"),
|
||||
Args: []string{"go", "tool", "cover", "-mode", "count", "-var", "GoCover_0_643131623532653536333031", "-o",
|
||||
"/go/src/goc/cmd/example-project/b/c/c.go", "/go/src/goc/cmd/example-project/b/c/c.go"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "normal with gopath",
|
||||
file: "c.go",
|
||||
coverVar: &FileVar{
|
||||
File: "example/b/c/c.go",
|
||||
Var: "GoCover_0_643131623532653536333031",
|
||||
},
|
||||
pkg: &Package{
|
||||
Dir: "/go/src/goc/cmd/example-project/b/c",
|
||||
},
|
||||
mode: "set",
|
||||
newgopath: "/go/src/goc",
|
||||
expectCmd: &exec.Cmd{
|
||||
Path: lookCmdPath("go"),
|
||||
Args: []string{"go", "tool", "cover", "-mode", "set", "-var", "GoCover_0_643131623532653536333031", "-o",
|
||||
"/go/src/goc/cmd/example-project/b/c/c.go", "/go/src/goc/cmd/example-project/b/c/c.go"},
|
||||
Env: append(os.Environ(), fmt.Sprintf("GOPATH=%v", "/go/src/goc")),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
cmd := buildCoverCmd(testCase.file, testCase.coverVar, testCase.pkg, testCase.mode, testCase.newgopath)
|
||||
if !reflect.DeepEqual(cmd, testCase.expectCmd) {
|
||||
t.Errorf("generated incorrect commands:\nGot: %#v\nExpected:%#v", cmd, testCase.expectCmd)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func lookCmdPath(name string) string {
|
||||
if filepath.Base(name) == name {
|
||||
if lp, err := exec.LookPath(name); err != nil {
|
||||
log.Fatalf("find exec %s err: %v", name, err)
|
||||
} else {
|
||||
return lp
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestDeclareCoverVars(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
pkg *Package
|
||||
expectCoverVar map[string]*FileVar
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
pkg: &Package{
|
||||
Dir: "/go/src/goc/cmd/example-project/b/c",
|
||||
GoFiles: []string{"c.go"},
|
||||
ImportPath: "example/b/c",
|
||||
},
|
||||
expectCoverVar: map[string]*FileVar{
|
||||
"c.go": {File: "example/b/c/c.go", Var: "GoCover_0_643131623532653536333031"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "more go files",
|
||||
pkg: &Package{
|
||||
Dir: "/go/src/goc/cmd/example-project/a/b",
|
||||
GoFiles: []string{"printf.go", "printf1.go"},
|
||||
ImportPath: "example/a/b",
|
||||
},
|
||||
expectCoverVar: map[string]*FileVar{
|
||||
"printf.go": {File: "example/a/b/printf.go", Var: "GoCover_0_326535623364613565313464"},
|
||||
"printf1.go": {File: "example/a/b/printf1.go", Var: "GoCover_1_326535623364613565313464"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
coverVar := declareCoverVars(testCase.pkg)
|
||||
if !reflect.DeepEqual(coverVar, testCase.expectCoverVar) {
|
||||
t.Errorf("generated incorrect cover vars:\nGot: %#v\nExpected:%#v", coverVar, testCase.expectCoverVar)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -116,6 +116,32 @@ func loadFileCover(coverCounters map[string][]uint32, coverBlocks map[string][]t
|
||||
coverBlocks[fileName] = block
|
||||
}
|
||||
|
||||
func clearValues() {
|
||||
|
||||
{{range $i, $pkgCover := .DepsCover}}
|
||||
{{range $file, $cover := $pkgCover.Vars}}
|
||||
clearFileCover(_cover{{$i}}.{{$cover.Var}}.Count[:])
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{range $file, $cover := .MainPkgCover.Vars}}
|
||||
clearFileCover({{$cover.Var}}.Count[:])
|
||||
{{end}}
|
||||
|
||||
{{range $k, $pkgCover := .CacheCover}}
|
||||
{{range $v, $cover := $pkgCover.Vars}}
|
||||
clearFileCover({{$pkgCover.Package.Name}}.{{$v}}.Count[:])
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
}
|
||||
|
||||
func clearFileCover(counter []uint32) {
|
||||
for i := range counter {
|
||||
counter[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func registerHandlers() {
|
||||
ln, host, err := listen()
|
||||
if err != nil {
|
||||
@ -127,13 +153,11 @@ func registerHandlers() {
|
||||
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 {
|
||||
@ -152,9 +176,8 @@ func registerHandlers() {
|
||||
|
||||
// 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")
|
||||
fmt.Fprint(w, "mode: {{.Mode }} \n")
|
||||
counters, blocks := loadValues()
|
||||
|
||||
var active, total int64
|
||||
var count uint32
|
||||
for name, counts := range counters {
|
||||
@ -180,7 +203,9 @@ func registerHandlers() {
|
||||
})
|
||||
|
||||
mux.HandleFunc("/v1/cover/clear", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "TO BE IMPLEMENTED!\n")
|
||||
clearValues()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintln(w,"clear call successfully")
|
||||
})
|
||||
|
||||
log.Fatal(http.Serve(ln, mux))
|
||||
|
@ -142,16 +142,17 @@ func profile(c *gin.Context) {
|
||||
}
|
||||
|
||||
func clear(c *gin.Context) {
|
||||
if err := LocalStore.Init(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
svrsUnderTest := LocalStore.GetAll()
|
||||
for svc, addrs := range svrsUnderTest {
|
||||
for _, addr := range addrs {
|
||||
pp, err := Client.Clear(addr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusExpectationFailed, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(c.Writer, "Register service %s: %s coverage counter %s", svc, addr, string(pp))
|
||||
}
|
||||
}
|
||||
|
||||
if err := Client.Clear(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, "TO BE IMPLEMENTED")
|
||||
}
|
||||
|
||||
func initSystem(c *gin.Context) {
|
||||
|
Loading…
Reference in New Issue
Block a user