Skip to content

Commit bc0da0b

Browse files
committed
add total coverage to env name
more effort more effort
1 parent 168f86c commit bc0da0b

File tree

8 files changed

+166
-63
lines changed

8 files changed

+166
-63
lines changed

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ linters-settings:
9292
govet:
9393
check-shadowing: true
9494
enable-all: true
95+
disable:
96+
- fieldalignment
9597
lll:
9698
line-length: 96
9799
tab-width: 1

.testcoverage.example.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1-
# (mandatory) Path to coverprofile file (output of `go test -coverprofile` command)
1+
# (mandatory)
2+
# Path to coverprofile file (output of `go test -coverprofile` command)
23
profile: cover.out
34

4-
# (optional) When specified reported file paths will not contain local prefix in the output
5+
# (optional; default false)
6+
# When set to `true` tool will output github-action friendly outputs
7+
githubActionOutput: true
8+
9+
# (optional)
10+
# When specified reported file paths will not contain local prefix in the output
511
localPrefix: "github.com/org/project"
612

713
# Holds coverage thresholds percentages, values should be in range [0-100]
814
threshold:
9-
# (optional; default 0) The minimum coverage that each file should have
15+
# (optional; default 0)
16+
# The minimum coverage that each file should have
1017
file: 80
1118

12-
# (optional; default 0) The minimum coverage that each package should have
19+
# (optional; default 0)
20+
# The minimum coverage that each package should have
1321
package: 80
1422

15-
# (optional; default 50) The minimum total coverage project should have
23+
# (optional; default 50)
24+
# The minimum total coverage project should have
1625
total: 95

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# go-test-coverage
22

3-
[![lint](https://github.com/vladopajic/go-test-coverage/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/vladopajic/go-test-coverage/actions/workflows/lint.yml)
43
[![test](https://github.com/vladopajic/go-test-coverage/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/vladopajic/go-test-coverage/actions/workflows/test.yml)
4+
[![lint](https://github.com/vladopajic/go-test-coverage/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/vladopajic/go-test-coverage/actions/workflows/lint.yml)
55
[![Go Report Card](https://goreportcard.com/badge/github.com/vladopajic/go-test-coverage?cache=v1)](https://goreportcard.com/report/github.com/vladopajic/go-test-coverage)
66
[![GoDoc](https://godoc.org/github.com/vladopajic/go-test-coverage?status.svg)](https://godoc.org/github.com/vladopajic/go-test-coverage)
77
[![Release](https://img.shields.io/github/release/vladopajic/go-test-coverage.svg?style=flat-square)](https://github.com/vladopajic/go-test-coverage/releases/latest)
@@ -31,21 +31,30 @@ steps:
3131
Example of [.testcoverage.yml](./.testcoverage.example.yml) config file.
3232
3333
```yml
34-
# (mandatory) Path to coverprofile file (output of `go test -coverprofile` command)
34+
# (mandatory)
35+
# Path to coverprofile file (output of `go test -coverprofile` command)
3536
profile: cover.out
3637

37-
# (optional) When specified reported file paths will not contain local prefix in the output
38+
# (optional; default true)
39+
# When set to `true` tool will output github-action friendly outputs
40+
githubActionOutput: true
41+
42+
# (optional)
43+
# When specified reported file paths will not contain local prefix in the output
3844
localPrefix: "github.com/org/project"
3945

4046
# Holds coverage thresholds percentages, values should be in range [0-100]
4147
threshold:
42-
# (optional; default 0) The minimum coverage that each file should have
48+
# (optional; default 0)
49+
# The minimum coverage that each file should have
4350
file: 80
4451

45-
# (optional; default 0) The minimum coverage that each package should have
52+
# (optional; default 0)
53+
# The minimum coverage that each package should have
4654
package: 80
4755

48-
# (optional; default 50) The minimum total coverage project should have
56+
# (optional; default 50)
57+
# The minimum total coverage project should have
4958
total: 95
5059
```
5160

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ inputs:
55
config:
66
description: Path of configuration file.
77
required: true
8+
outputs:
9+
total_coverage:
10+
description: Holds total coverage value.
811
runs:
912
using: docker
1013
image: docker://ghcr.io/vladopajic/go-test-coverage:v1.0.0

main.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,22 @@ func main() {
2727
os.Exit(1)
2828
}
2929

30-
if ok := testcoverage.Analyze(*cfg, stats); !ok {
30+
result := testcoverage.Analyze(cfg, stats)
31+
32+
testcoverage.ReportForHuman(result, cfg)
33+
34+
if cfg.GithubActionOutput {
35+
testcoverage.ReportForGithubAction(result, cfg)
36+
}
37+
38+
if !result.Pass() {
3139
os.Exit(1)
3240
}
3341
}
3442

3543
var errConfigNotSpecified = fmt.Errorf("-config argument not specified")
3644

37-
func readConfig() (*testcoverage.Config, error) {
45+
func readConfig() (testcoverage.Config, error) {
3846
configPath := ""
3947
flag.StringVar(
4048
&configPath,
@@ -45,17 +53,17 @@ func readConfig() (*testcoverage.Config, error) {
4553
flag.Parse()
4654

4755
if configPath == "" {
48-
return nil, errConfigNotSpecified
56+
return testcoverage.Config{}, errConfigNotSpecified
4957
}
5058

5159
cfg, err := testcoverage.ConfigFromFile(configPath)
5260
if err != nil {
53-
return nil, fmt.Errorf("failed loading config from file: %w", err)
61+
return testcoverage.Config{}, fmt.Errorf("failed loading config from file: %w", err)
5462
}
5563

5664
if err := cfg.Validate(); err != nil {
57-
return nil, fmt.Errorf("config file is not valid: %w", err)
65+
return testcoverage.Config{}, fmt.Errorf("config file is not valid: %w", err)
5866
}
5967

60-
return cfg, nil
68+
return *cfg, nil
6169
}

pkg/testcoverage/analyze.go

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
package testcoverage
22

33
import (
4-
"bufio"
5-
"fmt"
6-
"io"
7-
"os"
84
"strings"
9-
"text/tabwriter"
105
)
116

12-
//nolint:wsl // relax
13-
func Analyze(cfg Config, coverageStats []CoverageStats) bool {
14-
thr := cfg.Threshold
7+
type AnalyzeResult struct {
8+
FilesBelowThreshold []CoverageStats
9+
PackagesBelowThreshold []CoverageStats
10+
MeetsTotalCoverage bool
11+
TotalCoverage int
12+
}
13+
14+
func (r *AnalyzeResult) Pass() bool {
15+
return r.MeetsTotalCoverage &&
16+
len(r.FilesBelowThreshold) == 0 &&
17+
len(r.PackagesBelowThreshold) == 0
18+
}
1519

16-
out := bufio.NewWriter(os.Stdout)
17-
defer out.Flush()
20+
func Analyze(cfg Config, coverageStats []CoverageStats) AnalyzeResult {
21+
thr := cfg.Threshold
1822

1923
filesBelowThreshold := checkCoverageStatsBelowThreshold(coverageStats, thr.File)
2024
packagesBelowThreshold := checkCoverageStatsBelowThreshold(
@@ -23,44 +27,23 @@ func Analyze(cfg Config, coverageStats []CoverageStats) bool {
2327
totalStats := calcTotalStats(coverageStats)
2428
meetsTotalCoverage := totalStats.CoveredPercentage() >= thr.Total
2529

26-
fmt.Fprintf(out, "Files test coverage meeting the threshold\t(%d%%): ", thr.File)
27-
if len(filesBelowThreshold) > 0 {
28-
fmt.Fprintf(out, "FAIL")
29-
report(out, filesBelowThreshold, cfg.LocalPrefix)
30-
} else {
31-
fmt.Fprintf(out, "PASS")
30+
localPrefix := cfg.LocalPrefix
31+
if localPrefix != "" && (strings.LastIndex(localPrefix, "/") != len(localPrefix)-1) {
32+
localPrefix += "/"
3233
}
3334

34-
fmt.Fprintf(out, "\nPackages test coverage meeting the threshold\t(%d%%): ", thr.Package)
35-
if len(packagesBelowThreshold) > 0 {
36-
fmt.Fprintf(out, "FAIL")
37-
report(out, packagesBelowThreshold, cfg.LocalPrefix)
38-
} else {
39-
fmt.Fprintf(out, "PASS")
35+
return AnalyzeResult{
36+
FilesBelowThreshold: stripLocalPrefix(filesBelowThreshold, localPrefix),
37+
PackagesBelowThreshold: stripLocalPrefix(packagesBelowThreshold, localPrefix),
38+
MeetsTotalCoverage: meetsTotalCoverage,
39+
TotalCoverage: totalStats.CoveredPercentage(),
4040
}
41-
42-
fmt.Fprintf(out, "\nTotal test coverage meeting the threshold\t(%d%%): ", thr.Total)
43-
if !meetsTotalCoverage {
44-
fmt.Fprintf(out, "FAIL")
45-
} else {
46-
fmt.Fprintf(out, "PASS")
47-
}
48-
49-
fmt.Fprintf(out, "\nTotal test coverage: %d%%\n", totalStats.CoveredPercentage())
50-
51-
return len(filesBelowThreshold) == 0 && len(packagesBelowThreshold) == 0 && meetsTotalCoverage
5241
}
5342

54-
func report(w io.Writer, coverageStats []CoverageStats, localPrefix string) {
55-
localPrefix += "/"
56-
57-
tabber := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) //nolint:gomnd // relax
58-
defer tabber.Flush()
59-
60-
for _, stats := range coverageStats {
61-
name := strings.Replace(stats.name, localPrefix, "", 1)
62-
fmt.Fprintf(tabber, "\n%s\t%d%%", name, stats.CoveredPercentage())
43+
func stripLocalPrefix(coverageStats []CoverageStats, localPrefix string) []CoverageStats {
44+
for i, stats := range coverageStats {
45+
coverageStats[i].name = strings.Replace(stats.name, localPrefix, "", 1)
6346
}
6447

65-
fmt.Fprintf(tabber, "\n")
48+
return coverageStats
6649
}

pkg/testcoverage/config.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ const (
1919
)
2020

2121
type Config struct {
22-
Profile string `yaml:"profile"`
23-
LocalPrefix string `yaml:"localPrefix"`
24-
Threshold Threshold `yaml:"threshold"`
22+
Profile string `yaml:"profile"`
23+
LocalPrefix string `yaml:"localPrefix"`
24+
Threshold Threshold `yaml:"threshold"`
25+
GithubActionOutput bool `yaml:"githubActionOutput"`
2526
}
2627

2728
type Threshold struct {
@@ -37,6 +38,7 @@ func NewConfig() Config {
3738
Package: defaultPackageThreshold,
3839
Total: defaultTotalThreshold,
3940
},
41+
GithubActionOutput: true,
4042
}
4143
}
4244

pkg/testcoverage/report.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package testcoverage
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"os"
8+
"strconv"
9+
"text/tabwriter"
10+
)
11+
12+
func ReportForHuman(result AnalyzeResult, cfg Config) {
13+
thr := cfg.Threshold
14+
15+
out := bufio.NewWriter(os.Stdout)
16+
defer out.Flush()
17+
18+
{
19+
fmt.Fprintf(out, "Files meeting coverage threshold of (%d%%):\t", thr.File)
20+
if len(result.FilesBelowThreshold) > 0 {
21+
fmt.Fprintf(out, "FAIL")
22+
report(out, result.FilesBelowThreshold)
23+
} else {
24+
fmt.Fprintf(out, "PASS")
25+
}
26+
}
27+
28+
{
29+
fmt.Fprintf(out, "\nPackages meeting coverage threshold of (%d%%):\t", thr.Package)
30+
if len(result.PackagesBelowThreshold) > 0 {
31+
fmt.Fprintf(out, "FAIL")
32+
report(out, result.PackagesBelowThreshold)
33+
} else {
34+
fmt.Fprintf(out, "PASS")
35+
}
36+
}
37+
38+
{
39+
fmt.Fprintf(out, "\nTotal coverage meeting the threshold of (%d%%):\t", thr.Total)
40+
if !result.MeetsTotalCoverage {
41+
fmt.Fprintf(out, "FAIL")
42+
} else {
43+
fmt.Fprintf(out, "PASS")
44+
}
45+
}
46+
47+
fmt.Fprintf(out, "\nTotal test coverage: %d%%\n", result.TotalCoverage)
48+
}
49+
50+
func report(w io.Writer, coverageStats []CoverageStats) {
51+
tabber := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) //nolint:gomnd // relax
52+
defer tabber.Flush()
53+
54+
fmt.Fprintf(tabber, "\n\nIssues with:")
55+
56+
for _, stats := range coverageStats {
57+
fmt.Fprintf(tabber, "\n%s\t%d%%", stats.name, stats.CoveredPercentage())
58+
}
59+
60+
fmt.Fprintf(tabber, "\n")
61+
}
62+
63+
//nolint:lll // relax
64+
func ReportForGithubAction(result AnalyzeResult, cfg Config) {
65+
out := bufio.NewWriter(os.Stdout)
66+
defer out.Flush()
67+
68+
{
69+
msg := fmt.Sprintf("::set-output name=total_coverage::%s\n", strconv.Itoa(result.TotalCoverage))
70+
fmt.Fprint(out, msg)
71+
}
72+
73+
for _, stats := range result.FilesBelowThreshold {
74+
msg := fmt.Sprintf("::error file=%s,line=1::File test coverage below threshold of (%d%%)\n", stats.name, cfg.Threshold.File)
75+
fmt.Fprint(out, msg)
76+
}
77+
78+
for _, stats := range result.PackagesBelowThreshold {
79+
msg := fmt.Sprintf("::error ::Package (%s) test coverage below threshold of (%d%%)\n", stats.name, cfg.Threshold.Package)
80+
fmt.Fprint(out, msg)
81+
}
82+
83+
if !result.MeetsTotalCoverage {
84+
msg := fmt.Sprintf("::error ::Total coverage below threshold of (%d%%)\n", cfg.Threshold.Total)
85+
fmt.Fprint(out, msg)
86+
}
87+
}

0 commit comments

Comments
 (0)