Skip to content

Commit 911bb55

Browse files
authored
Implement partitiontest_linter (#2635)
1 parent 0d4b5bb commit 911bb55

File tree

8 files changed

+315
-1
lines changed

8 files changed

+315
-1
lines changed

.golangci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
run:
22
timeout: 5m
3-
tests: false
3+
tests: true
44

55
linters:
66
enable:
77
- golint
88
- misspell
99
- govet
1010
- ineffassign
11+
- partitiontest
1112

1213
disable:
1314
- deadcode
@@ -19,6 +20,11 @@ linters:
1920
- varcheck
2021

2122
linters-settings:
23+
custom:
24+
partitiontest:
25+
path: cmd/partitiontest_linter/plugin.so
26+
description: This custom linter checks files that end in '_test.go', specifically functions that start with 'Test' and have testing argument, for a line 'partitiontest.ParitionTest(<testing arg>)'
27+
original-url: github.com/algorand/go-algorand/cmd/partitiontest_linter
2228
# govet:
2329
# check-shadowing: true
2430

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,8 @@ include ./scripts/release/mule/Makefile.mule
322322

323323
archive:
324324
aws s3 cp tmp/node_pkgs s3://algorand-internal/channel/$(CHANNEL)/$(FULLBUILDNUMBER) --recursive --exclude "*" --include "*$(FULLBUILDNUMBER)*"
325+
326+
build_custom_linters:
327+
cd cmd/partitiontest_linter/
328+
go build -buildmode=plugin -trimpath plugin/plugin.go
329+
cd -

cmd/partitiontest_linter/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/algorand/go-algorand/cmd/partitiontest_linter
2+
3+
go 1.16
4+
5+
require golang.org/x/tools v0.1.3

cmd/partitiontest_linter/go.sum

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
2+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
3+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
4+
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
5+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
6+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
7+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
8+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
9+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
10+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
11+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
12+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
13+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
14+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
15+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
16+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
17+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
18+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
19+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
20+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
21+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
22+
golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8=
23+
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
24+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
25+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
26+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
27+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

cmd/partitiontest_linter/linter.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright (C) 2019-2021 Algorand, Inc.
2+
// This file is part of go-algorand
3+
//
4+
// go-algorand is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as
6+
// published by the Free Software Foundation, either version 3 of the
7+
// License, or (at your option) any later version.
8+
//
9+
// go-algorand is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package linter
18+
19+
import (
20+
"go/ast"
21+
"strings"
22+
23+
"golang.org/x/tools/go/analysis"
24+
)
25+
26+
const packageName string = "partitiontest"
27+
const functionName string = "PartitionTest"
28+
const fileNameSuffix string = "_test.go"
29+
const functionNamePrefix string = "Test"
30+
const parameterType string = "T"
31+
32+
// Analyzer initilization
33+
var Analyzer = &analysis.Analyzer{
34+
Name: "lint",
35+
Doc: "This custom linter checks inside files that end in '_test.go', and inside functions that start with 'Test' and have testing argument, for a line 'partitiontest.ParitionTest(<testing arg>)'",
36+
Run: run,
37+
}
38+
39+
func run(pass *analysis.Pass) (interface{}, error) {
40+
for _, f := range pass.Files {
41+
currentFileName := pass.Fset.File(f.Pos()).Name()
42+
if !strings.HasSuffix(currentFileName, fileNameSuffix) {
43+
continue
44+
}
45+
for _, decl := range f.Decls {
46+
fn, ok := decl.(*ast.FuncDecl)
47+
if !ok || fn.Recv != nil {
48+
// Ignore non-functions or functions with receivers.
49+
continue
50+
}
51+
52+
// Check that function name starts with "Test"
53+
if !strings.HasPrefix(fn.Name.Name, functionNamePrefix) {
54+
continue
55+
}
56+
57+
if !isTestParameterInFunction(fn.Type.Params.List[0].Type, parameterType) {
58+
continue
59+
}
60+
if !isSearchLineInFunction(fn) {
61+
pass.Reportf(fn.Pos(), "%s function is missing %s.%s(<%s type parameter>)", fn.Name.Name, packageName, functionName, parameterType)
62+
}
63+
64+
}
65+
}
66+
return nil, nil
67+
}
68+
69+
func isTestParameterInFunction(typ ast.Expr, wantType string) bool {
70+
ptr, ok := typ.(*ast.StarExpr)
71+
if !ok {
72+
// Not a pointer.
73+
return false
74+
}
75+
76+
if name, ok := ptr.X.(*ast.Ident); ok {
77+
return name.Name == wantType
78+
}
79+
if sel, ok := ptr.X.(*ast.SelectorExpr); ok {
80+
return sel.Sel.Name == wantType
81+
}
82+
return false
83+
}
84+
85+
func isSearchLineInFunction(fn *ast.FuncDecl) bool {
86+
for _, oneline := range fn.Body.List {
87+
if exprStmt, ok := oneline.(*ast.ExprStmt); ok {
88+
if call, ok := exprStmt.X.(*ast.CallExpr); ok {
89+
if fun, ok := call.Fun.(*ast.SelectorExpr); ok {
90+
if !doesPackageNameMatch(fun) {
91+
continue
92+
}
93+
if !doesFunctionNameMatch(fun) {
94+
continue
95+
}
96+
}
97+
98+
if !doesParameterNameMatch(call, fn) {
99+
continue
100+
}
101+
102+
return true
103+
}
104+
}
105+
}
106+
return false
107+
}
108+
109+
func doesPackageNameMatch(fun *ast.SelectorExpr) bool {
110+
if packageobject, ok := fun.X.(*ast.Ident); ok {
111+
if packageobject.Name == packageName {
112+
return true
113+
}
114+
}
115+
return false
116+
}
117+
118+
func doesFunctionNameMatch(fun *ast.SelectorExpr) bool {
119+
return fun.Sel.Name == functionName
120+
}
121+
122+
func doesParameterNameMatch(call *ast.CallExpr, fn *ast.FuncDecl) bool {
123+
for _, oneArg := range call.Args {
124+
125+
if realArg, ok := oneArg.(*ast.Ident); ok {
126+
if realArg.Obj.Name == fn.Type.Params.List[0].Names[0].Obj.Name {
127+
return true
128+
}
129+
}
130+
}
131+
return false
132+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (C) 2019-2021 Algorand, Inc.
2+
// This file is part of go-algorand
3+
//
4+
// go-algorand is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as
6+
// published by the Free Software Foundation, either version 3 of the
7+
// License, or (at your option) any later version.
8+
//
9+
// go-algorand is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package linter
18+
19+
import (
20+
"testing"
21+
22+
"golang.org/x/tools/go/analysis/analysistest"
23+
)
24+
25+
func TestAll(t *testing.T) {
26+
analysistest.Run(t, analysistest.TestData(), Analyzer)
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (C) 2019-2021 Algorand, Inc.
2+
// This file is part of go-algorand
3+
//
4+
// go-algorand is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as
6+
// published by the Free Software Foundation, either version 3 of the
7+
// License, or (at your option) any later version.
8+
//
9+
// go-algorand is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package main
18+
19+
import (
20+
linter "github.com/algorand/go-algorand/cmd/partitiontest_linter"
21+
"golang.org/x/tools/go/analysis"
22+
"golang.org/x/tools/go/analysis/singlechecker"
23+
)
24+
25+
type analyzerPlugin struct{}
26+
27+
// This must be implemented
28+
func (*analyzerPlugin) GetAnalyzers() []*analysis.Analyzer {
29+
return []*analysis.Analyzer{
30+
linter.Analyzer,
31+
}
32+
}
33+
34+
// This must be defined and named 'AnalyzerPlugin'
35+
var AnalyzerPlugin analyzerPlugin
36+
37+
func main() {
38+
singlechecker.Main(linter.Analyzer)
39+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (C) 2019-2021 Algorand, Inc.
2+
// This file is part of go-algorand
3+
//
4+
// go-algorand is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as
6+
// published by the Free Software Foundation, either version 3 of the
7+
// License, or (at your option) any later version.
8+
//
9+
// go-algorand is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
16+
17+
/*
18+
This file is input file for analyzer_test.go
19+
That's why we are using relative path in import below
20+
It is also why we named this file _test.go, since linter only looks at files that end in _test.go
21+
*/
22+
23+
package linter_testdata
24+
25+
import (
26+
"testing"
27+
28+
"../../../test/partitiontest"
29+
)
30+
31+
func notTestFunction() {}
32+
33+
func notTestFunctionWithWrongParam(t string) {}
34+
35+
func notTestFunctionWithCorrectParam(t *testing.T) {}
36+
37+
func notTestFunctionWithCorrectParamCorrectLine(t *testing.T) {
38+
partitiontest.PartitionTest(t)
39+
}
40+
41+
func notTestFunctionWithCorrectParamWrongLine(t *testing.T) {
42+
println("something")
43+
}
44+
45+
func TestFunctionWithCorrectParamOnly(t *testing.T) {} // want "function is missing partitiontest.PartitionTest"
46+
47+
func TestFunctionWithCorrectParamCorrectLine(t *testing.T) {
48+
partitiontest.PartitionTest(t)
49+
}
50+
51+
func TestFunctionWithCorrectParamBadLine(t *testing.T) { // want "function is missing partitiontest.PartitionTest"
52+
println("something")
53+
}
54+
55+
func TestFunctionWithDifferentName(n *testing.T) {
56+
partitiontest.PartitionTest(n)
57+
}
58+
59+
func TestFunctionWithCorrectParamNotFirstCorrectLine(t *testing.T) {
60+
println("something")
61+
partitiontest.PartitionTest(t)
62+
}
63+
64+
func TestFunctionWithCorrectParamNotLastCorrectLine(t *testing.T) {
65+
partitiontest.PartitionTest(t)
66+
println("something")
67+
}
68+
69+
func TestFunctionWithCorrectParamMiddleCorrectLine(t *testing.T) {
70+
println("something")
71+
partitiontest.PartitionTest(t)
72+
println("something")
73+
}

0 commit comments

Comments
 (0)