Merge pull request #37 from CarlJi/0610

Feature: add goc run command
This commit is contained in:
qiniu-bot 2020-06-16 20:29:13 +08:00 committed by GitHub
commit 103dafe41d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 505 additions and 262 deletions

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"github.com/qiniu/goc/pkg/build" "github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -45,14 +46,10 @@ goc build -- -ldflags "-extldflags -static" -tags="embed kodo"
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewBuild(buildFlags, packages, buildOutput) gocBuild := build.NewBuild(buildFlags, packages, buildOutput)
// remove temporary directory if needed // remove temporary directory if needed
defer func() { defer gocBuild.Clean()
if !debugGoc {
gocBuild.Clean()
}
}()
// doCover with original buildFlags, with new GOPATH( tmp:original ) // doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory // in the tmp directory
doCover(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir) cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory // do install in the temporary directory
gocBuild.Build() gocBuild.Build()
return return

View File

@ -13,6 +13,9 @@ var (
buildFlags string buildFlags string
packages string packages string
appArgs string appArgs string
goRunExecFlag string
goRunArguments string
) )
// addBasicFlags adds a // addBasicFlags adds a
@ -41,6 +44,8 @@ func addRunFlags(cmdset *pflag.FlagSet) {
addBuildFlags(cmdset) addBuildFlags(cmdset)
cmdset.Lookup("packages").Usage = "specify the package name, only ., ./... and *.go are supported" cmdset.Lookup("packages").Usage = "specify the package name, only ., ./... and *.go are supported"
cmdset.StringVar(&appArgs, "appargs", "", "specify the application's arguments") 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")
// bind to viper // bind to viper
viper.BindPFlags(cmdset) viper.BindPFlags(cmdset)
} }

View File

@ -17,14 +17,10 @@
package cmd package cmd
import ( import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"os"
"strings"
"github.com/qiniu/goc/pkg/cover" "github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
var coverCmd = &cobra.Command{ var coverCmd = &cobra.Command{
@ -52,7 +48,7 @@ goc cover --center=http://127.0.0.1:7777 --target=/path/to/target --mode=atomic
log.Fatalf("unknown -mode %v", mode) log.Fatalf("unknown -mode %v", mode)
} }
doCover(buildFlags, "", target) cover.Execute(buildFlags, "", target, mode, center)
}, },
} }
@ -61,166 +57,3 @@ func init() {
addCommonFlags(coverCmd.Flags()) addCommonFlags(coverCmd.Flags())
rootCmd.AddCommand(coverCmd) rootCmd.AddCommand(coverCmd)
} }
func doCover(args string, newgopath string, target string) {
if !isDirExist(target) {
log.Fatalf("target directory %s not exist", target)
}
listArgs := []string{"-json"}
if len(args) != 0 {
listArgs = append(listArgs, args)
}
listArgs = append(listArgs, "./...")
pkgs := cover.ListPackages(target, strings.Join(listArgs, " "), newgopath)
var seen = make(map[string]*cover.PackageCover)
var seenCache = make(map[string]*cover.PackageCover)
for _, pkg := range pkgs {
if pkg.Name == "main" {
log.Printf("handle package: %v", pkg.ImportPath)
// inject the main package
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: mode,
Center: center,
MainPkgCover: mainCover,
}
// handle its dependency
var internalPkgCache = make(map[string][]*cover.PackageCover)
tc.CacheCover = make(map[string]*cover.PackageCover)
for _, dep := range pkg.Deps {
if packageCover, ok := seen[dep]; ok {
tc.DepsCover = append(tc.DepsCover, packageCover)
continue
}
//only focus package neither standard Go library nor dependency library
if depPkg, ok := pkgs[dep]; ok {
if findInternal(dep) {
//scan exist cache cover to tc.CacheCover
if cache, ok := seenCache[dep]; ok {
log.Printf("cache cover exist: %s", cache.Package.ImportPath)
tc.CacheCover[cache.Package.Dir] = cache
continue
}
// add counter for internal package
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)
}
parentDir := getInternalParent(depPkg.Dir)
parentImportPath := getInternalParent(depPkg.ImportPath)
//if internal parent dir or import is root path, ignore the dep. the dep is Go library nor dependency library
if parentDir == "" {
continue
}
if parentImportPath == "" {
continue
}
pkg := &cover.Package{
ImportPath: parentImportPath,
Dir: parentDir,
}
// Some internal package have same parent dir or import path
// Cache all vars by internal parent dir for all child internal counter vars
cacheCover := cover.AddCacheCover(pkg, inPkgCover)
if v, ok := tc.CacheCover[cacheCover.Package.Dir]; ok {
for cVar, val := range v.Vars {
cacheCover.Vars[cVar] = val
}
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
} else {
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
}
// Cache all internal vars to internal parent package
inCover := cover.CacheInternalCover(inPkgCover)
if v, ok := internalPkgCache[cacheCover.Package.Dir]; ok {
v = append(v, inCover)
internalPkgCache[cacheCover.Package.Dir] = v
} else {
var covers []*cover.PackageCover
covers = append(covers, inCover)
internalPkgCache[cacheCover.Package.Dir] = covers
}
seenCache[dep] = cacheCover
continue
}
packageCover, err := cover.AddCounters(depPkg, mode, newgopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
}
tc.DepsCover = append(tc.DepsCover, packageCover)
seen[dep] = packageCover
}
}
if errs := cover.InjectCacheCounters(internalPkgCache, tc.CacheCover); len(errs) > 0 {
log.Fatalf("failed to inject cache counters for package: %s, err: %v", pkg.ImportPath, errs)
}
// inject Http Cover APIs
var httpCoverApis = fmt.Sprintf("%s/http_cover_apis_auto_generated.go", pkg.Dir)
if err := cover.InjectCountersHandlers(tc, httpCoverApis); err != nil {
log.Fatalf("failed to inject counters for package: %s, err: %v", pkg.ImportPath, err)
}
}
}
}
func isDirExist(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
// Refer: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L1334:6
// findInternal looks for the final "internal" path element in the given import path.
// If there isn't one, findInternal returns ok=false.
// Otherwise, findInternal returns ok=true and the index of the "internal".
func findInternal(path string) bool {
// Three cases, depending on internal at start/end of string or not.
// The order matters: we must return the index of the final element,
// because the final one produces the most restrictive requirement
// on the importer.
switch {
case strings.HasSuffix(path, "/internal"):
return true
case strings.Contains(path, "/internal/"):
return true
case path == "internal", strings.HasPrefix(path, "internal/"):
return true
}
return false
}
func getInternalParent(path string) string {
switch {
case strings.HasSuffix(path, "/internal"):
return strings.Split(path, "/internal")[0]
case strings.Contains(path, "/internal/"):
return strings.Split(path, "/internal/")[0]
case path == "internal":
return ""
case strings.HasPrefix(path, "internal/"):
return strings.Split(path, "internal/")[0]
}
return ""
}

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"github.com/qiniu/goc/pkg/build" "github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -42,14 +43,10 @@ goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewInstall(buildFlags, packages) gocBuild := build.NewInstall(buildFlags, packages)
// remove temporary directory if needed // remove temporary directory if needed
defer func() { defer gocBuild.Clean()
if !debugGoc {
gocBuild.Clean()
}
}()
// doCover with original buildFlags, with new GOPATH( tmp:original ) // doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory // in the tmp directory
doCover(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir) cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory // do install in the temporary directory
gocBuild.Install() gocBuild.Install()
}, },

73
cmd/run.go Normal file
View File

@ -0,0 +1,73 @@
/*
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"
"io/ioutil"
"net"
"github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var runCmd = &cobra.Command{
Use: "run",
Short: "Run covers and runs the named main Go package",
Long: `Run covers and runs the named main Go package,
It is exactly behave as 'go run .' in addition of some internal goc features.`,
Example: `
goc run .
`,
Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewBuild(buildFlags, packages, buildOutput)
gocBuild.GoRunExecFlag = goRunExecFlag
gocBuild.GoRunArguments = goRunArguments
defer gocBuild.Clean()
// only save services in memory
cover.DefaultStore = cover.NewMemoryStore()
// start goc server
var l = newLocalListener()
go cover.GocServer(ioutil.Discard).RunListener(l)
gocServer := fmt.Sprintf("http://%s", l.Addr().String())
fmt.Printf("[goc] goc server started: %s \n", gocServer)
// 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()
},
}
func init() {
addRunFlags(runCmd.Flags())
rootCmd.AddCommand(runCmd)
}
func newLocalListener() net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
log.Fatalf("failed to listen on a port: %v", err)
}
}
return l
}

View File

@ -35,10 +35,17 @@ type Build struct {
TmpDir string // the temporary directory to build the project 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 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 IsMod bool // determine whether it is a Mod project
BuildFlags string // Build flags
Packages string // Packages that needs to build
Root string // Project Root Root string // Project Root
Target string // the binary name that go build generate Target string // the binary name that go build generate
// keep compatible with go commands:
// go run [build flags] [-exec xprog] package [arguments...]
// go build [-o output] [-i] [build flags] [packages]
// go install [-i] [build flags] [packages]
BuildFlags string // Build flags
Packages string // Packages that needs to build
GoRunExecFlag string // for the -exec flags in go run command
GoRunArguments string // for the '[arguments]' parameters in go run command
} }
// NewBuild creates a Build struct which can build from goc temporary directory, // NewBuild creates a Build struct which can build from goc temporary directory,
@ -108,7 +115,30 @@ func (b *Build) determineOutputDir(outputDir string) string {
func (b *Build) validatePackageForBuild() bool { func (b *Build) validatePackageForBuild() bool {
if b.Packages == "." { if b.Packages == "." {
return true return true
} else { }
return false 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)
}
if err = cmd.Wait(); err != nil {
log.Fatalf("Fail to execute command: %v. The error is: %v", cmd.Args, err)
}
} }

View File

@ -17,9 +17,8 @@
package build package build
import ( import (
log "github.com/sirupsen/logrus"
"github.com/otiai10/copy" "github.com/otiai10/copy"
log "github.com/sirupsen/logrus"
) )
func (b *Build) cpGoModulesProject() { func (b *Build) cpGoModulesProject() {

View File

@ -18,9 +18,10 @@ package build
import ( import (
"fmt" "fmt"
log "github.com/sirupsen/logrus"
"os" "os"
"os/exec" "os/exec"
log "github.com/sirupsen/logrus"
) )
// NewInstall creates a Build struct which can install from goc temporary directory // NewInstall creates a Build struct which can install from goc temporary directory
@ -59,7 +60,6 @@ func (b *Build) Install() {
func (b *Build) validatePackageForInstall() bool { func (b *Build) validatePackageForInstall() bool {
if b.Packages == "." || b.Packages == "./..." { if b.Packages == "." || b.Packages == "./..." {
return true return true
} else { }
return false return false
} }
}

View File

@ -17,12 +17,12 @@
package build package build
import ( import (
log "github.com/sirupsen/logrus"
"os" "os"
"path/filepath" "path/filepath"
"github.com/otiai10/copy" "github.com/otiai10/copy"
"github.com/qiniu/goc/pkg/cover" "github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
) )
func (b *Build) cpLegacyProject() { func (b *Build) cpLegacyProject() {

View File

@ -23,9 +23,9 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
log "github.com/sirupsen/logrus"
"github.com/qiniu/goc/pkg/cover" "github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
) )
func (b *Build) MvProjectsToTmp() { func (b *Build) MvProjectsToTmp() {
@ -156,5 +156,8 @@ func (b *Build) findWhereToInstall() string {
// Clean clears up the temporary workspace // Clean clears up the temporary workspace
func (b *Build) Clean() error { func (b *Build) Clean() error {
if !viper.GetBool("debug") {
return os.RemoveAll(b.TmpDir) return os.RemoveAll(b.TmpDir)
} }
return nil
}

View File

@ -22,7 +22,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"fmt" "fmt"
log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -33,6 +32,8 @@ import (
"strings" "strings"
"time" "time"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -112,6 +113,125 @@ type PackageError struct {
Err string // the error itself Err string // the error itself
} }
//Execute execute go tool cover for all the .go files in the target folder
func Execute(args, newGopath, target, mode, center string) {
if !isDirExist(target) {
log.Fatalf("target directory %s not exist", target)
}
listArgs := []string{"-json"}
if len(args) != 0 {
listArgs = append(listArgs, args)
}
listArgs = append(listArgs, "./...")
pkgs := ListPackages(target, strings.Join(listArgs, " "), newGopath)
var seen = make(map[string]*PackageCover)
var seenCache = make(map[string]*PackageCover)
for _, pkg := range pkgs {
if pkg.Name == "main" {
log.Printf("handle package: %v", pkg.ImportPath)
// 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)
}
// new a testcover for this service
tc := TestCover{
Mode: mode,
Center: center,
MainPkgCover: mainCover,
}
// handle its dependency
var internalPkgCache = make(map[string][]*PackageCover)
tc.CacheCover = make(map[string]*PackageCover)
for _, dep := range pkg.Deps {
if packageCover, ok := seen[dep]; ok {
tc.DepsCover = append(tc.DepsCover, packageCover)
continue
}
//only focus package neither standard Go library nor dependency library
if depPkg, ok := pkgs[dep]; ok {
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)
tc.CacheCover[cache.Package.Dir] = cache
continue
}
// 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)
}
parentDir := getInternalParent(depPkg.Dir)
parentImportPath := getInternalParent(depPkg.ImportPath)
//if internal parent dir or import is root path, ignore the dep. the dep is Go library nor dependency library
if parentDir == "" {
continue
}
if parentImportPath == "" {
continue
}
pkg := &Package{
ImportPath: parentImportPath,
Dir: parentDir,
}
// Some internal package have same parent dir or import path
// Cache all vars by internal parent dir for all child internal counter vars
cacheCover := AddCacheCover(pkg, inPkgCover)
if v, ok := tc.CacheCover[cacheCover.Package.Dir]; ok {
for cVar, val := range v.Vars {
cacheCover.Vars[cVar] = val
}
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
} else {
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
}
// Cache all internal vars to internal parent package
inCover := CacheInternalCover(inPkgCover)
if v, ok := internalPkgCache[cacheCover.Package.Dir]; ok {
v = append(v, inCover)
internalPkgCache[cacheCover.Package.Dir] = v
} else {
var covers []*PackageCover
covers = append(covers, inCover)
internalPkgCache[cacheCover.Package.Dir] = covers
}
seenCache[dep] = cacheCover
continue
}
packageCover, err := AddCounters(depPkg, mode, newGopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
}
tc.DepsCover = append(tc.DepsCover, packageCover)
seen[dep] = packageCover
}
}
if errs := InjectCacheCounters(internalPkgCache, tc.CacheCover); len(errs) > 0 {
log.Fatalf("failed to inject cache counters for package: %s, err: %v", pkg.ImportPath, errs)
}
// 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)
}
}
}
}
// ListPackages list all packages under specific via go list command // ListPackages list all packages under specific via go list command
// The argument newgopath is if you need to go list in a different GOPATH // 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 {
@ -167,6 +287,48 @@ func AddCounters(pkg *Package, mode, newgopath string) (*PackageCover, error) {
}, nil }, nil
} }
func isDirExist(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
// Refer: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L1334:6
// hasInternalPath looks for the final "internal" path element in the given import path.
// If there isn't one, hasInternalPath returns ok=false.
// Otherwise, hasInternalPath returns ok=true and the index of the "internal".
func hasInternalPath(path string) bool {
// Three cases, depending on internal at start/end of string or not.
// The order matters: we must return the index of the final element,
// because the final one produces the most restrictive requirement
// on the importer.
switch {
case strings.HasSuffix(path, "/internal"):
return true
case strings.Contains(path, "/internal/"):
return true
case path == "internal", strings.HasPrefix(path, "internal/"):
return true
}
return false
}
func getInternalParent(path string) string {
switch {
case strings.HasSuffix(path, "/internal"):
return strings.Split(path, "/internal")[0]
case strings.Contains(path, "/internal/"):
return strings.Split(path, "/internal/")[0]
case path == "internal":
return ""
case strings.HasPrefix(path, "internal/"):
return strings.Split(path, "internal/")[0]
}
return ""
}
func buildCoverCmd(file string, coverVar *FileVar, pkg *Package, mode, newgopath string) *exec.Cmd { 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) // to construct: go tool cover -mode=atomic -o dest src (note: dest==src)
var newArgs = []string{"tool", "cover"} var newArgs = []string{"tool", "cover"}

View File

@ -18,7 +18,6 @@ package cover
import ( import (
"fmt" "fmt"
log "github.com/sirupsen/logrus"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -26,6 +25,8 @@ import (
"strings" "strings"
"testing" "testing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -225,3 +226,81 @@ func TestDeclareCoverVars(t *testing.T) {
} }
} }
func TestGetInternalParent(t *testing.T) {
var tcs = []struct {
ImportPath string
expectedParent string
}{
{
ImportPath: "a/internal/b",
expectedParent: "a",
},
{
ImportPath: "internal/b",
expectedParent: "",
},
{
ImportPath: "a/b/internal/b",
expectedParent: "a/b",
},
{
ImportPath: "a/b/internal",
expectedParent: "a/b",
},
{
ImportPath: "a/b/internal/c",
expectedParent: "a/b",
},
{
ImportPath: "a/b/c",
expectedParent: "",
},
{
ImportPath: "",
expectedParent: "",
},
}
for _, tc := range tcs {
actual := getInternalParent(tc.ImportPath)
if actual != tc.expectedParent {
t.Errorf("getInternalParent failed for importPath %s, expected %s, got %s", tc.ImportPath, tc.expectedParent, actual)
}
}
}
func TestFindInternal(t *testing.T) {
var tcs = []struct {
ImportPath string
expectedParent bool
}{
{
ImportPath: "a/internal/b",
expectedParent: true,
},
{
ImportPath: "internal/b",
expectedParent: true,
},
{
ImportPath: "a/b/internal",
expectedParent: true,
},
{
ImportPath: "a/b/c",
expectedParent: false,
},
{
ImportPath: "internal",
expectedParent: true,
},
}
for _, tc := range tcs {
actual := hasInternalPath(tc.ImportPath)
if actual != tc.expectedParent {
t.Errorf("hasInternalPath check failed for importPath %s", tc.ImportPath)
}
}
}

View File

@ -145,9 +145,10 @@ func clearFileCover(counter []uint32) {
func registerHandlers() { func registerHandlers() {
ln, host, err := listen() ln, host, err := listen()
if err != nil { if err != nil {
log.Fatalf("profile listen failed, err:%v", err) log.Fatalf("listen failed, err:%v", err)
} }
log.Println("profile listen on", host)
fmt.Printf("[goc] goc agent listened on: %s \n", host)
profileAddr := "http://" + host profileAddr := "http://" + host
if resp, err := registerSelf(profileAddr); err != nil { if resp, err := registerSelf(profileAddr); err != nil {
log.Fatalf("register address %v failed, err: %v, response: %v", profileAddr, err, string(resp)) log.Fatalf("register address %v failed, err: %v, response: %v", profileAddr, err, string(resp))
@ -220,7 +221,7 @@ func registerSelf(address string) ([]byte, error) {
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil && isNetworkError(err) { if err != nil && isNetworkError(err) {
log.Printf("[WARN]error occured:%v, try again", err) log.Printf("[goc][WARN]error occured:%v, try again", err)
resp, err = http.DefaultClient.Do(req) resp, err = http.DefaultClient.Do(req)
} }
defer resp.Body.Close() defer resp.Body.Close()

View File

@ -19,7 +19,6 @@ package cover
import ( import (
"bytes" "bytes"
"fmt" "fmt"
log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@ -28,18 +27,19 @@ import (
"os" "os"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/tools/cover" "golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov" "k8s.io/test-infra/gopherage/pkg/cov"
) )
// LocalStore implements the IPersistence interface // DefaultStore implements the IPersistence interface
var LocalStore Store var DefaultStore Store
// LogFile a file to save log. // LogFile a file to save log.
const LogFile = "goc.log" const LogFile = "goc.log"
func init() { func init() {
LocalStore = NewStore() DefaultStore = NewFileStore()
} }
// Run starts coverage host center // Run starts coverage host center
@ -48,16 +48,18 @@ func Run(port string) {
if err != nil { if err != nil {
log.Fatalf("failed to create log file %s, err: %v", LogFile, err) log.Fatalf("failed to create log file %s, err: %v", LogFile, err)
} }
r := GocServer(f)
// both log to stdout and file by default
mw := io.MultiWriter(f, os.Stdout)
r := GocServer(mw)
log.Fatal(r.Run(port)) log.Fatal(r.Run(port))
} }
// GocServer init goc server engine // GocServer init goc server engine
func GocServer(w io.Writer) *gin.Engine { func GocServer(w io.Writer) *gin.Engine {
if w != nil && w != os.Stdout { if w != nil {
gin.DefaultWriter = io.MultiWriter(w, os.Stdout) gin.DefaultWriter = w
} }
r := gin.Default() r := gin.Default()
// api to show the registered services // api to show the registered services
r.StaticFile(PersistenceFile, "./"+PersistenceFile) r.StaticFile(PersistenceFile, "./"+PersistenceFile)
@ -82,7 +84,7 @@ type Service struct {
//listServices list all the registered services //listServices list all the registered services
func listServices(c *gin.Context) { func listServices(c *gin.Context) {
services := LocalStore.GetAll() services := DefaultStore.GetAll()
c.JSON(http.StatusOK, services) c.JSON(http.StatusOK, services)
} }
@ -109,7 +111,7 @@ func registerService(c *gin.Context) {
log.Printf("the registed host %s of service %s is different with the real one %s, here we choose the real one", service.Name, host, realIP) log.Printf("the registed host %s of service %s is different with the real one %s, here we choose the real one", service.Name, host, realIP)
service.Address = fmt.Sprintf("http://%s:%s", realIP, port) service.Address = fmt.Sprintf("http://%s:%s", realIP, port)
} }
if err := LocalStore.Add(service); err != nil { if err := DefaultStore.Add(service); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@ -118,7 +120,7 @@ func registerService(c *gin.Context) {
} }
func profile(c *gin.Context) { func profile(c *gin.Context) {
svrsUnderTest := LocalStore.GetAll() svrsUnderTest := DefaultStore.GetAll()
var mergedProfiles = make([][]*cover.Profile, len(svrsUnderTest)) var mergedProfiles = make([][]*cover.Profile, len(svrsUnderTest))
for _, addrs := range svrsUnderTest { for _, addrs := range svrsUnderTest {
for _, addr := range addrs { for _, addr := range addrs {
@ -154,7 +156,7 @@ func profile(c *gin.Context) {
} }
func clear(c *gin.Context) { func clear(c *gin.Context) {
svrsUnderTest := LocalStore.GetAll() svrsUnderTest := DefaultStore.GetAll()
for svc, addrs := range svrsUnderTest { for svc, addrs := range svrsUnderTest {
for _, addr := range addrs { for _, addr := range addrs {
pp, err := NewWorker(addr).Clear() pp, err := NewWorker(addr).Clear()
@ -168,7 +170,7 @@ func clear(c *gin.Context) {
} }
func initSystem(c *gin.Context) { func initSystem(c *gin.Context) {
if err := LocalStore.Init(); err != nil { if err := DefaultStore.Init(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }

View File

@ -19,10 +19,11 @@ package cover
import ( import (
"bufio" "bufio"
"fmt" "fmt"
log "github.com/sirupsen/logrus"
"os" "os"
"strings" "strings"
"sync" "sync"
log "github.com/sirupsen/logrus"
) )
// Store persistents the registered service information // Store persistents the registered service information
@ -38,77 +39,82 @@ type Store interface {
// Init cleanup all the registered service information // Init cleanup all the registered service information
Init() error Init() error
// Set stores the services information into internal state
Set(services map[string][]string)
} }
// PersistenceFile is the file to save services address information // PersistenceFile is the file to save services address information
const PersistenceFile = "_svrs_address.txt" const PersistenceFile = "_svrs_address.txt"
// localStore holds the registered services into memory and persistent to a local file // fileStore holds the registered services into memory and persistent to a local file
type localStore struct { type fileStore struct {
mu sync.RWMutex mu sync.RWMutex
servicesMap map[string][]string
persistentFile string persistentFile string
memoryStore Store
} }
// Add adds the given service to localStore // NewFileStore creates a store using local file
func (l *localStore) Add(s Service) error { func NewFileStore() Store {
l.mu.Lock() l := &fileStore{
defer l.mu.Unlock() persistentFile: PersistenceFile,
// load to memory memoryStore: NewMemoryStore(),
if addrs, ok := l.servicesMap[s.Name]; ok {
for _, addr := range addrs {
if addr == s.Address {
log.Printf("service registered already, name: %s, address: %s", s.Name, s.Address)
return nil
} }
if err := l.load(); err != nil {
log.Fatalf("load failed, file: %s, err: %v", l.persistentFile, err)
} }
addrs = append(addrs, s.Address)
l.servicesMap[s.Name] = addrs return l
} else {
l.servicesMap[s.Name] = []string{s.Address}
} }
// Add adds the given service to file Store
func (l *fileStore) Add(s Service) error {
l.memoryStore.Add(s)
// persistent to local store // persistent to local store
l.mu.Lock()
defer l.mu.Unlock()
return l.appendToFile(s) return l.appendToFile(s)
} }
// Get returns the registered service information with the given name // Get returns the registered service information with the given name
func (l *localStore) Get(name string) []string { func (l *fileStore) Get(name string) []string {
l.mu.RLock() return l.memoryStore.Get(name)
defer l.mu.RUnlock()
return l.servicesMap[name]
} }
// Get returns all the registered service information // Get returns all the registered service information
func (l *localStore) GetAll() map[string][]string { func (l *fileStore) GetAll() map[string][]string {
l.mu.RLock() return l.memoryStore.GetAll()
defer l.mu.RUnlock()
return l.servicesMap
} }
// Init cleanup all the registered service information // Init cleanup all the registered service information
// and the local persistent file // and the local persistent file
func (l *localStore) Init() error { func (l *fileStore) Init() error {
if err := l.memoryStore.Init(); err != nil {
return err
}
l.mu.Lock() l.mu.Lock()
defer l.mu.Unlock() defer l.mu.Unlock()
if err := os.Remove(l.persistentFile); err != nil && !os.IsNotExist(err) { if err := os.Remove(l.persistentFile); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete file %s, err: %v", l.persistentFile, err) return fmt.Errorf("failed to delete file %s, err: %v", l.persistentFile, err)
} }
l.servicesMap = make(map[string][]string, 0)
return nil return nil
} }
// load all registered service from file to memory // load all registered service from file to memory
func (l *localStore) load() (map[string][]string, error) { func (l *fileStore) load() error {
var svrsMap = make(map[string][]string, 0) var svrsMap = make(map[string][]string, 0)
f, err := os.Open(l.persistentFile) f, err := os.Open(l.persistentFile)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return svrsMap, nil return nil
} }
return svrsMap, fmt.Errorf("failed to open file, path: %s, err: %v", l.persistentFile, err) return fmt.Errorf("failed to open file, path: %s, err: %v", l.persistentFile, err)
} }
defer f.Close() defer f.Close()
@ -129,13 +135,19 @@ func (l *localStore) load() (map[string][]string, error) {
} }
if err := ns.Err(); err != nil { if err := ns.Err(); err != nil {
return svrsMap, fmt.Errorf("read file failed, file: %s, err: %v", l.persistentFile, err) return fmt.Errorf("read file failed, file: %s, err: %v", l.persistentFile, err)
} }
return svrsMap, nil // set information to memory
l.memoryStore.Set(svrsMap)
return nil
} }
func (l *localStore) appendToFile(s Service) error { func (l *fileStore) Set(services map[string][]string) {
panic("TO BE IMPLEMENTED")
}
func (l *fileStore) appendToFile(s Service) error {
f, err := os.OpenFile(l.persistentFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) f, err := os.OpenFile(l.persistentFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil { if err != nil {
return err return err
@ -159,17 +171,67 @@ func split(r rune) bool {
return r == '&' return r == '&'
} }
// NewStore creates a store using local file // memoryStore holds the registered services only into memory
func NewStore() Store { type memoryStore struct {
l := &localStore{ mu sync.RWMutex
persistentFile: PersistenceFile, servicesMap map[string][]string
servicesMap: make(map[string][]string, 0),
} }
services, err := l.load() // NewMemoryStore creates a memory store
if err != nil { func NewMemoryStore() Store {
log.Fatalf("load failed, file: %s, err: %v", l.persistentFile, err) return &memoryStore{
servicesMap: make(map[string][]string, 0),
} }
}
// Add adds the given service to MemoryStore
func (l *memoryStore) Add(s Service) error {
l.mu.Lock()
defer l.mu.Unlock()
// load to memory
if addrs, ok := l.servicesMap[s.Name]; ok {
for _, addr := range addrs {
if addr == s.Address {
log.Printf("service registered already, name: %s, address: %s", s.Name, s.Address)
return nil
}
}
addrs = append(addrs, s.Address)
l.servicesMap[s.Name] = addrs
} else {
l.servicesMap[s.Name] = []string{s.Address}
}
return nil
}
// Get returns the registered service information with the given name
func (l *memoryStore) Get(name string) []string {
l.mu.RLock()
defer l.mu.RUnlock()
return l.servicesMap[name]
}
// Get returns all the registered service information
func (l *memoryStore) GetAll() map[string][]string {
l.mu.RLock()
defer l.mu.RUnlock()
return l.servicesMap
}
// Init cleanup all the registered service information
// and the local persistent file
func (l *memoryStore) Init() error {
l.mu.Lock()
defer l.mu.Unlock()
l.servicesMap = make(map[string][]string, 0)
return nil
}
func (l *memoryStore) Set(services map[string][]string) {
l.mu.Lock()
defer l.mu.Unlock()
l.servicesMap = services l.servicesMap = services
return l
} }

View File

@ -21,7 +21,7 @@ import (
) )
func TestLocalStore(t *testing.T) { func TestLocalStore(t *testing.T) {
localStore := NewStore() localStore := NewFileStore()
var tc1 = Service{ var tc1 = Service{
Name: "a", Name: "a",
Address: "http://127.0.0.1", Address: "http://127.0.0.1",
@ -44,7 +44,7 @@ func TestLocalStore(t *testing.T) {
localStore.Add(tc4) localStore.Add(tc4)
addrs := localStore.Get(tc1.Name) addrs := localStore.Get(tc1.Name)
if len(addrs) != 2 { if len(addrs) != 2 {
t.Error("unexpect result") t.Error("unexpected result")
} }
for _, addr := range addrs { for _, addr := range addrs {