...

Source file src/github.com/ory/go-acc/cmd/root.go

Documentation: github.com/ory/go-acc/cmd

     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  // RootCmd represents the base command when called without any subcommands
    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  			// The first tag indicates that we're now passing through all tags
    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  					// Remove go system messages, e.g. messages from go mod	like
    90  					//   go: finding ...
    91  					//   go: downloading ...
    92  					//   go: extracting ...
    93  					if len(s) > 0 && !strings.HasPrefix(s, "go: ") {
    94  						// Test if package name contains ignore string
    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  // Execute adds all child commands to the root command sets flags appropriately.
   192  // This is called by main.main(). It only needs to happen once to the rootCmd.
   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  // initConfig reads in config file and ENV variables if set.
   209  func initConfig() {
   210  	viper.AutomaticEnv() // read in environment variables that match
   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