Source file
src/go/types/errorcodes_test.go
1
2
3
4
5 package types_test
6
7 import (
8 "fmt"
9 "go/ast"
10 "go/constant"
11 "go/importer"
12 "go/parser"
13 "go/token"
14 "reflect"
15 "strings"
16 "testing"
17
18 . "go/types"
19 )
20
21 func TestErrorCodeExamples(t *testing.T) {
22 walkCodes(t, func(name string, value int, spec *ast.ValueSpec) {
23 t.Run(name, func(t *testing.T) {
24 doc := spec.Doc.Text()
25 examples := strings.Split(doc, "Example:")
26 for i := 1; i < len(examples); i++ {
27 example := examples[i]
28 err := checkExample(t, example)
29 if err == nil {
30 t.Fatalf("no error in example #%d", i)
31 }
32 typerr, ok := err.(Error)
33 if !ok {
34 t.Fatalf("not a types.Error: %v", err)
35 }
36 if got := readCode(typerr); got != value {
37 t.Errorf("%s: example #%d returned code %d (%s), want %d", name, i, got, err, value)
38 }
39 }
40 })
41 })
42 }
43
44 func walkCodes(t *testing.T, f func(string, int, *ast.ValueSpec)) {
45 t.Helper()
46 fset := token.NewFileSet()
47 files, err := pkgFiles(fset, ".", parser.ParseComments)
48 if err != nil {
49 t.Fatal(err)
50 }
51 conf := Config{Importer: importer.Default()}
52 info := &Info{
53 Types: make(map[ast.Expr]TypeAndValue),
54 Defs: make(map[*ast.Ident]Object),
55 Uses: make(map[*ast.Ident]Object),
56 }
57 _, err = conf.Check("types", fset, files, info)
58 if err != nil {
59 t.Fatal(err)
60 }
61 for _, file := range files {
62 for _, decl := range file.Decls {
63 decl, ok := decl.(*ast.GenDecl)
64 if !ok || decl.Tok != token.CONST {
65 continue
66 }
67 for _, spec := range decl.Specs {
68 spec, ok := spec.(*ast.ValueSpec)
69 if !ok || len(spec.Names) == 0 {
70 continue
71 }
72 obj := info.ObjectOf(spec.Names[0])
73 if named, ok := obj.Type().(*Named); ok && named.Obj().Name() == "errorCode" {
74 if len(spec.Names) != 1 {
75 t.Fatalf("bad Code declaration for %q: got %d names, want exactly 1", spec.Names[0].Name, len(spec.Names))
76 }
77 codename := spec.Names[0].Name
78 value := int(constant.Val(obj.(*Const).Val()).(int64))
79 f(codename, value, spec)
80 }
81 }
82 }
83 }
84 }
85
86 func readCode(err Error) int {
87 v := reflect.ValueOf(err)
88 return int(v.FieldByName("go116code").Int())
89 }
90
91 func checkExample(t *testing.T, example string) error {
92 t.Helper()
93 fset := token.NewFileSet()
94 src := fmt.Sprintf("package p\n\n%s", example)
95 file, err := parser.ParseFile(fset, "example.go", src, 0)
96 if err != nil {
97 t.Fatal(err)
98 }
99 conf := Config{
100 FakeImportC: true,
101 Importer: importer.Default(),
102 }
103 _, err = conf.Check("example", fset, []*ast.File{file}, nil)
104 return err
105 }
106
107 func TestErrorCodeStyle(t *testing.T) {
108
109
110 forbiddenInIdent := []string{
111
112 "illegal",
113
114 "argument",
115 "assertion",
116 "assignment",
117 "boolean",
118 "channel",
119 "condition",
120 "declaration",
121 "expression",
122 "function",
123 "initial",
124 "integer",
125 "interface",
126 "iterat",
127 "literal",
128 "operation",
129 "package",
130 "pointer",
131 "receiver",
132 "signature",
133 "statement",
134 "variable",
135 }
136 forbiddenInComment := []string{
137
138 "lhs", "rhs",
139
140 "builtin",
141
142 "ellipsis",
143 }
144 nameHist := make(map[int]int)
145 longestName := ""
146 maxValue := 0
147
148 walkCodes(t, func(name string, value int, spec *ast.ValueSpec) {
149 if name == "_" {
150 return
151 }
152 nameHist[len(name)]++
153 if value > maxValue {
154 maxValue = value
155 }
156 if len(name) > len(longestName) {
157 longestName = name
158 }
159 if token.IsExported(name) {
160
161
162 t.Errorf("%q is exported", name)
163 }
164 if name[0] != '_' || !token.IsExported(name[1:]) {
165 t.Errorf("%q should start with _, followed by an exported identifier", name)
166 }
167 lower := strings.ToLower(name)
168 for _, bad := range forbiddenInIdent {
169 if strings.Contains(lower, bad) {
170 t.Errorf("%q contains forbidden word %q", name, bad)
171 }
172 }
173 doc := spec.Doc.Text()
174 if doc == "" {
175 t.Errorf("%q is undocumented", name)
176 } else if !strings.HasPrefix(doc, name) {
177 t.Errorf("doc for %q does not start with the error code name", name)
178 }
179 lowerComment := strings.ToLower(strings.TrimPrefix(doc, name))
180 for _, bad := range forbiddenInComment {
181 if strings.Contains(lowerComment, bad) {
182 t.Errorf("doc for %q contains forbidden word %q", name, bad)
183 }
184 }
185 })
186
187 if testing.Verbose() {
188 var totChars, totCount int
189 for chars, count := range nameHist {
190 totChars += chars * count
191 totCount += count
192 }
193 avg := float64(totChars) / float64(totCount)
194 fmt.Println()
195 fmt.Printf("%d error codes\n", totCount)
196 fmt.Printf("average length: %.2f chars\n", avg)
197 fmt.Printf("max length: %d (%s)\n", len(longestName), longestName)
198 }
199 }
200
View as plain text