1 package cmd
2
3 import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "io"
8 "io/ioutil"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "strings"
13 "sync"
14
15 "github.com/ory/viper"
16 "github.com/pborman/uuid"
17 "github.com/spf13/cobra"
18 )
19
20 func check(err error) {
21 if err != nil {
22 _, _ = fmt.Fprintf(os.Stderr, "%v", err)
23 os.Exit(1)
24 }
25 }
26
27
28 var RootCmd = &cobra.Command{
29 Use: "go-acc <flags> <packages...>",
30 Short: "Receive accurate code coverage reports for Golang (Go)",
31 Args: cobra.MinimumNArgs(1),
32 Example: `$ go-acc github.com/some/package
33 $ go-acc -o my-coverfile.txt github.com/some/package
34 $ go-acc ./...
35 $ go-acc $(glide novendor)
36 $ go-acc --ignore pkga,pkgb .
37
38 You can pass all flags defined by "go test" after "--":
39 $ go-acc . -- -short -v -failfast
40
41 You can pick an alternative go test binary using:
42
43 GO_TEST_BINARY="go test"
44 GO_TEST_BINARY="gotest"
45 `,
46 RunE: func(cmd *cobra.Command, args []string) error {
47 mode, err := cmd.Flags().GetString("covermode")
48 if err != nil {
49 return err
50 }
51
52 if verbose, err := cmd.Flags().GetBool("verbose"); err != nil {
53 return err
54 } else if verbose {
55 fmt.Println("Flag -v has been deprecated, use `go-acc -- -v` instead!")
56 }
57
58 ignores, err := cmd.Flags().GetStringSlice("ignore")
59 if err != nil {
60 return err
61 }
62
63 payload := "mode: " + mode + "\n"
64
65 var packages []string
66 var passthrough []string
67 for _, a := range args {
68 if len(a) == 0 {
69 continue
70 }
71
72
73 if a[0] == '-' || len(passthrough) > 0 {
74 passthrough = append(passthrough, a)
75 continue
76 }
77
78 if len(a) > 4 && a[len(a)-4:] == "/..." {
79 var buf bytes.Buffer
80 c := exec.Command("go", "list", a)
81 c.Stdout = &buf
82 c.Stderr = &buf
83 if err := c.Run(); err != nil {
84 check(fmt.Errorf("unable to run go list: %w", err))
85 }
86
87 var add []string
88 for _, s := range strings.Split(buf.String(), "\n") {
89
90
91
92
93 if len(s) > 0 && !strings.HasPrefix(s, "go: ") {
94
95 ignore := false
96 for _, ignoreStr := range ignores {
97 if strings.Contains(s, ignoreStr) {
98 ignore = true
99 break
100 }
101 }
102
103 if !ignore {
104 add = append(add, s)
105 }
106 }
107 }
108
109 packages = append(packages, add...)
110 } else {
111 packages = append(packages, a)
112 }
113 }
114
115 files := make([]string, len(packages))
116 for k, pkg := range packages {
117 files[k] = filepath.Join(os.TempDir(), uuid.New()) + ".cc.tmp"
118
119 gotest := os.Getenv("GO_TEST_BINARY")
120 if gotest == "" {
121 gotest = "go test"
122 }
123
124 gt := strings.Split(gotest, " ")
125 if len(gt) != 2 {
126 gt = append(gt, "")
127 }
128
129 var c *exec.Cmd
130 ca := append(append(
131 []string{
132 gt[1],
133 "-covermode=" + mode,
134 "-coverprofile=" + files[k],
135 "-coverpkg=" + strings.Join(packages, ","),
136 },
137 passthrough...),
138 pkg)
139 c = exec.Command(gt[0], ca...)
140
141 stderr, err := c.StderrPipe()
142 check(err)
143
144 stdout, err := c.StdoutPipe()
145 check(err)
146
147 check(c.Start())
148
149 var wg sync.WaitGroup
150 wg.Add(2)
151 go scan(&wg, stderr)
152 go scan(&wg, stdout)
153
154 check(c.Wait())
155
156 wg.Wait()
157 }
158
159 for _, file := range files {
160 if _, err := os.Stat(file); os.IsNotExist(err) {
161 continue
162 }
163
164 p, err := ioutil.ReadFile(file)
165 check(err)
166
167 ps := strings.Split(string(p), "\n")
168 payload += strings.Join(ps[1:], "\n")
169 }
170
171 output, err := cmd.Flags().GetString("output")
172 check(err)
173
174 check(ioutil.WriteFile(output, []byte(payload), 0644))
175 return nil
176 },
177 }
178
179 func scan(wg *sync.WaitGroup, r io.ReadCloser) {
180 defer wg.Done()
181 scanner := bufio.NewScanner(r)
182 for scanner.Scan() {
183 line := scanner.Text()
184 if strings.Contains(line, "warning: no packages being tested depend on matches for pattern") {
185 continue
186 }
187 fmt.Println(strings.Split(line, "% of statements in")[0])
188 }
189 }
190
191
192
193 func Execute() {
194 if err := RootCmd.Execute(); err != nil {
195 fmt.Println(err)
196 os.Exit(-1)
197 }
198 }
199
200 func init() {
201 cobra.OnInitialize(initConfig)
202 RootCmd.Flags().BoolP("verbose", "v", false, "Does nothing, there for compatibility")
203 RootCmd.Flags().StringP("output", "o", "coverage.txt", "Location for the output file")
204 RootCmd.Flags().String("covermode", "atomic", "Which code coverage mode to use")
205 RootCmd.Flags().StringSlice("ignore", []string{}, "Will ignore packages that contains any of these strings")
206 }
207
208
209 func initConfig() {
210 viper.AutomaticEnv()
211
212 }
213
214 type filter struct {
215 dtl io.Writer
216 }
217
218 func (f *filter) Write(p []byte) (n int, err error) {
219 for _, ppp := range strings.Split(string(p), "\n") {
220 if strings.Contains(ppp, "warning: no packages being tested depend on matches for pattern") {
221 continue
222 } else {
223 nn, err := f.dtl.Write(p)
224 n = n + nn
225 if err != nil {
226 return n, err
227 }
228 }
229 }
230 return len(p), nil
231 }
232
View as plain text