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