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

feat: coverage diff #127

Merged
merged 16 commits into from
Nov 21, 2024
11 changes: 10 additions & 1 deletion .testcoverage.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,13 @@ exclude:
# Exclude files or packages matching their paths
paths:
- \.pb\.go$ # excludes all protobuf generated files
- ^pkg/bar # exclude package `pkg/bar`
- ^pkg/bar # exclude package `pkg/bar`

# File name of go-test-coverage breakdown file, which can be used to
# analyze coverage difference.
breakdown-file-name: ''

diff:
# File name of go-test-coverage breakdown file which will be used to
# report coverage difference.
base-breakdown-file-name: ''
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ lint: get-golangcilint
.PHONY: test
test:
go test -timeout=3s -race -count=10 -failfast -shuffle=on -short ./...
go test -timeout=10s -race -count=1 -failfast -shuffle=on ./... -coverprofile=./cover.profile -covermode=atomic -coverpkg=./...
go test -timeout=20s -race -count=1 -failfast -shuffle=on ./... -coverprofile=./cover.profile -covermode=atomic -coverpkg=./...

# Runs test coverage check
.PHONY: check-coverage
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ exclude:
paths:
- \.pb\.go$ # excludes all protobuf generated files
- ^pkg/bar # exclude package `pkg/bar`

# File name of go-test-coverage breakdown file, which can be used to
# analyze coverage difference.
breakdown-file-name: ''

diff:
# File name of go-test-coverage breakdown file which will be used to
# report coverage difference.
base-breakdown-file-name: ''
```

### Exclude Code from Coverage
Expand Down
14 changes: 14 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ inputs:
default: -1
type: number

breakdown-file-name:
description: File name of go-test-coverage breakdown file, which can be used to analyze coverage difference. Overrides value from configuration.
required: false
default: ""
type: string

diff-base-breakdown-file-name:
description: File name of go-test-coverage breakdown file used to calculate coverage difference from current (head).
required: false
default: ""
type: string

# Badge (as file)
badge-file-name:
description: If specified, a coverage badge will be generated and saved to the given file path.
Expand Down Expand Up @@ -123,6 +135,8 @@ runs:
- --threshold-file=${{ inputs.threshold-file }}
- --threshold-package=${{ inputs.threshold-package }}
- --threshold-total=${{ inputs.threshold-total }}
- --breakdown-file-name=${{ inputs.breakdown-file-name || '''''' }}
- --diff-base-breakdown-file-name=${{ inputs.diff-base-breakdown-file-name || '''''' }}
- --badge-file-name=${{ inputs.badge-file-name || '''''' }}
- --cdn-key=${{ inputs.cdn-key || '''''' }}
- --cdn-secret=${{ inputs.cdn-secret || '''''' }}
Expand Down
19 changes: 17 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ type args struct {
ThresholdFile int `arg:"-f,--threshold-file"`
ThresholdPackage int `arg:"-k,--threshold-package"`
ThresholdTotal int `arg:"-t,--threshold-total"`
BadgeFileName string `arg:"-b,--badge-file-name"`

BreakdownFileName string `arg:"--breakdown-file-name"`
DiffBaseBreakdownFileName string `arg:"--diff-base-breakdown-file-name"`

BadgeFileName string `arg:"-b,--badge-file-name"`

CDNKey string `arg:"--cdn-key"`
CDNSecret string `arg:"--cdn-secret"`
Expand All @@ -57,6 +61,9 @@ func newArgs() args {
ThresholdPackage: ciDefaultInt,
ThresholdTotal: ciDefaultInt,

BreakdownFileName: ciDefaultString,
DiffBaseBreakdownFileName: ciDefaultString,

// Badge
BadgeFileName: ciDefaultString,

Expand All @@ -81,7 +88,7 @@ func (args) Version() string {
return Name + " " + Version
}

//nolint:cyclop,maintidx,mnd // relax
//nolint:cyclop,maintidx,mnd,funlen // relax
func (a *args) overrideConfig(cfg testcoverage.Config) (testcoverage.Config, error) {
if !isCIDefaultString(a.Profile) {
cfg.Profile = a.Profile
Expand All @@ -107,6 +114,14 @@ func (a *args) overrideConfig(cfg testcoverage.Config) (testcoverage.Config, err
cfg.Threshold.Total = a.ThresholdTotal
}

if !isCIDefaultString(a.BreakdownFileName) {
cfg.BreakdownFileName = a.BreakdownFileName
}

if !isCIDefaultString(a.DiffBaseBreakdownFileName) {
cfg.Diff.BaseBreakdownFileName = a.DiffBaseBreakdownFileName
}

if !isCIDefaultString(a.BadgeFileName) {
cfg.Badge.FileName = a.BadgeFileName
}
Expand Down
66 changes: 56 additions & 10 deletions pkg/testcoverage/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,32 @@ import (
"bytes"
"fmt"
"io"
"os"
"strings"

"github.com/vladopajic/go-test-coverage/v2/pkg/testcoverage/coverage"
)

func Check(w io.Writer, cfg Config) bool {
stats, err := coverage.GenerateCoverageStats(coverage.Config{
Profiles: strings.Split(cfg.Profile, ","),
LocalPrefix: cfg.LocalPrefix,
ExcludePaths: cfg.Exclude.Paths,
})
currentStats, err := GenerateCoverageStats(cfg)
if err != nil {
fmt.Fprintf(w, "failed to generate coverage statistics: %v\n", err)
return false
}

result := Analyze(cfg, stats)
err = saveCoverageBreakdown(cfg, currentStats)
if err != nil {
fmt.Fprintf(w, "failed to save coverage breakdown: %v\n", err)
return false
}

baseStats, err := loadBaseCoverageBreakdown(cfg)
if err != nil {
fmt.Fprintf(w, "failed to load base coverage breakdown: %v\n", err)
return false
}

result := Analyze(cfg, currentStats, baseStats)

report := reportForHuman(w, result)

Expand Down Expand Up @@ -56,16 +65,53 @@ func reportForHuman(w io.Writer, result AnalyzeResult) string {
return buffer.String()
}

func Analyze(cfg Config, coverageStats []coverage.Stats) AnalyzeResult {
func GenerateCoverageStats(cfg Config) ([]coverage.Stats, error) {
return coverage.GenerateCoverageStats(coverage.Config{ //nolint:wrapcheck // err wrapped above
Profiles: strings.Split(cfg.Profile, ","),
LocalPrefix: cfg.LocalPrefix,
ExcludePaths: cfg.Exclude.Paths,
})
}

func Analyze(cfg Config, current, base []coverage.Stats) AnalyzeResult {
thr := cfg.Threshold
overrideRules := compileOverridePathRules(cfg)

return AnalyzeResult{
Threshold: thr,
FilesBelowThreshold: checkCoverageStatsBelowThreshold(coverageStats, thr.File, overrideRules),
FilesBelowThreshold: checkCoverageStatsBelowThreshold(current, thr.File, overrideRules),
PackagesBelowThreshold: checkCoverageStatsBelowThreshold(
makePackageStats(coverageStats), thr.Package, overrideRules,
makePackageStats(current), thr.Package, overrideRules,
),
TotalStats: coverage.CalcTotalStats(coverageStats),
TotalStats: coverage.CalcTotalStats(current),
HasBaseBreakdown: len(base) > 0,
Diff: calculateStatsDiff(current, base),
}
}

func saveCoverageBreakdown(cfg Config, stats []coverage.Stats) error {
if cfg.BreakdownFileName == "" {
return nil
}

//nolint:mnd,wrapcheck,gosec // relax
return os.WriteFile(cfg.BreakdownFileName, coverage.SerializeStats(stats), 0o644)
}

func loadBaseCoverageBreakdown(cfg Config) ([]coverage.Stats, error) {
if cfg.Diff.BaseBreakdownFileName == "" {
return nil, nil
}

data, err := os.ReadFile(cfg.Diff.BaseBreakdownFileName)
if err != nil {
return nil, fmt.Errorf("reading file content failed: %w", err)
}

stats, err := coverage.DeserializeStats(data)
if err != nil {
return nil, fmt.Errorf("parsing file failed: %w", err)
}

return stats, nil
}
Loading
Loading