Merge pull request #23 from memoryliu/mode

Add coverage clear feature
This commit is contained in:
qiniu-bot 2020-05-27 19:42:50 +08:00 committed by GitHub
commit 7f855deb68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 245 additions and 32 deletions

49
cmd/clear.go Normal file
View 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(&center, "center", "", "http://127.0.0.1:7777", "cover profile host center")
rootCmd.AddCommand(clearCmd)
}

View File

@ -30,6 +30,13 @@ var coverCmd = &cobra.Command{
Use: "cover", Use: "cover",
Short: "do cover for the target source ", Short: "do cover for the target source ",
Run: func(cmd *cobra.Command, args []string) { 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, "", "") doCover(cmd, args, "", "")
}, },
} }
@ -37,11 +44,13 @@ var coverCmd = &cobra.Command{
var ( var (
target string target string
center string center string
mode string
) )
func init() { func init() {
coverCmd.Flags().StringVarP(&center, "center", "", "http://127.0.0.1:7777", "cover profile host center") coverCmd.Flags().StringVarP(&center, "center", "", "http://127.0.0.1:7777", "cover profile host center")
coverCmd.Flags().StringVarP(&target, "target", "", ".", "target folder to cover") coverCmd.Flags().StringVarP(&target, "target", "", ".", "target folder to cover")
coverCmd.Flags().StringVarP(&mode, "mode", "", "count", "coverage mode: set, count, atomic")
rootCmd.AddCommand(coverCmd) rootCmd.AddCommand(coverCmd)
log.SetFlags(log.LstdFlags | log.Lshortfile) 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" { if pkg.Name == "main" {
log.Printf("handle package: %v", pkg.ImportPath) log.Printf("handle package: %v", pkg.ImportPath)
// inject the main package // inject the main package
mainCover, err := cover.AddCounters(pkg, newgopath) mainCover, err := cover.AddCounters(pkg, mode, newgopath)
if err != nil { if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", pkg.ImportPath, err) log.Fatalf("failed to add counters for pkg %s, err: %v", pkg.ImportPath, err)
} }
// new a testcover for this service // new a testcover for this service
tc := cover.TestCover{ tc := cover.TestCover{
Mode: "atomic", Mode: mode,
Center: center, Center: center,
MainPkgCover: mainCover, MainPkgCover: mainCover,
} }
@ -103,7 +112,7 @@ func doCover(cmd *cobra.Command, args []string, newgopath string, newtarget stri
} }
// add counter for internal package // add counter for internal package
inPkgCover, err := cover.AddCounters(depPkg, newgopath) inPkgCover, err := cover.AddCounters(depPkg, mode, newgopath)
if err != nil { if err != nil {
log.Fatalf("failed to add counters for internal pkg %s, err: %v", depPkg.ImportPath, err) 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 continue
} }
packageCover, err := cover.AddCounters(depPkg, newgopath) packageCover, err := cover.AddCounters(depPkg, mode, newgopath)
if err != nil { if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err) log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
} }

View File

@ -27,7 +27,7 @@ import (
// Action provides methods to contact with the coverd service under test // Action provides methods to contact with the coverd service under test
type Action interface { type Action interface {
Profile(host string) ([]byte, error) Profile(host string) ([]byte, error)
Clear() error Clear(host string) ([]byte, error)
InitSystem(host string) ([]byte, error) InitSystem(host string) ([]byte, error)
} }
@ -61,7 +61,14 @@ func (w *client) Profile(host string) ([]byte, error) {
return profile, err 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) { func (w *client) InitSystem(host string) ([]byte, error) {
u := fmt.Sprintf("%s%s", host, CoverInitSystemAPI) u := fmt.Sprintf("%s%s", host, CoverInitSystemAPI)

View File

@ -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 // 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) 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 { for file, coverVar := range coverVarMap {
var newArgs = args cmd := buildCoverCmd(file, coverVar, pkg, mode, newgopath)
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() out, err := cmd.CombinedOutput()
if err != nil { 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 }, 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 // declareCoverVars attaches the required cover variables names
// to the files, to be used when annotating the files. // to the files, to be used when annotating the files.
func declareCoverVars(p *Package) map[string]*FileVar { func declareCoverVars(p *Package) map[string]*FileVar {

View File

@ -17,6 +17,12 @@
package cover package cover
import ( import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
@ -95,3 +101,114 @@ func TestCovList(t *testing.T) {
assert.Equal(t, "100.0%", covF.Percentage()) assert.Equal(t, "100.0%", covF.Percentage())
assert.Equal(t, "0.0%", covF1.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)
}
})
}
}

View File

@ -116,6 +116,32 @@ func loadFileCover(coverCounters map[string][]uint32, coverBlocks map[string][]t
coverBlocks[fileName] = block 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() { func registerHandlers() {
ln, host, err := listen() ln, host, err := listen()
if err != nil { if err != nil {
@ -127,13 +153,11 @@ func registerHandlers() {
log.Fatalf("register address %v failed, err: %v, response: %v", profileAddr, err, string(resp)) log.Fatalf("register address %v failed, err: %v, response: %v", profileAddr, err, string(resp))
} }
go genProfileAddr(host) go genProfileAddr(host)
mux := http.NewServeMux() mux := http.NewServeMux()
// Coverage reports the current code coverage as a fraction in the range [0, 1]. // Coverage reports the current code coverage as a fraction in the range [0, 1].
// If coverage is not enabled, Coverage returns 0. // If coverage is not enabled, Coverage returns 0.
mux.HandleFunc("/v1/cover/coverage", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/v1/cover/coverage", func(w http.ResponseWriter, r *http.Request) {
counters, _ := loadValues() counters, _ := loadValues()
var n, d int64 var n, d int64
for _, counter := range counters { for _, counter := range counters {
for i := range counter { for i := range counter {
@ -152,9 +176,8 @@ func registerHandlers() {
// coverprofile reports a coverage profile with the coverage percentage // coverprofile reports a coverage profile with the coverage percentage
mux.HandleFunc("/v1/cover/profile", func(w http.ResponseWriter, r *http.Request) { 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() counters, blocks := loadValues()
var active, total int64 var active, total int64
var count uint32 var count uint32
for name, counts := range counters { for name, counts := range counters {
@ -180,7 +203,9 @@ func registerHandlers() {
}) })
mux.HandleFunc("/v1/cover/clear", func(w http.ResponseWriter, r *http.Request) { 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)) log.Fatal(http.Serve(ln, mux))

View File

@ -142,16 +142,17 @@ func profile(c *gin.Context) {
} }
func clear(c *gin.Context) { func clear(c *gin.Context) {
if err := LocalStore.Init(); err != nil { svrsUnderTest := LocalStore.GetAll()
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) for svc, addrs := range svrsUnderTest {
return 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) { func initSystem(c *gin.Context) {