Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add enforce option and pre-commit hook #22

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- id: go-carpet
name: go-carpet
entry: .pre-commit-runner.sh
language: script
files: \.go
description: enforce a minimum level of coverage on changed files
args:
- "-mincov=50"
15 changes: 15 additions & 0 deletions .pre-commit-runner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/bash

args=""
while [ $# -gt 0 ]; do
if [[ "$1" == -* ]]; then
args+="$1"
else
space_files=$@
files=${space_files// /,}
break
fi
shift
done

go-carpet -summary -enforce $args -file $files
50 changes: 39 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,54 @@ Usage

usage: go-carpet [options] [paths]
-256colors
use more colors on 256-color terminal (indicate the level of coverage)
-args string
pass additional arguments for go test
-file string
comma-separated list of files to test (default: all)
-func string
comma-separated functions list (default: all functions)
use more colors on 256-color terminal (indicate the level of coverage)
-args arguments
pass additional arguments for go test
-enforce
fail if any file's coverage is below mincov
-file files
comma-separated list of files to test (default: all)
-func functions
comma-separated functions list (default: all functions)
-include-vendor
include vendor directories for show coverage (Godeps, vendor)
include vendor directories for show coverage (Godeps, vendor)
-mincov float
coverage threshold of the file to be displayed (in percent) (default 100)
coverage threshold of the file to be displayed (in percent) (default 100)
-summary
only show summary for each file
only show summary for each file
-version
get version
get version

For view coverage in less, use `-R` option:

go-carpet | less -R

As a pre-commit hook
--------------------

[Pre-commit](https://pre-commit.com) is a tool that makes it easy to run checks on every Git commit.
You can use `go-carpet` as a pre-commit hook by installing pre-commit and including the following
in your `.pre-commit-config.yaml`:

```
repos:
...
- repo: https://github.com/msoap/go-carpet
rev: v1.11.0
hooks:
- id: go-carpet
```

By default, `go-carpet` will enforce 50% test coverage on every changed file. You can customize
the threshold in your hook with an arguments block as follows:

```
args:
- "-mincov=X"
```

You must have `go-carpet` installed in your PATH for this pre-commit hook to work.

Install
-------

Expand Down
4 changes: 4 additions & 0 deletions go-carpet.1
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ usage: go\-carpet [options] [paths]
use more colors on 256\-color terminal (indicate the level of coverage)
\-args string
pass additional arguments for go test
\-enforce
fail if any file's coverage is below mincov
\-file string
comma\-separated list of files to test (default: all)
\-func string
comma\-separated functions list (default: all functions)
\-include\-vendor
include vendor directories for show coverage (Godeps, vendor)
\-mincov float
coverage threshold of the file to be displayed (in percent) (default 100)
\-summary
only show summary for each file
\-version
Expand Down
58 changes: 39 additions & 19 deletions go-carpet.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ func guessAbsPathInGOPATH(GOPATH, relPath string) (absPath string, err error) {
return absPath, err
}

func getCoverForDir(coverFileName string, filesFilter []string, config Config) (result []byte, profileBlocks []cover.ProfileBlock, err error) {
func getCoverForDir(coverFileName string, filesFilter []string, config Config) (result []byte, belowMin bool, profileBlocks []cover.ProfileBlock, err error) {
coverProfile, err := cover.ParseProfiles(coverFileName)
if err != nil {
return result, profileBlocks, err
return result, belowMin, profileBlocks, err
}

for _, fileProfile := range coverProfile {
// Skip files if minimal coverage is set and is covered more than minimal coverage
if config.minCoverage > 0 && config.minCoverage < 100.0 && getStatForProfileBlocks(fileProfile.Blocks) > config.minCoverage {
if config.minCoverage >= 0 && config.minCoverage <= 100.0 && getStatForProfileBlocks(fileProfile.Blocks) >= config.minCoverage {
continue
}

Expand All @@ -179,31 +179,33 @@ func getCoverForDir(coverFileName string, filesFilter []string, config Config) (
}
} else if fileName, err = guessAbsPathInGoMod(fileProfile.FileName); err != errIsNotInGoMod {
if err != nil {
return result, profileBlocks, err
return result, belowMin, profileBlocks, err
}
} else {
// file in one dir in GOPATH
fileName, err = guessAbsPathInGOPATH(os.Getenv("GOPATH"), fileProfile.FileName)
if err != nil {
return result, profileBlocks, err
return result, belowMin, profileBlocks, err
}
}

if len(filesFilter) > 0 && !isSliceInString(fileName, filesFilter) {
continue
}

// If we get here, we're below minimum specified coverage and we weren't filtered out
belowMin = true
var fileBytes []byte
fileBytes, err = readFile(fileName)
if err != nil {
return result, profileBlocks, err
return result, belowMin, profileBlocks, err
}

result = append(result, getCoverForFile(fileProfile, fileBytes, config)...)
profileBlocks = append(profileBlocks, fileProfile.Blocks...)
}

return result, profileBlocks, err
return result, belowMin, profileBlocks, err
}

func getColorHeader(header string, addUnderiline bool) string {
Expand Down Expand Up @@ -355,15 +357,16 @@ func getTempFileName() (string, error) {

// Config - application config
type Config struct {
filesFilterRaw string
filesFilter []string
funcFilterRaw string
funcFilter []string
argsRaw string
minCoverage float64
colors256 bool
includeVendor bool
summary bool
filesFilterRaw string
filesFilter []string
funcFilterRaw string
funcFilter []string
argsRaw string
minCoverage float64
colors256 bool
includeVendor bool
summary bool
enforceCoverage bool
}

var config Config
Expand All @@ -376,20 +379,21 @@ func init() {
flag.BoolVar(&config.includeVendor, "include-vendor", false, "include vendor directories for show coverage (Godeps, vendor)")
flag.StringVar(&config.argsRaw, "args", "", "pass additional `arguments` for go test")
flag.Float64Var(&config.minCoverage, "mincov", 100.0, "coverage threshold of the file to be displayed (in percent)")
flag.BoolVar(&config.enforceCoverage, "enforce", false, "fail if any file's coverage is below mincov")
flag.Usage = func() {
fmt.Println(usageMessage)
flag.PrintDefaults()
os.Exit(0)
}
}

func main() {
func goCarpet() int {
versionFl := flag.Bool("version", false, "get version")
flag.Parse()

if *versionFl {
fmt.Println(version)
os.Exit(0)
return 0
}

config.filesFilter = grepEmptyStringSlice(strings.Split(config.filesFilterRaw, ","))
Expand Down Expand Up @@ -424,17 +428,23 @@ func main() {
log.Fatal(err)
}

someFileBelowMin := false
testsPass := true
for _, path := range testDirs {
if err = runGoTest(path, coverFileName, additionalArgs, false); err != nil {
log.Print(err)
testsPass = false
continue
}

coverInBytes, profileBlocks, errCover := getCoverForDir(coverFileName, config.filesFilter, config)
coverInBytes, belowMin, profileBlocks, errCover := getCoverForDir(coverFileName, config.filesFilter, config)
if errCover != nil {
log.Print(errCover)
continue
}
if belowMin {
someFileBelowMin = true
}
_, err = stdOut.Write(coverInBytes)
if err != nil {
log.Fatal(err)
Expand All @@ -451,4 +461,14 @@ func main() {
log.Fatal(err)
}
}

if !testsPass || (someFileBelowMin && config.enforceCoverage) {
return 1
}

return 0
}

func main() {
os.Exit(goCarpet())
}
24 changes: 16 additions & 8 deletions unix_only_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import (

func Test_getCoverForDir(t *testing.T) {
t.Run("error", func(t *testing.T) {
_, _, err := getCoverForDir("./testdata/not_exists.out", []string{}, Config{colors256: false})
_, _, _, err := getCoverForDir("./testdata/not_exists.out", []string{}, Config{colors256: false})
if err == nil {
t.Errorf("1. getCoverForDir() error failed")
}
})

t.Run("cover", func(t *testing.T) {
bytes, _, err := getCoverForDir("./testdata/cover_00.out", []string{}, Config{colors256: false})
bytes, _, _, err := getCoverForDir("./testdata/cover_00.out", []string{}, Config{colors256: false})
if err != nil {
t.Errorf("2. getCoverForDir() failed: %v", err)
}
Expand All @@ -31,7 +31,7 @@ func Test_getCoverForDir(t *testing.T) {
})

t.Run("cover with 256 colors", func(t *testing.T) {
bytes, _, err := getCoverForDir("./testdata/cover_00.out", []string{}, Config{colors256: true})
bytes, _, _, err := getCoverForDir("./testdata/cover_00.out", []string{}, Config{colors256: true})
if err != nil {
t.Errorf("5. getCoverForDir() failed: %v", err)
}
Expand All @@ -45,14 +45,14 @@ func Test_getCoverForDir(t *testing.T) {
})

t.Run("cover with 256 colors with error", func(t *testing.T) {
_, _, err := getCoverForDir("./testdata/cover_01.out", []string{}, Config{colors256: true})
_, _, _, err := getCoverForDir("./testdata/cover_01.out", []string{}, Config{colors256: true})
if err == nil {
t.Errorf("8. getCoverForDir() not exists go file")
}
})

t.Run("cover 01 without 256 colors", func(t *testing.T) {
bytes, _, err := getCoverForDir("./testdata/cover_00.out", []string{"file_01.go"}, Config{colors256: false})
bytes, _, _, err := getCoverForDir("./testdata/cover_00.out", []string{"file_01.go"}, Config{colors256: false})
if err != nil {
t.Errorf("9. getCoverForDir() failed: %v", err)
}
Expand All @@ -66,7 +66,7 @@ func Test_getCoverForDir(t *testing.T) {
})

t.Run("cover 02 without 256 colors", func(t *testing.T) {
bytes, _, err := getCoverForDir("./testdata/cover_02.out", []string{}, Config{colors256: false})
bytes, _, _, err := getCoverForDir("./testdata/cover_02.out", []string{}, Config{colors256: false})
if err != nil {
t.Errorf("12. getCoverForDir() failed: %v", err)
}
Expand All @@ -88,7 +88,7 @@ func Test_getCoverForDir_mincov_flag(t *testing.T) {
}

// cover_00.out has 100% coverage of 2 files
_, profileBlocks, err := getCoverForDir("./testdata/cover_00.out", []string{"file_01.go"}, conf)
_, belowMin, profileBlocks, err := getCoverForDir("./testdata/cover_00.out", []string{"file_01.go"}, conf)
if err != nil {
t.Errorf("getCoverForDir() failed with error: %s", err)
}
Expand All @@ -99,6 +99,10 @@ func Test_getCoverForDir_mincov_flag(t *testing.T) {
if expectLen != actualLen {
t.Errorf("1. minimum coverage 100%% should print all the blocks. want %v, got: %v", expectLen, actualLen)
}

if !belowMin {
t.Errorf("1. at least one file was below minimum, but we didn't detect it")
}
})

t.Run("covered 100% mincov 50%", func(t *testing.T) {
Expand All @@ -108,7 +112,7 @@ func Test_getCoverForDir_mincov_flag(t *testing.T) {
}

// cover_00.out has 100% coverage of 2 files
_, profileBlocks, err := getCoverForDir("./testdata/cover_00.out", []string{"file_01.go"}, conf)
_, belowMin, profileBlocks, err := getCoverForDir("./testdata/cover_00.out", []string{"file_01.go"}, conf)
if err != nil {
t.Errorf("getCoverForDir() failed with error: %s", err)
}
Expand All @@ -119,5 +123,9 @@ func Test_getCoverForDir_mincov_flag(t *testing.T) {
if expectLen != actualLen {
t.Errorf("2. minimum coverage 50%% for 100%% covered source should print nothing. want %v, got: %v", expectLen, actualLen)
}

if belowMin {
t.Errorf("2. all files were above minimum coverage but we thought one wasn't")
}
})
}