Skip to content

Commit 95d6aab

Browse files
committed
Tools apicovered and apiunused
Checks for coverage/usage of the go-gitlab package
1 parent a9713fa commit 95d6aab

File tree

11 files changed

+553
-4
lines changed

11 files changed

+553
-4
lines changed

GNUmakefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ lint-generated: generate ## Check that "make generate" was called. Note this onl
5050
exit 1; \
5151
}
5252

53+
apicovered: tool-apicovered ## Run an analysis tool to estimate the GitLab API coverage.
54+
@$(GOBIN)/apicovered ./gitlab
55+
56+
apiunused: tool-apiunused ## Run an analysis tool to output unused parts of the go-gitlab package.
57+
@$(GOBIN)/apiunused ./gitlab
58+
5359
SERVICE ?= gitlab-ce
5460
GITLAB_TOKEN ?= ACCTEST1234567890123
5561
GITLAB_BASE_URL ?= http://127.0.0.1:8080/api/v4
@@ -79,6 +85,12 @@ tool-tfplugindocs:
7985
tool-shfmt:
8086
@$(call install-tool, mvdan.cc/sh/v3/cmd/shfmt)
8187

88+
tool-apicovered:
89+
@$(call install-tool, ./cmd/apicovered)
90+
91+
tool-apiunused:
92+
@$(call install-tool, ./cmd/apiunused)
93+
8294
define install-tool
8395
cd tools && GOBIN=$(GOBIN) go install $(1)
8496
endef

tools/cmd/apicovered/main.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes/apicovered"
7+
"golang.org/x/tools/go/analysis/singlechecker"
8+
)
9+
10+
func main() {
11+
apicovered.Output = os.Stdout
12+
singlechecker.Main(apicovered.Analyzer)
13+
}

tools/cmd/apiunused/main.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes/apiunused"
7+
"golang.org/x/tools/go/analysis/singlechecker"
8+
)
9+
10+
func main() {
11+
apiunused.Output = os.Stdout
12+
singlechecker.Main(apiunused.Analyzer)
13+
}

tools/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ require (
66
github.com/bflad/tfproviderlint v0.27.1
77
github.com/golangci/golangci-lint v1.44.0
88
github.com/hashicorp/terraform-plugin-docs v0.5.1
9+
golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da
910
mvdan.cc/sh/v3 v3.4.2
1011
)

tools/go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
135135
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
136136
github.com/bflad/gopaniccheck v0.1.0 h1:tJftp+bv42ouERmUMWLoUn/5bi/iQZjHPznM00cP/bU=
137137
github.com/bflad/gopaniccheck v0.1.0/go.mod h1:ZCj2vSr7EqVeDaqVsWN4n2MwdROx1YL+LFo47TSWtsA=
138-
github.com/bflad/tfproviderlint v0.27.0 h1:KXF+dYaWJ/OSVyWIrk2NIYgQBMDDSOC4VQB/P+P5nhI=
139-
github.com/bflad/tfproviderlint v0.27.0/go.mod h1:7Z9Pyl1Z1UWJcPBuyjN89D2NaJGpjReQb5NoaaQCthQ=
140138
github.com/bflad/tfproviderlint v0.27.1 h1:sYlc6R8cQ0NtaCCA7Oh1ld8xfn0oiwn6mm4unooi2fo=
141139
github.com/bflad/tfproviderlint v0.27.1/go.mod h1:7Z9Pyl1Z1UWJcPBuyjN89D2NaJGpjReQb5NoaaQCthQ=
142-
github.com/bflad/tfproviderlint v0.28.0 h1:1JA8ys764WisN81Bx54L9ba85Dews8dU1VU2dZ3ByHk=
143-
github.com/bflad/tfproviderlint v0.28.0/go.mod h1:7Z9Pyl1Z1UWJcPBuyjN89D2NaJGpjReQb5NoaaQCthQ=
144140
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
145141
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
146142
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=

tools/passes/apicovered/apicovered.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package apicovered
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"reflect"
8+
"sort"
9+
"text/tabwriter"
10+
11+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes"
12+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes/gogitlab"
13+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes/usage"
14+
"golang.org/x/tools/go/analysis"
15+
)
16+
17+
var Output io.Writer = io.Discard
18+
19+
var Analyzer = &analysis.Analyzer{
20+
Doc: "Estimate usage of the go-gitlab package",
21+
Name: "apicovered",
22+
ResultType: reflect.TypeOf((*Result)(nil)),
23+
Requires: []*analysis.Analyzer{gogitlab.Analyzer, usage.Analyzer},
24+
Run: run,
25+
}
26+
27+
type Result struct {
28+
CoverageByFile map[string]Fraction
29+
}
30+
31+
type Fraction struct {
32+
Count int
33+
Total int
34+
}
35+
36+
func run(pass *analysis.Pass) (interface{}, error) {
37+
goGitLab := pass.ResultOf[gogitlab.Analyzer].(*gogitlab.Result)
38+
usage := pass.ResultOf[usage.Analyzer].(*usage.Result)
39+
40+
result := &Result{
41+
CoverageByFile: make(map[string]Fraction),
42+
}
43+
44+
process(result, goGitLab.TypeToFilenames, usage.Types)
45+
process(result, goGitLab.MethodToFilenames, usage.Methods)
46+
process(result, goGitLab.FieldToFilenames, usage.Fields)
47+
48+
if !passes.IsTestPackage(pass) {
49+
writeOutput(result)
50+
}
51+
52+
return result, nil
53+
}
54+
55+
func process(result *Result, nameToFilenames gogitlab.MultiMap, seen usage.Set) {
56+
for _, filenames := range nameToFilenames {
57+
for _, filename := range filenames {
58+
coverage := result.CoverageByFile[filename]
59+
coverage.Total++
60+
result.CoverageByFile[filename] = coverage
61+
}
62+
}
63+
64+
for name := range seen {
65+
for _, filename := range nameToFilenames[name] {
66+
coverage := result.CoverageByFile[filename]
67+
coverage.Count++
68+
result.CoverageByFile[filename] = coverage
69+
}
70+
}
71+
}
72+
73+
func writeOutput(result *Result) {
74+
type row struct {
75+
filename string
76+
coverageString string
77+
coveragePercent int
78+
}
79+
80+
newRow := func(filename string, fraction Fraction) row {
81+
return row{
82+
filename: filename,
83+
coverageString: fmt.Sprintf("%d/%d", fraction.Count, fraction.Total),
84+
coveragePercent: makePercent(fraction.Count, fraction.Total),
85+
}
86+
}
87+
88+
var rows []row
89+
90+
for filename, coverage := range result.CoverageByFile {
91+
rows = append(rows, newRow(filename, coverage))
92+
}
93+
94+
sort.Slice(rows, func(i, j int) bool {
95+
if rows[i].coveragePercent == rows[j].coveragePercent {
96+
return rows[i].filename < rows[j].filename
97+
}
98+
return rows[i].coveragePercent < rows[j].coveragePercent
99+
})
100+
101+
totalRow := func() row {
102+
var totalCoverage Fraction
103+
for _, coverage := range result.CoverageByFile {
104+
totalCoverage.Count += coverage.Count
105+
totalCoverage.Total += coverage.Total
106+
}
107+
return newRow("Total", totalCoverage)
108+
}()
109+
110+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
111+
112+
writeRow := func(row row) {
113+
fmt.Fprintf(w, "%s\t%d%%\t%s\n", row.filename, row.coveragePercent, row.coverageString)
114+
}
115+
116+
fmt.Fprintf(w, "Filename\tCoverage\tLines\n")
117+
fmt.Fprintln(w, "--------\t--------\t-----")
118+
119+
for _, row := range rows {
120+
writeRow(row)
121+
}
122+
123+
fmt.Fprintln(w, "---\t---\t---")
124+
writeRow(totalRow)
125+
126+
w.Flush()
127+
}
128+
129+
func makePercent(n, d int) int {
130+
if d == 0 {
131+
if n == 0 {
132+
return 100
133+
}
134+
return 0
135+
}
136+
return 100 * n / d
137+
}

tools/passes/apiunused/apiunused.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package apiunused
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"os"
7+
"reflect"
8+
9+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes"
10+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes/gogitlab"
11+
"github.com/gitlabhq/terraform-provider-gitlab/tools/passes/usage"
12+
"golang.org/x/tools/go/analysis"
13+
)
14+
15+
var Output io.Writer = io.Discard
16+
17+
var Analyzer = &analysis.Analyzer{
18+
Doc: "Estimate the unused pieces of the go-gitlab package",
19+
Name: "apiunused",
20+
ResultType: reflect.TypeOf((*Result)(nil)),
21+
Requires: []*analysis.Analyzer{gogitlab.Analyzer, usage.Analyzer},
22+
Run: run,
23+
}
24+
25+
type Result struct {
26+
UnusedByFile map[string]Unused
27+
}
28+
29+
type Unused struct {
30+
Types []string `json:"types,omitempty"`
31+
Funcs []string `json:"funcs,omitempty"`
32+
Methods []string `json:"methods,omitempty"`
33+
Fields []string `json:"fields,omitempty"`
34+
}
35+
36+
func run(pass *analysis.Pass) (interface{}, error) {
37+
goGitLab := pass.ResultOf[gogitlab.Analyzer].(*gogitlab.Result)
38+
usage := pass.ResultOf[usage.Analyzer].(*usage.Result)
39+
40+
result := &Result{
41+
UnusedByFile: make(map[string]Unused),
42+
}
43+
44+
processUnused(result, goGitLab.TypeToFilenames, usage.Types,
45+
func(item Unused, name string) Unused {
46+
item.Types = append(item.Types, name)
47+
return item
48+
})
49+
50+
processUnused(result, goGitLab.FuncToFilenames, usage.Funcs,
51+
func(item Unused, name string) Unused {
52+
item.Funcs = append(item.Funcs, name)
53+
return item
54+
})
55+
56+
processUnused(result, goGitLab.MethodToFilenames, usage.Methods,
57+
func(item Unused, name string) Unused {
58+
item.Methods = append(item.Methods, name)
59+
return item
60+
})
61+
62+
processUnused(result, goGitLab.FieldToFilenames, usage.Fields,
63+
func(item Unused, name string) Unused {
64+
item.Fields = append(item.Fields, name)
65+
return item
66+
})
67+
68+
if !passes.IsTestPackage(pass) {
69+
writeOutput(result)
70+
}
71+
72+
return result, nil
73+
}
74+
75+
func processUnused(result *Result, nameToFilenames gogitlab.MultiMap, seen usage.Set, mutFn func(Unused, string) Unused) {
76+
for name, filenames := range nameToFilenames {
77+
if !seen[name] {
78+
for _, filename := range filenames {
79+
result.UnusedByFile[filename] = mutFn(result.UnusedByFile[filename], name)
80+
}
81+
}
82+
}
83+
}
84+
85+
func writeOutput(result *Result) {
86+
e := json.NewEncoder(os.Stdout)
87+
e.SetIndent("", " ")
88+
89+
_ = e.Encode(result.UnusedByFile)
90+
}

tools/passes/gen_util.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package passes
2+
3+
import (
4+
"strings"
5+
6+
"golang.org/x/tools/go/analysis"
7+
)
8+
9+
func IsTestPackage(pass *analysis.Pass) bool {
10+
if strings.HasSuffix(pass.Pkg.Path(), ".test") {
11+
return true
12+
}
13+
14+
for _, f := range pass.Files {
15+
if strings.HasSuffix(pass.Fset.File(f.Pos()).Name(), "_test.go") {
16+
return true
17+
}
18+
}
19+
20+
return false
21+
}

0 commit comments

Comments
 (0)