-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathoptions_builder_test.go
More file actions
124 lines (113 loc) · 2.46 KB
/
options_builder_test.go
File metadata and controls
124 lines (113 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package httpx
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"testing"
)
func TestOptionFunctionsMatchBuilderMethods(t *testing.T) {
root, err := findRepoRoot()
if err != nil {
t.Fatalf("repo root: %v", err)
}
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, root, func(info os.FileInfo) bool {
name := info.Name()
return strings.HasPrefix(name, "options_") && strings.HasSuffix(name, ".go")
}, 0)
if err != nil {
t.Fatalf("parse options: %v", err)
}
var pkg *ast.Package
for _, p := range pkgs {
if p.Name == "httpx" {
pkg = p
break
}
}
if pkg == nil {
t.Fatal("package httpx not found")
}
methods := map[string]struct{}{}
functions := map[string]struct{}{}
for _, file := range pkg.Files {
for _, decl := range file.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok || fn.Name == nil {
continue
}
if fn.Recv != nil && len(fn.Recv.List) > 0 {
if receiverType(fn.Recv.List[0].Type) == "OptionBuilder" {
methods[fn.Name.Name] = struct{}{}
}
continue
}
if !fn.Name.IsExported() {
continue
}
if isOptionBuilderReturn(fn.Type.Results) {
functions[fn.Name.Name] = struct{}{}
}
}
}
var missing []string
for name := range methods {
if _, ok := functions[name]; !ok {
missing = append(missing, name)
}
}
for name := range functions {
if _, ok := methods[name]; !ok {
missing = append(missing, name)
}
}
if len(missing) > 0 {
t.Fatalf("option mismatch between functions and builder methods: %v", missing)
}
}
func receiverType(expr ast.Expr) string {
switch v := expr.(type) {
case *ast.Ident:
return v.Name
case *ast.StarExpr:
return receiverType(v.X)
case *ast.IndexExpr:
return receiverType(v.X)
case *ast.IndexListExpr:
return receiverType(v.X)
default:
return ""
}
}
func isOptionBuilderReturn(results *ast.FieldList) bool {
if results == nil || len(results.List) == 0 {
return false
}
last := results.List[len(results.List)-1]
if named, ok := last.Type.(*ast.Ident); ok && named.Name == "OptionBuilder" {
return true
}
if sel, ok := last.Type.(*ast.SelectorExpr); ok {
return sel.Sel.Name == "OptionBuilder"
}
return false
}
func findRepoRoot() (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
}
for {
if _, err := os.Stat(filepath.Join(wd, "go.mod")); err == nil {
return wd, nil
}
parent := filepath.Dir(wd)
if parent == wd {
return "", os.ErrNotExist
}
wd = parent
}
}