diff --git a/Makefile b/Makefile index 6be341a..78d3f40 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,9 @@ .PHONY: build test inttest clean build: - GOOS=linux GOARCH=amd64 go build -o bin/gcov2lcov-linux-amd64 . - GOOS=windows GOARCH=amd64 go build -o bin/gcov2lcov-win-amd64 . - GOOS=darwin GOARCH=amd64 go build -o bin/gcov2lcov-darwin-amd64 . + GOOS=linux GOARCH=amd64 go build -o bin/gcov2lcov-linux-amd64 ./cmd + GOOS=windows GOARCH=amd64 go build -o bin/gcov2lcov-win-amd64 ./cmd + GOOS=darwin GOARCH=amd64 go build -o bin/gcov2lcov-darwin-amd64 ./cmd test: go test ./... -coverprofile coverage.out diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..d6f7acf --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,68 @@ +// gcov2lcov - convert golang coverage files to the lcov format. +// +// Copyright (c) 2019 Jan Delgado +// Copyright (c) 2019 Richard S Allinson +// +// Credits: +// This tool is based on covfmt (https://github.com/ricallinson/covfmt) and +// uses some parts of goveralls (https://github.com/mattn/goveralls). +// +package main + +import ( + "flag" + "github.com/jandelgado/gcov2lcov" + "log" + "os" +) + +func main() { + os.Exit(gcovmain()) +} + +func gcovmain() int { + infileName := flag.String("infile", "", "go coverage file to read, default: ") + outfileName := flag.String("outfile", "", "lcov file to write, default: ") + useAbsoluteSourcePath := flag.Bool("use-absolute-source-path", false, + "use absolute paths for source file in lcov output, default: false") + flag.Parse() + if len(flag.Args()) > 0 { + flag.Usage() + return 1 + } + + infile := os.Stdin + outfile := os.Stdout + var err error + if *infileName != "" { + infile, err = os.Open(*infileName) + if err != nil { + log.Printf("error opening input file: %v\n", err) + return 2 + } + defer infile.Close() + } + if *outfileName != "" { + outfile, err = os.Create(*outfileName) + if err != nil { + log.Printf("error opening output file: %v\n", err) + return 3 + } + defer outfile.Close() + } + + var pathResolverFunc gcov2lcov.PathResolver + if *useAbsoluteSourcePath { + pathResolverFunc = gcov2lcov.AbsolutePathResolver + } else { + pathResolverFunc = gcov2lcov.RelativePathResolver + } + + err = gcov2lcov.ConvertCoverage(infile, outfile, pathResolverFunc) + if err != nil { + log.Printf("error: convert: %v", err) + return 4 + } + return 0 +} + diff --git a/main.go b/gcov2lcov.go similarity index 77% rename from main.go rename to gcov2lcov.go index e9d6496..5a5f7ee 100644 --- a/main.go +++ b/gcov2lcov.go @@ -7,12 +7,11 @@ // This tool is based on covfmt (https://github.com/ricallinson/covfmt) and // uses some parts of goveralls (https://github.com/mattn/goveralls). // -package main +package gcov2lcov import ( "bufio" "errors" - "flag" "go/build" "io" "log" @@ -41,6 +40,33 @@ type cacheEntry struct { var pkgCache = map[string]cacheEntry{} +type PathResolver func(name string) (string, error) + +func ConvertCoverage(in io.Reader, out io.Writer, pathResolverFunc PathResolver) error { + blocks, err := parseCoverage(in, pathResolverFunc) + if err != nil { + return err + } + return writeLcov(blocks, out) +} + +func AbsolutePathResolver(name string) (string, error) { + return name, nil +} + +func RelativePathResolver(name string) (string, error) { + name, err := findFile(name) + if err != nil { + return "", err + } + + if dir, ok := findRepositoryRoot(name); ok { + filename := strings.TrimPrefix(name, dir+string(os.PathSeparator)) + return filename, nil + } + return name, nil +} + // given a module+file spec (e.g. github.com/jandelgado/gcov2lcov/main.go), // strip of the module name and return the file name (e.g. main.go). func findFile(filePath string) (string, error) { @@ -73,18 +99,6 @@ func findRepositoryRoot(dir string) (string, bool) { return findRepositoryRoot(nextdir) } -func getSourceFileName(name string) string { - return name -} - -func getCoverallsSourceFileName(name string) string { - if dir, ok := findRepositoryRoot(name); ok { - filename := strings.TrimPrefix(name, dir+string(os.PathSeparator)) - return filename - } - return name -} - func keysOfMap(m map[int]int) []int { keys := make([]int, len(m)) i := 0 @@ -203,7 +217,7 @@ func parseCoverageLine(line string) (string, *block, error) { return path[0], b, err } -func parseCoverage(coverage io.Reader, pathResolverFunc func(string) string) (map[string][]*block, error) { +func parseCoverage(coverage io.Reader, pathResolverFunc PathResolver) (map[string][]*block, error) { scanner := bufio.NewScanner(coverage) blocks := map[string][]*block{} for scanner.Scan() { @@ -212,14 +226,12 @@ func parseCoverage(coverage io.Reader, pathResolverFunc func(string) string) (ma continue } if f, b, err := parseCoverageLine(line); err == nil { - f, err := findFile(f) + f, err = pathResolverFunc(f) if err != nil { log.Printf("warn: %v", err) continue } - f = pathResolverFunc(f) - // Make sure the filePath is a key in the map. if _, found := blocks[f]; !found { blocks[f] = []*block{} @@ -235,61 +247,3 @@ func parseCoverage(coverage io.Reader, pathResolverFunc func(string) string) (ma } return blocks, nil } - -func convertCoverage(in io.Reader, out io.Writer, pathResolverFunc func(string) string) error { - blocks, err := parseCoverage(in, pathResolverFunc) - if err != nil { - return err - } - return writeLcov(blocks, out) -} - -func main() { - os.Exit(gcovmain()) -} - -func gcovmain() int { - infileName := flag.String("infile", "", "go coverage file to read, default: ") - outfileName := flag.String("outfile", "", "lcov file to write, default: ") - useAbsoluteSourcePath := flag.Bool("use-absolute-source-path", false, - "use absolute paths for source file in lcov output, default: false") - flag.Parse() - if len(flag.Args()) > 0 { - flag.Usage() - return 1 - } - - infile := os.Stdin - outfile := os.Stdout - var err error - if *infileName != "" { - infile, err = os.Open(*infileName) - if err != nil { - log.Printf("error opening input file: %v\n", err) - return 2 - } - defer infile.Close() - } - if *outfileName != "" { - outfile, err = os.Create(*outfileName) - if err != nil { - log.Printf("error opening output file: %v\n", err) - return 3 - } - defer outfile.Close() - } - - var pathResolverFunc func(string) string - if *useAbsoluteSourcePath { - pathResolverFunc = getSourceFileName - } else { - pathResolverFunc = getCoverallsSourceFileName - } - - err = convertCoverage(infile, outfile, pathResolverFunc) - if err != nil { - log.Printf("error: convert: %v", err) - return 4 - } - return 0 -} diff --git a/main_test.go b/gcov2lcov_test.go similarity index 70% rename from main_test.go rename to gcov2lcov_test.go index 8d3817d..d7610b3 100644 --- a/main_test.go +++ b/gcov2lcov_test.go @@ -1,14 +1,10 @@ -// gcov2lcov - convert golang coverage files to the lcov format. -// (c) 2019 Jan Delgado -package main +package gcov2lcov import ( "bytes" - "os" + "github.com/stretchr/testify/assert" "strings" "testing" - - "github.com/stretchr/testify/assert" ) func TestKeysOfMapReturnsAllKeysOfMap(t *testing.T) { @@ -35,11 +31,11 @@ func TestParseCoverageLineFailsOnInvalidLines(t *testing.T) { } func TestParseCoverageLineOfParsesValidLineCorrectly(t *testing.T) { - line := "github.com/jandelgado/gcov2lcov/main.go:6.14,8.3 2 1" + line := "github.com/jandelgado/gcov2lcov/cmd/main.go:6.14,8.3 2 1" file, b, err := parseCoverageLine(line) assert.Nil(t, err) - assert.Equal(t, "github.com/jandelgado/gcov2lcov/main.go", file) + assert.Equal(t, "github.com/jandelgado/gcov2lcov/cmd/main.go", file) assert.Equal(t, 6, b.startLine) assert.Equal(t, 14, b.startChar) assert.Equal(t, 8, b.endLine) @@ -53,17 +49,17 @@ func TestParseCoverage(t *testing.T) { // note: in this integrative test, the package path must match the actual // repository name of this project. cov := `mode: set -github.com/jandelgado/gcov2lcov/main.go:6.14,8.3 2 1` +github.com/jandelgado/gcov2lcov/cmd/main.go:6.14,8.3 2 1` reader := strings.NewReader(cov) - res, err := parseCoverage(reader, getCoverallsSourceFileName) + res, err := parseCoverage(reader, RelativePathResolver) assert.NoError(t, err) assert.Equal(t, 1, len(res)) for k, blks := range res { assert.Equal(t, 1, len(blks)) b := blks[0] - assert.Equal(t, "main.go", k) + assert.Equal(t, "cmd/main.go", k) assert.Equal(t, 6, b.startLine) assert.Equal(t, 14, b.startChar) assert.Equal(t, 8, b.endLine) @@ -78,16 +74,16 @@ func TestConvertCoverage(t *testing.T) { // repository name of this project. Format: // name.go:line.column,line.column numberOfStatements count cov := `mode: set -github.com/jandelgado/gcov2lcov/main.go:6.14,8.3 2 1 -github.com/jandelgado/gcov2lcov/main.go:7.14,9.3 2 0 -github.com/jandelgado/gcov2lcov/main.go:10.1,11.10 2 2` +github.com/jandelgado/gcov2lcov/cmd/main.go:6.14,8.3 2 1 +github.com/jandelgado/gcov2lcov/cmd/main.go:7.14,9.3 2 0 +github.com/jandelgado/gcov2lcov/cmd/main.go:10.1,11.10 2 2` in := strings.NewReader(cov) out := bytes.NewBufferString("") - err := convertCoverage(in, out, getCoverallsSourceFileName) + err := ConvertCoverage(in, out, RelativePathResolver) expected := `TN: -SF:main.go +SF:cmd/main.go DA:6,1 DA:7,1 DA:8,1 @@ -103,12 +99,11 @@ end_of_record } func TestPathResolverFunc(t *testing.T) { - pwd, err := os.Getwd() + name, err := RelativePathResolver("github.com/jandelgado/gcov2lcov/cmd/main.go") assert.NoError(t, err) + assert.Equal(t, "cmd/main.go", name) - name := getCoverallsSourceFileName(pwd + "/main.go") - assert.Equal(t, "main.go", name) - - name = getSourceFileName(pwd + "/main.go") - assert.Equal(t, pwd+"/main.go", name) + name, err = AbsolutePathResolver("github.com/jandelgado/gcov2lcov/cmd/main.go") + assert.NoError(t, err) + assert.Equal(t, "github.com/jandelgado/gcov2lcov/cmd/main.go", name) }