diff --git a/cmd/clear.go b/cmd/clear.go new file mode 100644 index 0000000..a266b61 --- /dev/null +++ b/cmd/clear.go @@ -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) +} diff --git a/cmd/cover.go b/cmd/cover.go index 23487ba..7381eff 100644 --- a/cmd/cover.go +++ b/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) } diff --git a/pkg/cover/client.go b/pkg/cover/client.go index 6fde7bf..a1415e4 100644 --- a/pkg/cover/client.go +++ b/pkg/cover/client.go @@ -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) diff --git a/pkg/cover/cover.go b/pkg/cover/cover.go index 53eb044..4d2b429 100644 --- a/pkg/cover/cover.go +++ b/pkg/cover/cover.go @@ -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 { diff --git a/pkg/cover/cover_test.go b/pkg/cover/cover_test.go index 55ed0d7..4cbb06d 100644 --- a/pkg/cover/cover_test.go +++ b/pkg/cover/cover_test.go @@ -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) + } + }) + } + +} diff --git a/pkg/cover/instrument.go b/pkg/cover/instrument.go index 3abae22..6932b94 100644 --- a/pkg/cover/instrument.go +++ b/pkg/cover/instrument.go @@ -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)) diff --git a/pkg/cover/server.go b/pkg/cover/server.go index e6472eb..debe0e1 100644 --- a/pkg/cover/server.go +++ b/pkg/cover/server.go @@ -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) {