Repository: qiniu/goc
Pull request #23 (2020-05-26 12:42:01)

Add coverage clear feature
Add cover mode reference go cover

qiniu-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/qiniu/goc/pull/23#" title="Author self-approved">memoryliu</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=qiniu%2Fgoc).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/qiniu/goc/blob/master/OWNERS)~~ [memoryliu]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/qiniu/goc/pull/23?src=pr&el=h1) Report
> Merging [#23](https://codecov.io/gh/qiniu/goc/pull/23?src=pr&el=desc) into [master](https://codecov.io/gh/qiniu/goc/commit/a13481ecd1beb45b2ddd28fb21c470f766defe02&el=desc) will **decrease** coverage by `0.43%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/qiniu/goc/pull/23/graphs/tree.svg?width=650&height=150&src=pr&token=7AHPDDLXQ3)](https://codecov.io/gh/qiniu/goc/pull/23?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #23      +/-   ##
==========================================
- Coverage   33.01%   32.58%   -0.44%     
==========================================
  Files          10       10              
  Lines         527      534       +7     
==========================================
  Hits          174      174              
- Misses        332      339       +7     
  Partials       21       21              
```


| [Impacted Files](https://codecov.io/gh/qiniu/goc/pull/23?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/cover/client.go](https://codecov.io/gh/qiniu/goc/pull/23/diff?src=pr&el=tree#diff-cGtnL2NvdmVyL2NsaWVudC5nbw==) | `0.00% <0.00%> (ø)` | |
| [pkg/cover/cover.go](https://codecov.io/gh/qiniu/goc/pull/23/diff?src=pr&el=tree#diff-cGtnL2NvdmVyL2NvdmVyLmdv) | `40.13% <0.00%> (-0.27%)` | :arrow_down: |
| [pkg/cover/instrument.go](https://codecov.io/gh/qiniu/goc/pull/23/diff?src=pr&el=tree#diff-cGtnL2NvdmVyL2luc3RydW1lbnQuZ28=) | `0.00% <ø> (ø)` | |
| [pkg/cover/server.go](https://codecov.io/gh/qiniu/goc/pull/23/diff?src=pr&el=tree#diff-cGtnL2NvdmVyL3NlcnZlci5nbw==) | `0.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/qiniu/goc/pull/23?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/qiniu/goc/pull/23?src=pr&el=footer). Last update [8631044...7567ae2](https://codecov.io/gh/qiniu/goc/pull/23?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
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(&center, "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(&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)
 					}
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/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
-	}
-
-	if err := Client.Clear(); 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))
+		}
 	}
-	c.JSON(http.StatusOK, "TO BE IMPLEMENTED")
 }
 
 func initSystem(c *gin.Context) {


Test patch:
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)
+			}
+		})
+	}
+
+}