Repository: qiniu/goc
Pull request #42 (2020-06-18 08:22:08)

Enhance error handling
1. enhance error handling
2. remove `--packages` flags, replace with  args

```
goc install --packages="./..."
-->
goc install ./...
```



Patch:
diff --git a/cmd/build.go b/cmd/build.go
index 9bc6869..3a835c6 100644
--- a/cmd/build.go
+++ b/cmd/build.go
@@ -17,7 +17,9 @@
 package cmd
 
 import (
-	"log"
+	"os"
+
+	log "github.com/sirupsen/logrus"
 
 	"github.com/qiniu/goc/pkg/build"
 	"github.com/qiniu/goc/pkg/cover"
@@ -28,7 +30,7 @@ var buildCmd = &cobra.Command{
 	Use:   "build",
 	Short: "Do cover for all go files and execute go build command",
 	Long: `
-First of all, this build command will copy the project code and its necessary dependencies to a temporary directory, then do cover for the target in this temporary directory, finally go build command will be executed and binaries generated to their original place.
+Build command will copy the project code and its necessary dependencies to a temporary directory, then do cover for the target, binaries will be generated to their original place.
 `,
 	Example: `
 # Build the current binary with cover variables injected. The binary will be generated in the current folder.
@@ -44,7 +46,11 @@ goc build --output /to/this/path
 goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
 `,
 	Run: func(cmd *cobra.Command, args []string) {
-		runBuild()
+		wd, err := os.Getwd()
+		if err != nil {
+			log.Fatalf("Fail to build: %v", err)
+		}
+		runBuild(args, wd)
 	},
 }
 
@@ -52,14 +58,14 @@ var buildOutput string
 
 func init() {
 	addBuildFlags(buildCmd.Flags())
-	buildCmd.Flags().StringVar(&buildOutput, "output", "", "it forces build to write the resulting executable or object to the named output file or directory")
+	buildCmd.Flags().StringVar(&buildOutput, "output", "", "it forces build to write the resulting executable to the named output file")
 	rootCmd.AddCommand(buildCmd)
 }
 
-func runBuild() {
-	gocBuild, err := build.NewBuild(buildFlags, packages, buildOutput)
+func runBuild(args []string, wd string) {
+	gocBuild, err := build.NewBuild(buildFlags, args, wd, buildOutput)
 	if err != nil {
-		log.Fatalf("Fail to NewBuild: %v", err)
+		log.Fatalf("Fail to build: %v", err)
 	}
 	// remove temporary directory if needed
 	defer gocBuild.Clean()
diff --git a/cmd/commonflags.go b/cmd/commonflags.go
index f18f622..65a6bce 100644
--- a/cmd/commonflags.go
+++ b/cmd/commonflags.go
@@ -11,8 +11,8 @@ var (
 	mode       string
 	debugGoc   bool
 	buildFlags string
-	packages   string
-	appArgs    string
+	// packages   string
+	appArgs string
 
 	goRunExecFlag  string
 	goRunArguments string
@@ -35,14 +35,12 @@ func addCommonFlags(cmdset *pflag.FlagSet) {
 
 func addBuildFlags(cmdset *pflag.FlagSet) {
 	addCommonFlags(cmdset)
-	cmdset.StringVar(&packages, "packages", ".", "specify the package name, only . and ./... are supported")
 	// bind to viper
 	viper.BindPFlags(cmdset)
 }
 
 func addRunFlags(cmdset *pflag.FlagSet) {
 	addBuildFlags(cmdset)
-	cmdset.Lookup("packages").Usage = "specify the package name, only ., ./... and *.go are supported"
 	cmdset.StringVar(&appArgs, "appargs", "", "specify the application's arguments")
 	cmdset.StringVar(&goRunExecFlag, "exec", "", "same as -exec flag in 'go run' command")
 	cmdset.StringVar(&goRunArguments, "arguments", "", "same as 'arguments' in 'go run' command")
diff --git a/cmd/install.go b/cmd/install.go
index 028230f..e173715 100644
--- a/cmd/install.go
+++ b/cmd/install.go
@@ -17,6 +17,8 @@
 package cmd
 
 import (
+	"os"
+
 	"github.com/qiniu/goc/pkg/build"
 	"github.com/qiniu/goc/pkg/cover"
 	log "github.com/sirupsen/logrus"
@@ -27,7 +29,7 @@ var installCmd = &cobra.Command{
 	Use:   "install",
 	Short: "Do cover for all go files and execute go install command",
 	Long: `
-First of all, this install command will copy the project code and its necessary dependencies to a temporary directory, then do cover for the target in this temporary directory, finally go install command will be executed and binaries generated to their original place.
+Install command will copy the project code and its necessary dependencies to a temporary directory, then do cover for the target, binaries will be generated to their original place.
 `,
 	Example: `
 # Install all binaries with cover variables injected. The binary will be installed in $GOPATH/bin or $HOME/go/bin if directory existed.
@@ -40,7 +42,11 @@ goc install --center=http://127.0.0.1:7777
 goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
 `,
 	Run: func(cmd *cobra.Command, args []string) {
-		runInstall()
+		wd, err := os.Getwd()
+		if err != nil {
+			log.Fatalf("Fail to build: %v", err)
+		}
+		runInstall(args, wd)
 	},
 }
 
@@ -49,10 +55,10 @@ func init() {
 	rootCmd.AddCommand(installCmd)
 }
 
-func runInstall() {
-	gocBuild, err := build.NewInstall(buildFlags, packages)
+func runInstall(args []string, wd string) {
+	gocBuild, err := build.NewInstall(buildFlags, args, wd)
 	if err != nil {
-		log.Fatalf("Fail to NewInstall: %v", err)
+		log.Fatalf("Fail to install: %v", err)
 	}
 	// remove temporary directory if needed
 	defer gocBuild.Clean()
diff --git a/cmd/list.go b/cmd/list.go
index 194d486..5698596 100644
--- a/cmd/list.go
+++ b/cmd/list.go
@@ -42,6 +42,6 @@ goc list [flags]
 }
 
 func init() {
-	listCmd.Flags().StringVarP(&center, "center", "", "http://127.0.0.1:7777", "cover profile host center")
+	addBasicFlags(listCmd.Flags())
 	rootCmd.AddCommand(listCmd)
 }
diff --git a/cmd/root.go b/cmd/root.go
index b0d50d1..3263c43 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -17,7 +17,6 @@
 package cmd
 
 import (
-	"io/ioutil"
 	"path/filepath"
 	"runtime"
 	"strconv"
@@ -37,6 +36,7 @@ Find more information at:
 `,
 	PersistentPreRun: func(cmd *cobra.Command, args []string) {
 		log.SetReportCaller(true)
+		log.SetLevel(log.InfoLevel)
 		log.SetFormatter(&log.TextFormatter{
 			FullTimestamp: true,
 			CallerPrettyfier: func(f *runtime.Frame) (string, string) {
@@ -49,7 +49,13 @@ Find more information at:
 		})
 		if debugGoc == false {
 			// we only need log in debug mode
-			log.SetOutput(ioutil.Discard)
+			log.SetLevel(log.FatalLevel)
+			log.SetFormatter(&log.TextFormatter{
+				DisableTimestamp: true,
+				CallerPrettyfier: func(f *runtime.Frame) (string, string) {
+					return "", ""
+				},
+			})
 		}
 	},
 }
diff --git a/cmd/run.go b/cmd/run.go
index db65cd3..1dc28ae 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -20,6 +20,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net"
+	"os"
 
 	"github.com/qiniu/goc/pkg/build"
 	"github.com/qiniu/goc/pkg/cover"
@@ -36,7 +37,14 @@ It is exactly behave as 'go run .' in addition of some internal goc features.`,
 goc run .
 `,
 	Run: func(cmd *cobra.Command, args []string) {
-		gocBuild, _ := build.NewBuild(buildFlags, packages, buildOutput)
+		wd, err := os.Getwd()
+		if err != nil {
+			log.Fatalf("Fail to build: %v", err)
+		}
+		gocBuild, err := build.NewBuild(buildFlags, args, wd, buildOutput)
+		if err != nil {
+			log.Fatalf("Fail to run: %v", err)
+		}
 		gocBuild.GoRunExecFlag = goRunExecFlag
 		gocBuild.GoRunArguments = goRunArguments
 		defer gocBuild.Clean()
@@ -53,7 +61,9 @@ goc run .
 		// execute covers for the target source with original buildFlags and new GOPATH( tmp:original )
 		cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, gocServer)
 
-		gocBuild.Run()
+		if err := gocBuild.Run(); err != nil {
+			log.Fatalf("Fail to run: %v", err)
+		}
 	},
 }
 
diff --git a/pkg/build/build.go b/pkg/build/build.go
index d8e2319..53b8ce4 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -32,6 +32,7 @@ type Build struct {
 	Pkgs          map[string]*cover.Package // Pkg list parsed from "go list -json ./..." command
 	NewGOPATH     string                    // the new GOPATH
 	OriGOPATH     string                    // the original GOPATH
+	WorkingDir    string                    // the working directory
 	TmpDir        string                    // the temporary directory to build the project
 	TmpWorkingDir string                    // the working directory in the temporary directory, which is corresponding to the current directory in the project directory
 	IsMod         bool                      // determine whether it is a Mod project
@@ -53,17 +54,23 @@ type Build struct {
 
 // NewBuild creates a Build struct which can build from goc temporary directory,
 // and generate binary in current working directory
-func NewBuild(buildflags string, packages string, outputDir string) (*Build, error) {
+func NewBuild(buildflags string, args []string, workingDir string, outputDir string) (*Build, error) {
+	if err := checkParameters(args, workingDir); err != nil {
+		return nil, err
+	}
 	// buildflags = buildflags + " -o " + outputDir
 	b := &Build{
 		BuildFlags: buildflags,
-		Packages:   packages,
+		Packages:   strings.Join(args, " "),
+		WorkingDir: workingDir,
 	}
 	if false == b.validatePackageForBuild() {
 		log.Errorln(ErrWrongPackageTypeForBuild)
 		return nil, ErrWrongPackageTypeForBuild
 	}
-	b.MvProjectsToTmp()
+	if err := b.MvProjectsToTmp(); err != nil {
+		return nil, err
+	}
 	dir, err := b.determineOutputDir(outputDir)
 	b.Target = dir
 	if err != nil {
@@ -90,13 +97,13 @@ func (b *Build) Build() error {
 	err := cmd.Start()
 	if err != nil {
 		log.Errorf("Fail to execute: %v. The error is: %v", cmd.Args, err)
-		return fmt.Errorf("fail to execute: %v: %w", cmd.Args, err)
+		return fmt.Errorf("fail to execute: %v, err: %w", cmd.Args, err)
 	}
 	if err = cmd.Wait(); err != nil {
 		log.Errorf("go build failed. The error is: %v", err)
-		return fmt.Errorf("go build faileds: %w", err)
+		return fmt.Errorf("fail to execute: %v, err: %w", cmd.Args, err)
 	}
-	log.Println("Go build exit successful.")
+	log.Infoln("Go build exit successful.")
 	return nil
 }
 
@@ -107,57 +114,42 @@ func (b *Build) determineOutputDir(outputDir string) (string, error) {
 		log.Errorf("Can only be called after Build.MvProjectsToTmp(): %v", ErrWrongCallSequence)
 		return "", fmt.Errorf("can only be called after Build.MvProjectsToTmp(): %w", ErrWrongCallSequence)
 	}
-	curWorkingDir, err := os.Getwd()
-	if err != nil {
-		log.Errorf("Cannot get current working directory: %v", err)
-		return "", fmt.Errorf("cannot get current working directory: %w", err)
-	}
 
 	if outputDir == "" {
-		_, last := filepath.Split(curWorkingDir)
+		_, last := filepath.Split(b.WorkingDir)
 		if b.IsMod {
 			// in mod, special rule
 			// replace "_" with "-" in the import path
 			last = strings.ReplaceAll(last, "_", "-")
 		}
-		return filepath.Join(curWorkingDir, last), nil
+		return filepath.Join(b.WorkingDir, last), nil
 	}
 	abs, err := filepath.Abs(outputDir)
 	if err != nil {
 		log.Errorf("Fail to transform the path: %v to absolute path: %v", outputDir, err)
-		return "", fmt.Errorf("fail to transform the path %v to absolute path: %w", outputDir, err)
+		return "", err
 	}
 	return abs, nil
 }
 
 // validatePackageForBuild only allow . as package name
 func (b *Build) validatePackageForBuild() bool {
-	if b.Packages == "." {
+	if b.Packages == "." || b.Packages == "" {
 		return true
 	}
 	return false
 }
 
-// Run excutes the main package in addition with the internal goc features
-func (b *Build) Run() {
-	cmd := exec.Command("/bin/bash", "-c", "go run "+b.BuildFlags+" "+b.GoRunExecFlag+" "+b.Packages+" "+b.GoRunArguments)
-	cmd.Dir = b.TmpWorkingDir
-
-	if b.NewGOPATH != "" {
-		// Change to temp GOPATH for go install command
-		cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", b.NewGOPATH))
-	}
-
-	log.Printf("go build cmd is: %v", cmd.Args)
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	err := cmd.Start()
-	if err != nil {
-		log.Fatalf("Fail to start command: %v. The error is: %v", cmd.Args, err)
+func checkParameters(args []string, workingDir string) error {
+	if len(args) > 1 {
+		log.Errorln(ErrTooManyArgs)
+		return ErrTooManyArgs
 	}
 
-	if err = cmd.Wait(); err != nil {
-		log.Fatalf("Fail to execute command: %v. The error is: %v", cmd.Args, err)
+	if workingDir == "" {
+		return ErrInvalidWorkingDir
 	}
 
+	log.Infof("Working directory: %v", workingDir)
+	return nil
 }
diff --git a/pkg/build/errors.go b/pkg/build/errors.go
index c46cec7..6359434 100644
--- a/pkg/build/errors.go
+++ b/pkg/build/errors.go
@@ -9,6 +9,8 @@ var (
 	ErrGocShouldExecInProject     = errors.New("goc not executed in project directory")
 	ErrWrongPackageTypeForInstall = errors.New("packages only support \".\" and \"./...\"")
 	ErrWrongPackageTypeForBuild   = errors.New("packages only support \".\"")
+	ErrTooManyArgs                = errors.New("too many args")
+	ErrInvalidWorkingDir          = errors.New("the working directory is invalid")
 	ErrWrongCallSequence          = errors.New("function should be called in a specified sequence")
-	ErrNoplaceToInstall           = errors.New("no go env")
+	ErrNoplaceToInstall           = errors.New("dont know where to install")
 )
diff --git a/pkg/build/install.go b/pkg/build/install.go
index 5d20687..8546e46 100644
--- a/pkg/build/install.go
+++ b/pkg/build/install.go
@@ -20,21 +20,28 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"strings"
 
 	log "github.com/sirupsen/logrus"
 )
 
 // NewInstall creates a Build struct which can install from goc temporary directory
-func NewInstall(buildflags string, packages string) (*Build, error) {
+func NewInstall(buildflags string, args []string, workingDir string) (*Build, error) {
+	if err := checkParameters(args, workingDir); err != nil {
+		return nil, err
+	}
 	b := &Build{
 		BuildFlags: buildflags,
-		Packages:   packages,
+		Packages:   strings.Join(args, " "),
+		WorkingDir: workingDir,
 	}
 	if false == b.validatePackageForInstall() {
 		log.Errorln(ErrWrongPackageTypeForInstall)
 		return nil, ErrWrongPackageTypeForInstall
 	}
-	b.MvProjectsToTmp()
+	if err := b.MvProjectsToTmp(); err != nil {
+		return nil, err
+	}
 	return b, nil
 }
 
@@ -61,18 +68,18 @@ func (b *Build) Install() error {
 	err = cmd.Start()
 	if err != nil {
 		log.Errorf("Fail to execute: %v. The error is: %v", cmd.Args, err)
-		return fmt.Errorf("fail to execute: %v: %w", cmd.Args, err)
+		return err
 	}
 	if err = cmd.Wait(); err != nil {
 		log.Errorf("go install failed. The error is: %v", err)
-		return fmt.Errorf("go install failed: %w", err)
+		return err
 	}
 	log.Infof("Go install successful. Binary installed in: %v", whereToInstall)
 	return nil
 }
 
 func (b *Build) validatePackageForInstall() bool {
-	if b.Packages == "." || b.Packages == "./..." {
+	if b.Packages == "." || b.Packages == "" || b.Packages == "./..." {
 		return true
 	}
 	return false
diff --git a/pkg/build/legacy.go b/pkg/build/legacy.go
index 6627544..72f5c55 100644
--- a/pkg/build/legacy.go
+++ b/pkg/build/legacy.go
@@ -50,13 +50,6 @@ func (b *Build) cpLegacyProject() {
 // only cp dependency in root(current gopath),
 // skip deps in other GOPATHs
 func (b *Build) cpDepPackages(pkg *cover.Package, visited map[string]bool) {
-	/*
-		oriGOPATH := os.Getenv("GOPATH")
-		if oriGOPATH == "" {
-			oriGOPATH = filepath.Join(os.Getenv("HOME"), "go")
-		}
-		gopaths := strings.Split(oriGOPATH, ":")
-	*/
 	gopath := pkg.Root
 	for _, dep := range pkg.Deps {
 		src := filepath.Join(gopath, "src", dep)
diff --git a/pkg/build/run.go b/pkg/build/run.go
new file mode 100644
index 0000000..43ca1a4
--- /dev/null
+++ b/pkg/build/run.go
@@ -0,0 +1,52 @@
+/*
+ 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 build
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+
+	log "github.com/sirupsen/logrus"
+)
+
+// Run excutes the main package in addition with the internal goc features
+func (b *Build) Run() error {
+	cmd := exec.Command("/bin/bash", "-c", "go run "+b.BuildFlags+" "+b.GoRunExecFlag+" "+b.Packages+" "+b.GoRunArguments)
+	cmd.Dir = b.TmpWorkingDir
+
+	if b.NewGOPATH != "" {
+		// Change to temp GOPATH for go install command
+		cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", b.NewGOPATH))
+	}
+
+	log.Infof("go build cmd is: %v", cmd.Args)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	err := cmd.Start()
+	if err != nil {
+		log.Errorf("Fail to start command: %v. The error is: %v", cmd.Args, err)
+		return fmt.Errorf("fail to execute: %v, err: %w", cmd.Args, err)
+	}
+
+	if err = cmd.Wait(); err != nil {
+		log.Errorf("Fail to go run: %v. The error is: %v", cmd.Args, err)
+		return fmt.Errorf("fail to execute: %v, err: %w", cmd.Args, err)
+	}
+
+	return nil
+}
diff --git a/pkg/build/tmpfolder.go b/pkg/build/tmpfolder.go
index 166035e..5998ebd 100644
--- a/pkg/build/tmpfolder.go
+++ b/pkg/build/tmpfolder.go
@@ -29,15 +29,24 @@ import (
 	"github.com/spf13/viper"
 )
 
-func (b *Build) MvProjectsToTmp() {
+func (b *Build) MvProjectsToTmp() error {
 	listArgs := []string{"-json"}
 	if len(b.BuildFlags) != 0 {
 		listArgs = append(listArgs, b.BuildFlags)
 	}
 	listArgs = append(listArgs, "./...")
-	b.Pkgs = cover.ListPackages(".", strings.Join(listArgs, " "), "")
+	var err error
+	b.Pkgs, err = cover.ListPackages(b.WorkingDir, strings.Join(listArgs, " "), "")
+	if err != nil {
+		log.Errorln(err)
+		return err
+	}
 
-	b.mvProjectsToTmp()
+	err = b.mvProjectsToTmp()
+	if err != nil {
+		log.Errorf("Fail to move the project to temporary directory")
+		return err
+	}
 	b.OriGOPATH = os.Getenv("GOPATH")
 	if b.IsMod == true {
 		b.NewGOPATH = ""
@@ -53,22 +62,17 @@ func (b *Build) MvProjectsToTmp() {
 	if b.Root == "" && b.IsMod == false {
 		b.NewGOPATH = b.OriGOPATH
 	}
-	log.Printf("New GOPATH: %v", b.NewGOPATH)
-	return
+	log.Infof("New GOPATH: %v", b.NewGOPATH)
+	return nil
 }
 
 func (b *Build) mvProjectsToTmp() error {
-	path, err := os.Getwd()
-	if err != nil {
-		log.Errorf("Cannot get current working directory, the error is: %v", err)
-		return err
-	}
-	b.TmpDir = filepath.Join(os.TempDir(), TmpFolderName(path))
+	b.TmpDir = filepath.Join(os.TempDir(), TmpFolderName(b.WorkingDir))
 
 	// Delete previous tmp folder and its content
 	os.RemoveAll(b.TmpDir)
 	// Create a new tmp folder
-	err = os.MkdirAll(filepath.Join(b.TmpDir, "src"), os.ModePerm)
+	err := os.MkdirAll(filepath.Join(b.TmpDir, "src"), os.ModePerm)
 	if err != nil {
 		log.Errorf("Fail to create the temporary build directory. The err is: %v", err)
 		return err
@@ -77,6 +81,7 @@ func (b *Build) mvProjectsToTmp() error {
 
 	// traverse pkg list to get project meta info
 	b.IsMod, b.Root, err = b.traversePkgsList()
+	log.Infof("mod project? %v", b.IsMod)
 	if errors.Is(err, ErrShouldNotReached) {
 		return fmt.Errorf("mvProjectsToTmp with a empty project: %w", err)
 	}
@@ -84,7 +89,7 @@ func (b *Build) mvProjectsToTmp() error {
 	b.TmpWorkingDir, err = b.getTmpwd()
 	if err != nil {
 		log.Errorf("fail to get workding directory in temporary directory: %v", err)
-		return fmt.Errorf("fail to get workding directory in temporary directory: %w", err)
+		return fmt.Errorf("getTmpwd failed with error: %w", err)
 	}
 	// issue #14
 	// if b.Root == "", then the project is non-standard project
@@ -109,7 +114,7 @@ func TmpFolderName(path string) string {
 	sum := sha256.Sum256([]byte(path))
 	h := fmt.Sprintf("%x", sum[:6])
 
-	return "goc-" + h
+	return "goc-build-" + h
 }
 
 // traversePkgsList travse the Build.Pkgs list
@@ -128,7 +133,7 @@ func (b *Build) traversePkgsList() (isMod bool, root string, err error) {
 		isMod = true
 		return
 	}
-	log.Error("should not reach here")
+	log.Error(ErrShouldNotReached)
 	err = ErrShouldNotReached
 	return
 }
@@ -137,19 +142,13 @@ func (b *Build) traversePkgsList() (isMod bool, root string, err error) {
 // and store it in the Build.tmpWorkdingDir
 func (b *Build) getTmpwd() (string, error) {
 	for _, pkg := range b.Pkgs {
-		path, err := os.Getwd()
-		if err != nil {
-			log.Errorf("cannot get current working directory: %v", err)
-			return "", fmt.Errorf("cannot get current working directory: %w", err)
-		}
-
 		index := -1
 		var parentPath string
 		if b.IsMod == false {
-			index = strings.Index(path, pkg.Root)
+			index = strings.Index(b.WorkingDir, pkg.Root)
 			parentPath = pkg.Root
 		} else {
-			index = strings.Index(path, pkg.Module.Dir)
+			index = strings.Index(b.WorkingDir, pkg.Module.Dir)
 			parentPath = pkg.Module.Dir
 		}
 
@@ -157,7 +156,7 @@ func (b *Build) getTmpwd() (string, error) {
 			return "", ErrGocShouldExecInProject
 		}
 		// b.TmpWorkingDir = filepath.Join(b.TmpDir, path[len(parentPath):])
-		return filepath.Join(b.TmpDir, path[len(parentPath):]), nil
+		return filepath.Join(b.TmpDir, b.WorkingDir[len(parentPath):]), nil
 	}
 
 	return "", ErrShouldNotReached
@@ -168,16 +167,14 @@ func (b *Build) findWhereToInstall() (string, error) {
 		return GOBIN, nil
 	}
 
-	// old GOPATH dir
-	GOPATH := os.Getenv("GOPATH")
 	if false == b.IsMod {
 		if b.Root == "" {
 			return "", ErrNoplaceToInstall
 		}
 		return filepath.Join(b.Root, "bin"), nil
 	}
-	if GOPATH != "" {
-		return filepath.Join(strings.Split(GOPATH, ":")[0], "bin"), nil
+	if b.OriGOPATH != "" {
+		return filepath.Join(strings.Split(b.OriGOPATH, ":")[0], "bin"), nil
 	}
 	return filepath.Join(os.Getenv("HOME"), "go", "bin"), nil
 }
diff --git a/pkg/cover/cover.go b/pkg/cover/cover.go
index e28ae66..688095f 100644
--- a/pkg/cover/cover.go
+++ b/pkg/cover/cover.go
@@ -21,6 +21,7 @@ import (
 	"bytes"
 	"crypto/sha256"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -37,6 +38,11 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
+var (
+	ErrCoverPkgFailed  = errors.New("fail to inject code to project")
+	ErrCoverListFailed = errors.New("fail to list package dependencies")
+)
+
 // TestCover is a collection of all counters
 type TestCover struct {
 	Mode         string
@@ -114,9 +120,10 @@ type PackageError struct {
 }
 
 //Execute execute go tool cover for all the .go files in the target folder
-func Execute(args, newGopath, target, mode, center string) {
+func Execute(args, newGopath, target, mode, center string) error {
 	if !isDirExist(target) {
-		log.Fatalf("target directory %s not exist", target)
+		log.Errorf("Target directory %s not exist", target)
+		return ErrCoverPkgFailed
 	}
 
 	listArgs := []string{"-json"}
@@ -124,7 +131,11 @@ func Execute(args, newGopath, target, mode, center string) {
 		listArgs = append(listArgs, args)
 	}
 	listArgs = append(listArgs, "./...")
-	pkgs := ListPackages(target, strings.Join(listArgs, " "), newGopath)
+	pkgs, err := ListPackages(target, strings.Join(listArgs, " "), newGopath)
+	if err != nil {
+		log.Errorf("Fail to list all packages, the error: %v", err)
+		return err
+	}
 
 	var seen = make(map[string]*PackageCover)
 	var seenCache = make(map[string]*PackageCover)
@@ -134,7 +145,8 @@ func Execute(args, newGopath, target, mode, center string) {
 			// inject the main package
 			mainCover, err := AddCounters(pkg, mode, newGopath)
 			if err != nil {
-				log.Fatalf("failed to add counters for pkg %s, err: %v", pkg.ImportPath, err)
+				log.Errorf("failed to add counters for pkg %s, err: %v", pkg.ImportPath, err)
+				return ErrCoverPkgFailed
 			}
 
 			// new a testcover for this service
@@ -158,7 +170,7 @@ func Execute(args, newGopath, target, mode, center string) {
 					if hasInternalPath(dep) {
 						//scan exist cache cover to tc.CacheCover
 						if cache, ok := seenCache[dep]; ok {
-							log.Printf("cache cover exist: %s", cache.Package.ImportPath)
+							log.Infof("cache cover exist: %s", cache.Package.ImportPath)
 							tc.CacheCover[cache.Package.Dir] = cache
 							continue
 						}
@@ -166,7 +178,8 @@ func Execute(args, newGopath, target, mode, center string) {
 						// add counter for internal package
 						inPkgCover, err := AddCounters(depPkg, mode, newGopath)
 						if err != nil {
-							log.Fatalf("failed to add counters for internal pkg %s, err: %v", depPkg.ImportPath, err)
+							log.Errorf("failed to add counters for internal pkg %s, err: %v", depPkg.ImportPath, err)
+							return ErrCoverPkgFailed
 						}
 						parentDir := getInternalParent(depPkg.Dir)
 						parentImportPath := getInternalParent(depPkg.ImportPath)
@@ -212,7 +225,8 @@ func Execute(args, newGopath, target, mode, center string) {
 
 					packageCover, err := AddCounters(depPkg, mode, newGopath)
 					if err != nil {
-						log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
+						log.Errorf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
+						return err
 					}
 					tc.DepsCover = append(tc.DepsCover, packageCover)
 					seen[dep] = packageCover
@@ -220,21 +234,25 @@ func Execute(args, newGopath, target, mode, center string) {
 			}
 
 			if errs := InjectCacheCounters(internalPkgCache, tc.CacheCover); len(errs) > 0 {
-				log.Fatalf("failed to inject cache counters for package: %s, err: %v", pkg.ImportPath, errs)
+				log.Errorf("failed to inject cache counters for package: %s, err: %v", pkg.ImportPath, errs)
+				return ErrCoverPkgFailed
 			}
 
 			// inject Http Cover APIs
 			var httpCoverApis = fmt.Sprintf("%s/http_cover_apis_auto_generated.go", pkg.Dir)
 			if err := InjectCountersHandlers(tc, httpCoverApis); err != nil {
-				log.Fatalf("failed to inject counters for package: %s, err: %v", pkg.ImportPath, err)
+				log.Errorf("failed to inject counters for package: %s, err: %v", pkg.ImportPath, err)
+				return ErrCoverPkgFailed
 			}
 		}
 	}
+
+	return nil
 }
 
 // ListPackages list all packages under specific via go list command
 // The argument newgopath is if you need to go list in a different GOPATH
-func ListPackages(dir string, args string, newgopath string) map[string]*Package {
+func ListPackages(dir string, args string, newgopath string) (map[string]*Package, error) {
 	cmd := exec.Command("/bin/bash", "-c", "go list "+args)
 	log.Printf("go list cmd is: %v", cmd.Args)
 	cmd.Dir = dir
@@ -243,7 +261,8 @@ func ListPackages(dir string, args string, newgopath string) map[string]*Package
 	}
 	out, err := cmd.CombinedOutput()
 	if err != nil {
-		log.Fatalf("excute `go list -json ./...` command failed, err: %v, out: %v", err, string(out))
+		log.Errorf("excute `go list -json ./...` command failed, err: %v, out: %v", err, string(out))
+		return nil, ErrCoverListFailed
 	}
 
 	dec := json.NewDecoder(bytes.NewReader(out))
@@ -254,10 +273,12 @@ func ListPackages(dir string, args string, newgopath string) map[string]*Package
 			if err == io.EOF {
 				break
 			}
-			log.Fatalf("reading go list output: %v", err)
+			log.Errorf("reading go list output: %v", err)
+			return nil, ErrCoverListFailed
 		}
 		if pkg.Error != nil {
-			log.Fatalf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error)
+			log.Errorf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error)
+			return nil, ErrCoverPkgFailed
 		}
 
 		// for _, err := range pkg.DepsErrors {
@@ -266,7 +287,7 @@ func ListPackages(dir string, args string, newgopath string) map[string]*Package
 
 		pkgs[pkg.ImportPath] = &pkg
 	}
-	return pkgs
+	return pkgs, nil
 }
 
 // AddCounters add counters for all go files under the package


Test patch:
diff --git a/cmd/build_test.go b/cmd/build_test.go
index db005d6..9d46dee 100644
--- a/cmd/build_test.go
+++ b/cmd/build_test.go
@@ -17,13 +17,14 @@
 package cmd
 
 import (
-	"github.com/stretchr/testify/assert"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
 	"testing"
 	"time"
+
+	"github.com/stretchr/testify/assert"
 )
 
 var baseDir string
@@ -38,14 +39,14 @@ func TestGeneratedBinary(t *testing.T) {
 	workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
 	gopath := ""
 
-	os.Chdir(workingDir)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "on")
 
-	buildFlags, packages, buildOutput = "", ".", ""
-	runBuild()
+	buildFlags, buildOutput = "", ""
+	args := []string{"."}
+	runBuild(args, workingDir)
 
-	obj := filepath.Join(".", "simple-project")
+	obj := filepath.Join(workingDir, "simple-project")
 	fInfo, err := os.Lstat(obj)
 	assert.Equal(t, err, nil, "the binary should be generated.")
 	assert.Equal(t, startTime.Before(fInfo.ModTime()), true, obj+"new binary should be generated, not the old one")
diff --git a/cmd/install_test.go b/cmd/install_test.go
index 150d098..d18a674 100644
--- a/cmd/install_test.go
+++ b/cmd/install_test.go
@@ -17,13 +17,14 @@
 package cmd
 
 import (
-	"github.com/stretchr/testify/assert"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
 	"testing"
 	"time"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestInstalledBinaryForMod(t *testing.T) {
@@ -32,12 +33,12 @@ func TestInstalledBinaryForMod(t *testing.T) {
 	workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
 	gopath := filepath.Join(baseDir, "../tests/samples/simple_project", "testhome")
 
-	os.Chdir(workingDir)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "on")
 
-	buildFlags, packages, buildOutput = "", ".", ""
-	runInstall()
+	buildFlags, buildOutput = "", ""
+	args := []string{"."}
+	runInstall(args, workingDir)
 
 	obj := filepath.Join(gopath, "bin", "simple-project")
 	fInfo, err := os.Lstat(obj)
@@ -60,12 +61,12 @@ func TestInstalledBinaryForLegacy(t *testing.T) {
 	workingDir := filepath.Join(baseDir, "../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project")
 	gopath := filepath.Join(baseDir, "../tests/samples/simple_gopath_project")
 
-	os.Chdir(workingDir)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "off")
 
-	buildFlags, packages, buildOutput = "", ".", ""
-	runInstall()
+	buildFlags, buildOutput = "", ""
+	args := []string{"."}
+	runInstall(args, workingDir)
 
 	obj := filepath.Join(gopath, "bin", "simple_gopath_project")
 	fInfo, err := os.Lstat(obj)
diff --git a/pkg/build/build_test.go b/pkg/build/build_test.go
index fa25263..82df7ea 100644
--- a/pkg/build/build_test.go
+++ b/pkg/build/build_test.go
@@ -17,10 +17,13 @@
 package build
 
 import (
-	"github.com/stretchr/testify/assert"
+	"errors"
+	"fmt"
 	"os"
 	"path/filepath"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestInvalidPackage(t *testing.T) {
@@ -28,26 +31,62 @@ func TestInvalidPackage(t *testing.T) {
 	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
 	gopath := ""
 
-	os.Chdir(workingDir)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "on")
 
-	_, err := NewBuild("", "example.com/simple-project", "")
-	assert.Equal(t, err, ErrWrongPackageTypeForBuild, "the package name should be invalid")
+	_, err := NewBuild("", []string{"example.com/simple-project"}, workingDir, "")
+	if !assert.Equal(t, err, ErrWrongPackageTypeForBuild) {
+		assert.FailNow(t, "the package name should be invalid")
+	}
 }
 
 func TestBasicBuildForModProject(t *testing.T) {
-	workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
+	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
 	gopath := ""
 
-	os.Chdir(workingDir)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "on")
-
-	buildFlags, packages, buildOutput := "", ".", ""
-	gocBuild, err := NewBuild(buildFlags, packages, buildOutput)
-	assert.Equal(t, err, nil, "should create temporary directory successfully")
+	fmt.Println(workingDir)
+	buildFlags, args, buildOutput := "", []string{"."}, ""
+	gocBuild, err := NewBuild(buildFlags, args, workingDir, buildOutput)
+	if !assert.Equal(t, err, nil) {
+		assert.FailNow(t, "should create temporary directory successfully")
+	}
 
 	err = gocBuild.Build()
-	assert.Equal(t, err, nil, "temporary directory should build successfully")
+	if !assert.Equal(t, err, nil) {
+		assert.FailNow(t, "temporary directory should build successfully")
+	}
+}
+
+func TestCheckParameters(t *testing.T) {
+	err := checkParameters([]string{"aa", "bb"}, "aa")
+	assert.Equal(t, err, ErrTooManyArgs, "too many arguments should failed")
+
+	err = checkParameters([]string{"aa"}, "")
+	assert.Equal(t, err, ErrInvalidWorkingDir, "empty working directory should failed")
+}
+
+func TestDetermineOutputDir(t *testing.T) {
+	b := &Build{}
+	_, err := b.determineOutputDir("")
+	assert.Equal(t, errors.Is(err, ErrWrongCallSequence), true, "called before Build.MvProjectsToTmp() should fail")
+
+	b.TmpDir = "fake"
+	_, err = b.determineOutputDir("xx")
+	assert.Equal(t, err, nil, "should return a directory")
+}
+
+func TestInvalidPackageNameForBuild(t *testing.T) {
+	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
+	gopath := filepath.Join(baseDir, "../../tests/samples/simple_project", "testhome")
+
+	os.Setenv("GOPATH", gopath)
+	os.Setenv("GO111MODULE", "on")
+
+	buildFlags, packages := "", []string{"main.go"}
+	_, err := NewBuild(buildFlags, packages, workingDir, "")
+	if !assert.Equal(t, err, ErrWrongPackageTypeForBuild) {
+		assert.FailNow(t, "should not success with non . or ./... package")
+	}
 }
diff --git a/pkg/build/install_test.go b/pkg/build/install_test.go
index 9108b3f..30e996b 100644
--- a/pkg/build/install_test.go
+++ b/pkg/build/install_test.go
@@ -1,24 +1,42 @@
 package build
 
 import (
-	"github.com/stretchr/testify/assert"
 	"os"
 	"path/filepath"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestBasicInstallForModProject(t *testing.T) {
-	workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
-	gopath := filepath.Join(baseDir, "../tests/samples/simple_project", "testhome")
+	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
+	gopath := filepath.Join(baseDir, "../../tests/samples/simple_project", "testhome")
 
-	os.Chdir(workingDir)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "on")
 
-	buildFlags, packages := "", "."
-	gocBuild, err := NewInstall(buildFlags, packages)
-	assert.Equal(t, err, nil, "should create temporary directory successfully")
+	buildFlags, packages := "", []string{"."}
+	gocBuild, err := NewInstall(buildFlags, packages, workingDir)
+	if !assert.Equal(t, err, nil) {
+		assert.FailNow(t, "should create temporary directory successfully")
+	}
 
 	err = gocBuild.Install()
-	assert.Equal(t, err, nil, "temporary directory should build successfully")
+	if !assert.Equal(t, err, nil) {
+		assert.FailNow(t, "temporary directory should build successfully")
+	}
+}
+
+func TestInvalidPackageNameForInstall(t *testing.T) {
+	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
+	gopath := filepath.Join(baseDir, "../../tests/samples/simple_project", "testhome")
+
+	os.Setenv("GOPATH", gopath)
+	os.Setenv("GO111MODULE", "on")
+
+	buildFlags, packages := "", []string{"main.go"}
+	_, err := NewInstall(buildFlags, packages, workingDir)
+	if !assert.Equal(t, err, ErrWrongPackageTypeForInstall) {
+		assert.FailNow(t, "should not success with non . or ./... package")
+	}
 }
diff --git a/pkg/build/tmpfolder_test.go b/pkg/build/tmpfolder_test.go
index ed4f4b8..9373ac8 100644
--- a/pkg/build/tmpfolder_test.go
+++ b/pkg/build/tmpfolder_test.go
@@ -34,12 +34,10 @@ func TestNewDirParseInLegacyProject(t *testing.T) {
 	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project")
 	gopath := filepath.Join(baseDir, "../../tests/samples/simple_gopath_project")
 
-	os.Chdir(workingDir)
-
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "off")
 
-	b, _ := NewInstall("", ".")
+	b, _ := NewInstall("", []string{"."}, workingDir)
 	if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
 		t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
 	}
@@ -48,7 +46,7 @@ func TestNewDirParseInLegacyProject(t *testing.T) {
 		t.Fatalf("The New GOPATH is wrong. newgopath: %v, tmpdir: %v", b.NewGOPATH, b.TmpDir)
 	}
 
-	b, _ = NewBuild("", ".", "")
+	b, _ = NewBuild("", []string{"."}, workingDir, "")
 	if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
 		t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
 	}
@@ -62,12 +60,11 @@ func TestNewDirParseInModProject(t *testing.T) {
 	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
 	gopath := ""
 
-	os.Chdir(workingDir)
 	fmt.Println(gopath)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "on")
 
-	b, _ := NewInstall("", ".")
+	b, _ := NewInstall("", []string{"."}, workingDir)
 	if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
 		t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
 	}
@@ -76,7 +73,7 @@ func TestNewDirParseInModProject(t *testing.T) {
 		t.Fatalf("The New GOPATH is wrong. newgopath: %v, tmpdir: %v", b.NewGOPATH, b.TmpDir)
 	}
 
-	b, _ = NewBuild("", ".", "")
+	b, _ = NewBuild("", []string{"."}, workingDir, "")
 	if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
 		t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
 	}
@@ -91,12 +88,11 @@ func TestLegacyProjectNotInGoPATH(t *testing.T) {
 	workingDir := filepath.Join(baseDir, "../../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project")
 	gopath := ""
 
-	os.Chdir(workingDir)
 	fmt.Println(gopath)
 	os.Setenv("GOPATH", gopath)
 	os.Setenv("GO111MODULE", "off")
 
-	b, _ := NewBuild("", ".", "")
+	b, _ := NewBuild("", []string{"."}, workingDir, "")
 	if b.OriGOPATH != b.NewGOPATH {
 		t.Fatalf("New GOPATH should be same with old GOPATH, for this kind of project. New: %v, old: %v", b.NewGOPATH, b.OriGOPATH)
 	}
diff --git a/pkg/cover/cover_test.go b/pkg/cover/cover_test.go
index 1113b87..098cc67 100644
--- a/pkg/cover/cover_test.go
+++ b/pkg/cover/cover_test.go
@@ -27,6 +27,7 @@ import (
 
 	log "github.com/sirupsen/logrus"
 
+	"github.com/otiai10/copy"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -304,3 +305,40 @@ func TestFindInternal(t *testing.T) {
 		}
 	}
 }
+
+func TestExecuteForSimpleModProject(t *testing.T) {
+	workingDir := "../../tests/samples/simple_project"
+	gopath := ""
+
+	os.Setenv("GOPATH", gopath)
+	os.Setenv("GO111MODULE", "on")
+
+	testDir := filepath.Join(os.TempDir(), "goc-build-test")
+	copy.Copy(workingDir, testDir)
+
+	Execute("", gopath, testDir, "count", "http://127.0.0.1:7777")
+
+	_, err := os.Lstat(filepath.Join(testDir, "http_cover_apis_auto_generated.go"))
+	if !assert.Equal(t, err, nil) {
+		assert.FailNow(t, "should generate http_cover_apis_auto_generated.go")
+	}
+}
+
+func TestListPackagesForSimpleModProject(t *testing.T) {
+	workingDir := "../../tests/samples/simple_project"
+	gopath := ""
+
+	os.Setenv("GOPATH", gopath)
+	os.Setenv("GO111MODULE", "on")
+
+	pkgs, _ := ListPackages(workingDir, "-json ./...", "")
+	if !assert.Equal(t, len(pkgs), 1) {
+		assert.FailNow(t, "should only have one pkg")
+	}
+	if pkg, ok := pkgs["example.com/simple-project"]; ok {
+		assert.Equal(t, pkg.Module.Path, "example.com/simple-project")
+	} else {
+		assert.FailNow(t, "cannot get the pkg: example.com/simple-project")
+	}
+
+}