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",
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(&center, "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)
}

View File

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

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

View File

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

View File

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

View File

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